{"id":4760,"date":"2019-06-25T18:16:11","date_gmt":"2019-06-25T17:16:11","guid":{"rendered":"https:\/\/www.blopig.com\/blog\/?p=4760"},"modified":"2019-06-25T21:29:32","modified_gmt":"2019-06-25T20:29:32","slug":"why-you-should-care-about-type-hints-in-python","status":"publish","type":"post","link":"https:\/\/www.blopig.com\/blog\/2019\/06\/why-you-should-care-about-type-hints-in-python\/","title":{"rendered":"Why you should care about type hints in Python"},"content":{"rendered":"\n<p>Duck typing is great. Knowing that as long as my object does what the function expects it to, I can pass it to the function and get my results without having to worry about exactly what else my object might do. Coming from statically-typed languages such as Java and C++, this is incredibly liberating, and makes it easy to rapidly prototype complex and expressive code without worrying about checking types everywhere. This expressiveness, however, comes with a cost: type errors are only caught at runtime, and can be hard to debug if the original author didn\u2019t document what that one variable in that one function signature is expected to look like. <\/p>\n\n\n\n<!--more-->\n\n\n\n<p>For example, let\u2019s define two birds, both of which can fly, but only one of which honks:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class Duck:\n    def fly(self):\n        print(\"Ducks can fly\")\n    def quack(self):\n        print(\"Quackers!\")\n\nclass Goose:\n    def fly(self):\n        print(\"Geese can fly\")\n    def honk(self):\n        print(\"Honk!\")<\/pre>\n\n\n\n<p>Now let\u2019s add a function to make them migrate:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def migrate(bird):\n    bird.fly()<\/pre>\n\n\n\n<p>Notice how I don\u2019t have to tell migrate() what type of object to expect, and at no point am I required to assert that bird is, in fact, a bird. As long as bird can fly, it can migrate without any problems. The code will run as expected, we get our results, and the bird survives another winter. Indeed, anything that can fly can migrate, though the original author likely didn&#8217;t anticipate their code being used to fly Eurofighter Typhoons south for winter.<\/p>\n\n\n\n<p>Suppose, now, we want to make sure Bruce the Goose announces his arrival for the holidays. We might update the migrate() function as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def migrate(bird):\n    bird.fly()\n    bird.honk()<\/pre>\n\n\n\n<p>As long as we pass a Goose (or anything else that can fly and honk) to migrate, everything is fine. If however we want our ducks to migrate, we\u2019re met with the following runtime error:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"no-highlight\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">AttributeError: \u2018Duck' object has no attribute 'honk'<\/pre>\n\n\n\n<p>Not very helpful. We know \u2018honk\u2019 is a function, not an attribute, but this error doesn\u2019t tell us this. It\u2019s an error you get used to debugging after working with python packages for a while, but back when I was a noob this sort of problem caused me endless headaches. Worse, even if I used an IDE like PyCharm to inspect and debug my code, there is no guarantee that it would be able to figure out that migrate() needs a bird that can honk. The metadata simply isn\u2019t there.<\/p>\n\n\n\n<p>This is where type annotations can make our lives, and the lives of the unfortunates upon which we inflict our code, much easier. Introduced in Python 3.5 (yet another reason to retire your fossilized 2.x distribution) and described in PEP484, type hinting provides a syntax to annotate function arguments and returns with a type or set of types in order to inform the user (or their IDE) exactly what the function can be expected to handle, and what they can expect to get back out.&nbsp; This was later extended in PEP526 to provide a syntax for annotating variable declarations with types. Type hinting is entirely optional, and does not affect the execution of the code at all. If your IDE tells you a function expects a numpy array, but you know a list will work for your use case, your code won\u2019t break at runtime just because you fought the system.<\/p>\n\n\n\n<p>So if it doesn\u2019t stop the dreaded runtime errors, and doesn\u2019t even give us something more useful than \u2018AttributeError\u2019, why should we care about type hinting? Remember our poor ducks from earlier, and how PyCharm couldn\u2019t tell me they need to honk to make it home for Christmas? If I\u2019d annotated the migrate() function to tell the user it only works for geese, rather than any old bird, PyCharm would immediately recognize that I was trying to pass a Duck in place of a Goose and would warn me about a potential type mismatch. Here\u2019s what the annotated function looks like:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def migrate(bird: Goose) -> None:\n    bird.fly()\n    bird.honk()<\/pre>\n\n\n\n<p>Now, if we try to send the ducks south, we\u2019ll still get an AttributeError, but at least PyCharm can tell us what the problem is, saving us the pain of figuring out exactly what the functional difference between a Duck and Goose is. It&#8217;ll even tell you the function returns None, so you don&#8217;t accidentally try to use it for coconut delivery.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Duck typing is great. Knowing that as long as my object does what the function expects it to, I can pass it to the function and get my results without having to worry about exactly what else my object might do. Coming from statically-typed languages such as Java and C++, this is incredibly liberating, and [&hellip;]<\/p>\n","protected":false},"author":47,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","wikipediapreview_detectlinks":true,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"ngg_post_thumbnail":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[31,221],"tags":[],"ppma_author":[498],"class_list":["post-4760","post","type-post","status-publish","format-standard","hentry","category-notes","category-python"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"authors":[{"term_id":498,"user_id":47,"is_guest":0,"slug":"fergus","display_name":"Fergus Boyles","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/ba8c419ba77128aad589b66ba7ee13da74f4ce2d3108fd724ddcefa200b51c7b?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/4760","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/users\/47"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/comments?post=4760"}],"version-history":[{"count":5,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/4760\/revisions"}],"predecessor-version":[{"id":4773,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/4760\/revisions\/4773"}],"wp:attachment":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/media?parent=4760"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/categories?post=4760"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/tags?post=4760"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=4760"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}