zoukankan      html  css  js  c++  java
  • 关于Python中如何使用静态、类、抽象方法的权威指南(译)

          对于Python中静态、类、抽象方法的使用,我是一直很迷糊的。最近看到一篇技术文章对这方面解释的很好,在此翻译一下,加深印象,也为有需要的同学提供一个方便。

          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属性的访问时没有绑定。这是什么意思呢?我们马上就会知道只要我们继续调用它一下:

    >>> 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的实例。方法需要一个实例作为它的第一个参数(在Python 2中它必须是该类的一个实例,在Python 3中它可以是任何实例),让我们试一下:

    >>> 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 0x00000000025B3E48>>
    
    >>> 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 0x0000000002A95CF8>
    
    >>>
    
    >>> m == m.__self__.get_size
    
    True

          显然,我们依然有一个到对象的引用,如果有需要可以找回来。

          在Python 3中,附加到类的方法不再视为绑定方法了,仅作为简单函数。如果有需要他们绑定到一个对象。原理依然保持不变,但是模型简化了。

    >>> 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 0x0000000002907268>

          静态方法:

          静态方法是方法的一种特殊情况。有时候,你需要编写属于某个类的代码,但是从不使用对象本身。例如:

    >>> class Pizza(object):
    
    ...     @staticmethod
    
    ...     def mix_ingredients(x,y):
    
    ...             return x+y
    
    ...     def cook(self):
    
    ...             return self.mix_ingredient(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的类在没有重载cook本身的情况下,不能改变我们用于混合pizza的成分。

          类方法:

          说了这么多,那么什么是类方法?类方法是不绑定到对象但是绑定到类的方法。(注意我下面标红的部分,与原文有出入,我在Python 2.7.9和Python 3.4.3下运行得到的都是False)

    >>> 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
    

    False

    >>> Pizza.get_radius()
    
    42

          不管你使用什么方式来访问这个方法,它总是绑定于它依附的类,而且它的第一个参数是类本身(记住类也是对象)。

          那么,什么时候时候这种类型的方法呢?class方法常用于一下两种类型的方法中:

    •       工厂方法,即用于创建一个类的实例用于某种预处理。如果我们使用@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_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中,这种方法被描述为接口。

          在Python中最简单的写一个抽象方法的方式如下:

    class Pizza(object):
    
        def get_radius(self):
    
            raise NotImplementedError

          任何其他继承自Pizza的类应该实现并且重载get_radius方法。否则一个异常将会抛出。

          这种特殊的实现抽闲方法的方式有一个缺点。如果你写一个继承自Pizza的类并且忘记实现get_radius了,错误仅在你打算试用这个方法的时候抛出。

    >>> Pizza()
    
    <__main__.Pizza object at 0x0000000002B9C208>
    
    >>> Pizza().get_radius()
    
    Traceback (most recent call last):
    
      File "<stdin>", line 1, in <module>
    
      File "<stdin>", line 3, in get_radius
    
    NotImplementedError

          有一种方法可以早点触发这种方式,当对象被实例化之后,使用Python提供的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方法并不需要知道返回结果的对象其实是一个实现细节,不是一个让我们合约履行的标准。

          因此,你不能强迫你的抽象方法的实现是一个普通的或者类或者静态方法。从Python 3(这在Python 2是行不通的,参照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()来获取成分列表。

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

  • 相关阅读:
    docker容器的时间同步
    Java中的各种bean对应的意义(VO,PO,BO,QO, DAO,POJO,DTO)
    Vue-admin工作整理(十九):从数字渐变组件谈第三方JS库Count-to的使用
    HTTP 方法:Get与Post分析
    Java核心知识盘点(三)- 框架篇-Spring
    Java核心知识盘点(二)- 缓存使用
    Java核心知识盘点(一)- 数据存储
    Java基础知识盘点(三)- 线程篇
    Java基础知识盘点(二)- 集合篇
    Java基础知识盘点(一)- 基础篇
  • 原文地址:https://www.cnblogs.com/hiccup/p/4607216.html
Copyright © 2011-2022 走看看