Python 中方法的工作方式
方法是存储在类属性中的函数, 你可以用下面这种方式声明和访问一个函数
- >>> 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 在这里说明了什么? Pizza 类的属性 get_size 是 unbound(未绑定的), 这代表什么含义? 我们调用一下就明白了:
- >>> 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)
我们无法调用它(get_size), 因为它没有绑定到 Pizza 的任何实例上, 而且一个方法需要一个实例作为它的第一个参数(Python2 中必须是类的实例, Python3 没有这个强制要求), 让我们试一下:
- >>> Pizza.get_size(Pizza(42))
- 42
我们使用一个实例作为这个方法的第一个参数来调用它, 没有出现任何问题. 但是如果我说这不是一个方便的调用方法的方式, 你将会同意我的观点. 我们每次调用方法都要涉及 (这里我理解是引用) 类
来看 Python 打算为我们做些什么, 就是它从 Pizza 类中绑定所有的方法到这个类的任何实例上. 意思就是 Pizza 实例化后 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 引用, 因为这个方法已经绑定到了这个对象
如果你想知道这个绑定方法绑定到了哪一个对象, 这里有个快捷的方法:
- >>> m = Pizza(42).get_size
- >>> m.__self__
- <__main__.Pizza object at 0x7f3138827910>
- >>> # You could guess, look at this:
- ...
- >>> m == m.__self__.get_size
- True
明显可以看出, 我们仍然保持对我们对象的引用, 而且如果需要我们可以找到它
在 Python3 中, 类中的函数不再被认为是未绑定的方法(应该是作为函数存在), 如果需要, 会作为一个函数绑定到对象上, 所以原理是一样的(和 Python2), 只是模型被简化了
- >>> 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 0x7f307f984dd0>
静态方法
静态方法一种特殊方法, 有时你想把代码归属到一个类中, 但又不想和这个对象发生任何交互:
- class Pizza(object):
- @staticmethod
- def mix_ingredients(x, y):
- return x + y
- def cook(self):
- return self.mix_ingredients(self.cheese, self.vegetables)
上面这个例子, mix_ingredients 完全可以写成一个非静态方法, 但是这样会将 self 作为第一个参数传入. 在这个例子里, 装饰器 @staticmethod 会实现几个功能:
Python 不会为 Pizza 的实例对象实例化一个绑定方法, 绑定方法也是对象, 会产生开销, 静态方法可以避免这类情况
- >>> Pizza().cook is Pizza().cook
- False
- >>> Pizza().mix_ingredients is Pizza.mix_ingredients
- True
- >>> Pizza().mix_ingredients is Pizza().mix_ingredients
- True
简化了代码的可读性, 看到 @staticmethod 我们就会知道这个方法不会依赖这个对象的状态(一国两制, 高度自治)
允许在子类中重写 mix_ingredients 方法. 如果我们在顶级模型中定义了 mix_ingredients 函数, 继承自 Pizza 的类除了重写, 否则无法改变 mix_ingredients 的功能
类方法
什么是类方法, 类方法是方法不会被绑定到一个对象, 而是被绑定到一个类中
- >>> class Pizza(object):
- ... radius = 42
- ... @classmethod
- ... def get_radius(cls):
- ... return cls.radius
- ...
- >>>
- >>> Pizza.get_radius
- <bound method type.get_radius of <class '__main__.Pizza'>>
- >>> Pizza().get_radius
- <bound method type.get_radius of <class '__main__.Pizza'>>
- >>> Pizza.get_radius == Pizza().get_radius
- True
- >>> Pizza.get_radius()
- 42
无论以何种方式访问这个方法, 它都会被绑定到类中, 它的第一个参数必须是类本身(记住类也是对象)
什么时候使用类方法, 类方法在以下两种场合会有很好的效果:
1, 工厂方法, 为类创建实例, 例如某种程度的预处理. 如果我们使用 @staticmethod 代替, 我们必须要在代码中硬编码 Pizza(写死 Pizza), 这样从 Pizza 继承的类就不能使用了
- class Pizza(object):
- def __init__(self, ingredients):
- self.ingredients = ingredients
- @classmethod
- def from_fridge(cls, fridge):
- return cls(fridge.get_cheese() + fridge.get_vegetables())
2, 使用静态方法调用静态方法, 如果你需要将一个静态方法拆分为多个, 可以使用类方法来避免硬编码类名. 使用这种方法来声明我们的方法 Pizza 的名字永远不会被直接引用, 而且继承和重写方法都很方便
- class Pizza(object):
- def __init__(self, radius, height):
- self.radius = radius
- self.height = height
- @staticmethod
- def compute_area(radius):
- return math.pi * (radius ** 2)
- @classmethod
- def compute_volume(cls, height, radius):
- return height * cls.compute_area(radius)
- def get_volume(self):
- return self.compute_volume(self.height, self.radius)
抽象方法
抽象方法是定义在基类中的, 可以是不提供任何功能代码的方法
在 Python 中简单的写抽象方法的方式是:
- class Pizza(object):
- def get_radius(self):
- raise NotImplementedError
继承自 Pizza 的类都必须要实现并重写 get_redius, 否则就会报错
这种方式的抽象方法有一个问题, 如果你忘记实现了 get_radius, 只有在你调用这个方法的时候才会报错
- >>> Pizza()
- <__main__.Pizza object at 0x7fb747353d90>
- >>> Pizza().get_radius()
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "<stdin>", line 3, in get_radius
- NotImplementedError
使用 python 的 abc 模块可以是这个异常被更早的触发
- import abc
- class BasePizza(object):
- __metaclass__ = abc.ABCMeta
- @abc.abstractmethod
- def get_radius(self):
- """Method that should do something."""
- 使用 abc 和它的特殊类, 如果你尝试实例化 BasePizza 或者继承它, 都会得到 TypeError 错误
- >>> BasePizza()
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius
- 备注: 我使用 Python3.6 实现的代码
- In [8]: import abc
- ...:
- ...: class BasePizza(abc.ABC):
- ...:
- ...: @abc.abstractmethod
- ...: def get_radius(self):
- ...: """:return"""
- ...:
- In [9]: BasePizza()
- ---------------------------------------------------------------------------
- TypeError Traceback (most recent call last)
- <ipython-input-9-70b53ea21e68> in <module>()
- ----> 1 BasePizza()
- TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius
- 混合静态, 类和抽象方法
- 当需要创建类和继承时, 如果你需要混合这些方法装饰器, 这里有一些小窍门建议给你
- 记住要将方法声明为抽象, 不要冻结这个方法的原型. 意思是它 (声明的方法) 必须要执行, 但是它在执行的时候, 参数不会有任何限制
- import abc
- class BasePizza(object):
- __metaclass__ = abc.ABCMeta
- @abc.abstractmethod
- def get_ingredients(self):
- """Returns the ingredient list."""
- class Calzone(BasePizza):
- def get_ingredients(self, with_egg=False):
- egg = Egg() if with_egg else None
- return self.ingredients + egg
- 这样是有效的, 因为 Calzone 实现了我们为 BasePizza 定义的接口要求, 这意味着我们也可以将它实现为一个类或者静态方法, 例如:
- import abc
- class BasePizza(object):
- __metaclass__ = abc.ABCMeta
- @abc.abstractmethod
- def get_ingredients(self):
- """Returns the ingredient list."""
- class DietPizza(BasePizza):
- @staticmethod
- def get_ingredients():
- return None
- 这也是正确的, 它实现了抽要 BasePizza 的要求, 事实上是 get_ingredioents 方法不需要知道对象返回的结果,
- 因此, 你不需要强制抽象方法实现成为常规方法, 类或者静态方法. 在 python3 中, 可以将 @staticmethod 和 @classmethod 装饰器放在 @abstractmethod 上面
- import abc
- class BasePizza(object):
- __metaclass__ = abc.ABCMeta
- ingredient = ['cheese']
- @classmethod
- @abc.abstractmethod
- def get_ingredients(cls):
- """Returns the ingredient list."""
- return cls.ingredients
- 和 Java 的接口相反, 你可以在抽象方法中实现代码并通过 super()调用它
- import abc
- class BasePizza(object):
- __metaclass__ = abc.ABCMeta
- default_ingredients = ['cheese']
- @classmethod
- @abc.abstractmethod
- def get_ingredients(cls):
- """Returns the ingredient list."""
- return cls.default_ingredients
- class DietPizza(BasePizza):
- def get_ingredients(self):
- return ['egg'] + super(DietPizza, self).get_ingredients()
在上面的例子中, 继承 BasePizza 来创建的每个 Pizza 都必须重写 get_ingredients 方法, 但是可以使用 super()来获取 default_ingredients
来源: https://www.cnblogs.com/flashBoxer/p/9814012.html