Python 类扩展与高级应用技巧
1. 类扩展之 Mixin 模式
在编程中,有时我们有一组有用的方法,希望在不同类中复用,以扩展其功能,但这些类之间可能没有继承关系,不能简单地将方法附加到一个公共基类上。这时,Mixin 模式就派上用场了。
1.1 Mixin 类的定义
以下是几个示例 Mixin 类:
class LoggedMappingMixin:
'''
添加 get/set/delete 操作的日志记录,用于调试。
'''
__slots__ = ()
def __getitem__(self, key):
print('Getting ' + str(key))
return super().__getitem__(key)
def __setitem__(self, key, value):
print('Setting {} = {!r}'.format(key, value))
return super().__setitem__(key, value)
def __delitem__(self, key):
print('Deleting ' + str(key))
return super().__delitem__(key)
class SetOnceMappingMixin:
'''
允许键只设置一次。
'''
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key) + ' already set')
return super().__setitem__(key, value)
class StringKeysMappingMixin:
'''
禁止键为字符串以外的类型。
'''
__slots__ = ()
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError('keys must be strings')
return super().__setitem__(key, value)
这些 Mixin 类本身单独使用没有太大意义,需要通过多重继承混入其他类中。
1.2 Mixin 类的使用示例
class LoggedDict(LoggedMappingMixin, dict):
pass
d = LoggedDict()
d['x'] = 23
# 输出: Setting x = 23
d['x']
# 输出: Getting x
# 输出: 23
del d['x']
# 输出: Deleting x
从这个示例可以看出,Mixin 类与现有的类(如
dict
)结合使用,共同提供所需的功能。
1.3 Mixin 类的注意事项
- 不能直接创建实例 :Mixin 类不是为了直接创建实例而设计的,它们需要混入到其他实现所需功能的类中。
-
通常无自身状态
:Mixin 类一般没有
__init__()方法和实例变量,__slots__ = ()明确表示它们没有自己的实例数据。如果要定义有__init__()方法和实例变量的 Mixin 类,需要注意变量名避免冲突,并且__init__()方法要正确调用其他类的__init__()方法。 -
super()的使用 :super()是编写 Mixin 类的关键,它将方法调用委托给方法解析顺序(MRO)中的下一个类。例如,在LoggedMappingMixin中使用super().__getitem__()实际上会调用dict.__getitem__()。
1.4 装饰器实现 Mixin
除了多重继承,还可以使用装饰器实现类似 Mixin 的功能。
def LoggedMapping(cls):
cls_getitem = cls.__getitem__
cls_setitem = cls.__setitem__
cls_delitem = cls.__delitem__
def __getitem__(self, key):
print('Getting ' + str(key))
return cls_getitem(self, key)
def __setitem__(self, key, value):
print('Setting {} = {!r}'.format(key, value))
return cls_setitem(self, key, value)
def __delitem__(self, key):
print('Deleting ' + str(key))
return cls_delitem(self, key)
cls.__getitem__ = __getitem__
cls.__setitem__ = __setitem__
cls.__delitem__ = __delitem__
return cls
@LoggedMapping
class LoggedDict(dict):
pass
这种方式不使用多重继承,而是通过装饰器对类定义进行修改,替换部分方法。
2. 实现状态对象或有限状态机
在某些应用中,我们需要根据对象的内部状态执行不同的操作,但又不想让代码被大量的条件判断所充斥。这时可以采用将每个操作状态编码为单独的类,让主类将操作委托给状态类的方法。
2.1 简单实现
class Connection:
def __init__(self):
self.new_state(ClosedConnectionState)
def new_state(self, newstate):
self._state = newstate
# 委托给状态类
def read(self):
return self._state.read(self)
def write(self, data):
return self._state.write(self, data)
def open(self):
return self._state.open(self)
def close(self):
return self._state.close(self)
# 连接状态基类
class ConnectionState:
@staticmethod
def read(conn):
raise NotImplementedError()
@staticmethod
def write(conn, data):
raise NotImplementedError()
@staticmethod
def open(conn):
raise NotImplementedError()
@staticmethod
def close(conn):
raise NotImplementedError()
# 关闭连接状态
class ClosedConnectionState(ConnectionState):
@staticmethod
def read(conn):
raise RuntimeError('Not open')
@staticmethod
def write(conn, data):
raise RuntimeError('Not open')
@staticmethod
def open(conn):
conn.new_state(OpenConnectionState)
@staticmethod
def close(conn):
raise RuntimeError('Already closed')
# 打开连接状态
class OpenConnectionState(ConnectionState):
@staticmethod
def read(conn):
print('reading')
@staticmethod
def write(conn, data):
print('writing')
@staticmethod
def open(conn):
raise RuntimeError('Already open')
@staticmethod
def close(conn):
conn.new_state(ClosedConnectionState)
c = Connection()
print(c._state)
# 输出: <class '__main__.ClosedConnectionState'>
try:
c.read()
except RuntimeError as e:
print(e)
# 输出: Not open
c.open()
print(c._state)
# 输出: <class '__main__.OpenConnectionState'>
c.read()
# 输出: reading
c.write('hello')
# 输出: writing
c.close()
print(c._state)
# 输出: <class '__main__.ClosedConnectionState'>
这个实现将不同的连接状态封装在不同的类中,避免了大量的条件判断,使代码更易于维护和理解。
2.2 直接操作
__class__
属性的实现
class Connection:
def __init__(self):
self.new_state(ClosedConnection)
def new_state(self, newstate):
self.__class__ = newstate
def read(self):
raise NotImplementedError()
def write(self, data):
raise NotImplementedError()
def open(self):
raise NotImplementedError()
def close(self):
raise NotImplementedError()
class ClosedConnection(Connection):
def read(self):
raise RuntimeError('Not open')
def write(self, data):
raise RuntimeError('Not open')
def open(self):
self.new_state(OpenConnection)
def close(self):
raise RuntimeError('Already closed')
class OpenConnection(Connection):
def read(self):
print('reading')
def write(self, data):
print('writing')
def open(self):
raise RuntimeError('Already open')
def close(self):
self.new_state(ClosedConnection)
c = Connection()
print(c)
# 输出: <__main__.ClosedConnection object at ...>
try:
c.read()
except RuntimeError as e:
print(e)
# 输出: Not open
c.open()
print(c)
# 输出: <__main__.OpenConnection object at ...>
c.read()
# 输出: reading
c.close()
print(c)
# 输出: <__main__.ClosedConnection object at ...>
这种实现方式消除了额外的间接层,当状态改变时,实例直接改变其类型。虽然可能会让一些纯面向对象编程者不满,但技术上是可行的,并且可能会提高程序的执行效率。
3. 调用对象方法(传入方法名作为字符串)
有时候我们需要根据字符串形式的方法名调用对象的方法,有两种常见的实现方式。
3.1 使用
getattr()
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Point({!r:},{!r:})'.format(self.x, self.y)
def distance(self, x, y):
return math.hypot(self.x - x, self.y - y)
p = Point(2, 3)
d = getattr(p, 'distance')(0, 0)
print(d)
这种方式通过
getattr()
查找对象的属性(方法),然后将其作为函数调用。
3.2 使用
operator.methodcaller()
import operator
points = [
Point(1, 2),
Point(3, 0),
Point(10, -3),
Point(-5, -7),
Point(-1, 8),
Point(3, 2)
]
# 按到 (0, 0) 的距离排序
points.sort(key=operator.methodcaller('distance', 0, 0))
print(points)
operator.methodcaller()
创建一个可调用对象,并固定方法的参数,方便多次使用相同参数调用方法。
4. 实现访问者模式
当需要处理复杂的数据结构,且其中的不同类型对象需要不同的处理方式时,访问者模式是一个很好的解决方案。
4.1 数据结构的定义
class Node:
pass
class UnaryOperator(Node):
def __init__(self, operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self, value):
self.value = value
# 表示 1 + 2 * (3 - 4) / 5
t1 = Sub(Number(3), Number(4))
t2 = Mul(Number(2), t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1), t3)
4.2 访问者类的实现
class NodeVisitor:
def visit(self, node):
methname = 'visit_' + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)
def generic_visit(self, node):
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)
def visit_Negate(self, node):
return -node.operand
e = Evaluator()
result = e.visit(t4)
print(result)
# 输出: 0.6
访问者模式将数据结构的操作与数据结构本身分离,提高了代码的通用性。通过动态生成方法名并使用
getattr()
获取方法,避免了大量的
if
语句,使代码更易于维护。
5. 无递归实现访问者模式
在处理深度嵌套的数据结构时,递归实现的访问者模式可能会达到 Python 的递归限制。可以使用生成器和栈来消除递归。
5.1 无递归访问者类的实现
import types
class Node:
pass
class NodeVisitor:
def visit(self, node):
stack = [node]
last_result = None
while stack:
try:
last = stack[-1]
if isinstance(last, types.GeneratorType):
stack.append(last.send(last_result))
last_result = None
elif isinstance(last, Node):
stack.append(self._visit(stack.pop()))
else:
last_result = stack.pop()
except StopIteration:
stack.pop()
return last_result
def _visit(self, node):
methname = 'visit_' + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)
def generic_visit(self, node):
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
def visit_Sub(self, node):
yield (yield node.left) - (yield node.right)
def visit_Mul(self, node):
yield (yield node.left) * (yield node.right)
def visit_Div(self, node):
yield (yield node.left) / (yield node.right)
def visit_Negate(self, node):
yield - (yield node.operand)
a = Number(0)
for n in range(1, 100000):
a = Add(a, Number(n))
e = Evaluator()
result = e.visit(a)
print(result)
这种实现利用生成器的
yield
特性,将节点发送回
visit()
方法进行处理,避免了递归调用,解决了递归深度限制的问题。
6. 循环数据结构的内存管理
在处理循环数据结构(如树、图等)时,Python 的垃圾回收机制可能会出现问题,因为循环引用会导致对象的引用计数永远不为 0。可以使用
weakref
库创建弱引用解决这个问题。
6.1 弱引用的使用示例
import weakref
class Node:
def __init__(self, value):
self.value = value
self._parent = None
self.children = []
def __repr__(self):
return 'Node({!r:})'.format(self.value)
@property
def parent(self):
return self._parent if self._parent is None else self._parent()
@parent.setter
def parent(self, node):
self._parent = weakref.ref(node)
def add_child(self, child):
self.children.append(child)
child.parent = self
root = Node('parent')
c1 = Node('child')
root.add_child(c1)
print(c1.parent)
# 输出: Node('parent')
del root
print(c1.parent)
# 输出: None
弱引用不会增加对象的引用计数,当对象的其他引用消失时,对象可以被正常垃圾回收。
7. 让类支持比较操作
如果希望类的实例能够使用常见的比较运算符(如
>=
,
!=
,
<=
等),可以使用
functools.total_ordering
装饰器简化实现。
7.1 示例代码
from functools import total_ordering
class Room:
def __init__(self, name, length, width):
self.name = name
self.length = length
self.width = width
self.square_feet = self.length * self.width
@total_ordering
class House:
def __init__(self, name, style):
self.name = name
self.style = style
self.rooms = list()
@property
def living_space_footage(self):
return sum(r.square_feet for r in self.rooms)
def add_room(self, room):
self.rooms.append(room)
def __str__(self):
return '{}: {} square foot {}'.format(self.name,
self.living_space_footage,
self.style)
def __eq__(self, other):
return self.living_space_footage == other.living_space_footage
def __lt__(self, other):
return self.living_space_footage < other.living_space_footage
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))
h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))
houses = [h1, h2, h3]
print('Is h1 bigger than h2?', h1 > h2)
print('Is h2 smaller than h3?', h2 < h3)
print('Is h2 greater than or equal to h1?', h2 >= h1)
print('Which one is biggest?', max(houses))
print('Which is smallest?', min(houses))
total_ordering
装饰器根据定义的
__eq__()
和一个比较方法(如
__lt__()
)自动生成其他比较方法,减少了手动编写大量比较方法的工作量。
8. 创建缓存实例
在创建类的实例时,有时希望对于相同的参数返回缓存的实例引用。
8.1 简单缓存实现
# 被查询的类
class Spam:
def __init__(self, name):
self.name = name
# 支持缓存
import weakref
_spam_cache = weakref.WeakValueDictionary()
def get_spam(name):
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s
a = get_spam('foo')
b = get_spam('bar')
c = get_spam('foo')
print(a is c)
# 输出: True
使用
WeakValueDictionary
作为缓存,当实例不再被使用时,缓存中的引用会自动消失,避免了内存泄漏。
8.2 更完善的实现
可以将缓存代码封装在一个管理类中,提高代码的可维护性和灵活性。
import weakref
class CachedSpamManager:
def __init__(self):
self._cache = weakref.WeakValueDictionary()
def get_spam(self, name):
if name not in self._cache:
s = Spam(name)
self._cache[name] = s
else:
s = self._cache[name]
return s
def clear(self):
self._cache.clear()
class Spam:
manager = CachedSpamManager()
def __init__(self, name):
self.name = name
@classmethod
def get_spam(cls, name):
return cls.manager.get_spam(name)
a = Spam.get_spam('foo')
b = Spam.get_spam('bar')
c = Spam.get_spam('foo')
print(a is c)
# 输出: True
这种实现方式将缓存逻辑封装在
CachedSpamManager
类中,方便对缓存进行管理和扩展。同时,可以通过一些方式限制用户直接创建实例,避免绕过缓存机制。
综上所述,Python 提供了多种高级技巧来扩展类的功能、处理复杂的数据结构和优化代码性能。合理运用这些技巧可以使代码更加健壮、高效和易于维护。
Python 类扩展与高级应用技巧
9. 各类技巧总结与对比
为了更清晰地理解上述各种 Python 高级技巧,下面通过表格进行总结对比:
| 技巧名称 | 主要用途 | 实现方式 | 优点 | 缺点 |
| — | — | — | — | — |
| Mixin 模式 | 复用方法扩展类功能 | 多重继承或装饰器 | 提高代码复用性,降低耦合度 | 可能导致类继承关系复杂 |
| 状态机模式 | 处理对象不同状态下的操作 | 状态类委托或操作
__class__
属性 | 避免大量条件判断,代码易维护 | 可能不符合纯面向对象编程理念 |
| 字符串调用方法 | 根据字符串方法名调用对象方法 |
getattr()
或
operator.methodcaller()
| 灵活调用方法 | 可能增加代码理解难度 |
| 访问者模式 | 处理复杂数据结构中不同类型对象 | 动态生成方法名并使用
getattr()
| 分离操作与数据结构,代码通用 | 递归实现可能达到递归限制 |
| 无递归访问者模式 | 解决深度嵌套数据结构递归限制问题 | 生成器和栈 | 避免递归深度限制 | 实现逻辑较复杂 |
| 循环数据结构内存管理 | 解决循环引用导致的内存回收问题 |
weakref
库创建弱引用 | 避免内存泄漏 | 增加代码复杂度 |
| 类比较操作支持 | 让类实例支持常见比较运算符 |
functools.total_ordering
装饰器 | 减少手动编写比较方法工作量 | 依赖装饰器 |
| 创建缓存实例 | 相同参数返回缓存实例引用 | 缓存字典或管理类 | 避免重复创建实例,节省内存 | 可能增加代码复杂度和维护成本 |
10. 实际应用场景分析
这些高级技巧在实际项目中有广泛的应用场景,以下是一些具体示例:
10.1 Web 开发
- Mixin 模式 :在 Django 框架中,可以使用 Mixin 模式为视图类添加额外的功能,如权限验证、日志记录等。
from django.views.generic import View
class LoggedViewMixin:
def dispatch(self, request, *args, **kwargs):
print(f"Request received: {request.path}")
return super().dispatch(request, *args, **kwargs)
class MyView(LoggedViewMixin, View):
def get(self, request):
return HttpResponse("Hello, World!")
- 访问者模式 :在处理复杂的表单数据时,访问者模式可以将数据验证和处理逻辑分离,提高代码的可维护性。
10.2 游戏开发
- 状态机模式 :游戏角色的状态管理,如站立、行走、攻击等状态,可以使用状态机模式进行实现,使角色状态转换逻辑清晰。
class Character:
def __init__(self):
self.state = StandingState()
def change_state(self, new_state):
self.state = new_state
def action(self):
self.state.action(self)
class StandingState:
def action(self, character):
print("Character is standing.")
class WalkingState:
def action(self, character):
print("Character is walking.")
- 创建缓存实例 :游戏中的一些资源,如纹理、音效等,可以使用缓存实例的方式,避免重复加载,提高游戏性能。
11. 技巧使用建议与注意事项
在使用这些高级技巧时,需要注意以下几点:
11.1 Mixin 模式
- 避免 Mixin 类之间的依赖关系过于复杂,否则会导致代码难以理解和维护。
- 确保 Mixin 类的方法名不会与其他类的方法名冲突。
11.2 状态机模式
- 状态类的设计要合理,避免状态类过多导致代码膨胀。
-
在操作
__class__属性时,要确保不会引发意外的错误。
11.3 字符串调用方法
-
要确保传入的字符串方法名是合法的,否则会引发
AttributeError异常。 - 尽量避免在性能敏感的代码中使用,因为字符串查找和方法调用会有一定的开销。
11.4 访问者模式
- 递归实现时要注意 Python 的递归限制,可以考虑使用无递归实现。
- 当数据结构发生变化时,可能需要修改访问者类的实现。
11.5 无递归访问者模式
- 理解生成器和栈的工作原理,确保代码逻辑正确。
- 处理不同类型的返回值时要小心,避免出现意外错误。
11.6 循环数据结构内存管理
-
正确使用
weakref库,避免引入新的内存泄漏问题。 - 注意弱引用的生命周期,确保在需要时对象仍然存在。
11.7 类比较操作支持
-
确保定义的
__eq__()和比较方法逻辑正确,否则会导致比较结果错误。 - 装饰器生成的方法可能会影响代码的可读性,需要适当添加注释。
11.8 创建缓存实例
- 合理设计缓存的过期策略,避免缓存数据过时。
- 注意缓存的并发访问问题,确保线程安全。
12. 未来发展趋势与展望
随着 Python 语言的不断发展,这些高级技巧也可能会有新的变化和应用。
12.1 语言特性的增强
Python 可能会引入更多的语言特性来简化这些高级技巧的实现。例如,未来可能会有更简洁的方式来实现 Mixin 模式或状态机模式。
12.2 与其他技术的融合
这些技巧可能会与其他技术,如机器学习、大数据等进行更深入的融合。例如,在处理大规模数据时,使用访问者模式可以更高效地对数据进行处理和分析。
12.3 代码自动化工具的发展
可能会出现更多的代码自动化工具,帮助开发者更方便地使用这些高级技巧。例如,自动生成 Mixin 类或状态机类的工具。
13. 总结
Python 中的这些高级技巧为开发者提供了强大的工具,能够处理各种复杂的编程场景。通过合理运用 Mixin 模式、状态机模式、访问者模式等技巧,可以提高代码的复用性、可维护性和性能。同时,在使用这些技巧时,要注意各种注意事项,避免引入不必要的问题。随着 Python 语言的发展,这些技巧也将不断演变和完善,为开发者带来更多的便利。
附录:部分技巧的流程图
13.1 无递归访问者模式流程图
graph TD;
A[开始] --> B[初始化栈和结果变量];
B --> C{栈是否为空};
C -- 否 --> D{栈顶元素类型};
D -- 生成器 --> E[发送结果并添加新元素到栈];
E --> F[清空结果变量];
F --> C;
D -- 节点 --> G[调用对应方法并添加结果到栈];
G --> C;
D -- 其他 --> H[弹出栈顶元素作为结果];
H --> C;
C -- 是 --> I[返回最终结果];
13.2 创建缓存实例流程图
graph TD;
A[创建实例请求] --> B{参数是否在缓存中};
B -- 是 --> C[返回缓存实例];
B -- 否 --> D[创建新实例];
D --> E[将新实例存入缓存];
E --> F[返回新实例];
通过以上的介绍和分析,相信你对 Python 的这些高级技巧有了更深入的理解。在实际编程中,可以根据具体的需求选择合适的技巧,提高代码的质量和性能。
超级会员免费看
3307

被折叠的 条评论
为什么被折叠?



