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

71 阅读2分钟

在Python中,创建一个不变的类型,该类型能够对自身的哈希值和相等性进行排序,但可以轻松地进行子类化。尝试使用namedtuple:

a21da0c9cc41827eba9fd6b106c73e7.png

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):
    # 想拥有 self.length!放在哪里?

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

简单地创建另一个named tuple(根据文档)意味着不会继承任何方法!

如何以最简单和最容易的方式实现如下效果?

  1. 有多个Command的子类(例如,十六进制文本,1 或 0 等),但没有复杂的内容。

  2. 良好地处理多重继承不是必需的。

  3. 解决方案 以下是一个满足需求的元类:

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

然后,可以照着以下方式构造几个类:

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__)

以下是一些测试代码:

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"

输出结果:

Hello from Foo
Hello from Bar
Goodbye from Bar
Immutable
issubclass(Bar, Foo): True
Can put it in a dict