Ron HD
Ron HD

Reputation: 388

Meaning of staticmethod object's description?

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

Answers (2)

chepner
chepner

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

user2357112
user2357112

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

Related Questions