Effective Python note(2)

Effective Python note(2)

类与继承:

尽量用辅助类来维护程序的状态,而不要用字典和元组

​ 在Python编程中,当需要在程序中维护状态时,应该优先考虑使用自定义的辅助类(辅助类是指那些主要用于封装数据和行为的类),而不是依赖于内置的字典(dict)或元组(tuple)类型。这是因为随着程序的复杂性增加,使用字典和元组可能会导致代码难以理解和维护

主要考虑两点

  • 代码的可维护性:随着需求的变化,如果使用字典和元组来维护状态,可能需要对数据结构进行复杂的修改,这会导致代码难以理解和维护
  • 辅助类的优势:使用辅助类可以提供更明确的接口和更好的封装。当状态变得复杂时,将数据结构拆解为多个辅助类,可以创建一个抽象层,使得代码更加清晰和易于维护

假设我们需要管理一个学生的成绩,包括学生的名字和他们的成绩:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
grades = {}
grades['Alice'] = 95
grades['Bob'] = 85

def report_grade(name, score):
grades[name] = score

# 计算平均成绩
def average_grade():
total = sum(grades.values())
count = len(grades)
return total / count if count else 0

# 使用辅助类的示例
class StudentGradebook:
def __init__(self):
self.grades = {}

def add_student(self, name, score):
self.grades[name] = score

def report_grade(self, name, score):
self.grades[name] = score

def average_grade(self):
total = sum(self.grades.values())
count = len(self.grades)
return total / count if count else 0

# 使用辅助类来管理成绩
gradebook = StudentGradebook()
gradebook.add_student('Alice', 95)
gradebook.add_student('Bob', 85)

print("Average grade:", gradebook.average_grade())

@classmethod 形式的多态去通用地构建对象

​ 建议提倡在Python中使用@classmethod来实现类方法的多态性。由于Python不像C++一样,同一个类可以有不同的构造函数,Python只有__init__构造,因此使用@classmethod

通用构造器(Generic Constructor):使用@classmethod定义的类方法可以作为通用构造器,它允许你为类定义一个统一的创建接口,而不需要为每个子类单独定义构造器。这在创建具有不同配置或行为的实例时特别有用

​ 假设我们有一个Shape类,它是一个抽象的图形基类。我们想要创建一个通用的构造器,它可以根据不同的参数创建不同类型的子类实例,比如CircleRectangle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Shape:
def __init__(self, name):
self.name = name

@classmethod
def create_shape(cls, shape_type, **kwargs):
if shape_type == 'circle':
return Circle(**kwargs)
elif shape_type == 'rectangle':
return Rectangle(**kwargs)
else:
raise ValueError(f"Unknown shape type: {shape_type}")

class Circle(Shape):
def __init__(self, radius):
super().__init__('Circle')
self.radius = radius

class Rectangle(Shape):
def __init__(self, width, height):
super().__init__('Rectangle')
self.width = width
self.height = height

# 使用通用构造器创建实例
circle = Shape.create_shape('circle', radius=5)
rectangle = Shape.create_shape('rectangle', width=4, height=6)

print(circle.name, circle.radius) # 输出: Circle 5
print(rectangle.name, rectangle.width, rectangle.height) # 输出: Rectangle 4 6

继承 collections.abc以实现自定义的容器类型

​ 需要创建自定义的容器类型时,应该继承自collections.abc模块中的抽象基类(Abstract Base Classes,简称ABC)。这些ABC提供了各种容器类型(如SequenceMappingSet等)的标准接口和行为。通过继承这些ABC,你的自定义类将自动获得这些标准行为,同时确保了与Python内置容器类型相似的接口一致性。

  • 自定义容器的挑战:在Python中,创建自定义容器类型通常需要实现一系列特殊的方法(如__len____getitem____setitem__等),这些方法定义了容器的基本行为。手动实现这些方法可能会非常繁琐,并且容易出错。
  • 利用ABC的优势:通过继承collections.abc中的ABC,你可以确保你的自定义类遵循了正确的接口。如果遗漏了某个方法,Python解释器会在你尝试实例化该类时抛出错误,从而帮助你发现问题。此外,一旦你的类实现了ABC要求的所有方法,它就可以被视为一个合法的容器类型,并且可以使用Python内置的函数和方法,如len()in操作符等。

​ 为了创建一个自定义的二叉树数据结构,并且希望它能够像Python内置的序列类型(如列表)那样被处理,我们可以继承collections.abc中的Sequence抽象基类。这样,我们的二叉树就可以使用len()__getitem__()等内置方法,同时保持二叉树的特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from collections.abc import Sequence

class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None

class BinaryTree(Sequence):
def __init__(self, root=None):
self.root = root

def __len__(self):
if self.root is None:
return 0
return 1 + len(self.root.left) + len(self.root.right)

def __getitem__(self, index):
if self.root is None:
raise IndexError("Tree is empty")
return self._get_element_at_index(index)

def _get_element_at_index(self, index):
if index == 0:
return self.root.value
left_len = len(self.root.left)
if index < left_len:
return self.root.left._get_element_at_index(index)
elif index == left_len:
return self.root.value
else:
return self.root.right._get_element_at_index(index - left_len - 1)

def insert(self, value):
if self.root is None:
self.root = TreeNode(value)
else:
self._insert_into_tree(self.root, value)

def _insert_into_tree(self, node, value):
if value < node.value:
if node.left is None:
node.left = TreeNode(value)
else:
self._insert_into_tree(node.left, value)
else:
if node.right is None:
node.right = TreeNode(value)
else:
self._insert_into_tree(node.right, value)

# 使用自定义的二叉树
tree = BinaryTree()
tree.insert(5)
tree.insert(3)
tree.insert(7)

print(len(tree)) # 输出: 3
print(tree[0]) # 输出: 5
print(tree[1]) # 输出: 3
print(tree[2]) # 输出: 7

​ 在这个例子中,BinaryTree类继承自Sequence,它提供了__len____getitem__方法,使得二叉树可以像序列一样被访问

还有一些小建议:

  • 只在使用 Mix-in 组件制作工具类时进行多重继承
  • 多用 public 属性(_name),少用 private 属性(__name)
  • super去初始化父类

元类及属性:

​ 又复杂又难看又用不着,以后有时间再更