{"id":1470,"date":"2021-03-16T10:45:11","date_gmt":"2021-03-15T22:45:11","guid":{"rendered":"http:\/\/p-s.co.nz\/wordpress\/?p=1470"},"modified":"2021-03-19T10:12:29","modified_gmt":"2021-03-18T22:12:29","slug":"first-impressions-of-python-pattern-matching","status":"publish","type":"post","link":"http:\/\/p-s.co.nz\/wordpress\/first-impressions-of-python-pattern-matching\/","title":{"rendered":"First Impressions of Python Pattern Matching"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Controversy &#8211; Python Adding Everything But the Kitchen Sink?<\/h2>\n\n\n\n<p>Is Python piling in too many new features taken from other languages? Is Pattern Matching yet another way of doing things for a language which has prided itself on there being one obvious way of doing things? In short, is Python being ruined by people who don&#8217;t appreciate the benefits of Less Is More?<\/p>\n\n\n\n<p>Short answer: No ;-). Long answer: see below. Changed answer: see <a href=\"http:\/\/p-s.co.nz\/wordpress\/second-impressions-of-python-pattern-matching\/\" target=\"_blank\" rel=\"noreferrer noopener\">Second Impressions of Python Pattern Matching<\/a><\/p>\n\n\n\n<p>I appreciate arguments for simplicity, and I want the bar for new features to be high, but I am glad Pattern Matching made its way in. It will be the One Obvious Way for doing, errr, pattern matching. Pattern matching may not be as crucial in a dynamically typed language like Python but it is still useful. And the syntax is nice too. <code>match<\/code> and <code>case<\/code> are pretty self-explanatory. Of course, there are some gotchas to watch out for but pattern matching is arguably one of the most interesting additions to Python since f-strings in Python 3.6. So let&#8217;s have a look and see what we can do with it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is Pattern Matching?<\/h2>\n\n\n\n<p>Not having used pattern matching before in other languages I wasn&#8217;t quite sure how to think about it. According to Tom\u00e1\u0161 Karabela I&#8217;ve been using something similar without realising it (<a href=\"https:\/\/youtu.be\/SYTVSeTgL3s?t=27\" data-type=\"URL\">Python 3.10 Pattern Matching in Action<\/a>). But what is Pattern Matching? And why would I use it in Python?<\/p>\n\n\n\n<p>I have found it useful to think of Pattern Matching as a Switch statement on steroids.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-style-large is-layout-flow wp-block-quote-is-layout-flow\"><p>Pattern Matching is a Switch Statement on Steroids<\/p><\/blockquote>\n\n\n\n<p>The switch aspect is the way the code passes through various expressions until it matches. That makes total sense &#8211; one of the earliest things we need to do in programming is respond according the value \/ nature of something. Even SQL has <code>CASE WHEN<\/code> statements.<\/p>\n\n\n\n<p>The steroids aspect has two parts:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Unpacking<\/h3>\n\n\n\n<p>Unpacking is beautiful and elegant so it is a real pleasure to find it built into Python&#8217;s Pattern Matching. <code>case (x, y):<\/code> looks for a two-tuple and unpacks into <code>x<\/code> and <code>y<\/code> names ready to use in the code under the case condition. <code>case str(x):<\/code> looks for a string and assigns the name <code>x<\/code> to it. Handy.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Type Matching<\/h3>\n\n\n\n<p>Duck typing can be a wonderful thing but sometimes it is useful to match on type. <code>case Point(x=x, y=y):<\/code> only matches if an instance of the <code>Point<\/code> class. <code>case int(x):<\/code> only matches if an integer. Note &#8211; case Point(x, y): doesn&#8217;t work in a case condition because positional sub-patterns aren&#8217;t allowed. Confused? More detail on possible gotchas below:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Gotchas<\/h2>\n\n\n\n<p>Sometimes you think exactly the same as the language feature, sometimes not. Here are some mistakes I made straight away. Typically they were caused by a basic misunderstanding of how Pattern Matching &#8220;thinks&#8221;.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Patterns aren&#8217;t Objects<\/h3>\n\n\n\n<p><code>case Point(x, y):<\/code> seems to me to be an obvious way of looking for a <code>Point<\/code> object and unpacking its values into <code>x<\/code> and <code>y<\/code> but it isn&#8217;t allowed. It is the correct syntax for instantiating a <code>Point<\/code> object but we are not instantiating an object and supplying the object the case condition &#8211; instead we are supply a pattern to be matched and unpacked. We have to have a firm grasp on the notion that Python patterns are not objects.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-style-large is-layout-flow wp-block-quote-is-layout-flow\"><p>Patterns Ain&#8217;t Objects<\/p><\/blockquote>\n\n\n\n<p>If we forget we get a TypeError:<\/p>\n\n\n\n<p><code>case Point(0, 999):<br>TypeError: Point() accepts 0 positional sub-patterns (2 given)<\/code><\/p>\n\n\n\n<p>Note, you must match the parameter names (the left side) but can unpack to any variable names you like (the right side). It may feel a bit odd being forced to use what feel like keyword arguments when the original class definition is positional but we must remember that we aren&#8217;t making an object &#8211; we are designing a pattern and collecting variables \/ names.<\/p>\n\n\n\n<p><code>case Point(x=0, y=y):<\/code> the <code>x=<\/code> and <code>y=<\/code> are the required syntax for a pattern. We insist on <code>x<\/code> being <code>0<\/code> but <code>y<\/code> can be anything (which we add the name <code>y<\/code> to). We could equally have written <code>case Point(x=0, y=y_val):<\/code> or <code>case Point(x=0, y=spam):<\/code>.<\/p>\n\n\n\n<p><code>case Point:<\/code>, <code>case int:<\/code>, <code>case str:<\/code>, <code>case float:<\/code> don&#8217;t work as you might expect. They match anything and assign it to the name <code>Point<\/code> or <code>int<\/code> or <code>str<\/code> etc. Definitely NOT what you want. The only protection is when you accidentally do this before other case conditions &#8211; e.g.<\/p>\n\n\n\n<p><code>case int:<br>^<br>SyntaxError: name capture 'int' makes remaining patterns unreachable<\/code><\/p>\n\n\n\n<p>This might become a common error because of our experience with <code>isinstance<\/code> where we supply the type e.g. <code>isinstance(x, int)<\/code>. Remember:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-style-large is-layout-flow wp-block-quote-is-layout-flow\"><p>case Patterns have to be Patterns<\/p><\/blockquote>\n\n\n\n<p>Instead, using the example of integer patterns, we need <code>case int():<\/code>, or, if we want to &#8220;capture&#8221; the value into, say, <code>x<\/code>, <code>case int(x):<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Guards and Traditional Switch Statements<\/h2>\n\n\n\n<p>It is very common in switch \/ case when statements to have conditions. Sometimes it is the whole point of the construct &#8211; we supply one value and respond differently according to its values \/ attributes. E.g. in rough pseudocode if temp &lt; 0 freezing, if &gt; 100 boiling, otherwise normal. In Pattern Matching value conditions are secondary. We match on a pattern first and then, perhaps evaluating the unpacked variables in an expression, we apply a guard condition.<\/p>\n\n\n\n<p><code>case float(x) if abs(x) &lt; 100:<br>...<br>case float(x) if abs(x) &lt; 200:<br>etc<\/code><\/p>\n\n\n\n<p>Depending on how it&#8217;s used we could think of &#8220;Pattern Matching&#8221; as &#8220;Pattern and Guard Condition Matching&#8221;.<\/p>\n\n\n\n<p>The most similar to a classic switch construct would be:<\/p>\n\n\n\n<p><code>match:<\/code><br>    <code>  case val if val &lt; 10:<br>        ...<br>    case val if val &lt; 20:<br>        ...<br>    etc<\/code><\/p>\n\n\n\n<p>One final thought: there seems to be nothing special about the &#8220;default&#8221; option (using switch language) &#8211; namely, <code>case _:<\/code>. It merely captures anything that hasn&#8217;t already been matched and puts <code>_<\/code> as the name i.e. it is a throwaway variable. We could capture and use that value with a normal variable name although that is optional because there&#8217;s nothing stopping us from referencing the original name fed into match. But, for example, <code>case mopup:<\/code> would work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to play with it on Linux<\/h2>\n\n\n\n<p>Make image using Dockerfile e.g. the following based on <a href=\"https:\/\/jdhao.github.io\/2021\/01\/17\/install_python3_in_ubuntu_docker\/\">Install Python3 in Ubuntu Docker<\/a> (I added vim and a newer Ubuntu image plus changed apt-get to apt (even though it allegedly has an unstable cli interface):<\/p>\n\n\n\n<p><code>FROM ubuntu:20.04<\/code><\/p>\n\n\n\n<p><code>RUN apt update &amp;&amp; apt install -y software-properties-common gcc &amp;&amp; \\<br>add-apt-repository -y ppa:deadsnakes\/ppa<\/code><\/p>\n\n\n\n<p><code>RUN apt update &amp;&amp; apt install -y python3.10 python3-distutils python3-pip python3-apt vim<\/code><\/p>\n\n\n\n<p><code>docker build --tag pyexp .<\/code><\/p>\n\n\n\n<p>(don&#8217;t forget the dot at the end &#8211; that&#8217;s a reference to the path to find Dockerfile)<\/p>\n\n\n\n<p>Then make container:<\/p>\n\n\n\n<p><code>docker create --name pyexp_cont pyexp<\/code><\/p>\n\n\n\n<p>and run it with access to bash command line<\/p>\n\n\n\n<p><code>docker container run -it pyexp \/bin\/bash<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Useful Links<\/h2>\n\n\n\n<p><a rel=\"noreferrer noopener\" href=\"https:\/\/mathspp.com\/blog\/pydonts\/pattern-matching-tutorial-for-pythonic-code\" target=\"_blank\">Pattern matching tutorial for Pythonic code | Pydon&#8217;t<\/a><\/p>\n\n\n\n<p><a rel=\"noreferrer noopener\" href=\"https:\/\/www.youtube.com\/watch?v=SYTVSeTgL3s\" target=\"_blank\">Python 3.10 Pattern Matching in Action<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/www.python.org\/dev\/peps\/pep-0622\" target=\"_blank\" rel=\"noreferrer noopener\">PEP 622<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Controversy &#8211; Python Adding Everything But the Kitchen Sink? Is Python piling in too many new features taken from other languages? Is Pattern Matching yet another way of doing things for a language which has prided itself on there being &hellip; <a href=\"http:\/\/p-s.co.nz\/wordpress\/first-impressions-of-python-pattern-matching\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,3,6],"tags":[24,26,15,25,27],"class_list":["post-1470","post","type-post","status-publish","format-standard","hentry","category-programming","category-python","category-ubuntu","tag-docker","tag-pattern-matching","tag-python","tag-python3","tag-python3-10"],"_links":{"self":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts\/1470"}],"collection":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/comments?post=1470"}],"version-history":[{"count":3,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts\/1470\/revisions"}],"predecessor-version":[{"id":1479,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts\/1470\/revisions\/1479"}],"wp:attachment":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/media?parent=1470"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/categories?post=1470"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/tags?post=1470"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}