转自 http://www.xdarui.com/archives/261.html
原文http://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods
代码审查对发现人们很难理解的问题来说绝对是一种很赞的方式。我(Julien Danjou)最近在做OpenStack patches校对的时候发现人们不能够正确的理解与使用Python提供的不同的函数修饰。所以我这里提供一个链接以便下次review的时候帖出。
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
>
Pizza
这个类中的
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
)
Pizza
的实例中去,而方法的第一个参数希望接收一个实例。(Python2中实例必需是对应的那个类,而Python3中可以是任何东西)让我这样试试:
-
>>> Pizza.
get_size
(Pizza
(
42
)
)
-
42
所以Python帮我们做了这些事情,给所有Pizza
的实例绑定所有它的方法。这意味着get_size
是Pizza
实例的已绑定方法:方法的第一个参数是实 例它本身。
-
>>> Pizza
(
42
).
get_size
-
<bound method Pizza.
get_size of
<
__main__.
Pizza
object at
0x7f3138827910
>>
-
>>> Pizza
(
42
).
get_size
(
)
-
42
get_size()
方法传入任何参数,因为它已经绑定了,
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中,直接使用类中的方法不再视为未绑定方法,而是一个简单的函数,而只是在需要的时候才绑定到对象上。所以基本原则没变只是简化了模型。
-
>>>
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
-
类方法
说到这,倒底什么是类方法?类方法是指不是绑定在对象上而是类上的方法!。
-
>>>
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
- 工厂方法,用于创建一个类的实例比如一些预处理。如果我们用
@staticmethod
,我们就得将类名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_circumference (radius ):
-
return math. pi * (radius ** 2 )
-
-
@ classmethod
-
def compute_volume (cls , height , radius ):
-
return height * cls. compute_circumference (radius )
-
-
def get_volume ( self ):
-
return self. compute_volume ( self. height , self. radius )
抽象方法
抽象方法是基类中定义的方法,但却没有任何实现。在Java中,可以把方法申明成一个接口。而在Python中实现一个抽象方法的最简便方法是:
-
class Pizza ( object ):
-
def get_radius ( self ):
-
raise NotImplementedError
Pizza
继承下来的子类都必需实现get_radius
方法,否则就会产生一个错误。这种方式实现在抽象方法也有缺点,如果你写了一个类继承自Pizza
但却忘了实现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
-
import abc
-
-
class BasePizza ( object ):
-
__metaclass__ = abc. ABCMeta
-
-
@abc. abstractmethod
-
def get_radius ( self ):
-
"""Method that should do something."""
abc
以及其特定的方法,一旦你尝试去实例化BasePizza
类或任意从其继承下来的类的时候都会得到一个错误。-
>>> BasePizza ( )
-
Traceback (most recent call last ):
-
File "<stdin>" , line 1 , in <module >
-
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_ingredients
方法返回结果时无需关心对象,不用关心有何约束。因此,你不能强 制要求抽象方法或类的实现是有规律的,或者说不应该这样做。从Python3开始(Python2不会生效,见 issue5867),可以在@abstractmethod
之上使用@staticmethod
和@classmethod
。-
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
作为类的方法,你就错了。这只是意味着你在BasePizza
类中,实现get_ingredients
只是一个类方法。抽象类中实现抽象方法?是的!Python中抽象方法与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()
通过默认机制获取配料表。-
-