简体   繁体   English

如何将函数作为类属性赋值成为Python中的方法?

[英]How does assignment of a function as a class attribute become a method in Python?

>>> 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) classmethod()包含的函数转换为类方法(实际上,这非常直观)
  • Turn functions wrapped in staticmethod() into functions staticmethod()包含的函数转换为函数

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. 似乎第一个应该有一个instancemethod() ,对于最后一个,根本不应该有一个包装器函数。 I understand that these are for uses within a class block, but why should they apply outside of it? 我知道这些是用于class块中的用途,但为什么它们应用于它之外呢?

But more importantly, how exactly does assignment of the function into a class work? 但更重要的是,如何将函数分配到一个类中? What magic happens that resolves those 3 things? 什么魔法可以解决这3件事?

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. 我不认为这与设置属性有很大关系。

Descriptors are the magic 1 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. 描述是,当你从一个实例或类检索,轮流一个普通的功能为结合或未结合的方法,因为他们需要不同的结合策略都只是功能魔1。 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. classmethodstaticmethod装饰器实现其他绑定策略,而staticmethod实际上只返回raw函数,这与从非函数callable对象获得的行为相同。

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装饰器,在访问它时返回底层函数。

>>> @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: 使用__call__方法的普通对象不会被转换:

>>> 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 . 1具体功能是func_descr_get对象/ funcobject.c

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. 描述符是如何传递接收器对象,因为方法的第一个参数是在Python中实现的。 You can read more detail about Python attribute lookup from here . 您可以从此处阅读有关Python属性查找的更多详细信息。 The following shows on a bit lower level, what is happening when you do A.func = func; 以下显示在较低级别,当您执行A.func = func时发生的情况; A.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. classmethodstaticmethod只是略有不同的描述符,classmethod返回绑定到类型对象的绑定方法对象,而staticmethod只返回原始函数。

What you have to consider is that in Python everything is an object . 你需要考虑的是,在Python中, 一切都是一个对象 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 . 如果你有一个功能def foo(bar): print bar ,你可以做spam = foo并调用spam(1) ,当然, 1

Objects in Python keep their instance attributes in a dictionary called __dict__ with a "pointer" to other objects. Python中的对象将其实例属性保存在名为__dict__的字典中,并带有指向其他对象的“指针”。 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. 由于Python中的函数也是对象 ,它们可以作为简单变量进行分配和操作,传递给其他函数等.Python的面向对象实现利用了这一点,并将方法视为属性,作为__dict__函数。对象。

Instance methods' first parameter is always the instance object itself , generally called self (but this could be called this or banana ). 实例方法的第一个参数始终是实例对象本身 ,通常称为self (但这可以称为thisbanana )。 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()) ). 当一个方法直接在class上调用时,它被解除绑定到任何实例,因此你必须给它一个实例对象作为第一个参数( 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. 当你调用一个绑定函数A().func() )时,方法的第一个参数self是隐式的,但在窗帘后面,Python与直接调用未绑定函数并将实例对象作为传递方式完全相同第一个参数。

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. 如果理解了这一点,那么分配A.func = func (在窗帘后面做A.__dict__["func"] = func )的事实A.func = func你留下一个未绑定的方法,这并不令人惊讶。

In your example the cls in def func(cls): pass actually what will be passed on is the instance ( self ) of type A . 在您的例子中, clsdef func(cls): pass实际究竟会转嫁是实例( self型的) 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. 当你应用classmethodstaticmethod 装饰器时 ,除了在调用函数之前获取在函数/方法调用期间获得的第一个参数,并将其转换为其他参数之外,别做什么。

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. classmethod获取第一个参数,获取实例的class对象,并将其作为第一个参数传递给函数调用,而staticmethod只丢弃第一个参数并在没有它的情况下调用函数。

Point 1: The function func you defined exists as a First-Class Object in Python. 第1点:您定义的函数func在Python中作为First-Class对象存在。

Point 2: Classes in Python store their attributes in their __dict__ . 第2点:Python中的类将其属性存储在__dict__

So what happens when you pass a function as the value of a class attribute in Python? 那么当你在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. 该函数存储在类' __dict__ ,使其成为通过调用您指定给它的属性名访问该类的方法。

Relating to MTsoul's comment to Gabriel Hurley's answer: 关于MTsoul对Gabriel Hurley的回答的评论:

What is different is that func has a __call__() method, making it "callable", ie you can apply the () operator to it. 不同的是func有一个__call__()方法,使其“可调用”,即你可以将()运算符应用于它。 Check out the Python docs (search for __call__ on that page). 查看Python文档 (在该页面上搜索__call__ )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM