Неожиданное поведение setattr () внутри Metaclass __init__ - PullRequest
0 голосов
/ 07 июня 2018

Вот базовый Metaclass, намеревающийся предоставить пользовательский __init__:

class D(type):
    def __init__(cls, name, bases, attributes):
        super(D, cls).__init__(name, bases, attributes)
        for hook in R.registered_hooks:
            setattr(cls, hook.__qualname__, hook)

Цикл for выполняет итерации по списку функций, вызывая для каждой из них setattr(cls, hook.__qualname__, hook).

Вот класс, который использует вышеуказанный метакласс:

class E(metaclass=D):
    def __init__(self):
        pass

Странная вещь:

E().__dict__   prints {}

Но когда я звоню

`E.__dict__   prints {'f1': <function f1 at 0x001>, 'f2': <function f2 at 0x002>}`

Я ожидал добавить эти функции в качестве атрибутов к атрибутам экземпляра , поскольку __init__ обеспечивает пользовательскую инициализацию для класса, но кажется, что атрибуты были добавленына Атрибуты класса .

В чем причина этого и как я могу добавить атрибуты к экземпляру в этом сценарии?Спасибо!

Ответы [ 3 ]

0 голосов
/ 07 июня 2018

Для вызова __init__ при создании экземпляра объекта obj необходимо определить type(obj).__init__.Используя метакласс, вы определили type(type(obj)).__init__.Вы работаете на неправильном уровне.

В этом случае вам нужно только наследование, а не метакласс.

class D():
    def __init__(self, *args):
        print('D.__init__ was called')

class E(D):
    pass

e = E() # prints: D.__init__ was called

Обратите внимание, что вы все равно обнаружите, что e.__dict__ пусто.

print(e.__dict__) # {}

Это потому, что у ваших экземпляров нет атрибутов, методы сохраняются и ищутся в классе.

0 голосов
/ 07 июня 2018

Если вы используете метакласс, это означает, что вы меняете определение класса, что косвенно влияет на атрибуты экземпляров этого класса.Предполагая, что вы действительно хотите сделать это:

Вы создаете пользовательский метод __init__.Как и при обычном создании, __init__ вызывается для объекта, который уже был создан.Как правило, когда вы делаете что-то осмысленное с метаклассами, вам нужно добавить к dict класса до создания экземпляра, что делается в методе __new__, чтобы избежать проблем, которые могут возникнуть в подклассах.Это должно выглядеть примерно так:

class D(type):

    def __new__(cls, name, bases, dct):
        for hook in R.registered_hooks:
            dct[hook.__qualname__] = hook
        return super(D, cls).__new__(cls, name, bases, dct)

Это не изменит __dict__ экземпляров этого класса, но разрешение атрибутов для экземпляров также ищет атрибуты класса.Поэтому, если вы добавите foo в качестве атрибута для E в метаклассе, E().foo вернет ожидаемое значение, даже если оно не отображается в E().__dict__.

0 голосов
/ 07 июня 2018

Они добавляются как атрибуты экземпляра.Экземпляр, однако, является class , как экземпляр метакласса.Вы не определяете метод __init__ для E;вы определяете метод, который type использует для инициализации E.

Если вы просто хотите, чтобы атрибуты были добавлены в экземпляр E, вы работаете на неправильном уровне;Вы должны определить миксин и использовать для этого множественное наследование.

...