stav
stav

Reputation: 1755

Instance of inner class as default value for outer class method

I'd like to use an instance of an inner class (in this case a namedtuple although the exact same symptoms occur for an inner class defined with class) as a default value for an outer class method (in this case the constructor). However, when this code is imported from a different module the outer class definition seems to be missing.

Example:

# mymodule.py

from typing import NamedTuple, Tuple

class IdSignal():
    Cfg = NamedTuple('IdSignalCfg', [
        ('nfft', int),
        ('limits', Tuple[float, float]),
        ('min_spacing', float),
        ('nmix', int)])
    Cfg.__new__.__defaults__ = (
        512,
        (1500, 7500),
        200,
        3
    )

    def __init__(self, cfg = IdSignal.Cfg()):
        self.cfg = cfg

Now executing import mymodule throws:

Exception has occurred: NameError
name 'IdSignal' is not defined
  File "...", line 18, in IdSignal
    def __init__(self, cfg = IdSignal.Cfg()):
  File "...", line 5, in <module>
    class IdSignal():
  ...
    import mymodule

Confusingly, both pylint and mypy don't recognize any error in the above code.

Can this be achieved any other way?

I understand I can use None as a default value and instantiate IdSignal.Cfg within the constructor. If this is the only solution, I'd like to understand why is the above code failing?

Upvotes: 2

Views: 212

Answers (1)

chepner
chepner

Reputation: 530872

At the time __init__ is defined, the name IdSignal isn't yet bound to the class. (That doesn't happen until the entire body of the class statement is evaluated, and the result of that evaluation is passed to the relevant metaclass.) However, Cfg is also not yet a class attribute; it's just a name in the same "scope" in which __init__ is defined, so you don't need to qualify the name.

def __init__(self, cfg=Cfg()):
    self.cfg = cfg

A class statement like

class Foo:
    x = 3
    def __init__(self, y):
        self.y = y

is roughly equivalent to

# These names don't really matter, but I'm using different
# names than what the attributes will be just to emphasize
# the point that they really are distinct objects before
# the new class is ever created.

class_x = 3

def some_init(self, y):
    self.y = y

Foo = type('Foo', (object,), {'__init__': some_init, 'x': class_x})

Notice that the name Foo doesn't come into existence until the very end. A class statement doesn't define a new scope like a module or function does, but neither are names defined in a class statement part of any enclosing scope; think of them as temporary names that are discarded once the class is created.

Upvotes: 7

Related Questions