Access base class attribute in derived class - in "class scope"
Asked Answered
L

3

4
class Outer(object):
    class InnerBase(object): _var = {'foo', 'bar'}
    class Derived(InnerBase):
        _var = _var | {'baz'} # NameError: name '_var' is not defined
        _var = InnerBase._var | {'baz'} #  name 'InnerBase' is not defined
        _var = Outer.InnerBase._var | {'baz'} #  free variable 'Outer'
        # referenced before assignment in enclosing scope

Moving _var in Outer does not help - moving it in module scope would work but defeats the purpose of having classes. So how to go about that ?

EDIT: coming from Java so the scoping rules of classes are a head scratcher for me - a briefing would be appreciated. This works btw:

    class Derived(InnerBase): pass
    Derived._var = InnerBase._var | {'baz'}

but it's not the pinnacle of elegance.

Related: Nested classes' scope? - but here we specifically want to access our parent class (rather than the Outer type)

EDIT2: What I am actually after is a _var = __class__._var-like syntax (or hack), or an explanation as to why it's not there

Liberticide answered 26/2, 2015 at 5:13 Comment(0)
G
2

You can bypass the class statement and use an explicit call to type.

class Outer(object):
    class InnerBase(object): _var = {'foo', 'bar'}
    Derived = type('Derived',
                   (InnerBase,),
                   {'_var': InnerBase._var | {'baz'}}
                  )

This works because Derived._var is not being set via an assignment statement in the class statement defining Derived, but is imported from a dictionary you create in the same environment as InnerBase itself.

Giffer answered 25/4, 2015 at 17:0 Comment(0)
C
4

Python never searches for a name in enclosing class statements. Mark Lutz uses the acronym LEGB to summarize scope in his introduction to Python (Learning Python): Python searches the local scope, then the local scope of any enclosing def statements, then the global scope, and finally the built-in scope. Class statements are excluded from this scope list; Python does not search enclosing class statements for a name.

One solution is to un-nest your classes. In Python, using un-nested classes is often preferred for its simplicity. But, of course, there are good reasons to nest classes as well. Why have you nested InnerBase? I wonder if you might have nested that class because of your experience in Java. Would the following work for you just as well?

class InnerBase(object):
    _var = {'foo', 'bar'}

class Derived(InnerBase):
    _var = InnerBase._var | {'baz'}

>>> Derived._var
set(['baz', 'foo', 'bar'])

The moment you nest these two class statements under another class statement they will be excluded from name searches, since they have become part of the larger class statement and are thus excluded from the searching of the various scopes.

Cressy answered 25/4, 2015 at 16:21 Comment(3)
No those nested classes fit naturally there (I am actually refactoring an existing design - I intend to eradicate those classes, so this is again a curiosity question). Could we use somehow the fact that we (Derived) have actually _var already ? (If only _var = __class__._var would compile...)Liberticide
Derived doesn't have a _var. Its base class does, and Derived._var would work because of how inherited attribute lookup works. However, the LEGB rule says that Derived doesn't actually a way to access its base class until after the class definition is complete (at least, when using a class statement).Giffer
Accepted chepner's answer for the 'hack' part (trust me I had searched) - but yours offers the more insight in 'scoping rules of classes' part. Feel free to add links to the LEGB acronym :)Liberticide
G
2

You can bypass the class statement and use an explicit call to type.

class Outer(object):
    class InnerBase(object): _var = {'foo', 'bar'}
    Derived = type('Derived',
                   (InnerBase,),
                   {'_var': InnerBase._var | {'baz'}}
                  )

This works because Derived._var is not being set via an assignment statement in the class statement defining Derived, but is imported from a dictionary you create in the same environment as InnerBase itself.

Giffer answered 25/4, 2015 at 17:0 Comment(0)
E
-1

When you put it in a method it works.

    def __init__(self):
        _var = Outer.InnerBase._var | {'baz'}
        #_var = super(Outer.Derived, self)._var | {'baz'}    # or with super
        print(_var)

So my impression is that it's all about initialization.

Ejectment answered 25/4, 2015 at 13:41 Comment(3)
Off topic - I want to access it in class scope - not once every time I initializeLiberticide
I don't find it so off-topic, you can always use staticmethod or classmethod.Ejectment
Static and class method would blow the same way - don't take it personally, in this community it is customary to delete one's answer if did not quite work ;)Liberticide

© 2022 - 2024 — McMap. All rights reserved.