不可变类型允许在 Python 中进行子类化

发布于 2024-09-27 09:23:18 字数 836 浏览 4 评论 0原文

我想要拥有不可变的类型,理想情况下,它们可以整理自己的散列和相等性,但可以轻松地进行子类化。我开始使用 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 等),但没什么复杂的。善用多重继承并不是必要的。

I want to have immutable types that can, ideally, sort out their own hashing and equality, but can be easily subclassed. I started off using 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)

...but this does not lend itself to subclassing. Directly subclassing this means that the name of the tuple remains the same (for printing... not such a big deal), but more importantly you can't add fields:

class LimitedLengthCommand(Command):

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

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

Simply creating another named tuple (as per the docs) means I don't inherit any methods!

What is the simplest and easiest way to do something like this? I intend to have multiple subclasses of Command (eg. hex literals, 1-or-0, etc), but nothing complicated. Playing nice with multiple inheritance is not essential.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

筱果果 2024-10-04 09:23:18

这是一个元类可以做你想做的事(我认为)。它的工作原理是将要继承的方法存储在字典中,然后手动将它们插入到新类字典中。它还存储传递给 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__ 来查找元类字典并从中找到适当的方法。这需要以某种方式将基类硬编码到方法中,可能使用闭包。

Here's a metaclass to do what you want (I think). It works by storing the methods to be inherited in a dictionary and manually inserting them into the new classes dictionary. It also stores the attribute string that gets passed to the namedtuple constructor and merges that with the attribute string from the subclass. It then passes that to namedtuple and returns a class that's inherited from the resulting namedtuple with all appropriate methods in its dictionary. Because the metaclass is derived from abc.ABCMeta, you get working type checking for free. Here's how constructing a couple of classes looks:

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

Here's the metaclass:

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

And here's some paltry test code.

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"

Hope that helps. If you would prefer not to attach every method to every class that is supposed to inherit it, you could also provide a default __getattr__ that looks through the metaclasses dictionary and finds the appropriate method out of that. That would require somehow hardcoding the baseclass into method, probably using a closure.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文