Reputation: 20618
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?
Upvotes: 7
Views: 341
Reputation: 280227
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.
Upvotes: 5
Reputation: 101909
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'>
Upvotes: 9