Почему я должен использовать метод __prepare__ для получения пространства имен класса?

Примечание Этот вопрос не касается типа данных Python 3 Enum, это просто пример, который я использую.

С PEP 3115 Python 3 добавил __prepare__ 1 до type с целью обеспечения использования пользовательского пространства имен при создании классов. Например, новый тип данных Enum использует __prepare__ для возврата экземпляра private _EnumDict для использования в качестве нового пространства имен Enum.

Однако я видел несколько примеров, когда SO 2 of EnumMeta был подклассом, создавая новое пространство имен для класса в методе metaclass __new__, но вместо вызова __prepare__ вместо этого используется метод для приобретения этого нового пространства имен, type(clsdict)(). Есть ли риск сделать это таким образом?


1 Подпись для __prepare__:

@classmethod
def __prepare__(metacls, cls, bases, **kwds):

и для __new__:

def __new__(metacls, cls, bases, clsdict, **kwds):

2 Пример использования type(clsdict):

from этот ответ

class CountryCodeMeta(enum.EnumMeta):
    def __new__(metacls, cls, bases, classdict):
        data = classdict['data']
        names = [(country['alpha-2'], int(country['country-code'])) for country in data]

  -->   temp = type(classdict)()
        for name, value in names:
            temp[name] = value

        excluded = set(temp) | set(('data',))
        temp.update(item for item in classdict.items() if item[0] not in excluded)

        return super(CountryCodeMeta, metacls).__new__(metacls, cls, bases, temp)

Ответ 1

Да, есть риски.

По крайней мере, существуют две причины для получения нового пространства имен, вызывая __prepare__ вместо выполнения type(clsdict)():

  • При запуске на Python 2 clsdict есть dict, а исходный __prepare__ никогда не запускался для начала (__prepare__ только Python 3) - другими словами, если __prepare__ возвращает что-то помимо нормального dict, type(clsdict)() не получит его.

  • Любые атрибуты, установленные __prepare__ на clsdict, не будут установлены при использовании type(clsdict)(); т.е. если __prepare__ имеет clsdict.spam = 'eggs', то type(clsdict)() не будет иметь атрибут spam. Обратите внимание, что эти атрибуты находятся в самом пространстве имен для использования метаклассом и не отображаются в пространстве имен.

Подводя итог: есть веские причины использовать __prepare__() для получения соответствующего словаря классов, а для ярлыка type(clsdict)() нет.