让简单的接口接受函数
Python 有许多内置的 API,都允许我们传入某个函数来定制它的行为。这种函数可以叫作挂钩。
names = ['Socrates','Archimedes','Plato','Aristotle']
names.sort(key=len)
print(names)
>>>
['Plato','Socrates','Aristotle','Archimedes']用函数来当作挂钩,比定义成类更简单。同时,挂钩函数与其他函数一样,都是 Python 里的头等对象,即可以像 Python 中其他值一样传递与引用。
使用函数作为挂钩时,函数一般都是无状态的函数。那么如果想要用函数来维护状态,可以考虑定义一个带有 __call__ 方法的新类,而不要用有状态的闭包来实现。
class BetterCountMissing:
def __init__(self):
self.added = 0
def __call__(self):
self.added += 1
return 0
counter = BetterCountMissing()
result = defaultdict(counter, {"hi": 1, "oo":2})
result["gg"] += 1
result["hh"] += 1
assert counter.added == 2通过 @classmethod 多态来构造对象
class BaseInput:
def __init__(self):
pass
def do_something(self):
raise NotImplementError
@classmethod
def create_input(cls, config):
raise NotImplementError
class PathInput(BaseInput):
def __init__(self, path):
self.path = path
def do_something(self):
with open(self.path, 'r') as fp:
yield fp.readline()
@classmethod
def create_input(cls, config):
return cls(config["input_path"])
class BaseOutput:
def __init__(self):
pass
def do_something(self):
raise NotImplementError
@classmethod
def create_output(cls, config):
raise NotImplementError
class PathOutput(BaseInput):
def __init__(self, path):
self.path = path
def do_something(self, text):
with open(self.path, 'a') as fp:
fp.write(text)
@classmethod
def create_output(cls, config):
return cls(config["output_path"])
def pipeline(input_class, output_class, config):
input_cls = input_class.create_input(config)
output_cls = output_class.create_output(config)
for anything in input_cls.do_something():
output_cls.do_something(anything)
pipeline(PathInput, PathOutput, {"input_path": "1.txt", "output_path": "2.txt"})使用 super 初始化超类
Python 内置了 super 函数并且规定了标准的方法解析顺序(method resolution order,MRO)。super 能够确保菱形继承体系中的共同超类只初始化一次(其他案例参见第 48 条)。MRO 可以确定超类之间的初始化顺序,它遵循 C3 线性化(C3 linearization)算法。
优雅的用多重继承来封装逻辑
如果既要通过多重继承来方便地封装逻辑,又想避开可能出现的问题,那么就应该把有待继承的类写成 mix-in 类。这种类只提供一小套方法给子类去沿用,而不定义自己实例级别的属性,也不需要 __init__ 构造函数。
例如,定义一个可以实现转换为字典形式的 to_dict 的超类。
class ToDictMinin:
def to_dict(self):
return self._traverse_dict(self.__dict__)
def _traverse_dict(self, instance_dict):
output={}
for key,value in instance_dict.items():
output[key] = self._traverse(key,value)
return output
def _traverse(self,key,value):
if isinstance(value,ToDictMixin):
return value.to_dict()
elif isinstance(value,dict):
return self._traverse_dict(value)
elif isinstance(value,list):
return [self._traverse(key,i) for i in value]
elif hasattr(value, '__dict__'):
return self._traverse_dict(value.__dict__)
else:
return value可以创建基于该超类的二叉树,那么这个二叉树就拥有了 to_dict 功能。这种继承方式避免了多重继承时可能引发的一些问题,如 __init__ 函数内使用 super 时混乱的初始化顺序。
class BinaryTree(ToDictMixin):
def __init__(self,value,left=None,right=None):
self.value=value
self.left=left
self.right= right
tree = BinaryTree(10,
left=BinaryTree(7,right=BinaryTree(9)),
right=BinaryTree(13,left=BinaryTree(11)))
print(tree.to_dict())如果子类要定制(或者说修改)mix-in 所提供的功能,那么可以在自己的代码里面覆盖相关的实例方法。
通过将多个 mix-in 提供的功能组合起来,就可以实现更复杂的功能。
Python 类的 public 与 private 属性
Python 类的属性只有两种访问级别,也就是 public 与 private。
class MyObject:
def __init__(self):
self.public_field=5
self.__private_field=10 # 双下划线 —— private 属性
def get_private_field(self):
return self.__private_fieldprivate 属性只能给自己的类使用,子类无法访问超类的 private 属性。
class MyParentObject:
def __init__(self):
self.__private_field=71
class MyChildObject(MyParentObject):
def get_private_field(self):
return self.__private_field
baz = MyChildObject()
baz.get_private_field() # Error!而这种防止访问的方式是通过变换属性名称来实现的。
当 Python 编译器看到 MyChildObject.get_private_field 这样的方法想要访问 __private_field 属性时,它会把下划线和类名加在这个属性名称的前面,所以代码实际上访问的是 _MyChildObject__private_field。在上面的例子中,__private_field 是在 MyParentObject 的 __init__ 里面定义的,所以,它变换之后的真实名称是 _MyParentObject__private_field。子类不能通过 __private_field 来访问这个属性,因为这样写实际上是在访问不存的 _MyChildObject__private_field,而不是 _MyParentObject__private_field。
了解名称变换规则后,我们就可以从任何一个类里面访问 private 属性。无论是子类还是外部的类,都可以不经许可就访问到这些属性。
assert baz._MyParentObject__private_field == 71从 collections.abc 继承来辅助补充容器功能
Python 本身提供了一些内置的容器类型,例如 list、tuple、set、dict 等,也可以用来管理数据。通过继承这些内置容器进行继承,可以拥有这些内置容器的功能。
例如下面的 FrequencyList 类继承自 list,并增加了 frequency 方法来统计每个元素的数目。
class FrequencyList(list):
def __init__(self,members):
super().__init__(members)
def frequency(self):
counts ={}
for item in self:
counts[item]=counts.get(item,0) +1
return counts然而,有时候并不想继承 list,但拥有 list 中的“根据下标访问元素”、“获取对象长度”的功能。
针对“根据下标访问元素”功能,Python 实际上是调用对象的 __getitem__(idx) 函数。针对“获取对象长度”功能,Python 实际上调用对象的 __len__ 函数。因此,假如设计了类 Obj,那么实现了 __getitem__(idx) 函数和 __len__ 函数,就可以拥有 list 中的“根据下标访问元素”、“获取对象长度”的功能。
class Obj:
def __init__(self):
self.data = [1, 3, 4]
def __getitem__(self, index): # 这里没做异常处理
return self.data[index]
def __len__(self):
return len(self.data)
obj = Obj()
print(obj[1])
print(len(obj))但是,有时候不清楚应该要实现哪些函数