Python权威指南之如何使用静态类或抽象函数

转自  http://www.xdarui.com/archives/261.html

发表时间: 2013-09-04 22:24:30  阅读: 1670  标签: Python  

原文http://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods

代码审查对发现人们很难理解的问题来说绝对是一种很赞的方式。我(Julien Danjou)最近在做OpenStack patches校对的时候发现人们不能够正确的理解与使用Python提供的不同的函数修饰。所以我这里提供一个链接以便下次review的时候帖出。

Python中方法是怎么工作的

一个方法就是一个函数,它依付于一个类之上。你可以像这样去定义和使用:


   
   
  1. >>> class Pizza ( object ):
  2. ... def __init__ ( self , size ):
  3. ... self. size = size
  4. ... def get_size ( self ):
  5. ... return self. size
  6. ...
  7. >>> Pizza. get_size
  8. <unbound method Pizza. get_size >
这里Python告诉了我们 Pizza这个类中的 get_size方法没绑定,这是什么意思?我们尝试去调用下就知道了:

   
   
  1. >>> Pizza. get_size ( )
  2. Traceback (most recent call last ):
  3. File "<stdin>" , line 1 , in <module >
  4. TypeError: unbound method get_size ( ) must be called with Pizza instance as first argument (got nothing instead )
因为它并未绑定到任何一个 Pizza的实例中去,而方法的第一个参数希望接收一个实例。(Python2中实例必需是对应的那个类,而Python3中可以是任何东西)让我这样试试:

   
   
  1. >>> Pizza. get_size (Pizza ( 42 ) )
  2. 42
可以运行了!我们在调用的时候给第一个参数传入了它的实例,所以一切都正常了。但是你不得不诚认,这样调用不是很方便。每次调用这个方法的时候我们都得引用一个类,而且我们也不知 道这个对象是属于哪个类,这样肯定是不是长久之计。

所以Python帮我们做了这些事情,给所有Pizza的实例绑定所有它的方法。这意味着get_sizePizza实例的已绑定方法:方法的第一个参数是实 例它本身。


   
   
  1. >>> Pizza ( 42 ). get_size
  2. <bound method Pizza. get_size of < __main__. Pizza object at 0x7f3138827910 >>
  3. >>> Pizza ( 42 ). get_size ( )
  4. 42
正如我们所期望,我们必没有给 get_size()方法传入任何参数,因为它已经绑定了, Pizza实例自身会自动设置成方法的第一个参数。这可以证明:

   
   
  1. >>> m = Pizza ( 42 ). get_size
  2. >>> m ( )
  3. 42
事实上,你甚至都不需要为你的 Pizza对象保存引用,因其方法与对象绑定了,所以其方法本身就够了。但是如果我们想知道这个方法倒底绑定到了哪个对象上该怎么做?这有一 个小技巧:

   
   
  1. >>> m = Pizza ( 42 ). get_size
  2. >>> m.__self__
  3. < __main__. Pizza object at 0x7f3138827910 >
  4. >>> # You could guess, look at this:
  5. ...
  6. >>> m == m.__self__. get_size
  7. True
显然,我仍有一个我们这个对象的引用,我们随时可以通过它找回。

在Python3中,直接使用类中的方法不再视为未绑定方法,而是一个简单的函数,而只是在需要的时候才绑定到对象上。所以基本原则没变只是简化了模型。


   
   
  1. >>> class Pizza ( object ):
  2. ... def __init__ ( self , size ):
  3. ... self. size = size
  4. ... def get_size ( self ):
  5. ... return self. size
  6. ...
  7. >>> Pizza. get_size
  8. <function Pizza. get_size at 0x7f307f984dd0 >

静态方法

静态方法是方法中的一个特例。有时,你写了一段属于某个类的代码,但是它并未使用这个类。比如:


   
   
  1. class Pizza ( object ):
  2. @ staticmethod
  3. def mix_ingredients (x , y ):
  4. return x + y
  5.  
  6. def cook ( self ):
  7. return self. mix_ingredients ( self. cheese , self. vegetables )
