Python中方法的工做方式
方法是存储在类属性中的函数,你能够用下面这种方式声明和访问一个函数html
>>> 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(未绑定的),这表明什么含义?咱们调用一下就明白了:python
>>> 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
咱们使用一个实例做为这个方法的第一个参数来调用它,没有出现任何问题。可是若是我说这不是一个方便的调用方法的方式,你将会赞成个人观点。咱们每次调用方法都要涉及(这里我理解是引用)类this
来看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实例,下面是一个更明显的例子:3d
>>> m = Pizza(42).get_size >>> m() 42
事实上是,你甚至不须要对Pizza引用,由于这个方法已经绑定到了这个对象code
若是你想知道这个绑定方法绑定到了哪个对象,这里有个快捷的方法:htm
>>> 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),只是模型被简化了blog
>>> 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
不管以何种方式访问这个方法,它都会被绑定到类中,它的第一个参数必须是类自己(记住类也是对象)
何时使用类方法,类方法在如下两种场合会有很好的效果:
一、工厂方法,为类建立实例,例如某种程度的预处理。若是咱们使用@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())
二、使用静态方法调用静态方法,若是你须要将一个静态方法拆分为多个,可使用类方法来避免硬编码类名。使用这种方法来声明咱们的方法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/flash...
推荐阅读:https://www.roncoo.com/course...