Python中允许子类化的不可变类型

3 投票
1 回答
784 浏览
提问于 2025-04-16 05:18

我想要一些不可变的类型,理想情况下,它们可以自己处理哈希和相等性,但又能很容易地被子类化。我开始使用namedtuple

class Command(namedtuple('Command', 'cmd_string')):

  def valid_msg(msg):
    return True

  def make_command(msg):
    if self.valid_msg(msg):
      return '%s:%s' % (self.cmd_string, msg)
    else:
      raise ValueError(INVALID_MSG)

...但是这不太适合子类化。直接对它进行子类化意味着元组的名称保持不变(在打印时...这不是大问题),但更重要的是,你不能添加字段:

class LimitedLengthCommand(Command):

  # I want to have self.length! Where does it go?

  def valid_msg(msg):
    return len(msg) <= self.length

简单地创建另一个命名元组(按照文档的说法)意味着我不会继承任何方法!

那么,做类似的事情最简单、最容易的方法是什么呢?我打算有多个Command的子类(比如十六进制字面量、1或0等等),但没有什么复杂的。与多重继承友好相处并不是必须的。

1 个回答

1

这里有一个元类,可以实现你想要的功能(我觉得是这样的)。它的工作原理是把要继承的方法存储在一个字典里,然后手动把这些方法放到新类的字典中。它还会保存传给 namedtuple 构造函数的属性字符串,并把这个字符串和子类的属性字符串合并。接着,它把合并后的字符串传给 namedtuple,并返回一个从生成的 namedtuple 继承的类,这个类的字典里包含了所有合适的方法。因为这个元类是从 abc.ABCMeta 继承来的,所以你可以免费获得有效的类型检查。下面是构建几个类的样子:

class Foo(object):
    __metaclass__ = ImmutableMeta
    _attributes_ = 'a b'

    def sayhi(self):
        print "Hello from {0}".format(type(self).__name__)

class Bar(Foo):
    _attributes_ = 'c'

    def saybye(self):
        print "Goodbye from {0}".format(type(self).__name__)

这是元类的代码:

import collections as co
import abc

class ImmutableMeta(abc.ABCMeta):

    _classes = {}

    def __new__(meta, clsname, bases, clsdict):
        attributes = clsdict.pop('_attributes_')

        if bases[0] is object:
            # 'new' class
            methods = clsdict
        else:
            # we're 'inheriting' from an existing class
            base = bases[0]
            attributes = meta._classes[base]['attributes'] + ' ' + attributes
            base_methods = meta._classes[base]['methods'].copy()
            base_methods.update(clsdict)
            methods = base_methods

        # construct the actual base class and create the return class
        new_base = co.namedtuple(clsname + 'Base', attributes)
        cls = super(ImmutableMeta, meta).__new__(meta, clsname, (new_base,),
                                                 methods)

        # register the data necessary to 'inherit' from the class
        # and make sure that it passes typechecking
        meta._classes[cls] = {'attributes': attributes,
                              'methods': methods}
        if bases[0] is not object:
            base.register(cls)
        return cls

还有一些简单的测试代码。

a = Foo(1, 2)
a.sayhi()

b = Bar(1, 2, 3)
b.sayhi()  # 'inherited' from class Foo
b.saybye()

try:
    b.c = 1         # will raise an AttributeError
except AttributeError:
    print "Immutable"

print "issubclass(Bar, Foo): {0}".format(issubclass(Bar, Foo))

try:
   d =  {b: 1}        # No problems
except TypeError:
    print "Cant put it in a dict"
else:
    print "Can put it in a dict"

希望这对你有帮助。如果你不想把每个方法都附加到每个应该继承它的类上,你也可以提供一个默认的 __getattr__ 方法,它会在元类的字典里查找合适的方法。这需要某种方式把基类硬编码到方法里,可能需要用到闭包。

撰写回答