0

I'm confused when working with nested classes in python. In the code below, I can't access the attribute 'A' defined in 'Outer' class from 'Inner' class - the statement 'B = Outer.A' will raise a 'NameError: name 'Outer' is not defined' error. However, I can access the attribute 'A' if and only if I call it inside a function 'foo_working' from the inner class, and it won't work if I define it in the way of 'foo_not_working'.

It seems I miss some important concept about scope here. I would really appreciate if someone can help explain.

class Outer:

    A = 'from outer'

    class Inner:

        B = Outer.A

        @staticmethod
        def foo_working():
            print(Outer.A)

        @staticmethod
        def foo_not_working(something=Outer.A):
            print(something)

Updates:

Here is where the issue originated from - I'm working with SQLAlchemy where all the tables are defined as classes, and I would like to wrap them into a parent class such as 'MyTables' so it's easier to be managed and imported to other script. I would also like to define the associations in 'MyTables' class and allow some table class to call as 'secondary' in 'relationship' function. However, because of the issue above I can't achieve this (as the reason explained by @Barmar) unless I define all table class at a module level.

I'm new to SQLAlchemy and wonder if there is a solution for it?

  • not working
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy import create_engine, Column, Integer, Text
from sqlalchemy import Table, ForeignKey


class MyTables:
    
    Base = declarative_base()
    
    _user_order = Table(
        '_user_order', Base.metadata,
        Column('user_id', ForeignKey('user.user_id'), primary_key=True),
        Column('order_id', ForeignKey('order.order_id'), primary_key=True)
    )
    
    class User(Base):
        
        __tablename__ = 'user'
        user_id = Column(Integer, primary_key=True)
        user_name = Column(Text)
        
    class Order(Base):
        
        __tablename__ = 'order'
        order_id = Column(Integer, primary_key=True)
        order_name = Column(Text)
        users = relationship('User', secondary=_user_order, backref='orders')

    database_path = 'sqlite:///../data/database.db'
    engine = create_engine(url=database_path, echo=False, future=True)
    Base.metadata.create_all(engine)

  • working
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy import create_engine, Column, Integer, Text
from sqlalchemy import Table, ForeignKey

Base = declarative_base()

_user_order = Table(
    '_user_order', Base.metadata,
    Column('user_id', ForeignKey('user.user_id'), primary_key=True),
    Column('order_id', ForeignKey('order.order_id'), primary_key=True)
)


class User(Base):
    __tablename__ = 'user'
    user_id = Column(Integer, primary_key=True)
    user_name = Column(Text)


class Order(Base):
    __tablename__ = 'order'
    order_id = Column(Integer, primary_key=True)
    order_name = Column(Text)
    users = relationship('User', secondary=_user_order, backref='orders')


database_path = 'sqlite:///../data/database.db'
engine = create_engine(url=database_path, echo=False, future=True)
Base.metadata.create_all(engine)
  • 1
    The variable `Outer` isn't assigned until the class definition is complete. So you can't refer to it in code that runs while the class is being defined. That includes top-level code in the class (or nested classes) and method parameter default values. – Barmar Apr 13 '22 at 20:20
  • 1
    This has nothing to do with scope, it's just order of execution. – Barmar Apr 13 '22 at 20:21
  • 1
    Because `Outer` does not exist when the class definition statment of `Inner` is executing. You could do something like `Outer.Inner.B = Outer.A`. Or really, the best solution would simply be *not to nest your classes* – juanpa.arrivillaga Apr 13 '22 at 20:22
  • Thanks @Barmar !! I have two follow up questions: 1. How can this explain that I can define a function in the way of 'foo_working' while not 'foo_not_working'? 2. My issue actually originated from working with SQLAlchemy, where all the tables are defined as classes, and I would like to wrap them into an outer class (i.e. MyTables). However, it won't allow me to define all association tables in the outer class and call them from the inner classes that define tables. I can add some sample code if that helps clarifying my question. – anthonym24 Apr 13 '22 at 20:32
  • 1
    1. Because the function body isn't executed while you're defining the class, it's executed when you call the function. By that time, the class has been fully defined. 2. If your question is about SqlAlchemy, post what you're actually trying to do. It may have its own mechanisms intended for this. – Barmar Apr 13 '22 at 20:36
  • Thanks @juanpa.arrivillaga !! What you suggested about 'Outer.Inner.B = Outer.A' still resulted in same error. I'm working with SQLAlchemy where all the tables are defined as classes. I would need to wrap the tables into a class for better management. So is there any alternative way to do so rather than having nested classes? – anthonym24 Apr 13 '22 at 20:37
  • @anthonym24 no, **after** the class definition, do `Outer.Inner.B = Outer.A` **completely outside the class definition**, indented at module scope – juanpa.arrivillaga Apr 13 '22 at 20:42
  • 1
    @anthonym24 you *don't* have to "wrap the tables into a class for better management". Just don't do it. That's what *modules* are for. Or maybe even a `types.SimpleNamespace`. Or just **don't nest the definition** and add the clesses to the other class *after*, e..g `Wrapper.Table1 = Table1; Wrapper.Table2 = Table2` – juanpa.arrivillaga Apr 13 '22 at 20:44
  • Related: [UnboundLocalError: local variable referenced before assignment why LEGB Rule not applied in this case](https://stackoverflow.com/q/45195109/674039) – wim Apr 13 '22 at 20:53
  • In order to pre-compile "foo_working", the interpreter can just put in code to "go lookup Outer when this runs". It doesn't care at "compile" time. It can do the lookup when you call the function. But to pre-compile the `foo_not_working` function, it has to actually FIND `Outer.A` in order to save the default value for that parameter. `Outer` at that point does not exist. – Tim Roberts Apr 13 '22 at 20:54

0 Answers0