Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Other people have mentioned that the self-referential case from this post is doable. But what is not doable (to my knowledge) is this kind of self-reference:

    class Node(typing.NamedTuple):  # or dataclass
        child: 'Node'
The error is example.py:4: error: Recursive types not fully supported yet, nested types replaced with "Any".

This error has been in there for several years at least. And it always bites me when I forget. It's particularly cryptic when the self-reference is many levels deep. I've even had the error message break in these cases where it prints out a line number from the wrong file. So, then you have to hunt to see where the self-reference is hiding.



Just tried this myself, and it seems to only be an issue for typing.NamedTuple. Though, it still manages to do type checking correctly...

    from __future__ import annotations
    from typing import Optional, NamedTuple
    from dataclasses import dataclass

    class Foo:
        def __init__(self, foo: Foo = None) -> None:
            self.child: Optional[Foo] = foo

    Foo(Foo(Foo(None))) # Works
    Foo(Foo(Foo(1))) # error: Argument 1 to "Foo" has incompatible type "int"; expected "Optional[Foo]"

    @dataclass
    class Bar:
        child: Optional[Bar]

    Bar(Bar(Bar(None))) # Works
    Bar(Bar(Bar('bar'))) # error: Argument 1 to "Bar" has incompatible type "str"; expected "Optional[Bar]"

    class Hep(NamedTuple): # error: Recursive types not fully supported yet, nested types replaced with "Any"
        child: Optional[Hep]

    Hep(Hep(Hep(None))) # Works
    Hep(Hep(Hep({'a': 'b'}))) # error: Argument 1 to "Hep" has incompatible type "Dict[str, str]"; expected "Optional[Hep]"


It looks like the constructor is being typechecked, but probably not attribute access. `val.child.junk` will pass because `val.child` is `Any` and you can do any-thing to an Any.


That seems to work too..

    a = Foo(Foo(Foo(None)))
    b = Bar(Bar(Bar(None)))
    c = Hep(Hep(Hep(None)))

    a.child.junk = 1
    # error: Item "Foo" of "Optional[Foo]" has no attribute "junk"
    # error: Item "None" of "Optional[Foo]" has no attribute "junk"

    b.child.junk = 'a'
    # error: Item "Bar" of "Optional[Bar]" has no attribute "junk"
    # error: Item "None" of "Optional[Bar]" has no attribute "junk"

    c.child.junk = 1.1
    # error: Item "Hep" of "Optional[Hep]" has no attribute "junk"
    # error: Item "None" of "Optional[Hep]" has no attribute "junk"

    a.child = 1
    # error: Incompatible types in assignment (expression has type "int", variable has type "Optional[Foo]")

    b.child = 1
    # error: Incompatible types in assignment (expression has type "int", variable has type "Optional[Bar]")

    c.child = 1
    # error: Property "child" defined in "Hep" is read-only
    # error: Incompatible types in assignment (expression has type "int", variable has type "Optional[Hep]")
I'm using mypy 0.701 with Python 3.7.3.


Hey, thanks a lot for pointing this out! Yet another reason to use data classes vs. named tuples.

Looks like just plain class level attribute declaration also works:

    class Node:
        child: 'Node'
I wonder why `typing.NamedTuple` is unique as far as it not working. I know they don't use `eval` and templating to create it anymore. From looking at the code [0], they're using metaclass / __new__. But other than the fact that it uses metaclasses, I'm not sure why it'd have an error. Obviously there's cycle handling in mypy, otherwise none of the examples would work. If I use an example with a metaclass, that also typechecks. So, it's not metaclasses that trigger the error.

    class NodeBase(type):
        def __new__(cls, name, bases, attrs):
            return super().__new__(cls, name, bases, attrs)


    class Node(metaclass=NodeBase):
        child: 'Node'  # works
Edit: it looks like there is now work going into this and other cases where recursive types don't work [1][2]. For example: `Callback = typing.Callable[[str], Callback]` and `Foo = Union[str, List['Foo']]`.

[0] https://github.com/python/cpython/blob/3.8/Lib/typing.py#L15...

[1] https://github.com/python/mypy/issues/731

[2] https://github.com/python/mypy/issues/6204




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: