Метакласс и __prepare__ ()
Я учу себя о __prepare__
функция. И я вижу этот фрагмент в PEP3115
# 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
Мой вопрос в этой строке: result.member_names = classdict.member_names
Как могла переменная classdict
получить атрибут от member_table
учебный класс? Я вижу __prepare__
Функция возвращает экземпляр member_table, но как связь между member_table()
а также classdict.member_names
генерируется?
Большое спасибо всем вам!
2 ответа
Это довольно просто, так как это именно то, что делает подготовка.
3.3.3.3. Подготовка пространства имен класса. После определения соответствующего метакласса, пространство имен класса готовится. Если метакласс имеет
__prepare__
атрибут, это называетсяnamespace = metaclass.__prepare__(name, bases, **kwds)
(где дополнительные ключевые аргументы, если таковые имеются, взяты из определения класса).Если метакласс не имеет
__prepare__
атрибут, то пространство имен класса инициализируется как пустое упорядоченное отображение.
https://docs.python.org/3/reference/datamodel.html
Что означает, classdict
атрибут, который передается в метакласс __new__
а также __init__
Методы точно такой же объект, который возвращается __prepare__
,
Этот объект должен быть экземпляром отображения, то есть объектом, который ведет себя как диктат и имеет по крайней мере __setitem__
метод. это __setitem__
Метод вызывается Python для всех переменных, установленных внутри самого объявленного тела класса.
То есть для обычного класса без пользовательского метакласса переменные записываются в словарь (упорядоченный словарь, начиная с Python 3.6).
Это происходит, когда Python выполняет каждое утверждение внутри тела класса. Это тот же объект, который возвращается, если один вызов locals()
внутри тела класса:
In [21]: class M(type):
...: def __prepare__(self, *args):
...: class CustomDict(dict):
...: __repr__ = lambda self: "I am a custom dict: " + str(id(self))
...: namespace = CustomDict()
...: print("From __prepare__", namespace)
...: return namespace
...:
...: def __new__(metacls, name, bases, namespace):
...: print("From __new__:", namespace)
...: return super().__new__(metacls, name, bases, namespace)
...:
...:
In [22]: class Test(metaclass=M):
...: def __init__(self):
...: ...
...: print("From class body:", locals(), locals()["__init__"])
...:
...:
From __prepare__ I am a custom dict: 140560887720440
From class body: I am a custom dict: 140560887720440 <function Test.__init__ at 0x7fd6e1bd7158>
From __new__: I am a custom dict: 140560887720440
Основным вариантом использования, когда эта функция была впервые разработана, вероятно, была именно возможность сделать порядок объявлений внутри тела класса осмысленным. Это __prepare__
метод может просто вернуть collections.OrderedDict
экземпляр и __new__
или же __init__
будет действовать в соответствии с этим приказом. Начиная с Python 3.6, упорядочение атрибутов класса по умолчанию - и __prepare__
Эта функция остается настолько продвинутой, что ее действительно нужно придумать.
Комментарий в вашем комментарии сбивает с толку информацию о роли возвращаемого объекта, и принятый ответ не проясняет ситуацию.
Пространство имен, возвращаемое
__prepare__
это просто временный объект во время создания класса, который будет передан
__new__
, это не то пространство имен, которое ваш класс будет использовать во время выполнения после создания. (классы имеют оптимизированный объект пространства имен, называемый MappingProxy, который обеспечивает, чтобы ключи были строками)
Вы можете увидеть эффект в этом скрипте:
class CustomDict(dict):
def __setitem__(self, key, value):
print(f"setting {key} to {value}")
super().__setitem__(key, value)
class PrintAssignmentMeta(type):
@classmethod
def __prepare__(mcs, name, bases):
return CustomDict()
def __new__(mcs, name, bases, namespace):
return super().__new__(mcs, name, bases, namespace)
class PrintAssignmentClass(metaclass=PrintAssignmentMeta):
# these print
a = 3
b = 4
# this does not print because the class-namespace no longer involves your custom setitem
PrintAssignmentClass.c = 5
# but all three are in the namespace of the class
print(PrintAssignmentClass.a, PrintAssignmentClass.b, PrintAssignmentClass.c)
print(PrintAssignmentClass.__dict__)