这种情景下,写了这样的非静态方法 mix_ingredients也能工作,但会传递一个并不会使用的参数 self。这里个注解 @staticmethod可以帮我们做这些情况:
  • Python不会给每一个Pizza实例都增加一个绑定的方法。绑定方法也是一个函数,所以创建它们也有额外的开销。通过静态方法可以避免这些开销:
    
         
         
    1. >>> Pizza ( ). cook is Pizza ( ). cook
    2. False
    3. >>> Pizza ( ). mix_ingredients is Pizza. mix_ingredients
    4. True
    5. >>> Pizza ( ). mix_ingredients is Pizza ( ). mix_ingredients
    6. True

类方法

说到这,倒底什么是类方法?类方法是指不是绑定在对象上而是类上的方法!。


   
   
  1. >>> class Pizza ( object ):
  2. ... radius = 42
  3. ... @ classmethod
  4. ... def get_radius (cls ):
  5. ... return cls. radius
  6. ...
  7. >>>
  8. >>> Pizza. get_radius
  9. <bound method type. get_radius of < class '__main__.Pizza' >>
  10. >>> Pizza ( ). get_radius
  11. <bound method type. get_radius of < class '__main__.Pizza' >>
  12. >>> Pizza. get_radius is Pizza ( ). get_radius
  13. True
  14. >>> Pizza. get_radius ( )
  15. 42
