7.2 Python中方法的运行机制

在此之前你可能已经写过很多方法但从未多想,但为了理解装饰器的行为,你就需要知道方法背后的运行机制。

方法是指作为类属性保存的函数。让我们来看一下当直接访问这样一个属性时到底发生了什么。在Python 2中情况如示例7.5所示,在Python 3中情况如示例7.6所示。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

示例 7.5  Python 2 的方法

>>> class Pizza(object):
...   def __init__(self, size):
...     self.size = size
...   def get_size(self):
...     return self.size
...
>>> Pizza.get_size
<unbound method Pizza.get_size>

Python 2会提示get_size属性是类Pizza的一个未绑定方法。

示例 7.6 Python 3 的方法

>>> class Pizza(object):
...   def __init__(self, size):
...     self.size = size
...   def get_size(self):
...     return self.size
...
>>> Pizza.get_size
<function Pizza.get_size at 0x7fdbfd1a8b90>

Python 3中已经完全删除了未绑定方法这个概念,它会提示get_size是一个函数。

两种情况的本质是一样的:get_size是一个并未关联到任何特定对象的函数,如果试图调用它的话,Python会抛出错误(在Python 2中情况如示例7.7所示,在Python 3中情况如示例7.8所示)。

示例 7.7 Python 2 中调用未绑定的get_size

>>> Pizza.get_size()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: unbound method get_size() must be called with Pizza instance as first argument (got nothing instead)

示例 7.8 Python 3 中调用未绑定的get_size

>>> Pizza.get_size()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: get_size() missing 1 required positional argument: 'self'

Python 2中拒绝该方法调用是因为它是未绑定的。Python 3允许调用,但会提示未提供必需的self参数。这使得Python 3更加灵活,不仅可以向方法传入该类的任意实例,还可以传入任何对象,只要它包含方法期望的属性:

>>> Pizza.get_size(Pizza(42))
42

尽管不太方便,但它能运行:每次调用类的一个方法都要对该类进行引用。

所以Python通过将类的方法绑定给实例为我们完成了后续工作。换句话说,可以通过任何Pizza访问get_size方法,进一步说,Python会自动将对象本身传给方法的self参数,如示例7.9所示。

示例 7.9  调用绑定的get_size

>>> Pizza(42).get_size
<bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>>
>>> Pizza(42).get_size()
42

不出所料,不需要传入任何参数给get_size,因为它是绑定方法:它的self参数会自动设置为Pizza的实例。下面是一个更好的例子:

>>> m = Pizza(42).get_size
>>> m()
42

一旦有了对绑定方法的引用则无需保持对Pizza对象的引用。如果有了对方法的引用但是想知道它被绑定到了哪个对象,可以查看方法的__self__属性:

>>> m = Pizza(42).get_size
>>> m.__self__
<__main__.Pizza object at 0x7f3138827910>
>>> m == m.__self__.get_size
True

显然,仍然可以保持对对象的引用,并随时在需要的时候访问它。