查看原文
其他

为什么要创建一个不能被实例化的类?

Python猫 2021-03-15

The following article is from 未闻Code Author kingname


花下猫语:今天分享的文章介绍了 Python 中的进阶内容 Mixins,文章的切入点很有意思,是从“为什么要创建一个不能实例化的类”引入的,全文简短而清晰,就算是根本没接触过这个概念的人,也能基本把握住其要点,值得推荐一读!

剧照 | 《霸王别姬》
来源:kingname@未闻Code公众号
当我们创建一个Python 类并初始化时,一般代码这样写:
class People:
def __init__(self, name):
self.name = name

def say(self):
print(f'我叫做:{self.name}')


kingname = People('kingname')
kingname.say()
运行效果如下图所示:
上面是众所周知的写法。但如果有一天,你发现我写了这样一个类:
class People:
def say(self):
print(f'我叫做:{self.name}')

def __new__(self):
raise Exception('不能实例化这个类')


kingname = People()
kingname.say()
一旦初始化就会报错,如下图所示:
你会不会感到非常奇怪?一个不能被初始化的类,有什么用?
这就要引入我们今天讨论的一种设计模式——混入(Mixins)。
Python 由于多继承的原因,可能会出现钻石继承[1]又叫菱形继承。为了保留多继承的优点,但又摒除缺点,于是有了混入这种编程模式。
Mixins 是一个 Python 类,它只有方法,没有状态,不应该被初始化。它只能作为父类被继承。每个 Mixins 类只有一个或者少数几个方法。不同的 Mixin 的方法互不重叠。
例如,我们现在有一个类 People
class People():
def __init__(self, name, age):
self.age = age
self.name = name

def say(self):
print(f'我叫做:{self.name},我今年{self.age}岁')

kingname = People('kingname', 28)
pm = People('pm', 25)
kingname > pm
显然,这样写会报错,因为两个类的实例是不能比较大小的:
但在现实生活中,当我们说 某人比另一个人大时,实际上是指的某人的年龄比另一人年龄大。所以如果要让这两个实例比较大小,我们需要实现多个魔术方法:
class People():
def __init__(self, name, age):
self.age = age
self.name = name

def say(self):
print(f'我叫做:{self.name},我今年{self.age}岁')

def __ne__(self, other):
return self.age != other.age

def __lt__(self, other):
return self.age < other.age

def __le__(self, other):
return self.age <= other.age

def __gt__(self, other):
return self.age > other.age

def __ge__(self, other):
return self.age >= other.age
运行效果如下图所示:
但如果这几个魔术方法会在多个类中使用,那么我们就可以把它抽出来,作为一个父类:
class ComparableMixin(object):
def __ne__(self, other):
return self.age != other.age
def __lt__(self, other):
return self.age < other.age
def __le__(self, other):
return self.age <= other.age
def __gt__(self, other):
return self.age > other.age
def __ge__(self, other):
return self.age >= other.age
然后在使用 People 类继承它:
本质上,混入的写法与普通的类继承类没有什么区别。但是 在写 Mixins 类的时候,我们不会写__init__方法,也不会写类属性。并且 Mixin 类中的方法看起来更像是工具方法。
我们可以写很多个 Mixin 类,然后用一个子类去继承他们。由于这些 Mixin 类提供的各个工具方法互不相关,所以不存在菱形继承的问题。但是在子类中却可以分别调用这些工具方法,从而扩展子类的功能。
最后,我们对比一下抽象类(Abstract Class)接口(Interface)混入(Mixins)的区别:
抽象类:
  • 包含一个或多个抽象方法。
  • 允许包含状态(实例变量)和非抽象方法。
接口:
  • 只能包含抽象方法。
混入:
  • 不能包含状态(实例变量)。
  • 包含一个或多个非抽象方法。

参考资料

[1]

钻石继承: https://en.wikipedia.org/wiki/Multiple_inheritance

优质文章,推荐阅读:

微软官方上线Python教程

Python 工匠:编写地道循环的两个建议

如何给列表降维?sum()函数的妙用

Python 进阶之源码分析:如何将一个类方法变为多个方法?

感谢创作者的好文

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存