3

I have this code:

class Attributes(object):
    class __metaclass__(type):
        def __init__(self, cls_name, cls_bases, cls_dict):
            # super(Attributes.__metaclass__, self) # NameError: global name 'Attributes' is not defined
            super(__metaclass__, self).__init__(
                cls_name, cls_bases, cls_dict)

gives

NameError: global name '__metaclass__' is not defined

Why __metaclass__ variable is not found in the outer scope?

warvariuc
  • 57,116
  • 41
  • 173
  • 227
  • Could you give me more code to work with, so that I can just quickly run it and see if I can reproduce the problem? – Games Brainiac Oct 24 '13 at 08:07
  • I've tried reproducing the problem, and it turns out that you cannot have classes within classes that having __init__ methods that call to super. Its not just the 3rd level of inheritance, even if you tried to call `super` inside of `Attributes`, then it would not work. You would need to call `SimpleModel.Attributes` to get it. – Games Brainiac Oct 24 '13 at 08:18

3 Answers3

2

try this instead

super(Attributes.__metaclass__, self).__init__(cls_name, cls_bases, cls_dict)
warvariuc
  • 57,116
  • 41
  • 173
  • 227
Vorsprung
  • 32,923
  • 5
  • 39
  • 63
  • Thanks for the workaround. I've done a different workaround, putting the class in question on the top level of the module. – warvariuc Oct 24 '13 at 08:29
  • 1
    I've simplified the example in the question. If you try using `super(Attributes.__metaclass__, self)` - you get `NameError: global name 'Attributes' is not defined`. So your solution does not work. – warvariuc Oct 24 '13 at 11:46
  • Warwaruk's right. The `Attributes` isn't existing yet when the first instance of metaclass is created: The metaclass gets created first, then at once an instance is created (automatically) before the outer class is finished. – Alfe Oct 24 '13 at 12:05
1

While creating a class, only its name is visible. Its contents does not exist yet until the class is finished being created. The parts inside the class, therefore, cannot access any fields of the class during creation. So you will need to use fully qualified names to denote that you want to access fields of the class

You are currently creating a class SimpleModel and while doing so you are creating a class Attributes and while doing so a class __metaclass__. Since while you do this, the class SimpleModel isn't existing yet, the method __init__ is not yet part of anything existing. It first gets created and then, later, will be made part of the class __metaclass__. Hence it cannot know the identifier __metaclass__. And since __metaclass__ also never becomes a global, when called, this identifier cannot be known.

That's the reason why you have no __metaclass__ in your scope at this point, but when later __init__ gets called, only a SimpleModel is available via the global scope, so you can base your name on it: SimpleModel.Attributes.__metaclass__.

