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] = scoredef 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
类,它是一个抽象的图形基类。我们想要创建一个通用的构造器,它可以根据不同的参数创建不同类型的子类实例,比如Circle
和Rectangle
。
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 = radiusclass 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) print (rectangle.name, rectangle.width, rectangle.height)
继承 collections.abc
以实现自定义的容器类型
需要创建自定义的容器类型时,应该继承自collections.abc
模块中的抽象基类(Abstract Base Classes,简称ABC)。这些ABC提供了各种容器类型(如Sequence
、Mapping
、Set
等)的标准接口和行为。通过继承这些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)) print (tree[0 ]) print (tree[1 ]) print (tree[2 ])
在这个例子中,BinaryTree
类继承自Sequence
,它提供了__len__
和__getitem__
方法,使得二叉树可以像序列一样被访问
还有一些小建议:
只在使用 Mix-in 组件制作工具类时进行多重继承
多用 public 属性(_name
),少用 private 属性(__name
)
用super
去初始化父类
元类及属性:
又复杂又难看又用不着,以后有时间再更