Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 418
Lone Learner Avatar asked Mar 10 '14 07:03

Lone Learner


People also ask

What does Staticmethod do in Python?

The staticmethod() returns a static method for a function passed as the parameter.

Can you call a static method without an instance?

Static method can be called without creating an object or instance. Simply create the method and call it directly.

What is the point of @staticmethod?

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.

What does Staticmethod decorator do Python?

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.


Video Answer


2 Answers

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'>
like image 94
Bakuriu Avatar answered Dec 11 '22 04:12

Bakuriu


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.

like image 43
user2357112 supports Monica Avatar answered Dec 11 '22 03:12

user2357112 supports Monica