Alfe
  • 56,346
  • 20
  • 107
  • 159
  • Inside `__init__` not only the class is already created, but also it's instance is created and passed as `self`. In this case `self` is a class which is instance of its metaclass. – warvariuc Oct 24 '13 at 09:32
  • @warwaruk: You are talking about runtime, I'm talking about compile time (so to speak, if one can really separate the two in Python). When `__init__` gets _compiled_, the classes do not yet exist. That's the underlying reason why Python does not allow references to local classes without fully qualifying the names. Question: should I elaborate on that aspect in my answer? (I added a (hopefully) clarifying sentence in the second paragraph.) Notice that I talk about "creating a class", not about "creating an instance" … – Alfe Oct 24 '13 at 09:53
  • I get the cited exception during runtime, because after `Attributes` class is fully defined, it's created by calling `Attributes.__metaclass__`s `__new__` and `__init__`. Creating a class means creating an instance of its metaclass. I think you are wrong. – warvariuc Oct 24 '13 at 09:56
  • I think you might be confused by this construction: `class __metaclass__(AttributesMeta)` which is actually equaivalent to `__metaclass__ = AnotherAttributeMetaclass`, where `AnotherAttributeMetaclass` would be defined earlier with the same body of `__metaclass__` class in the question. See also http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python – warvariuc Oct 24 '13 at 09:59
  • Of course you get the exception during runtime. But because during compile time the identifiers aren't known. Any proposals on how to make that any clearer in my answer? – Alfe Oct 24 '13 at 09:59
  • I think your answer doesn't apply to the question. It answers some other question. – warvariuc Oct 24 '13 at 10:00
  • Python doesn't have `compile time` in the sense of C/C++. When a module is imported it's actually runtime. – warvariuc Oct 24 '13 at 10:01
  • @warwaruk, if you try to solve the issue by your `AnotherAttributeMetaclass` approach, you will solve the issue by introducing the name to the global scope. You can also achieve this by simply putting a `__metaclass__ = SimpleModel.Attributes.__metaclass__` after the original approach. – Alfe Oct 24 '13 at 10:02
  • @warwaruk: »Python doesn't have compile time« — as I said above (I'm sorry, but if you don't read what I write, then your critique seems unbased). But it has a time when the classes get created, and that differs from the time when the instance gets created. What I tried to reason in my answer is _why_ Python does not support accessing the local class names. I don't think that's beside the scope of the question, and if you try to answer that, you will have something similar to say as I did. – Alfe Oct 24 '13 at 10:03
  • > But it has a time when the classes get created, and that differs from the time when the instance gets created. < The situation is my question is special in that an instance of a class is created during module import. That instance is class `Attributes`, which is an instance of metaclass `__metaclass__` – warvariuc Oct 24 '13 at 10:10
  • > That's the reason why you have no `__metaclass__` in your scope at this point, but only a SimpleModel on which you will have to base your name: `SimpleModel.Attributes.__metaclass__`. < This is also wrong - see my comment to the other answer. – warvariuc Oct 24 '13 at 10:18
  • It may be that your situation is special in that your instance is created during module import, but that is of no concern here. The problem arises from something else (what I wrote). Concerning that statement you found wrong: I stand to it. `__metaclass__` is not known because at the time of creation of the class, `__metaclass__` is not existing and also later will never get introduced to the global scope. But I understand that this does not seem to fit your view of the things. – Alfe Oct 24 '13 at 10:27
  • When `__metaclass__.__init__` is called, `__metaclass__` class is already defined and has a name, which is not accessible, because for now it resides only in `Attributes.__dict__`. Inside `__metaclass__.__init__` `Attributes` class is already created and is being initialized, but `Attributes` name is not accessible yet. – warvariuc Oct 24 '13 at 11:22
  • But the availability of the name of the newly created class is not important here, because in the question i was expecting to access `__metaclass__` no as attribute of another object (e.g. `ModelBase.Attributes.__metaclass__`) but as a non local variable like in case of a nested function: `def a(): b = 1; def c(): print b`. – warvariuc Oct 24 '13 at 11:23
  • I want to point out again, that your answer contains a false statement: > That's the reason why you have no `__metaclass__` in your scope at this point, but only a SimpleModel on which you will have to base your name: `SimpleModel.Attributes.__metaclass__`. < `SimpleModel.Attributes.__metaclass__` does not work, because `SimpleModel` name is not available. – warvariuc Oct 24 '13 at 11:24
  • I am continuing this discussion, because i am pretty sure i understand you point of view and i consider it wrong. if you prove me wrong - ok, i will learn smth new. – warvariuc Oct 24 '13 at 11:25
  • When `__metaclass__.__init__` is _created_, there is no `Attributes` class in existence. _Were_ it existing already, Python could be designed differently and could allow access to the locals of the surrounding scopes as, for instance, in nested functions. The main difference here is that classes only get _created_ when the _creation_ walkthrough of the whole class source (including all its nested classes) is finished. With nested functions, however, to have an opposite example, the inner functions get _created_ when the outer one gets _called_ (not _created_). That's the fundamental point. – Alfe Oct 24 '13 at 11:30
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/39908/discussion-between-alfe-and-warwaruk) – Alfe Oct 24 '13 at 11:34
0

Looks like the answer is this:

Outer scope for a method is not class body, but outer function in which the class is contained or the module.

That's why in the case of

class Test():
    attr_1 = 'smth'
    def a_method(self):
        attr_1  # not visible on this line

attr_1 is not visible inside method Test.a_method

And the solution is to define metaclass at global level:

class AttributesMeta(type):
    def __init__(self, cls_name, cls_bases, cls_dict):
        super(__metaclass__, self).__init__(
            cls_name, cls_bases, cls_dict)

class Attributes(object):
    __metaclass__ = AttributesMeta
warvariuc
  • 57,116
  • 41
  • 173
  • 227