博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于Python中如何使用静态、类、抽象方法的权威指南
阅读量:4882 次
发布时间:2019-06-11

本文共 6388 字,大约阅读时间需要 21 分钟。

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

      Python中方法是如何工作的:

      方法即函数,作为一个类的属性存储。你能像如下申明和访问一个函数:

 
>>> class Pizza(object):
...     def __init__(self,size):
...             self.size = size
...     def get_size(self):
...             return self.size
...
>>> Pizza.get_size

      Python在这里告诉我们,Pizza类的get_size属性的访问时没有绑定。这是什么意思呢?我们马上就会知道只要我们继续调用它一下:

 
>>> Pizza.get_size()
Traceback (most recent call last):
File "
", line 1, in
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
>
>>> 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

      静态方法:

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

 
>>> 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
>
>>> Pizza().get_radius
>
>>> 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 "
", line 1, in
File "
", 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 "
", line 1, in
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是行不通的,参照)开始,它现在可以在@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://www.cnblogs.com/hiccup/p/5423980.html

你可能感兴趣的文章
【1】自定义WindowsForm分页控件使用【共两篇】
查看>>
堆的插入删除
查看>>
期末大作业
查看>>
[转载] C++ 类中的类成员变量怎么调用带参数的构造函数来初始化?
查看>>
123D
查看>>
你知道各调的特点吗?
查看>>
luogu P1908 逆序对
查看>>
linux用户和组管理,/etc/passwd 、/etc/shadow和/etc/group 文件内容解释
查看>>
点分治详解
查看>>
Linux--多网卡的7种Bond模式
查看>>
页面中图片保持不拉伸
查看>>
管理表分区
查看>>
OpenSessionInViewFilter配置
查看>>
p 3750
查看>>
Vue.js--计算属性缓存与method的区别
查看>>
关于MAC升级后,vim更新插件报错
查看>>
npm scripts的生命周期管理
查看>>
JS 中 ++i 和i++的区别
查看>>
hadoop多次格式化后,导致datanode启动不了
查看>>
linux 下ab压力测试
查看>>