0

Notes On Similar Q&As:


I'm trying to do a simple nesting of multiple Python @dataclass decorated classes within another class, and have later classes refer back to the earlier ones. If I do not nest them at all, they work as expected, being able to include the first class defined into an object in the second class:

from dataclasses import dataclass, field

@dataclass
class A:
  z:int = field(default=0)

@dataclass
class B:
  a:A = field(default=A(z=1)) ### Object that is class A is included in class B

b = B(a=A(z=3))
print(f'b = B(a=A(z=3)); b.a.z={b.a.z}; b={b}')

But if I try to do the same inside of another class (in this case, not a dataclass), the "B" class cannot see the "A" class. In the below code, the definition of a as a type of A fails with a NameError: "name A is not defined". I've tried A and C.A, neither work.

Note that the other functions in the C class are able to see both A and B just fine, just inside dataclass B cannot see dataclass A.

class C:
  @dataclass
  class A:
    z:int = field(default=0)

  @dataclass
  class B:
    a:A = field(default=A(z=1)) ### NameError: name 'A' is not defined

  def __init__(self):
    self.b = C.B(a=C.A(z=3))

  def print_info(self):
    print(f'b = C.B(a=C.A(z=3)); b.a.z={self.b.a.z}; b={b}')

c = C()
c.print_info()

However, if I convert these to normal Python classes, it works in the nested case:

Rechecking, it turns out this is broken in normal classes as well (per comment below).

Strangely, if one nests dataclass A inside dataclass B, with B still inside class C, it does work - B has direct access to A, but nothing else in class C has direct access to A.

Question

Is it possible to define nested dataclasses with having the later ones access the earlier ones at the same level? If so, how?

6
  • 1
    "Flat is better than nested." - The Zen of Python – I don't see a good reason to nest classes at all here. Also, do note that what you're doing in both of these cases is share a single A instance between all instances of B. That's likely not what you want since these aren't frozen dataclasses. Commented Oct 25, 2021 at 5:39
  • 2
    As another aside, if you remove the @dataclass decorators from the nested-dataclasses example, you'll find it still raises the NameError, so this isn't really about dataclasses... Commented Oct 25, 2021 at 5:41
  • @AKX In my "considerably more complex" real-world code, it feels like the right thing to nest them to get functional groupings, but it's true that I could leave them at least flat per module. Regarding the NameError - you are right! I messed up the way I had the file laid out, if I cut it down to just that example, it's also broken. Commented Oct 25, 2021 at 5:50
  • 2
    Modules are great for functional grouping. I don't think there are many, if any, nested classes in the standard library... Commented Oct 25, 2021 at 5:54
  • Based on my mistake on the normal classes, it is a duplicate of stackoverflow.com/q/42185472/6501141, voting to reclose it. Your answer does add some clarity that isn't at that Q&A though. Thanks! Commented Oct 25, 2021 at 6:06

1 Answer 1

1

To my best understanding, this is due to the semantics for class definitions – emphasis mine:

The class’s suite is then executed in a new execution frame, using a newly created local namespace and the original global namespace. (Usually, the suite contains mostly function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved.

That is, any class definition only ever has the module-global namespace (which does not yet contain C in this case, since its suite hasn't finished executing) and a new empty local namespace.

EDIT

Based on the above, this can be hacked together to work, but I really wouldn't do this...

from dataclasses import dataclass, field


class C:
    @dataclass
    class A:
        z: int = field(default=0)

    globals()["A"] = A  # "and the original global namespace..."

    @dataclass
    class B:
        a: A = field(default=A(z=1))

    def __init__(self):
        self.b = C.B(a=C.A(z=3))

    def print_info(self):
        print(f"{self.b.a.z=}")


c = C()
c.print_info()
Sign up to request clarification or add additional context in comments.

6 Comments

This makes sense now. The nested classes don't actually exist unless they exist through an instance of that class, unless they were to somehow be made static so they could be referenced directly by the class (which may not even be possible like it is with functions/methods). I need to embrace the zen of Python in this case, which means flattening my class definitions, and splitting into more modules if I really need the functional grouping...
"The nested classes don't actually exist unless they exist through an instance of that class" No, they do exist alright, it's just that the namespace in which they exist is local and anonymous until the outer class is "fully defined", as it were.
Anyway, see my edit – by injecting the inner class into the global namespace, this can be made to work, but it's very ugly.
I think you can still reference the inner class without having to make it global, but you reference it through C (as that's where it's defined): inner_instance = C.A(z=0)
@Richard I doubt that since the name C is not yet a thing while the inner body is being evaluated.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.