如何优雅子类化Python列表,使相加、切片等操作后仍保持子类类型?
问题:列表子类操作返回普通列表的解决方法
我想扩展标准列表的功能,子类化是很自然的选择。我在遗传编程项目里用它存储逆波兰表示法(RPN)的数学表达式,代码如下:
class RPN(list): def evaluate(self, vars): # 计算RPN表达式 pass def to_infix(self): # 转换为中缀形式 pass @staticmethod def from_infix(expr): # 从中缀表达式字符串创建新的RPN对象 pass
这样实现没问题,我可以像RPN([5, 6, operator.add, 7, 8, operator.add, operator.mult])这样创建RPN对象,也能正常工作。
但问题是,对这些RPN对象执行列表类操作(比如相加、切片等)时,除了append外,其他操作返回的都是普通列表:
rpn1 = RPN([5, 6, operator.add]) rpn2 = RPN([7, 8, operator.add]) rpn3 = rpn1 + rpn2 # type(rpn3) == list rpn4 = rpn1[0:1] # type(rpn4) == list
我知道这是因为新对象创建时默认用的是列表类型。我可以重写__add__、切片方法等,但这会增加大量代码;或者每次把结果重新包装成RPN对象,比如rpn3 = RPN(rpn1 + rpn2),但又太繁琐。有没有更好的解决方法?
注:该问题曾被标记为《How to use list comprehension in list derived class》的重复问题,但那篇帖子是询问如何在列表子类中使用列表推导式(通过在
__init__中重新赋值self的特殊方式),而我的问题与之完全不同,只是两者恰好都可以通过collections.UserList解决,因此我认为这两个问题并非重复。
解决方案:使用
collections.UserList Python标准库中的collections.UserList是专门为继承设计的列表包装类,它内部维护一个data属性存储实际列表数据,所有列表操作都会返回子类实例,完美解决你的问题。
修改后的代码如下:
from collections import UserList import operator class RPN(UserList): def evaluate(self, vars): # 实现RPN表达式计算逻辑 stack = [] for item in self.data: if callable(item): b = stack.pop() a = stack.pop() stack.append(item(a, b)) elif item in vars: stack.append(vars[item]) else: stack.append(item) return stack[0] def to_infix(self): # 实现转中缀逻辑 stack = [] for item in self.data: if callable(item): b = stack.pop() a = stack.pop() stack.append(f"({a} {item.__name__} {b})") else: stack.append(str(item)) return stack[0] @staticmethod def from_infix(expr): # 实现从中缀转RPN的逻辑(示例,可根据需求完善) # 这里用简单的示例实现,实际需要处理运算符优先级等 tokens = expr.split() output = [] operators = [] precedence = {'+':1, '-':1, '*':2, '/':2} for token in tokens: if token.isdigit(): output.append(int(token)) elif token in precedence: while operators and precedence[operators[-1]] >= precedence[token]: output.append(getattr(operator, operators.pop())) operators.append(token) elif token == '(': operators.append(token) elif token == ')': while operators[-1] != '(': output.append(getattr(operator, operators.pop())) operators.pop() while operators: output.append(getattr(operator, operators.pop())) return RPN(output)
现在执行列表操作时,返回的都是RPN实例:
rpn1 = RPN([5, 6, operator.add]) rpn2 = RPN([7, 8, operator.add]) rpn3 = rpn1 + rpn2 # type(rpn3) == RPN rpn4 = rpn1[0:1] # type(rpn4) == RPN
为什么UserList能解决问题?
UserList内部将所有列表操作都代理到self.data,并且在需要返回新列表的操作(如__add__、切片__getitem__)中,会自动创建当前子类的实例返回,而不是普通列表。- 相比直接继承
list,UserList的设计更友好,不需要手动重写大量特殊方法来保持子类类型。
内容的提问来源于stack exchange,提问作者Steven Dickinson
相关产品推荐
相关产品推荐

