问题
>>> class A(object): pass
>>> def func(cls): pass
>>> A.func = func
>>> A.func
<unbound method A.func>
How does this assignment create a method? It seems unintuitive that assignment does the following for classes:
- Turn functions into unbound instance methods
- Turn functions wrapped in
classmethod()
into class methods (actually, this is pretty intuitive) - Turn functions wrapped in
staticmethod()
into functions
It seems that for the first, there should be an instancemethod()
, and for the last one, there shouldn't be a wrapper function at all. I understand that these are for uses within a class
block, but why should they apply outside of it?
But more importantly, how exactly does assignment of the function into a class work? What magic happens that resolves those 3 things?
Even more confusing with this:
>>> A.func
<unbound method A.func>
>>> A.__dict__['func']
<function func at 0x...>
But I think this is something to do with descriptors, when retrieving attributes. I don't think it has much to do with the setting of attributes here.
回答1:
You're right that this has something to do with descriptor protocol. Descriptors are how passing the receiver object as the first parameter of a method is implemented in Python. You can read more detail about Python attribute lookup from here. The following shows on a bit lower level, what is happening when you do A.func = func; A.func:
# A.func = func
A.__dict__['func'] = func # This just sets the attribute
# A.func
# The __getattribute__ method of a type object calls the __get__ method with
# None as the first parameter and the type as the second.
A.__dict__['func'].__get__(None, A) # The __get__ method of a function object
# returns an unbound method object if the
# first parameter is None.
a = A()
# a.func()
# The __getattribute__ method of object finds an attribute on the type object
# and calls the __get__ method of it with the instance as its first parameter.
a.__class__.__dict__['func'].__get__(a, a.__class__)
# This returns a bound method object that is actually just a proxy for
# inserting the object as the first parameter to the function call.
So it's the looking up of the function on a class or an instance that turns it into a method, not assigning it to a class attribute.
classmethod
and staticmethod
are just slightly different descriptors, classmethod returning a bound method object bound to a type object and staticmethod just returns the original function.
回答2:
Descriptors are the magic1 that turns an ordinary function into a bound or unbound method when you retrieve it from an instance or class, since they’re all just functions that need different binding strategies. The classmethod
and staticmethod
decorators implement other binding strategies, and staticmethod
actually just returns the raw function, which is the same behavior you get from a non-function callable
object.
See “User-defined methods” for some gory details, but note this:
Also notice that this transformation only happens for user-defined functions; other callable objects (and all non-callable objects) are retrieved without transformation.
So if you wanted this transformation for your own callable object, you could just wrap it in a function, but you could also write a descriptor to implement your own binding strategy.
Here’s the staticmethod
decorator in action, returning the underlying function when it’s accessed.
>>> @staticmethod
... def f(): pass
>>> class A(object): pass
>>> A.f = f
>>> A.f
<function f at 0x100479398>
>>> f
<staticmethod object at 0x100492750>
Whereas a normal object with a __call__
method doesn’t get transformed:
>>> class C(object):
... def __call__(self): pass
>>> c = C()
>>> A.c = c
>>> A.c
<__main__.C object at 0x10048b890>
>>> c
<__main__.C object at 0x10048b890>
1 The specific function is func_descr_get
in Objects/funcobject.c.
回答3:
What you have to consider is that in Python everything is an object. By establishing that it is easier to understand what is happening. If you have a function def foo(bar): print bar
, you can do spam = foo
and call spam(1)
, getting of course, 1
.
Objects in Python keep their instance attributes in a dictionary called __dict__
with a "pointer" to other objects. As functions in Python are objects as well, they can be assigned and manipulated as simple variables, passed around to other functions, etc. Python's implementation of object orientation takes advantage of this, and treats methods as attributes, as functions that are in the __dict__
of the object.
Instance methods' first parameter is always the instance object itself, generally called self
(but this could be called this
or banana
). When a method is called directly on the class
, it is unbound to any instance, so you have to give it an instance object as the first parameter (A.func(A())
). When you call a bound function (A().func()
), the first parameter of the method, self
, is implicit, but behind the curtains Python does exactly the same as calling directly on the unbound function and passing the instance object as the first parameter.
If this is understood, the fact that assigning A.func = func
(which behind the curtains is doing A.__dict__["func"] = func
) leaves you with an unbound method, is unsurprising.
In your example the cls
in def func(cls): pass
actually what will be passed on is the instance (self
) of type A
. When you apply the classmethod or staticmethod decorators do nothing more than take the first argument obtained during the call of the function/method, and transform it into something else, before calling the function.
classmethod
takes the first argument, gets the class
object of the instance, and passes that as the first argument to the function call, while staticmethod
simply discards the first parameter and calls the function without it.
回答4:
Point 1: The function func
you defined exists as a First-Class Object in Python.
Point 2: Classes in Python store their attributes in their __dict__
.
So what happens when you pass a function as the value of a class attribute in Python? That function is stored in the class' __dict__
, making it a method of that class accessed by calling the attribute name you assigned it to.
回答5:
Relating to MTsoul's comment to Gabriel Hurley's answer:
What is different is that func
has a __call__()
method, making it "callable", i.e. you can apply the ()
operator to it. Check out the Python docs (search for __call__
on that page).
来源:https://stackoverflow.com/questions/2307653/how-does-assignment-of-a-function-as-a-class-attribute-become-a-method-in-python