无论你是如何使用类方法,它始终都是连接到类,而且第一个参数是类本身(记住类也是对象)。我们何时使用这种方法?类方法大多有这两种用法:
  • 工厂方法,用于创建一个类的实例比如一些预处理。如果我们用@staticmethod,我们就得将类名Pizza硬编码进函数,任何继承自Pizza的类 都无法使用工厂方法的自身用处。
    
         
         
    1. class Pizza ( object ):
    2. def __init__ ( self , ingredients ):
    3. self. ingredients = ingredients
    4.  
    5. @ classmethod
    6. def from_fridge (cls , fridge ):
    7. return cls (fridge. get_cheese ( ) + fridge. get_vegetables ( ) )
  • 静态方法之前的调用:如果你把静态方法分散成很多静态方法,你不能直接硬编码类名而应该用类方法。使用这种方式申明一个类方法,Pizza类名不会被直接使用,而且 继承的子类和重载父类都会很好的工作。
    
         
         
    1. class Pizza ( object ):
    2. def __init__ ( self , radius , height ):
    3. self. radius = radius
    4. self. height = height
    5.  
    6. @ staticmethod
    7. def compute_circumference (radius ):
    8. return math. pi * (radius ** 2 )
    9.  
    10. @ classmethod
    11. def compute_volume (cls , height , radius ):
    12. return height * cls. compute_circumference (radius )
    13.  
    14. def get_volume ( self ):
    15. return self. compute_volume ( self. height , self. radius )
    • 抽象方法

      抽象方法是基类中定义的方法,但却没有任何实现。在Java中,可以把方法申明成一个接口。而在Python中实现一个抽象方法的最简便方法是:

      
            
            
      1. class Pizza ( object ):
      2. def get_radius ( self ):
      3. raise NotImplementedError
      任何从 Pizza继承下来的子类都必需实现 get_radius方法,否则就会产生一个错误。这种方式实现在抽象方法也有缺点,如果你写了一个类继承自 Pizza但却忘了实现 get_radius时,只有当你用到了那个方法时才会抛错。
      
            
            
      1. >>> Pizza ( )
      2. < __main__. Pizza object at 0x7fb747353d90 >
      3. >>> Pizza ( ). get_radius ( )
      4. Traceback (most recent call last ):
      5. File "<stdin>" , line 1 , in <module >
      6. File "<stdin>" , line 3 , in get_radius
      7. NotImplementedError
      这里有一个简单的办法可以在类被实例化后触发它,使用Python提供的 abc模块。
      
            
            
      1. import abc
      2.  
      3. class BasePizza ( object ):
      4. __metaclass__ = abc. ABCMeta
      5.  
      6. @abc. abstractmethod
      7. def get_radius ( self ):
      8. """Method that should do something."""
      使用 abc以及其特定的方法,一旦你尝试去实例化 BasePizza类或任意从其继承下来的类的时候都会得到一个错误。
      
            
            
      1. >>> BasePizza ( )
      2. Traceback (most recent call last ):
      3. File "<stdin>" , line 1 , in <module >
      4. TypeError: Can 't instantiate abstract class BasePizza with abstract methods get_radius

      混搭使用静态、类以及抽象方法

      当创建类及继承时,你就得使用混搭这些方式的方法了。这里一些提示。

      请记住,申明类时应先申明成抽象类,并且不要把类的原型写死。这意味着,抽象方法必需得实现,但我可以在实现抽象方法时可以任意指定参数列表。

      
            
            
      1. import abc
      2.  
      3. class BasePizza ( object ):
      4. __metaclass__ = abc. ABCMeta
      5.  
      6. @abc. abstractmethod
      7. def get_ingredients ( self ):
      8. """Returns the ingredient list."""
      9.  
      10. class Calzone (BasePizza ):
      11. def get_ingredients ( self , with_egg = False ):
      12. egg = Egg ( ) if with_egg else None
      13. return self. ingredients + egg
      Calzone继承 BasePizza得按要求实现对应的接口,这非常有用。这意味着,我们可以实现成一个类或一个静态方法,比如:
      
            
            
      1. import abc
      2.  
      3. class BasePizza ( object ):
      4. __metaclass__ = abc. ABCMeta
      5.  
      6. @abc. abstractmethod
      7. def get_ingredients ( self ):
      8. """Returns the ingredient list."""
      9.  
      10. class DietPizza (BasePizza ):
      11. @ staticmethod
      12. def get_ingredients ( ):
      13. return None
      这是正确的而且也实现了抽象类 BasePizza所有要求。事实上一个实现细节是 get_ingredients方法返回结果时无需关心对象,不用关心有何约束。因此,你不能强 制要求抽象方法或类的实现是有规律的,或者说不应该这样做。从Python3开始(Python2不会生效,见 issue5867),可以在 @abstractmethod之上使用 @staticmethod@classmethod
      
            
            
      1. import abc
      2.  
      3. class BasePizza ( object ):
      4. __metaclass__ = abc. ABCMeta
      5.  
      6. ingredient = [ 'cheese' ]
      7.  
      8. @ classmethod
      9. @abc. abstractmethod
      10. def get_ingredients (cls ):
      11. """Returns the ingredient list."""
      12. return cls. ingredients
      不要误读成这个:如果你尝试去强迫子类实现 get_ingredients作为类的方法,你就错了。这只是意味着你在 BasePizza类中,实现 get_ingredients只是一个类方法。

      抽象类中实现抽象方法?是的!Python中抽象方法与Java相反。你可以在抽象方法中加入代码并通过super()调用:

      
            
            
      1. import abc
      2.  
      3. class BasePizza ( object ):
      4. __metaclass__ = abc. ABCMeta
      5.  
      6. default_ingredients = [ 'cheese' ]
      7.  
      8. @ classmethod
      9. @abc. abstractmethod
      10. def get_ingredients (cls ):
      11. """Returns the ingredient list."""
      12. return cls. default_ingredients
      13.  
      14. class DietPizza (BasePizza ):
      15. def get_ingredients ( self ):
      16. return [ 'egg' ] + super (DietPizza , self ). get_ingredients ( )
      这种情况下,所有从 BasePizza类中继承下来的pizza都得实现 get_ingredients方法,但是我们可以使用 super()通过默认机制获取配料表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值