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

发布于 2024-08-22 08:29:29 字数 713 浏览 9 评论 0原文

>>> class A(object): pass
>>> def func(cls): pass
>>> A.func = func
>>> A.func
<unbound method A.func>

这个作业如何创建一个方法?赋值对类执行以下操作似乎不直观:

  • 将函数转换为未绑定的实例方法
  • 将 classmethod() 中包装的函数转换为类方法(实际上,这非常直观)
  • 将 staticmethod 中包装的函数() 到函数中

似乎第一个,应该有一个 instancemethod(),而对于最后一个,根本不应该有包装函数。我知道这些是在 class 块内使用的,但为什么它们应该应用于它的外部呢?

但更重要的是,将函数分配到类中究竟是如何进行的?到底发生了什么魔法可以解决这三件事呢?

更令人困惑的是:

>>> A.func
<unbound method A.func>
>>> A.__dict__['func']
<function func at 0x...>

但我认为这与检索属性时的描述符有关。我认为这与这里的属性设置没有太大关系。

>>> 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

花间憩 2024-08-29 08:29:29

你是对的,这与描述符协议有关。描述符是 Python 中实现将接收者对象作为方法的第一个参数传递的方式。您可以阅读有关 Python 属性查找的更多详细信息

# 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.

因此,在类或实例上查找函数将其转换为方法,而不是将其分配给类属性。

classmethodstaticmethod 只是描述符略有不同,classmethod 返回绑定到类型对象的绑定方法对象,而 staticmethod 仅返回原始函数。

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.

情未る 2024-08-29 08:29:29

描述符是神奇的1,可以将普通的当您从实例或类检索它时,将函数放入绑定或未绑定方法中,因为它们都只是需要不同绑定策略的函数。 classmethodstaticmethod 装饰器实现其他绑定策略,而 staticmethod 实际上只返回原始函数,这与从非-函数可调用对象。

请参阅“用户定义的方法”了解一些血淋淋的细节,但请注意这:

另请注意,此转换仅发生在用户定义的函数中;其他可调用对象(以及所有不可调用对象)无需转换即可检索。

因此,如果您想要为自己的可调用对象进行此转换,您可以将其包装在函数中,但您也可以编写一个描述符来实现您自己的绑定策略。

这是正在运行的 staticmethod 装饰器,在访问它时返回底层函数。

>>> @staticmethod
... def f(): pass
>>> class A(object): pass
>>> A.f = f
>>> A.f
<function f at 0x100479398>
>>> f
<staticmethod object at 0x100492750>

而具有 __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 具体函数是 Objects/funcobject.c

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.

滥情空心 2024-08-29 08:29:29

你必须考虑的是,在Python中 一切都是一个对象 。通过确定这一点,可以更容易地理解正在发生的事情。如果你有一个函数 def foo(bar): print bar,你可以执行 spam = foo 并调用 spam(1),得到当然,1

Python 中的对象将其实例属性保存在名为 __dict__ 的字典中,并带有指向其他对象的“指针”。由于 Python 中的函数也是对象,它们可以是作为简单变量进行分配和操作,传递给其他函数等。Python 的面向对象实现利用了这一点,并将方法视为属性,即对象的 __dict__ 中的函数。

实例方法第一个参数始终是实例对象本身,一般称为self (但这可以称为 thisbanana)。当直接在类上调用方法时,它不会绑定到任何实例,因此您必须为其提供一个实例对象作为第一个参数(A.func(A())< /代码>)。当您调用 绑定函数 (A().func()),该方法的第一个参数 self 是隐式的,但在幕后 Python 的作用与直接调用未绑定函数并传递参数完全相同实例对象作为第一个参数。

如果理解了这一点,那么分配 A.func = func (幕后正在执行 A.__dict__["func"] = func)这一事实会给您留下一个未绑定的方法,这并不奇怪。

在您的示例中,def func(cls): pass中的cls实际上将传递的是类型的实例(self) >A。当您应用 classmethodstaticmethod 装饰器 只是获取函数/方法调用过程中获得的第一个参数,并将其转换为在调用该函数之前执行其他操作。

classmethod 采用第一个参数,获取实例的 class 对象,并将其作为第一个参数传递给函数调用,而 staticmethod 只是简单地丢弃第一个参数并在没有它的情况下调用该函数。

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.

樱花细雨 2024-08-29 08:29:29

第 1 点:您定义的函数 func 作为 First-Class 对象存在 在 Python 中。

第 2 点:Python 中的类将其属性存储在 __dict__ 中。

那么当你在 Python 中传递一个函数作为类属性的值时会发生什么?该函数存储在类的 __dict__ 中,使其成为该类的一个方法,通过调用您分配给它的属性名称来访问。

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.

冰之心 2024-08-29 08:29:29

关于 MTsoul 对 Gabriel Hurley 的回答的评论:

不同的是 func 有一个 __call__() 方法,使其“可调用”,即您可以应用 () 运算符。查看 Python 文档(在该文档中搜索 __call__页)。

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文