python: время __new__ в метаклассе - PullRequest
       3

python: время __new__ в метаклассе

4 голосов
/ 16 сентября 2010

Следующий код не компилируется;он говорит

NameError: имя 'поля' не определено

в последней строке.Это потому, что __new__ не вызывается до тех пор, пока не будет достигнуто назначение fields?Что мне делать?

class Meta(type):
    def __new__(mcs, name, bases, attr):
        attr['fields'] = {}
        return type.__new__(mcs, name, bases, attr)

class A(metaclass = Meta):
    def __init__(self, name):
        pass

class B(A):
    fields['key'] = 'value'

РЕДАКТИРОВАТЬ:

Я обнаружил, что это не проблема с синхронизацией;это проблема с сокрытием имени.Работает нормально, если я пишу A.fields.

Я хотел бы знать, почему я не могу использовать fields или super().fields.

1 Ответ

4 голосов
/ 16 сентября 2010

fields['key'] = 'value' запускается до запуска механизма метакласса.

class foo(object):
    var1 = 'bar'

    def foobar(self):
        pass

, когда python выполняет оператор class, он входит в новое локальное пространство имен.

  1. оценивает оператор var1 = 'bar'.это эквивалентно locals()['var1'] = 'bar'

  2. , затем оно вычисляет оператор def foobar.это эквивалентно locals()['var'] = the result of compiling the function

  3. Это , затем передает locals() вместе с именем класса, унаследованными классами и метаклассом метаклассу __new__ методу,В этом примере метакласс просто type.

  4. Затем он выходит из нового локального пространства имен и вставляет объект класса, возвращенный из __new__, во внешнее пространство имен с именем foo.

Ваш код работает, когда вы используете A.fields, потому что класс A уже создан и, следовательно, вышеописанный процесс был выполнен с вашей Meta установкой fields in A.

Вы не можете использовать super().fields, потому что имя класса B не определено для передачи super в момент запуска super.то есть вам нужно, чтобы оно было super(B).fields, но B определено после создания класса.

Update

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

def MakeFields(**fields):
    return fields

class Meta(type):
    def __new__(mcs, name, bases, attr):
        for base in bases:
            if hasattr(base, 'fields'):
                inherited = getattr(base, 'fields')
                try:
                    attr['fields'].update(inherited)
                except KeyError:
                    attr['fields'] = inherited
                except ValueError:
                    pass
        return type.__new__(mcs, name, bases, attr)

class A(metaclass=Meta):
    fields = MakeFields(id='int',name='varchar') 

class B(A):
    fields = MakeFields(count='int')

class C(B):
    pass

class Test(object):
    fields = "asd"

class D(C, Test):
    pass

print C.fields
print D.fields
...