Неизменяемые типы, позволяющие создавать подклассы в Python - PullRequest
3 голосов
/ 11 октября 2010

Я хочу иметь неизменяемые типы, которые в идеале могут сортировать свое собственное хеширование и равенство, но могут быть легко разделены на подклассы. Я начал использовать 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 голос
/ 11 октября 2010

Вот метакласс, чтобы делать то, что вы хотите (я думаю). Он работает, сохраняя методы, которые должны быть унаследованы, в словаре и вручную вставляя их в словарь новых классов. Он также хранит строку атрибута, которая передается конструктору 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__, которое просматривает словарь метаклассов и находит из этого соответствующий метод. Это потребует некоторого жесткого кодирования базового класса в методе, возможно, с использованием замыкания.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...