{"id":108340,"date":"2026-02-12T10:44:34","date_gmt":"2026-02-12T10:44:34","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=108340"},"modified":"2026-02-12T15:34:31","modified_gmt":"2026-02-12T15:34:31","slug":"python-is-good-but-not-perfect-here-are-10-reasons-to-avoid-it","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/python\/python-is-good-but-not-perfect-here-are-10-reasons-to-avoid-it\/","title":{"rendered":"Python is good, but not perfect &#8211; here are 10 reasons to avoid it"},"content":{"rendered":"\n<p>Four years ago I wrote a blog on this site explaining why <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/10-reasons-python-better-than-c-sharp\/\" target=\"_blank\" rel=\"noreferrer noopener\">Python is better than C#<\/a><ins> and, arguably, most other programming languages.<\/ins>&nbsp;To redress the balance, here are 10 reasons why you might want to avoid getting caught up in <a href=\"https:\/\/www.python.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Python\u2019s<\/a> oh-so-tempting coils &#8211; particularly when building large, long-lived systems.<\/p>\n\n\n\n<p>If this sounds like an attempt to have my cake and eat it, my defense is that I follow in my work what I preach here: I use Python for ad-hoc jobs, at which it is unsurpassed. For larger systems &#8211; such as our MV website &#8211; I use <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/csharp\/\" target=\"_blank\" rel=\"noreferrer noopener\">C#<\/a>, due to its strengths in maintainability, tooling as well as the practical consideration that my <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/10-reasons-why-visual-basic-is-better-than-c\/\" target=\"_blank\" rel=\"noreferrer noopener\">personal preference for Visual Basic<\/a> is not shared by the wider team.<\/p>\n\n\n\n<p><em>This article was peer reviewed by <a href=\"https:\/\/www.iadas.net\/membership\/bio\/rutuja-patil\/24147\" target=\"_blank\" rel=\"noreferrer noopener\">Rutuja Patil<\/a>.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-1-argument-types\">1. Argument Types<\/h2>\n\n\n\n<p>Consider this Python function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >def formal_name(\n\tfirst_name: str,\n\tlast_name: str\n) -&gt; str:\n\t\n\t\"\"\"\n\t\tCreates a formal name (eg \"Tom\", \"Cruise\" becomes \"Tom Cruise\")\n\t\"\"\"\n\treturn \"{0} {1}\".format(\n\t\tfirst_name.capitalize(),\n\t\tlast_name.capitalize()\n\t)\n\n# show this name\nprint(formal_name(\"Bob\",\"Smith\"))<\/pre><\/div>\n\n\n\n<p>These <code>str<\/code> expressions are called <a href=\"https:\/\/typing.python.org\/en\/latest\/spec\/annotations.html\" target=\"_blank\" rel=\"noreferrer noopener\">type annotations<\/a>, introduced in Python 3.5. They are written using the following syntax:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"249\" height=\"135\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-21.png\" alt=\"The syntax str expressions are written with.\" class=\"wp-image-108341\"\/><\/figure>\n\n\n\n<p>Type annotations are a useful feature because they enable development tools to provide more accurate auto-completion when accessing object attributes &#8211; for example, in the following code, the type annotation allows tooling to infer that <code>last_name<\/code> holds a string:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"355\" height=\"179\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-22.png\" alt=\"last_name holds a string\" class=\"wp-image-108342\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-22.png 355w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-22-300x151.png 300w\" sizes=\"auto, (max-width: 355px) 100vw, 355px\" \/><\/figure>\n\n\n\n<p>Sadly, however, this type information is not enforced at runtime and is only relied upon when incompatible operations are performed inside the function. You can make horrible calls like this (below), and they will initially run perfectly:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># print a name consisting of a date and pi\nprint(formal_name(2025-12-31,3.14))\n<\/pre><\/div>\n\n\n\n<p>This code won\u2019t crash at the time the function is called as Python will quite happily create two arguments: one called <code>first_name<\/code> (to hold a date), and another called <code>last_name<\/code> (to hold a float value). It&#8217;ll only crash when the code attempts to capitalize &#8216;pi&#8217;!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"995\" height=\"127\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-23.png\" alt=\"\" class=\"wp-image-108343\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-23.png 995w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-23-300x38.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-23-768x98.png 768w\" sizes=\"auto, (max-width: 995px) 100vw, 995px\" \/><\/figure>\n\n\n\n<p>This is a typical Python feature, placing the onus on the developer to follow conventions, and is a recipe for bugs when developer A passes inappropriate data types to a function written by developer B simply because they can. C# programmers &#8211; look on and despair.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-2-module-inconsistency\">2. Module Inconsistency<\/h2>\n\n\n\n<p>Python modules are its superpower: whatever you want to do, there\u2019s a module for it. To test this theory, I asked <a href=\"https:\/\/www.red-gate.com\/simple-talk\/opinion\/editorials\/artificial-intelligence-chatgpt-gobbledegook\/\" target=\"_blank\" rel=\"noreferrer noopener\">ChatGPT<\/a> to help me find a module to manage my (imaginary) Lego collection, more in hope than expectation, and got this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"257\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-24-1024x257.png\" alt=\"\" class=\"wp-image-108344\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-24-1024x257.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-24-300x75.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-24-768x193.png 768w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-24.png 1078w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Truly, there is a module for everything! However, because Python is open-source and its development relies on the goodwill of volunteers, these modules aren\u2019t always consistent &#8211; and that\u2019s an understatement!<\/p>\n\n\n\n<p>Here is one example to illustrate this. The following code uses the popular <a href=\"https:\/\/beautiful-soup-4.readthedocs.io\/en\/latest\/\" target=\"_blank\" rel=\"noreferrer noopener\">BeautifulSoup module<\/a> to print out the first hyperlink on Simple Talk:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >import requests\nfrom bs4 import BeautifulSoup as bs\n\n# go to Simple Talk website\nvisit = requests.get(\"https:\/\/www.red-gate.com\/simple-talk\/\")\n\n# check visit went OK\nif visit.status_code != 200:\n\tprint(visit.status_code)\n\tquit()\n\n# get the underlying HTML and parse it\nhtml = visit.text\nsoup = bs(html, \"html.parser\")\n\n# show first hyperlink\nfirst_link = soup.body.a\nprint(first_link.get(\"href\"))\n<\/pre><\/div>\n\n\n\n<p>However, there is no reliable auto-completion to help you with the penultimate line, as this information is only available at runtime. It\u2019s up to you to guess that you can refer to the body of the HTML page in this way:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"389\" height=\"128\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-25.png\" alt=\"\" class=\"wp-image-108345\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-25.png 389w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-25-300x99.png 300w\" sizes=\"auto, (max-width: 389px) 100vw, 389px\" \/><\/figure>\n\n\n\n<p>BeautifulSoup is an excellent and powerful module \u2013 and the online documentation is, fortunately, very good \u2013 but, much like third-party Python libraries, it doesn\u2019t always follow what you might think of as standard Python conventions. This feels like a side effect of open-source development; perhaps there\u2019s something to be said for having Microsoft in overall control of a codebase after all!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-3-data-type-inconsistency\">3. Data Type Inconsistency<\/h2>\n\n\n\n<p>C# may have a bewildering variety of data types, but at least they are universal: an <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.int32?view=net-10.0\" target=\"_blank\" rel=\"noreferrer noopener\">int32<\/a> will always be an int32, no matter which namespace you\u2019re referencing. This is not the case with Python.<\/p>\n\n\n\n<p>Initially, Python\u2019s numeric types are refreshingly simple (and dare one say, Pythonic?):<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Data type<\/strong><\/td><td><strong>What it contains<\/strong><\/td><\/tr><tr><td><strong>str<\/strong><\/td><td>Any string of text<\/td><\/tr><tr><td><strong>int<\/strong><\/td><td>Any whole number<\/td><\/tr><tr><td><strong>float<\/strong><\/td><td>Any decimal number<\/td><\/tr><tr><td><strong>bool<\/strong><\/td><td>Something which can be either true or false<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>However, many of the most important modules are written in <a href=\"https:\/\/www.w3schools.com\/c\/c_intro.php\" target=\"_blank\" rel=\"noreferrer noopener\">C<\/a>, and therefore use C\u2019s data types. As a result, <a href=\"https:\/\/numpy.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">NumPy<\/a> defines its own set of numeric types (which <a href=\"https:\/\/pandas.pydata.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pandas<\/a> largely build):<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Type of data<\/strong><\/td><td><strong>NumPy data type<\/strong><\/td><td><strong>C equivalents<\/strong><\/td><\/tr><tr><td><em>Integer<\/em><\/td><td><strong>np.int16<\/strong>, <strong>np.int32<\/strong> or <strong>np.int64<\/strong><\/td><td><strong>int16<\/strong>, <strong>int32<\/strong>, <strong>int64<\/strong><\/td><\/tr><tr><td><em>Floating point<\/em><\/td><td><strong>np.float32, np.float64<\/strong><strong><\/strong><\/td><td><strong>single<\/strong>, <strong>double<\/strong><\/td><\/tr><tr><td><em>Boolean<\/em><\/td><td><strong>bool8<\/strong><\/td><td><strong>bool<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>This often means you have to learn one way of doing things in \u201cvanilla\u201d Python and another way in, for example, Pandas. Here\u2019s one of the most egregious examples (I\u2019m so proud of using this word in anger, as I\u2019ve finally learned what it means!). Suppose we want to show, in a Pandas data frame of films, the number of characters in each film\u2019s title (shown twice below):<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"939\" height=\"191\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-26.png\" alt=\"\" class=\"wp-image-108346\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-26.png 939w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-26-300x61.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-26-768x156.png 768w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><\/figure>\n\n\n\n<p>Here\u2019s some sample code to do this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># create a dictionary to hold some films\nfilms_dict = {\n\t\"Id\": [ 1,2,3,5,6 ],\n\t\"Title\": [ \"Jurassic Park\", \"Spider-Man\", \"King Kong\", \"Superman Returns\", \"Titanic\"],\n\t\"Run Time\": [ 126, 121, 187, 154, 194 ],\n\t\"Genre\": [ \"Adventure\", \"Action\",\"Adventure\", \"Action\",\"Romance\"]\n}\n\nimport pandas as pd\n\n# add a field giving length of each film name using a standard\n# Python list comprehension and the \"len\" function\nfilms_dict[\"Title length 1\"] = [len(title) for title in films_dict[\"Title\"]]\n\n# create a dataframe based on this dictionary\nfilm_dataframe = pd.DataFrame(films_dict)\n\n# add a column showing the film name length, converting the Title column\n# (which is held as an object) to a string and then taking the length of this\nfilm_dataframe[\"Title length 2\"] = film_dataframe[\"Title\"].str.len()\nprint(film_dataframe)<\/pre><\/div>\n\n\n\n<p>In vanilla Python you find the length of each string using the built-in <code>len<\/code> function, but once you\u2019re in Pandas you have to convert the column to a string using the <code>str<\/code> property and <em>then<\/em> apply the <code>len<\/code> method &#8211; neither of which are part of standard Python. Inconsistency like this makes Python harder to learn and more unpredictable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-4-python-isn-t-always-as-pythonic-as-it-thinks-it-is\">4. Python isn&#8217;t always as &#8216;pythonic&#8217; as it thinks it is<\/h2>\n\n\n\n<p>While most things in Python are beautifully simple, not everything is. Here are four of my pet bugbears.<\/p>\n\n\n\n<p>If you want to check if something is contained in something else, you have to use this syntax:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >person_names = [\"BRIAN\",\"SALLY\",\"MAX\"]\n\n# test if someone in the list\nif person_name in person_names:\n<\/pre><\/div>\n\n\n\n<p>It would be so much more intuitive to use:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># test if someone in the list\nif person_names.contains(person_name):\n<\/pre><\/div>\n\n\n\n<p>Bizarrely, there is a <a href=\"https:\/\/wiki.python.org\/moin\/DunderAlias\" target=\"_blank\" rel=\"noreferrer noopener\">dunder<\/a> (double-underscore, but you knew that, I\u2019m sure) method called <code>contains<\/code>,which does let you do this.<\/p>\n\n\n\n<p>My second bugbear is the lack of a <code>.length<\/code> property for any sequence:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># this works ...\nprint(len(\"Hippopotamus\"))\n\n# ... but this doesn't\nprint(\"Hippopotamus\".length)\n<\/pre><\/div>\n\n\n\n<p>There\u2019s a hidden <code>len<\/code> property \u2013 why not expose a length property, to make this consistent with the<br>way the rest of string properties and methods work?<\/p>\n\n\n\n<p>A third bugbear \u2013 why when you write to a text file is there no <code>writeline<\/code> method?<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"366\" height=\"124\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-27.png\" alt=\"\" class=\"wp-image-108350\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-27.png 366w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-27-300x102.png 300w\" sizes=\"auto, (max-width: 366px) 100vw, 366px\" \/><\/figure>\n\n\n\n<p>This means you have to remember to include a carriage return whenever you write something to a text file.&nbsp;<\/p>\n\n\n\n<p>And finally, the way you loop over dictionary items is just odd, and hard to remember:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># create a dictionary to hold some films\nfilms_dict = {\n\t\"Jurassic Park\": (126, \"Adventure\", \"Bit scary\"),\n\t\"Titanic\": (194, \"Action\", \"Good, but too long\"),\n\t\"King Kong\": (187, \"Adventure\", \"Not seen\")\n}\n\n# show the film name and genre\nfor key, value in films_dict.items():\n\tprint(\"{0} <\/pre><\/div>\n\n\n\n<p>This would produce this output:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"325\" height=\"100\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-28.png\" alt=\"\" class=\"wp-image-108351\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-28.png 325w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-28-300x92.png 300w\" sizes=\"auto, (max-width: 325px) 100vw, 325px\" \/><\/figure>\n\n\n\n<p>But why do you need to put <code>.items<\/code> after the name of the dictionary, when Python knows it\u2019s a dictionary? And why is <code>items<\/code> a method, instead of a property?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-5-indentation-good-or-bad\">5. Indentation &#8211; good or bad?<\/h2>\n\n\n\n<p>One of the most distinctive features of Python is its reliance on <a href=\"https:\/\/www.w3schools.com\/python\/gloss_python_indentation.asp\" target=\"_blank\" rel=\"noreferrer noopener\">indentation<\/a> for meaning, but this can create code which is difficult to read (and easy to insert bugs within). This pseudo-code would create a series of parties for each of my circles of friends:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># loop over all of my circles of friends (in reality\n# this would be a very short loop)\nFor each of my circles of friends:\n\n\t# for each circle, create a party for its members\n\tCreate a new party for this circle\n\tFor each friend in this circle:\n\n\t\t# for each friend in this circle, invite them\n\t\t# to this particular party IF I like them enough\n\t\tIf I really like them:\n\t\t\tAdd them to my Xmas card list\n\t\t\tInvite them to this party\n<\/pre><\/div>\n\n\n\n<p>This variation would invite every friend to every party, regardless of how much I liked them:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># loop over all of my circles of friends (in reality\n# this would be a very short loop)\nFor each of my circles of friends:\n\n\t# for each circle, create a party for its members\n\tCreate a new party for this circle\n\tFor each friend in this circle:\n\n\t\t# for each friend in this circle, invite them\n\t\t# to this particular party IF I like them enough\n\t\tIf I really like them:\n\t\t\tAdd them to my Xmas card list\n\tInvite them to this party\n<\/pre><\/div>\n\n\n\n<p>You could of course make the same mistake in C#-style code, but it\u2019s a bit more obvious where loops and conditions begin and end:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># loop over all of my circles of friends (in reality\n# this would be a very short loop)\nFor each of my circles of friends {\n\n\t# for each circle, create a party for its members\n\tCreate a new party for this circle\n\tFor each friend in this circle {\n\n\t\t# for each friend in this circle, invite them\n\t\t# to this particular party IF I like them enough\n\t\tIf I really like them {\n\t\t\tAdd them to my Xmas card list\n\t\t\tInvite them to this party\n\t\t}\n\t}\n}\n<\/pre><\/div>\n\n\n\n<p>Of course, it\u2019s clearest of all in Visual Basic, but that\u2019s another story \u2026\ud83d\ude42<\/p>\n\n\n\n<p>The problem really comes when I copy code from a website, and lose the indentation:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># loop over all of my circles of friends (in reality\n# this would be a very short loop)\nFor each of my circles of friends:\n\n# for each circle, create a party for its members\nCreate a new party for this circle\nFor each friend in this circle:\n\n# for each friend in this circle, invite them\n# to this particular party IF I like them enough\nIf I really like them:\nAdd them to my Xmas card list\nInvite them to this party\n<\/pre><\/div>\n\n\n\n<p>The chances of introducing a bug in this code are high, whereas with almost any other language it would still be clear where loops and conditions begin and end. In Visual Studio, for example, you could press Ctrl + K followed by Ctrl + D to restore the correct indentation automatically, an option that simply doesn\u2019t work the same way for Python.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-6-oh-those-classes\">6. Oh, Those Classes!<\/h2>\n\n\n\n<p>I\u2019ve left this until halfway down my list because I didn\u2019t want to lose too many readers at the start, but classes in Python are just pants. Fortunately, the majority of Python programmers never discover this because you can create ad-hoc programs in Python without ever creating classes!<\/p>\n\n\n\n<p>Suppose you want to create a simple (if pointless) class to allow someone to create a pet object like this in Python:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># example use of class - creating Neo the Cat\nmy_pet = Pet(\"Neo\", \"Cat\")\n\n# create and show description of pet\nmy_pet.Document()\nprint(my_pet.output)\n<\/pre><\/div>\n\n\n\n<p>Or like this in C# (I\u2019m outputting the text to a web page):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/\/ example use of class - creating Neo the Cat\nvar my_pet = new Pet(\"Neo\", \"Cat\");\n\n\/\/ show what we've created\nmy_pet.Document();\nPetList = my_pet.Output;\n<\/pre><\/div>\n\n\n\n<p>Either of these code snippets should produce output like this for a cat called Neo:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"251\" height=\"110\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-29.png\" alt=\"\" class=\"wp-image-108352\"\/><\/figure>\n\n\n\n<p>Here is the code you could create in C#:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >public class Pet\n {\n\t public string Output = \"\";\n\t \/\/ a class to hold instances of people's pets\n\n\t \/\/ private variables\n\t private string name = null;\n\t private int? legs = null;\n\n\t \/\/ used to determine number of legs given animal type\n\t private Dictionary&lt;string, int&gt; animalLegs = new Dictionary&lt;string, int&gt;\n\t { { \"dog\", 4 }, { \"cat\", 4 }, { \"bird\", 2 }, { \"fish\", 0 } };\n\n\t public Pet(string petName, string petAnimal)\n\t {\n\n\t\t \/\/ the constructor: when you create a pet, must give it a \n\t\t \/\/ name and say what sort of animal it is\n\t\t name = petName;\n\n\t\t \/\/ setting the animal type will set the number of legs\n\t\t Animal = petAnimal;\n\t }\n\n\t \/\/ read\/write property for the animal type\n\t private string animal = null;\n\t public string Animal\n\t {\n\t\t get\n\t\t {\n\t\t\t return animal;\n\t\t }\n\t\t set\n\t\t {\n\t\t\t animal = value;\n\t\t\t if (animalLegs.TryGetValue(value.ToLower(), out int legCount))\n\t\t\t {\n\t\t\t\t legs = legCount;\n\t\t\t }\n\t\t\t else\n\t\t\t {\n\t\t\t\t legs = null;\n\t\t\t }\n\t\t }\n\t }\n\n\t public void Document()\n\t {\n\n\t\t \/\/ a method to print out the pet's details\n\t\t Output += $\"Pet Name: {name}&lt;br \/&gt;\";\n\t\t Output += $\"Animal Type: {animal}&lt;br \/&gt;\";\n\t\t Output += $\"Number of Legs: {legs}\";\n\t }\n }\n<\/pre><\/div>\n\n\n\n<p>The Animal property is public, and it\u2019s &#8216;getter&#8217; and &#8216;setter&#8217; define the public interface, so when you consume the class you only see the publicly exposed properties and methods of each object:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"259\" height=\"219\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-30.png\" alt=\"\" class=\"wp-image-108353\"\/><\/figure>\n\n\n\n<p>This is as it should be. Compare this with the Python (rough) equivalent:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >class Pet:\n\t# a class to hold instances of people's pets\n\n\t# used to determine number of legs given animal type\n\t_leg_lookup = { \"dog\": 4, \"cat\": 4, \"bird\": 2, \"fish\": 0\t}\n\n\tdef __init__(self, name, animal):\n\n\t\t# the constructor: when you create a pet, must give it a \n\t\t# name and say what sort of animal it is\n\t\tself.Name = name\n\t\t\n\t\t# setting the animal type will set the number of legs too\n\t\tself.Animal = animal  \n\n\t\t# the final output\n\t\tself.output = \"\";\n\n\t# read\/write property for the animal type\n\t@property\n\tdef Animal(self):\n\t\treturn self._animal\n\n\t@Animal.setter\n\tdef Animal(self, value):\n\t\tself._animal = value\n\t\t\n\t\t# set legs based on lookup, defaulting to None if unknown\n\t\tself.Legs = self._leg_lookup.get(value.lower(), None)\n\n\tdef Document(self):\n\n\t\t# a method to print out the pet's details\n\t\tself.output += f\"Pet Name: {self.Name}\\n\";\n\t\tself.output += f\"Animal Type: {self.Animal}\\n\"\n\t\tself.output += f\"Number of Legs: {self.Legs}\"\n<\/pre><\/div>\n\n\n\n<p>When I come to consume the class, I see not only the properties I want to see, but also the ones I&#8217;d prefer to be private (so, not visible):<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"316\" height=\"249\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-31.png\" alt=\"\" class=\"wp-image-108354\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-31.png 316w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-31-300x236.png 300w\" sizes=\"auto, (max-width: 316px) 100vw, 316px\" \/><\/figure>\n\n\n\n<p>The syntax of a read-write property is itself the opposite of a thing of beauty:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># read\/write property for the animal type\n\t@property\n\tdef Animal(self):\n\t\treturn self._animal\n\n\t@Animal.setter\n\tdef Animal(self, value):\n\t\tself._animal = value\n\t\t\n\t\t# set legs based on lookup, defaulting to None if unknown\n\t\tself.Legs = self._leg_lookup.get(value.lower(), None)\n<\/pre><\/div>\n\n\n\n<p>There\u2019s no obvious reason why this should be the syntax for the <code>get<\/code> and <code>set<\/code> parts of a property. The constructor (the bit of code which runs whenever an instance of a class is initialized) is nasty too:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >def __init__(self, name, animal):\n\n\t\t# the constructor: when you create a pet, must give it a \n\t\t# name and say what sort of animal it is\n\t\tself.Name = name\n\t\t\n\t\t# setting the animal type will set the number of legs too\n\t\tself.Animal = animal  \n\n\t\t# the final output\n\t\tself.output = \"\";<\/pre><\/div>\n\n\n\n<p>It\u2019s irritating having to keep passing a reference to the object being created (<code>self<\/code>) to each method and property, and this implicit way of defining properties is anything but clear.<\/p>\n\n\n\n<p>I could go on, but I think I already have for long enough! It\u2019s as if whoever created Python abandoned usability when they got to classes.<\/p>\n\n\n\n<section id=\"my-first-block-block_ab13027ba538adbeca124683f6c27609\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Enjoying this article? Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Get selected articles, event information, podcasts and other industry content delivered straight to your inbox.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Subscribe now: Enjoying this article? Subscribe to the Simple Talk newsletter\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-7-python-is-interpreted-not-compiled\">7. Python is interpreted &#8211; not compiled<\/h2>\n\n\n\n<p>Python is <a href=\"https:\/\/inventwithpython.com\/blog\/is-python-compiled-or-interpreted.html#:~:text=The%20short%20answer%20is%3A%20Python,because%20it's%20written%20in%20C.\" target=\"_blank\" rel=\"noreferrer noopener\">interpreted, not compiled<\/a>. What this means is that, every time you run a Python program, the interpreter has to read through it line-by-line in sequential order, executing each line as it does so.<\/p>\n\n\n\n<p>There are two main downsides arising from Python\u2019s approach. The first is that Python runs more slowly than a compiled language like C#. I will freely admit I\u2019m not an authority on the subject, and can only dutifully report what others tell me: that Python uses more memory, has no support for true parallel execution of threads, and loads more slowly. For the programs I write, Python\u2019s speed has never been an issue, but that\u2019s because I rarely work with machine learning, <a href=\"https:\/\/www.red-gate.com\/blog\/inside-perspectives-what-does-ai-mean-for-redgate\" target=\"_blank\" rel=\"noreferrer noopener\">AI<\/a>, or vast amounts of data.<\/p>\n\n\n\n<p>The second downside is that Python forces you to write programs in a certain order, and in particular you must declare functions before you can call them:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >def get_bmi(\n\tweight_in_kilos:float,\n\theight_in_metres: float\n) -&gt; float:\n\n\t\"\"\"\n\tCalculates someone's BMI\n\t\"\"\"\n\treturn weight_in_kilos \/ (height_in_metres ** 2)\n\n# show my BMI\nprint(get_bmi(80,1.8))\n<\/pre><\/div>\n\n\n\n<p>Compare this with the more logical C# version, where you can call a function at the top of your program:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >Console.WriteLine(getBMI(80.0, 1.8).ToString(\"0.000\"));\n\n\nprivate double getBMI(\n\tdouble weightKilos,\n\tdouble heightMetres\n)\n{\n\t\/\/ calculate someonne's BMI\n\treturn weightKilos \/ (heightMetres * heightMetres);\n}\n<\/pre><\/div>\n\n\n\n<p>C# first compiles any program into an executable before running it, which means you can put the functions and function calls in any order you like.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-8-dynamically-typed-variables\">8. Dynamically-typed variables<\/h2>\n\n\n\n<p>I suspect that the average C# programmer would have put this at the start of their list, but I can only report on nuisances in the order in which I perceive them!<\/p>\n\n\n\n<p>Consider this block of code:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># start with a simple integer counter\ncount = 0\t\t  \ncount += 1\t\t \n\n# much later in the code, someone\n# accidentally assigns it as a string\ncount = \"one\"\t  \n\n# then within a later loop, Python will crash\ncount += 1\n<\/pre><\/div>\n\n\n\n<p>This creates an <code>int<\/code> variable called <code>count<\/code>, but then replaces it with a <code>str<\/code> variable with the same name (I\u2019m aware that this isn\u2019t exactly what happens, but it\u2019s a good model of it). The final line will then crash, since it\u2019s trying to add 1 to a string. You wouldn\u2019t and couldn\u2019t get this bug in C#:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"593\" height=\"278\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-32.png\" alt=\"\" class=\"wp-image-108355\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-32.png 593w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-32-300x141.png 300w\" sizes=\"auto, (max-width: 593px) 100vw, 593px\" \/><\/figure>\n\n\n\n<p>The problem isn\u2019t that Python implicitly converts data types from one thing to another \u2013 it doesn\u2019t \u2013 but that Python doesn\u2019t prevent you rebinding a name to a value of a different type. I have to admit that I\u2019ve never found this to be a problem myself, but enough other people do that it would be remiss of me not to include it in this list.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-9-releasing-and-packaging-systems\">9. Releasing and packaging systems<\/h2>\n\n\n\n<p>The great benefit of a compiled language is that you can produce a single executable file. Here is the single file containing all of the C# code for our website (apart from the bits embedded in web pages):<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"621\" height=\"96\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-33.png\" alt=\"\" class=\"wp-image-108356\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-33.png 621w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-33-300x46.png 300w\" sizes=\"auto, (max-width: 621px) 100vw, 621px\" \/><\/figure>\n\n\n\n<p>This makes distributing a system comparatively simple \u2013 you just copy over the single executable file to the relevant network drive. OK, I\u2019m over-simplifying, particularly as you have to ensure that all the referenced packages are also included, but you the principle holds.<\/p>\n\n\n\n<p>With Python, things are more complicated as you have to package together all of the <code>.py<\/code> files that you\u2019ve created along with any third-party modules they depend on. I\u2019ll admit that the only time I ever tried doing this (using <a href=\"https:\/\/pyinstaller.org\/en\/stable\/\" target=\"_blank\" rel=\"noreferrer noopener\">PyInstaller<\/a>), I gave up.<\/p>\n\n\n\n<p>The situation is further complicated by the way Python uses virtual environments to manage different versions of modules. In the simple diagram below there are two virtual environments: the red one containing modules used by the timesheet system, and the blue one containing modules used by the expenses system.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"518\" height=\"328\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-34.png\" alt=\"\" class=\"wp-image-108357\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-34.png 518w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-34-300x190.png 300w\" sizes=\"auto, (max-width: 518px) 100vw, 518px\" \/><\/figure>\n\n\n\n<p>Unfortunately, new versions of modules (and of Python) are released quite often, so you can end up with several different virtual environments on your computer &#8211; each containing more-or-less duplicate files for your Python interpreter and the modules you&#8217;re importing.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-10-python-s-guis-are-hard-to-use\">10. Python&#8217;s GUIs are hard to use<\/h2>\n\n\n\n<p>Python comes with a built-in GUI called <a href=\"https:\/\/docs.python.org\/3\/library\/tkinter.html\" target=\"_blank\" rel=\"noreferrer noopener\">TKinter<\/a>, which can be used to create tools like <a href=\"https:\/\/www.nytimes.com\/games\/wordle\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">Wordle<\/a>, and a while ago I did exactly that. However, TKinter is also quite limited, so I then spent some considerable time trying to write a GUI using <a href=\"https:\/\/wiki.python.org\/moin\/PyQt\" target=\"_blank\" rel=\"noreferrer noopener\">PyQt<\/a> &#8211; another popular Python module for creating a forms-based system &#8211; using the separate <a href=\"https:\/\/www.pythonguis.com\/installation\/install-qt-designer-standalone\/\" target=\"_blank\" rel=\"noreferrer noopener\">Qt Designer<\/a> to draw my forms on-screen:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"721\" height=\"388\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-35.png\" alt=\"\" class=\"wp-image-108358\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-35.png 721w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/02\/image-35-300x161.png 300w\" sizes=\"auto, (max-width: 721px) 100vw, 721px\" \/><\/figure>\n\n\n\n<p>Reader, eventually <a href=\"https:\/\/www.wiseowl.co.uk\/other-blogs\/blogs\/hardware-software\/it-musings\/pyqt5-editor-progress\/\" target=\"_blank\" rel=\"noreferrer noopener\">I gave up<\/a>. The biggest problem was working with a built-in widget called <code>QTableView<\/code> to control the display of rows of data, which I found pretty much unmanageable. I\u2019ve also worked with <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/from-winform-to-wpf-a-quick-reference-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">WPF and Windows Forms<\/a> and have to say that WinForms was by far the simplest of the GUI interfaces to work with &#8211; although I accept it\u2019s platform-dependent and not as powerful.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-so-what-do-i-use-python-for-where-does-it-shine\">So, what <em>do<\/em> I use Python for &#8211; where does it shine?<\/h2>\n\n\n\n<p>Python remains my preferred language for solving one-off problems, whether that involves processing files, converting images, finding broken links on a website, automating the submission of prompts to AI tools or any other ad-hoc problem. I find the syntax easy to remember and a pleasure to use. I\u2019m a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/10-reasons-python-better-than-c-sharp\/\" target=\"_blank\" rel=\"noreferrer noopener\">particular fan<\/a> of sequences and slicing in Python.<\/p>\n\n\n\n<p>However, if you asked me to build a larger system with a longer lifetime, perhaps involving a team of programmers, Python&#8217;s class model alone would push me towards a more conventional language such as C#. As they say in the UK (I have no idea whether this expression is universally known in English) &#8211; <em>horses for courses!<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-latest-from-simple-talk\"><strong>The latest from Simple Talk:<\/strong><\/h3>\n\n\n<ul class=\"wp-block-latest-posts__list wp-block-latest-posts\"><li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/data-privacy-and-protection\/how-to-build-a-privacy-aware-analytics-layer-with-sql-4-top-techniques\/\">How to build a privacy-aware analytics layer with SQL (4 top techniques)<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/why-database-ownership-is-so-fragmented-in-2026-and-what-you-can-do-about-it\/\">Why database ownership is so fragmented in 2026 \u2013 and what you can do about it<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/cloud\/how-to-ensure-success-following-a-cloud-migration\/\">How to ensure success following a cloud migration<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/how-dataops-helps-reduce-gen-ai-risk-improve-data-quality\/\">How DataOps principles help to reduce GenAI risk and improve data quality<\/a><\/li>\n<li><a class=\"wp-block-latest-posts__post-title\" href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-coalesce-explained-how-to-handle-null-values-easily\/\">Why COALESCE might be the most useful SQL function you\u2019re not using right<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Here are 10 reasons to avoid using Python for large or long-lived systems despite its strengths, featuring key limitations such as dynamic typing, module inconsistencies, and performance and maintainability concerns.&hellip;<\/p>\n","protected":false},"author":9783,"featured_media":108362,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143526,53,146042],"tags":[4144,5021],"coauthors":[6782],"class_list":["post-108340","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","category-featured","category-python","tag-opinion","tag-python"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/108340","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/9783"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=108340"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/108340\/revisions"}],"predecessor-version":[{"id":108375,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/108340\/revisions\/108375"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/108362"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=108340"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=108340"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=108340"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=108340"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}