2

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?

2
  • Could you also post the code for the outer class? Edit - My bad Cfg is the inner class, thanks for clarifying for me chepner Commented Dec 19, 2018 at 13:54
  • IdSignal is the outer class; the inner class is the namedtuple type Cfg. Commented Dec 19, 2018 at 13:55

1 Answer 1

7

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.