[英]Difference between foo.bar() and bar(foo)?
Consider: 考虑:
class Parent():
def __init__(self, last_name, eye_color):
self.last_name = last_name
self.eye_color = eye_color
def show_info(self):
print("Last Name - "+self.last_name)
print("Eye Color - "+self.eye_color)
billy_cyrus = Parent("Cyrus", "blue")
The above is from the Udacity Python course. 以上内容来自Udacity Python课程。 I discovered I'm able to call
show_info
for instance billy_cyrus
using either of the following: 我发现我可以使用以下任一方法调用
show_info
,例如billy_cyrus
:
billy_cyrus.show_info()
Parent.show_info(billy_cyrus)
I'm curious as to why. 我很好奇为什么。 Is there a difference between the two methods?
这两种方法有区别吗? If so when would one be used vs. the other?
如果是这样的话什么时候会用到另一个呢? I'm using Python 3.6 if that matters.
如果重要的话,我正在使用Python 3.6。
In terms of just calling the method, there is no difference most of the time. 就调用方法而言,大部分时间都没有区别。 In terms of how the underlying machinery, works, there is a bit of a difference.
就底层机械如何工作而言,存在一点差异。
Since show_info
is a method , it is a descriptor in the class. 由于
show_info
是一个方法 ,因此它是类中的描述符 。 That means that when you access it through an instance in which it is not shadowed by another attribute , the .
这意味着当你通过一个没有被另一个属性遮蔽的实例访问它时,
.
operator calls __get__
on the descriptor to create a bound method for that instance. operator在描述符上调用
__get__
以为该实例创建绑定方法。 A bound method is basically a closure that passes in the self
parameter for you before any of the other arguments you supply. 绑定方法基本上是一个闭包,它在您提供的任何其他参数之前为您传递
self
参数。 You can see the binding happen like this: 您可以看到绑定发生如下:
>>> billy_cyrus.show_info
<bound method Parent.show_info of <__main__.Parent object at 0x7f7598b14be0>>
A different closure is created every time you use the .
每次使用时都会创建一个不同的闭包
.
operator on a class method. 类方法的运算符。
If you access the method through the class object, on the other hand, it does not get bound. 另一方面,如果通过类对象访问该方法,则它不会受到约束。 The method is a descriptor, which is just a regular attribute of the class:
该方法是一个描述符,它只是该类的常规属性:
>>> Parent.show_info
<function __main__.Parent.show_info>
You can simulate the exact behavior of binding a method before calling it by calling its __get__
yourself: 在调用方法之前,您可以通过自己调用
__get__
来模拟绑定方法的确切行为:
>>> bound_meth = Parent.show_info.__get__(billy_cyrus, type(billy_cyrus))
>>> bound_meth
<bound method Parent.show_info of <__main__.Parent object at 0x7f7598b14be0>>
Again, this will not make any difference to you in 99.99% of cases, since functionally bound_meth()
and Parent.bound_meth(billy_cyrus)
end up calling the same underlying function object with the same parameters. 同样,这对99.99%的情况没有任何影响,因为函数
bound_meth()
和Parent.bound_meth(billy_cyrus)
最终调用具有相同参数的相同底层函数对象。
Where it matters 哪里重要
There are a couple of places where it matters how you call a class method. 有几个地方重要的是如何调用类方法。 One common use case is when you override a method, but want to use the definition provided in the parent class.
一个常见的用例是覆盖方法,但希望使用父类中提供的定义。 For example, say I have a class that I made "immutable" by overriding
__setattr__
. 例如,假设我有一个类,我通过覆盖
__setattr__
使其成为“不可变的”。 I can still set attributes on the instance, as in the __init__
method shown below: 我仍然可以在实例上设置属性,如下面显示的
__init__
方法:
class Test:
def __init__(self, a):
object.__setattr__(self, 'a', a)
def __setattr__(self, name, value):
raise ValueError('I am immutable!')
If I tried to do a normal call to __setattr__
in __init__
by doing self.a = a
, a ValueError
would be raised every time. 如果我试图通过
self.a = a
对__init__
的self.a = a
进行正常调用, __setattr__
每次都会引发一个ValueError
。 But by using object.__setattr__
, I can bypass this limitation. 但是通过使用
object.__setattr__
,我可以绕过这个限制。 Alternatively, I could do super().__setattr__('a', a)
for the same effect, or self.__dict__['a'] = a
for a very similar one. 或者,我可以为相同的效果执行
super().__setattr__('a', a)
,或者对于非常相似的self.__dict__['a'] = a
。
@Silvio Mayolo's answer has another good example, where you would deliberately want to use the class method as a function that could be applied to many objects. @Silvio Mayolo的答案有另一个很好的例子,你有意将class方法用作可以应用于许多对象的函数。
Another place it matters (although not in terms of calling methods), is when you use other common descriptors like property
. 另一个重要的地方(虽然不是在调用方法方面),是你使用其他常见的描述符,如
property
。 Unlike methods, properties are data-descriptors . 与方法不同,属性是数据描述符 。 This means that they define a
__set__
method (and optionally __delete__
) in addition to __get__
. 这意味着除了
__get__
之外,它们还定义了一个__set__
方法(以及可选的__delete__
)。 A property creates a virtual attribute whose getter and setter are arbitrarily complex functions instead of just simple assignments. 属性创建一个虚拟属性,其getter和setter是任意复杂的函数,而不仅仅是简单的赋值。 To properly use a property, you have to do it through the instance.
要正确使用属性,您必须通过实例执行此操作。 For example:
例如:
class PropDemo:
def __init__(self, x=0):
self.x = x
@property
def x(self):
return self.__dict__['x']
@x.setter
def x(self, value):
if value < 0:
raise ValueError('Not negatives, please!')
self.__dict__['x'] = value
Now you can do something like 现在你可以做点什么了
>>> inst = PropDemo()
>>> inst.x
0
>>> inst.x = 3
>>> inst.x
3
If you try to access the property through the class, you can get the underlying descriptor object since it will be an unbound attribute: 如果您尝试通过类访问该属性,则可以获取基础描述符对象,因为它将是未绑定的属性:
>>> PropDemo.x
<property at 0x7f7598af00e8>
On a side note, hiding attributes with the same name as a property in __dict__
is a neat trick that works because data descriptors in a class __dict__
trump entries in the instance __dict__
, even though instance __dict__
entries trump non-data-descriptors in a class. 在一个侧面说明,隐藏具有相同名称的属性在属性
__dict__
是一个整洁的把戏,因为数据描述符的工作在一个类中__dict__
实例中的王牌项目__dict__
,即使实例__dict__
项王牌非数据描述符类。
Where it can Get Weird 哪里可以变得怪异
You can override a class method with an instance method in Python. 您可以使用Python中的实例方法覆盖类方法。 That would mean that
type(foo).bar(foo)
and foo.bar()
don't call the same underlying function at all. 这意味着
type(foo).bar(foo)
和foo.bar()
根本不会调用相同的底层函数。 This is irrelevant for magic methods because they always use the former invocation, but it can make a big difference for normal method calls. 这与魔术方法无关,因为它们总是使用前一次调用,但它可以对普通方法调用产生很大的影响。
There are a few ways to override a method on an instance . 有几种方法可以覆盖实例上的方法 。 The one I find most intuitive is to set the instance attribute to a bound method.
我觉得最直观的一个是将实例属性设置为绑定方法。 Here is an example of a modified
billy_cyrus
, assuming the definition of Parent
in the original question: 以下是修改后的
billy_cyrus
,假设原始问题中包含Parent
的定义:
def alt_show_info(self):
print('Another version of', self)
billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)
In this case, calling the method on the instance vs the class would have completely different results. 在这种情况下,调用实例上的方法与类将产生完全不同的结果。 This only works because methods are non-data descriptors by the way.
这只能起作用,因为方法是非数据描述符。 If they were data descriptors (with a
__set__
method), the assignment billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)
would not override anything but would instead just redirect to __set__
, and manually setting it in b billy_cyrus
's __dict__
would just get it ignored, as happens with a property. 如果它们是数据描述符(使用
__set__
方法),则赋值billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)
不会覆盖任何内容,而只是重定向到__set__
,并在b billy_cyrus
的__dict__
手动设置它只会让它被忽略,就像财产一样。
Additional Resources 其他资源
Here are a couple of resources on descriptors: 以下是关于描述符的几个资源:
There is no semantic difference between the two. 两者之间没有语义差异。 It's entirely a matter of style.
这完全是一种风格问题。 You would generally use
billy_cyrus.show_info()
in normal use, but the fact that the second approach is allowed permits you to use Parent.show_info
to get the method as a first-class object itself. 您通常会在正常使用中使用
billy_cyrus.show_info()
,但允许第二种方法的事实允许您使用Parent.show_info
将该方法作为第一类对象本身。 If that was not allowed, then it would not be possible (or at least, it would be fairly difficult) to do something like this. 如果不允许这样做,那么做这样的事情是不可能的(或者至少是相当困难的)。
function = Parent.show_info
so_many_billy_cyrus = [billy_cyrus, billy_cyrus, billy_cyrus]
map(function, so_many_billy_cyrus)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.