Упорядоченные классы в Python 3 - PullRequest
4 голосов
/ 24 ноября 2011

Я пытаюсь использовать «упорядоченный класс», как описано в PEP 3115 (то есть, класс, члены которого могут быть доступны в порядке, в котором они были объявлены). Реализация дана там

# The custom dictionary
class member_table(dict):
    def __init__(self):
        self.member_names = []

    def __setitem__(self, key, value):
        # if the key is not already defined, add to the
        # list of keys.
        if key not in self:
            self.member_names.append(key)

        # Call superclass
        dict.__setitem__(self, key, value)

# The metaclass
class OrderedClass(type):

    # The prepare function
    @classmethod
    def __prepare__(metacls, name, bases): # No keywords in this case
        return member_table()

    # The metaclass invocation
    def __new__(cls, name, bases, classdict):
        # Note that we replace the classdict with a regular
        # dict before passing it to the superclass, so that we
        # don't continue to record member names after the class
        # has been created.
        result = type.__new__(cls, name, bases, dict(classdict))
        result.member_names = classdict.member_names
        return result

class MyClass(metaclass=OrderedClass):
    # method1 goes in array element 0
    def method1(self):
        pass

    # method2 goes in array element 1
    def method2(self):
        pass

Есть несколько вещей, которые меня смущают. Во-первых, есть ли причина, по которой __prepare__ является classmethod? В определении не используется metacls - это просто соглашение?

Во-вторых, когда я пробую этот код, '__module__' заканчивается в MyClass.member_names до 'method1' и 'method2', что явно противоречит комментариям, которые утверждают, что 'method1' является первым элементом. Почему этот специальный атрибут попадает в список, а другие нет? Есть ли другие, которые могут меня удивить (кроме __doc__, если у класса есть строка документации, и любая, которую я определил явно)?

Наконец, эта реализация не извлекает member_names из базовых классов. Если я хочу добиться этого, есть ли что-то не так со следующим изменением на __prepare__ (кроме того факта, что он не проверяет дубликаты)?

@classmethod
def __prepare__(metacls, name, bases):
    prep_dict = member_table()
    for base in bases:
        try:
            prep_dict.member_names.extend(base.member_names)
        except AttributeError:
            pass
    return prep_dict

1 Ответ

5 голосов
/ 08 декабря 2011

Во-первых, есть ли причина, по которой __prepare__ является методом класса?В определении не используются метаклы - это просто соглашение?

Как указано в комментариях, когда вызывается __prepare__, сам класс еще не создан.Это означает, что если это обычный метод (с self в качестве первого параметра) - не будет никакого значения, чтобы стать self в этом вызове.

И это имеет смысл, потому что __prepare__ делает - возвращает объект, похожий на dict, который будет использоваться вместо обычного dict при разборе тела класса - так что он вообще не зависит от создаваемого класса.

Если, как упомянуто вкомментарии, это был staticmethod (что означает, что он не получит первый параметр metacls), тогда __prepare__ не сможет получить доступ к любым другим методам метакласса - это излишне ограничивает.

Ида, точно так же, как self, metacls в данном случае это просто соглашение - обычный идентификатор Python.(Обратите внимание, что когда методы классов используются в обычных классах, первый параметр обычно обозначается cls вместо metacls.)

Во-вторых, когда я пытаюсь этот код, __module__ заканчиваетсяMyClass.member_names перед method1 и method2, очевидно противоречащим комментариям, которые утверждают, что method1 является первым элементом.Почему этот специальный атрибут попадает в список, а другие нет?Есть ли другие, которые могут меня удивить (кроме __doc__, если у класса есть строка документации, и любая, которую я определяю явно)?

Возможно, потому что специальный __module__ член для классов был продуманпосле того, как они подумали о методе __prepare__ для метаклассов.(Я не смог проверить путем поиска в Google для определения PEP __module__, но я бы поспорил, что это так.)

И нет, нет никаких гарантий, что будущие версии Python не добавят ещемагические атрибуты для классов, которые могут находиться перед вашими явными членами класса в словаре.

Но с метаклассами вы полностью контролируете - вы можете просто настроить свой объект типа dict (member_table в вашем коде), чтобы несчитать любые атрибуты, начинающиеся с "__" - например.Или даже вообще не добавлять его в конечный экземпляр класса (риск того, что ваши классы, определенные таким образом, не будут работать с некоторыми функциями Python).

Наконец, эта реализация не извлекает имена членов из базыклассы.Если я хочу добиться этого, есть ли что-то не так со следующим изменением на __prepare__?

Читая его, я не вижу ничего плохого в предложенной вами реализации, но вам придется проверить, конечно.

Обновление (2013-06-30): эта тема активно обсуждается в списке разработчиков Python, и похоже, что из Python 3.4 все классы будут упорядочены по умолчанию, без необходимости использовать метакласс или __prepare__ только для заказа

...