方法在Python中是如何工做的python
方法就是一个函数,它做为一个类属性而存在,你能够用以下方式来声明、访问一个函数:函数
Pythonthis
1编码 2spa 3.net 43d 5对象 6继承 7接口 8 |
>>> 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在告诉你,属性_get_size是类Pizza的一个未绑定方法。这是什么意思呢?很快咱们就会知道答案:
Python
1 2 3 4 |
>>> 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) |
咱们不能这么调用,由于它尚未绑定到Pizza类的任何实例上,它须要一个实例做为第一个参数传递进去(Python2必须是该类的实例,Python3中能够是任何东西),尝试一下:
Python
1 2 |
>>> Pizza.get_size(Pizza(42)) 42 |
太棒了,如今用一个实例做为它的的第一个参数来调用,整个世界都清静了,若是我说这种调用方式还不是最方便的,你也会这么认为的;没错,如今每次调用这个方法的时候咱们都不得不引用这个类,若是不知道哪一个类是咱们的对象,长期看来这种方式是行不通的。
那么Python为咱们作了什么呢,它绑定了全部来自类_Pizza的方法以及该类的任何一个实例的方法。也就意味着如今属性get_size是Pizza的一个实例对象的绑定方法,这个方法的第一个参数就是该实例自己。
Python
1 2 3 4 |
>>> Pizza(42).get_size <bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>> >>> Pizza(42).get_size() 42 |
和咱们预期的同样,如今再也不须要提供任何参数给_get_size,由于它已是绑定的,它的self参数会自动地设置给Pizza实例,下面代码是最好的证实:
Python
1 2 3 |
>>> m = Pizza(42).get_size >>> m() 42 |
更有甚者,你都不必使用持有Pizza对象的引用了,由于该方法已经绑定到了这个对象,因此这个方法对它本身来讲是已经足够了。
也许,若是你想知道这个绑定的方法是绑定在哪一个对象上,下面这种手段就能得知:
Python
1 2 3 4 5 6 7 |
>>> 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保持一致,可是模块更简洁:
Python
1 2 3 4 5 6 7 8 |
>>> 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> |
静态方法是一类特殊的方法,有时你可能须要写一个属于这个类的方法,可是这些代码彻底不会使用到实例对象自己,例如:
Python
1 2 3 4 5 6 7 |
class Pizza(object): def mix_ingredients(x, y): return x + y
def cook(self): return self.mix_ingredients(self.cheese, self.vegetables) |
这个例子中,若是把_mix_ingredients做为非静态方法一样能够运行,可是它要提供self参数,而这个参数在方法中根本不会被使用到。这里的@staticmethod装饰器能够给咱们带来一些好处:
Python
1 2 3 4 5 6 |
>>> Pizza().cook is Pizza().cook False >>> Pizza().mix_ingredients is Pizza.mix_ingredients True >>> Pizza().mix_ingredients is Pizza().mix_ingredients True |
话虽如此,什么是类方法呢?类方法不是绑定到对象上,而是绑定在类上的方法。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
>>> 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 is Pizza().get_radius True >>> Pizza.get_radius() 42 |
不管你用哪一种方式访问这个方法,它老是绑定到了这个类身上,它的第一个参数是这个类自己(记住:类也是对象)。
何时使用这种方法呢?类方法一般在如下两种场景是很是有用的:
Python
1 2 3 4 5 6 7 |
class Pizza(object): def __init__(self, ingredients): self.ingredients = ingredients
@classmethod def from_fridge(cls, fridge): return cls(fridge.get_cheese() + fridge.get_vegetables()) |
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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) |
抽象方法是定义在基类中的一种方法,它没有提供任何实现,相似于Java中接口(Interface)里面的方法。
在Python中实现抽象方法最简单地方式是:
Python
1 2 3 |
class Pizza(object): def get_radius(self): raise NotImplementedError |
任何继承自_Pizza的类必须覆盖实现方法get_radius,不然会抛出异常。
这种抽象方法的实现有它的弊端,若是你写一个类继承Pizza,可是忘记实现get_radius,异常只有在你真正使用的时候才会抛出来。
Python
1 2 3 4 5 6 7 |
>>> 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模块,对象被初始化以后就能够抛出异常:
Python
1 2 3 4 5 6 7 8 |
import abc
class BasePizza(object): __metaclass__ = abc.ABCMeta
@abc.abstractmethod def get_radius(self): """Method that should do something.""" |
使用abc后,当你尝试初始化BasePizza或者任何子类的时候立马就会获得一个TypeError,而无需等到真正调用get_radius的时候才发现异常。
Python
1 2 3 4 |
>>> BasePizza() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius |
当你开始构建类和继承结构时,混合使用这些装饰器的时候到了,因此这里列出了一些技巧。
记住,声明一个抽象的方法,不会固定方法的原型,这就意味着虽然你必须实现它,可是我能够用任何参数列表来实现:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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对象所定义的接口需求。一样咱们也能够用一个类方法或静态方法来实现:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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_ingredients方法并不须要知道返回结果是什么,结果是实现细节,不是契约条件。
所以,你不能强制抽象方法的实现是一个常规方法、或者是类方法仍是静态方法,也没什么可争论的。从Python3开始(在Python2中不能如你期待的运行,见issue5867),在abstractmethod方法上面使用@staticmethod和@classmethod装饰器成为可能。
Python
1 2 3 4 5 6 7 8 9 10 11 12 |
import abc
class BasePizza(object): __metaclass__ = abc.ABCMeta
ingredient = ['cheese']
@classmethod @abc.abstractmethod def get_ingredients(cls): """Returns the ingredient list.""" return cls.ingredients |
别误会了,若是你认为它会强制子类做为一个类方法来实现get_ingredients那你就错了,它仅仅表示你实现的get_ingredients在BasePizza中是一个类方法。
能够在抽象方法中作代码的实现?没错,Python与Java接口中的方法相反,你能够在抽象方法编写实现代码经过super()来调用它。(译注:在Java8中,接口也提供的默认方法,容许在接口中写方法的实现)
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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() |
这个例子中,你构建的每一个pizza都经过继承BasePizza的方式,你不得不覆盖get_ingredients方法,可是可以使用默认机制经过super()来获取ingredient列表。