classification
Title: Weird typing annotation closure behavior
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.10, Python 3.9, Python 3.8, Python 3.7, Python 3.6
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: conchylicultor, eric.smith, gvanrossum, joel.larose, larry, xtreak
Priority: normal Keywords:

Created on 2021-04-06 10:37 by conchylicultor, last changed 2021-04-09 14:09 by gvanrossum. This issue is now closed.

Messages (12)
msg390304 - (view) Author: conchylicultor (conchylicultor) * Date: 2021-04-06 10:37
I observe some strange closure behavior for typing annotations when the name is defined

```
x: x = 1  # Works, __annotation__ == {'x': 1}
```

This creates issue, for example:

```
from ... import losses

class A:
  # AttributeError: 'Losses' object has no attribute 'Losses'
  losses: losses.Losses = losses.Losses()
```
msg390314 - (view) Author: conchylicultor (conchylicultor) * Date: 2021-04-06 11:45
Interestingly mypy, pylint correctly resolve the closure.

```
class A:
    dataclasses: dataclasses.Field = dataclasses.field()

A.dataclasses.other  # mypy error: "Field[Any]" has no attribute "other"
```

So the current workaround is to use quotes:

```
class A:
  # Type correctly inferred and no closure runtime error
  losses: 'losses.Losses' = losses.Losses()
```
msg390355 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-06 17:38
Do you have an actual use case for self-referential annotations?
msg390531 - (view) Author: conchylicultor (conchylicultor) * Date: 2021-04-08 15:08
> Do you have an actual use case for self-referential annotations?

I'm not sure I understand the question. My use case is the following:

```
from ... import losses

class A:
  losses: losses.Losses = losses.Losses()
```

Currently this is failing be cause this get resolved as:

```
class A:
  name: <module losses>.Losses().Losses = <module losses>.Losses()
```
Instead of what I want/expected:
```
class A:
  name: <module losses>.Losses = <module losses>.Losses()
```

I would expect that both "losses.Losses" on the left and right of the `=` refer to the outer module (`name: <module losses>.Losses`), while currently it is resolved as `name: name.Losses`
msg390533 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-08 15:24
By "use case", I mean, what problem are you solving in a useful program by doing this?  So far it seems like a pointless exercise in seeing what funny behavior you can try with annotations.
msg390534 - (view) Author: conchylicultor (conchylicultor) * Date: 2021-04-08 15:47
The above example is a real world example I have currently have. Basically I have some dataclass based configuration like:

in losses.py:
```
class LossesParams:
  ...
```
in dataset.py:
```
class DatasetParams:
  ...
```
in config.py:
```
@dataclasses.dataclass
class Params:
  losses: losses.LossesParams = dataclasses.field()
  dataset: dataset.DatasetParams = dataclasses.field()
```
I want to use params as:
```
param = Params()
param.datasets.batch_size = 123
```
However the above code fail at `dataset: dataset.DatasetParams = dataclasses.field()` due to the closure issue.

The example is simplified but this is a very concrete problem I encountered.
msg390549 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-04-08 20:06
Can you put together an example we can run? Either from a single file, or multiple modules in the current directory? The "..." import makes it complicated to reproduce.
msg390590 - (view) Author: conchylicultor (conchylicultor) * Date: 2021-04-09 06:29
Sure, here is a minimal reproductible example demonstrating the issue:

```
import pathlib


class C:
    pathlib: pathlib.Path = None
```

Which raises:

```
  File "....py", line 5, in C
    pathlib: pathlib.Path = None
AttributeError: 'NoneType' object has no attribute 'Path'
```
msg390591 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2021-04-09 07:05
I guess this is similar to the explanation at https://bugs.python.org/issue36363#msg338389

> The problem in the original code is that the annotation references a global name that is shadowed by a local (to the class body) name, and because of the initialization, the latter takes precedence.  (To see for yourself, use the dis module to disassemble the code for Spam and Spaz.)
msg390595 - (view) Author: Joël Larose (joel.larose) Date: 2021-04-09 07:51
An easy workaround would be to alias your import or to import your class directly.

```
from ... import losses as l

class A:
  losses: l.Losses = l.Losses()
```

or 

```
from ...losses import Losses

class A:
  losses: Losses = Losses()
```
msg390627 - (view) Author: conchylicultor (conchylicultor) * Date: 2021-04-09 13:57
Yes, I know I can rename the closure, or wrap the annotation in 'quote'.

I just wanted to point this out as it felt confusing to me.
For instance, it feels inconsistent with:

```
def fn(datetime: datetime.Time):  # Works as expected
```

Or:

```
@dataclass
class A:
  datetime: datetime.Time = field(default_factory=datetime.Time.now)
```

The `datetime.Time.now` and the `datetime.Time` of the same statement refer to different objects.

This might be the expected behavior, but this is confusing nonetheless.
msg390631 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-04-09 14:09
Just Don't Do This.
History
Date User Action Args
2021-04-09 14:09:13gvanrossumsetstatus: open -> closed
resolution: wont fix
messages: + msg390631

stage: resolved
2021-04-09 13:57:42conchylicultorsetmessages: + msg390627
2021-04-09 07:51:55joel.larosesetnosy: + joel.larose
messages: + msg390595
2021-04-09 07:05:41xtreaksetnosy: + xtreak, gvanrossum
messages: + msg390591
2021-04-09 06:29:43conchylicultorsetmessages: + msg390590
2021-04-08 20:06:53eric.smithsetmessages: + msg390549
2021-04-08 20:05:55eric.smithsetmessages: - msg390548
2021-04-08 20:05:33eric.smithsetnosy: + eric.smith
messages: + msg390548
2021-04-08 15:47:11conchylicultorsetmessages: + msg390534
2021-04-08 15:24:16larrysetmessages: + msg390533
2021-04-08 15:08:59conchylicultorsetmessages: + msg390531
2021-04-06 17:38:25larrysetnosy: + larry
messages: + msg390355
2021-04-06 11:45:28conchylicultorsetmessages: + msg390314
2021-04-06 10:42:18conchylicultorsetcomponents: + Library (Lib), - Interpreter Core
2021-04-06 10:38:48conchylicultorsettype: behavior
components: + Interpreter Core
versions: + Python 3.6, Python 3.7, Python 3.8, Python 3.9, Python 3.10
2021-04-06 10:37:43conchylicultorcreate