Why is the name of the containing class not recognized as a return value function annotation? [duplicate]

StackOverflow https://stackoverflow.com/questions/15741887

Question

I was going to use Python function annotations to specify the type of the return value of a static factory method. I understand this is one of the desired use cases for annotations.

class Trie:
    @staticmethod
    def from_mapping(mapping) -> Trie:
        # docstrings and initialization ommitted
        trie = Trie()
        return trie

PEP 3107 states that:

Function annotations are nothing more than a way of associating arbitrary Python expressions with various parts of a function at compile-time.

Trie is a valid expression in Python, isn't it? Python doesn't agree or rather, can't find the name:

def from_mapping(mapping) -> Trie:
NameError: name 'Trie' is not defined

It's worth noting that this error does not happen if a fundamental type (such as object or int) or a standard library type (such as collections.deque) is specified.

What is causing this error and how can I fix it?

Was it helpful?

Solution 2

PEP 484 provides an official solution to this in the form of forward references.

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

In the case of the question code:

class Trie:
    @staticmethod
    def from_mapping(mapping) -> Trie:
        # docstrings and initialization ommitted
        trie = Trie()
        return trie

Becomes:

class Trie:
    @staticmethod
    def from_mapping(mapping) -> 'Trie':
        # docstrings and initialization ommitted
        trie = Trie()
        return trie

OTHER TIPS

Trie is a valid expression, and evaluates to the current value associated with the name name Trie. But that name is not defined yet -- a class object is only bound to its name after the class body has run to completition. You'll note the same behavior in this much simpler example:

class C:
    myself = C
    # or even just
    C

Normally, the workaround would be setting the class attribute after the class has been defined, outside the class body. This is not a really good option here, though it works. Alternatively, you could use any placeholder value in the initial definition, then replace it in the __annotations__ (which is legal because it's a regular dictionary):

class C:
    def f() -> ...: pass
print(C.f.__annotations__)
C.f.__annotations__['return'] = C
print(C.f.__annotations__)

It does feel rather hacky though. Depending on your use case, it might be possible to instead use a sentinel object (e.g. CONTAINING_CLASS = object()) and leave interpreting that to whatever actually processes the annotations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top