views:

38

answers:

1

The following code doesn't compile; it says

NameError: name 'fields' is not defined

in the last line. Is it because __new__ isn't called until after the fields assignment is reached? What should I do?

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'

EDIT:

I found that it's not a problem with timing; it's a problem with name hiding. Works fine if I write A.fields instead.

I'd like to know why I can't use fields or super().fields.

+1  A: 

The fields['key'] = 'value' runs before the metaclass machinery kicks in.

class foo(object):
    var1 = 'bar'

    def foobar(self):
        pass

when python hits the class statement, it enters a new local namespace.

  1. it evaluates the var1 = 'bar' statement. this is equivalent to locals()['var1'] = 'bar'

  2. it then evaluates the def foobar statement. this is equivalent to locals()['var'] = the result of compiling the function

  3. It then passes locals(), along with the classname, the inherited classes and the metaclass to the metaclasses __new__ method. In the example case, the metaclass is simply type.

  4. It then exits the new local namespace and sticks the class object returned from __new__ in the outer namespace with the name foo.

Your code works when you use A.fields because the class A has already been created and the above process has hence been executed with your Meta installing fields in A.

You can't use super().fields because the classname B is not defined to pass to super at the time that super would run. that is that you would need it to be super(B).fields but B is defined after class creation.

Update

Here's some code that will do what you want based on your reply to my comment on the question.

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
aaronasterling
But if that's the case, why does `A.fields['key'] = 'value'` work fine?
max
@max see my update.
aaronasterling
Ahh, so class name isn't usable at the "class level", only inside the individual class methods.. That makes sense, since the code inside the functions isn't evaluated until much later, when the class already exists.
max