
{"id":5200,"date":"2025-03-29T22:47:53","date_gmt":"2025-03-30T02:47:53","guid":{"rendered":"https:\/\/ikriv.com\/blog\/?p=5200"},"modified":"2025-03-29T22:47:53","modified_gmt":"2025-03-30T02:47:53","slug":"python-the-land-of-abstract-static-methods","status":"publish","type":"post","link":"https:\/\/ikriv.com\/blog\/?p=5200","title":{"rendered":"Python: the land of abstract static methods"},"content":{"rendered":"<h2>Summary<\/h2>\n<p>Unlike many other languages, Python allows abstract static methods (<a href=\"https:\/\/docs.python.org\/3\/library\/abc.html#abc.abstractmethod\">see the reference<\/a>).<\/p>\n<p>A class directly or indirectly deriving from <code>abc.ABC<\/code> cannot be instantiated, unless all its abstract methods are redefined. This includes static methods. It is possible to call an &#8220;abstract&#8221; static method, but an attempt to instantiate a class containing it leads to a <code>TypeError<\/code>.<\/p>\n<h2>Example<\/h2>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom abc import abstractmethod, ABC\r\n\r\nclass A(ABC):\r\n    @staticmethod\r\n    @abstractmethod\r\n    def static_method():\r\n        print(&quot;A.static_method()&quot;)\r\n\r\nA.static_method() # prints A.static_method()\r\n\r\nobj = A()         # raises TypeError: Can&#039;t instantiate abstract class A \r\n                  # without an implementation for abstract method &#039;static_method&#039;\r\n<\/pre>\n<h2>Some Mechanics<\/h2>\n<p>In Python, <code>@abstractmethod<\/code> is a decorator, not a keyword. It is defined in a library as follows:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef abstractmethod(funcobj):\r\n    funcobj.__isabstractmethod__ = True\r\n    return funcobj\r\n<\/pre>\n<h3>Any function or class can be marked abstract<\/h3>\n<p><code>@abstractmethod<\/code> is a regular decorator that can technically be applied to <em>any<\/em> function or class. It is only checked by the constructor of <code>abc.ABC<\/code>. Any occurrences of <code>@abstractmethod<\/code> outside of classes descending from <code>abc.ABC<\/code> are ignored.<\/p>\n<p>All of the following is legal in Python as of version 3.13, although it is confusing and not recommended for production use:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom abc import abstractmethod\r\n\r\n# don&#039;t do this in production\r\n@abstractmethod\r\ndef my_func():\r\n    @abstractmethod\r\n    def inner_func():\r\n        pass\r\n    \r\n@abstractmethod\r\nclass Foo:\r\n    pass\r\n<\/pre>\n<h3>It only matters, if ABC is involved<\/h3>\n<p>You can create an instance of a class with an abstract method, as long as it is not a descendant of <code>abc.ABC<\/code>:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom abc import abstractmethod\r\n\r\nclass Base: # does not derive from abc.ABC\r\n    @abstractmethod\r\n    def method(self):\r\n        pass\r\n    \r\nobj = Base() # works; it would fail only if Base derives from abc.ABC\r\n<\/pre>\n<h3>Classes can be abstract methods too<\/h3>\n<p>It is possible to decorate classes with <code>@abstractmethod<\/code>, even though it is probably not the intended use. Having an abstract inner class in a class descended from <code>abc.ABC<\/code> will trigger an exception upon instantiation. The error message still says you need to implement a &#8220;method&#8221;:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom abc import abstractmethod, ABC\r\n\r\nclass AbstractBase(ABC):\r\n    @abstractmethod\r\n    class Inner: # don&#039;t do this in production\r\n        pass\r\n\r\nobj = AbstractBase() # TypeError: Can&#039;t instantiate abstract class AbstractBase \r\n                     # without an implementation for abstract method &#039;Inner&#039;\r\n<\/pre>\n<h3>You can override an abstract method with anything<\/h3>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom abc import abstractmethod, ABC\r\n\r\nclass Base(ABC):\r\n    @abstractmethod\r\n    def method(self):\r\n        pass\r\n\r\n# don&#039;t do this in production\r\nclass DerivedA(Base):\r\n    def method(self, arg1, arg2): # wrong number of arguments\r\n        pass \r\n\r\nclass DerivedB(Base):\r\n    @staticmethod\r\n    def method():                 # static method overriding a regular method\r\n        pass\r\n\r\nclass DerivedC(Base):\r\n    method = 10                   # integer field overriding an abstract method\r\n    \r\nclass Derivedd(Base):\r\n    class method:                 # inner class overriding an abstract method\r\n        pass\r\n\r\nmonsters = &#x5B;DerivedA(), DerivedB(), DerivedC(), DerivedD()] # all cool, no errors\r\n<\/pre>\n<h3>Calling abstract methods is OK<\/h3>\n<p>Abstract methods can have implementations. It is possible to call an abstract static method:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom abc import abstractmethod, ABC\r\n\r\nclass AbstractBase(ABC):\r\n    @staticmethod\r\n    @abstractmethod\r\n    def abstract_static_method():\r\n        print(&quot;method()&quot;)\r\n\r\nAbstractBase.abstract_static_method() # prints &quot;method()&quot;\r\nobj = AbstractBase()                  # raises TypeError\r\n<\/pre>\n<h2>Are abstract static methods useful?<\/h2>\n<p>Abstract static methods make zero sense in classic OOP, but the fact that an example of such method is included in the Python documentation means that this feature is intentional. Compare it to the &#8220;abstract inner class&#8221; feature, that is missing from the documentation, and also produces a weird error message.<\/p>\n<p>One take on it is that this allows to make static methods part of a derived class contract: the derived class is not &#8220;complete&#8221; unless it implements these static methods.<br \/>\nAs far as I know, it is not possible to specify such contract in Java or C#. In C++, we can achieve similar behavior using concepts:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ C++: concept to check for static method\r\ntemplate &lt;typename T&gt;\r\nconcept HasStaticMethod = requires {\r\n    { T::my_static_method() } -&gt; std::same_as&lt;void&gt;;\r\n};\r\n\r\ntemplate &lt;HasStaticMethod T&gt;\r\nclass DoSomething {\r\n   ...\r\n};\r\n<\/pre>\n<p>Class <code>DoSometing<\/code> won&#8217;t accept parameter T unless it defines a static method <code>void my_static_method()<\/code>.<\/p>\n<h2>In conclusion<\/h2>\n<p>Python&#8217;s &#8220;ask for forgiveness, not permission&#8221; philosophy fully applies to its abstract methods. They are implemented by a library feature that can be abused in many different ways, and is not really strongly enforced. Sometimes it can lead to interesting creative solutions, but oftentimes it leads to confusing and outright incorrect code. But that&#8217;s the nature Python: what other languages consider a sin, in Python is forgiven by default.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary Unlike many other languages, Python allows abstract static methods (see the reference). A class directly or indirectly deriving from abc.ABC cannot be instantiated, unless all its abstract methods are <a href=\"https:\/\/ikriv.com\/blog\/?p=5200\" class=\"more-link\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"Layout":"","footnotes":""},"categories":[4,26],"tags":[],"class_list":["entry","author-ikriv","post-5200","post","type-post","status-publish","format-standard","category-hack","category-python"],"_links":{"self":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/5200","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5200"}],"version-history":[{"count":12,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/5200\/revisions"}],"predecessor-version":[{"id":5212,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/5200\/revisions\/5212"}],"wp:attachment":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5200"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5200"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5200"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}