I am trying to understand how static methods work internally. I know how to use @staticmethod
decorator but I will be avoiding its use in this post in order to dive deeper into how static methods work and ask my questions.
From what I know about Python, if there is a class A
, then calling A.foo()
calls foo()
with no arguments whereas calling A().foo()
calls foo()
with one argument where that one argument is the instance A()
itself.
However, in case of static methods, it seems always foo()
is called with no arguments whether we call it as A.foo()
or A().foo()
.
Proof below:
>>> class A:
... x = 'hi'
... def foo():
... print('hello, world')
... bar = staticmethod(foo)
...
>>> A.bar()
hello, world
>>> A().bar()
hello, world
>>> A.bar
<function A.foo at 0x00000000005927B8>
>>> A().bar
<function A.foo at 0x00000000005927B8>
>>> A.bar(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes 0 positional arguments but 1 was given
>>> A().bar(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes 0 positional arguments but 1 was given
So am I right in concluding that the staticmethod()
function does some magic such that foo()
is always called with 0 arguments?
If I were to define my own staticmethod()
in my own Python code, how would I do it? Is it even possible to define such a method from our own Python code, or can such a function be only defined as a builtin?
The staticmethod() returns a static method for a function passed as the parameter.
Static method can be called without creating an object or instance. Simply create the method and call it directly.
Static methods can be used to group similar utility methods under the same class. For methods within a class, you either need to add self as the first argument or decorate the method with @staticmethod . "Non-decorated methods" without arguments will raise an error.
The @staticmethod is a built-in decorator that defines a static method in the class in Python. A static method doesn't receive any reference argument whether it is called by an instance of a class or by the class itself.
It's implemented as a descriptor. For example:
In [1]: class MyStaticMethod(object):
...: def __init__(self, func):
...: self._func = func
...: def __get__(self, inst, cls):
...: return self._func
...:
In [2]: class A(object):
...: @MyStaticMethod
...: def foo():
...: print('Hello, World!')
...:
In [3]: A.foo()
Hello, World!
In [4]: A().foo()
Hello, World!
In the same way you can define classmethod
, just passing the cls
to the original function:
In [5]: from functools import partial
...:
...: class MyClassMethod(object):
...: def __init__(self, func):
...: self._func = func
...: def __get__(self, inst, cls):
...: return partial(self._func, cls)
In [6]: class A(object):
...: @MyClassMethod
...: def foo(cls):
...: print('In class: {}'.format(cls))
...:
In [7]: A.foo()
In class: <class '__main__.A'>
In [8]: A().foo()
In class: <class '__main__.A'>
Methods, including instance, static, and class methods, work through the descriptor protocol. If an object in a class dict implements the __get__
special method:
class Descriptor(object):
def __get__(self, instance, klass):
return instance, klass
class HasDescriptor(object):
descriptor = Descriptor()
x = HasDescriptor()
then the following attribute accesses:
x.descriptor
HasDescriptor.descriptor
will call the descriptor's __get__
method to compute their value, like so:
descriptor.__get__(x, HasDescriptor)
descriptor.__get__(None, HasDescriptor)
Functions, staticmethod
, and classmethod
all implement __get__
to make method access work. You can do the same:
class MyStaticMethod(object):
def __init__(self, f):
self.f = f
def __get__(self, instance, klass):
return self.f
There are also __set__
and __delete__
methods that allow you to control setting and deleting attributes, respectively. Methods don't use these, but property
does.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With