简体   繁体   English

foo.bar()和bar(foo)之间的区别?

[英]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.

相关问题 在 python 中,“import foo.bar as bar”和“from foo import bar”有什么区别? - In python, what is the difference between 'import foo.bar as bar' and 'from foo import bar'? 在python装饰器中foo = bar(foo)和something = bar(foo)之间有什么区别? - what is difference between foo=bar(foo) and something=bar(foo) in decorator in python? Python:将foo.bar导入为bar与从foo导入导入 - Python: import foo.bar as bar vs from foo import bar 本地导入的`python -m foo.bar`与`python foo / bar.py` - `python -m foo.bar` vs. `python foo/bar.py` for local imports 谷歌 foo.bar 招聘挑战跳过工作 - Google foo.bar hiring challenge skipping work Google foo.bar 挑战问题:无法导入库 - Google foo.bar Challenge Issue: Can't import libraries 布尔类型的any([class.method(),foo.bar()等etc.()])参数中的foo不可迭代 - foo in any([class.method(), foo.bar(), etc.etc()]) argument of type bool not iterable 使用属性装饰器后,python object 有两个非常相似的属性(foo.bar 和 foo._bar)。 那样行吗? - After using property decorator, python object has two very similar attributes (foo.bar and foo._bar). Is that ok? foo = list,foo = []和foo = list()之间的区别? - Difference between foo=list, foo=[], and foo=list()? isinstance(foo,bar) vs type(foo) is bar - isinstance(foo,bar) vs type(foo) is bar
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM