What magic does staticmethod() do, so that the static method is always called without the instance parameter?

StackOverflow https://stackoverflow.com/questions/22294450

문제

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?

도움이 되었습니까?

해결책

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top