Reputation: 388
I understand the @staticmethod
decorator in practice. But a bug in mocking a static method led me down the Python semantics rabbit hole. This description in The standard type hierarchy section is confusing me:
Static method objects provide a way of defeating the transformation of function objects to method objects described above. A static method object is a wrapper around any other object, usually a user-defined method object. When a static method object is retrieved from a class or a class instance, the object actually returned is the wrapped object, which is not subject to any further transformation. Static method objects are not themselves callable, although the objects they wrap usually are. Static method objects are created by the built-in staticmethod() constructor.
The staticmethod()
constructor takes a function object as sole argument. How can it wrap any other object than a function object? Even if this doesn't fail, how does it make any sense?
How is it usually a wrapper around a user-defined method object instead of a function object? User-defined method objects, when called, add the object they're called on to the start of the argument list, then call the function object stored on the class (ignoring all the various special cases).
How is it that static method objects are not themselves callable? How do calls to these work, then?
Upvotes: 2
Views: 306
Reputation: 530882
You can see that staticmethod
can take any argument:
>>> x = staticmethod(3)
and that it is, indeed, not callable:
>>> x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
staticmethod
doesn't do much more than store a reference to its argument. The "magic" happens when you try to access a staticmethod
object as the attribute of a class
object or an instance of a class. When you do, you get the result of the staticmethod
method's __get__
method, which is... the thing you originally wrapped.
>>> x.__get__(x)
3
Don't worry about why we passed x
as an argument; suffice it to say, staticmethod.__get__
mostly ignores its argument(s).
When you wrap a function in a class
statement, the staticmethod
saves a reference to the function, to be called later when you ask for it.
>>> class Foo(object):
... @staticmethod
... def x():
... pass
...
>>> type(Foo.__dict__['x'])
<type 'staticmethod'>
>>> type(Foo.x)
<type 'function'>
Instance methods work the way they do because function.__get__
returns an instance of method
, which is in some sense just the original function partially applied the instance that invokes it. You may have seen that x.foo()
is the same as type(x).foo(x)
. The reason that is true is because x.foo
first resolves to type(x).foo
, which itself evaluates to type(x).__dict__['foo'].__get__(x, type(x)
. The return value of function.__get__
is basically a wrapper around the function foo
, but with x
already supplied as the first argument.
staticmethod
's main purpose is to provide a different __get__
method.
Incidentally, classmethod
serves the same purpose. classmethod.__get__
returns something that calls the wrapped function with the class as the first argument, whether you invoke the class method from an instance of the class or the class itself.
Upvotes: 3
Reputation: 280227
How can it wrap any other object than a function object?
Pretty easily.
class Example(object):
example = staticmethod(5)
print(Example.example) # prints 5
You can pass anything you want to the staticmethod
constructor.
Even if this doesn't fail, how does it make any sense?
It usually doesn't, but staticmethod
doesn't check.
How is it usually a wrapper around a user-defined method object instead of a function object?
It's not. That part's just wrong.
How is it that static method objects are not themselves callable? How do calls to these work, then?
The descriptor protocol. Static method objects have a __get__
method that returns whatever object they wrap. Attribute access invokes this __get__
method and returns what __get__
returns.
Upvotes: 2