You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优雅子类化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 15:27:23