{"action":"created","issue":{"url":"https://api.github.com/repos/python/typing/issues/258","repository_url":"https://api.github.com/repos/python/typing","labels_url":"https://api.github.com/repos/python/typing/issues/258/labels{/name}","comments_url":"https://api.github.com/repos/python/typing/issues/258/comments","events_url":"https://api.github.com/repos/python/typing/issues/258/events","html_url":"https://github.com/python/typing/issues/258","id":169207731,"number":258,"title":"Proposal: syntax for variable and attribute annotations (PEP 526)","user":{"login":"gvanrossum","id":2894642,"avatar_url":"https://avatars.githubusercontent.com/u/2894642?v=3","gravatar_id":"","url":"https://api.github.com/users/gvanrossum","html_url":"https://github.com/gvanrossum","followers_url":"https://api.github.com/users/gvanrossum/followers","following_url":"https://api.github.com/users/gvanrossum/following{/other_user}","gists_url":"https://api.github.com/users/gvanrossum/gists{/gist_id}","starred_url":"https://api.github.com/users/gvanrossum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gvanrossum/subscriptions","organizations_url":"https://api.github.com/users/gvanrossum/orgs","repos_url":"https://api.github.com/users/gvanrossum/repos","events_url":"https://api.github.com/users/gvanrossum/events{/privacy}","received_events_url":"https://api.github.com/users/gvanrossum/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/python/typing/labels/enhancement","name":"enhancement","color":"84b6eb"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":103,"created_at":"2016-08-03T19:02:03Z","updated_at":"2016-08-18T12:56:00Z","closed_at":null,"body":"## Introduction\r\n\r\nThis issue is reserved for substantive work on [PEP 526](https://www.python.org/dev/peps/pep-0526/), \"Syntax for Variable and Attribute Annotations\". For textual nits please comment directly on the latest PR for this PEP in the [peps repo](https://github.com/python/peps).\r\n\r\nI sent a [strawman proposal](https://mail.python.org/pipermail/python-ideas/2016-August/041294.html) to python-ideas. The feedback was mixed but useful -- people tried to poke holes in it from many angles.\r\n\r\nIn this issue I want to arrive at a more solid specification. I'm out of time right now, but here are some notes:\r\n\r\n- Class variables vs. instance variables\r\n- Specify instance variables in class body vs. in `__init__` or `__new__`\r\n- Thinking with your runtime hat on vs. your type checking hat\r\n- Importance of `a: <type>` vs. how it strikes people the wrong way\r\n- Tuple unpacking is a mess, let's avoid it entirely\r\n- Collecting the types in something similar to `__annotations__`\r\n- Cost of doing that for locals\r\n- Cost of introducing a new keywords\r\n\r\n### Work in progress here!\r\n\r\nI'm updating the issue description to avoid spamming subscribers to this tracker. I'll keep doing this until we have reasonable discussion.\r\n\r\n## Basic proposal\r\n\r\nMy basic observation is that introducing a new keyword has two downsides: (a) choice of a good keyword is hard (e.g. it can't be 'var' because that is way too common a variable name, and it can't be 'local' if we want to use it for class variables or globals,) and (b) no matter what we choose, we'll still need a `__future__` import.\r\n\r\nSo I'm proposing something keyword-free:\r\n```python\r\na: List[int] = []\r\nb: Optional[int] = None\r\n```\r\nThe idea is that this is pretty easy to explain to someone who's already familiar with function annotations.\r\n\r\n### Multiple types/variables\r\n\r\nAn obvious question is whether to allow combining type declarations with tuple unpacking (e.g. `a, b, c = x`). This leads to (real or perceived) ambiguity, and I propose *not* to support this. If there's a type annotation there can only be one variable to its left, and one value to its right. This still allows tuple *packing* (just put the tuple in parentheses) but it disallows tuple *unpacking*. (It's been proposed to allow multiple parenthesized variable names, or types inside parentheses, but none of these look attractive to me.)\r\n\r\nThere's a similar question about what to about the type of `a = b = c = x`. My answer to this is the same: Let's not go there; if you want to add a type you have to split it up.\r\n\r\n### Omitting the initial value\r\n\r\nMy next step is to observe that sometimes it's convenient to decouple the type declaration from the initialization. One example is a variable that is initialized in each branch of a big sequence of `if`/`elif`/etc. blocks, where you want to declare its type before entering the first `if`, and there's no convenient initial value (e.g. `None` is not valid because the type is not `Optional[...]`). So I propose to allow leaving out the assignment:\r\n```python\r\nlog: Logger\r\nif develop_mode():\r\n    log = heavy_logger()\r\nelif production_mode():\r\n    log = fatal_only_logger()\r\nelse:\r\n    log = default_logger()\r\nlog.info(\"Server starting up...\")\r\n```\r\nThe line `log: Logger` looks a little odd at first but I believe you can get used to it easily. Also, it is again similar to what you can do in function annotations. (However, don't hyper-generalize. A line containing just `log` by itself means something different -- it's probably a `NameError`.)\r\n\r\nNote that this is something that you currently can't do with `# type` comments -- you currently have to put the type on the (lexically) first assignment, like this:\r\n```python\r\nif develop_mode():\r\n    log = heavy_logger()  # type: Logger\r\nelif production_mode():\r\n    log = fatal_only_logger()  # (No type declaration here!)\r\n# etc.\r\n```\r\n(In this particular example, a type declaration may be needed because `heavy_logger()` returns a subclass of `Logger`, while other branches produce different subclasses; in general the type checker shouldn't just compute the common superclass because then a type error would just infer the type `object`.)\r\n\r\n#### What about runtime\r\n\r\nSuppose we have `a: int` -- what should this do at runtime? Is it ignored, or does it initialize `a` to `None`, or should we perhaps introduce something new like JavaScript's `undefined`? I feel quite strongly that it should leave `a` uninitialized, just as if the line was not there at all.\r\n\r\n## Instance variables and class variables\r\n\r\nBased on working with mypy since last December I feel strongly that it's very useful to be able to declare the types of instance variables in class bodies. In fact this is one place where I find the value-less notation (`a: int`) particularly useful, to declare instance variables that should always be initialized by `__init__` (or `__new__`), e.g. variables whose type is mutable or cannot be `None`.\r\n\r\nWe still need a way to declare class variables, and here I propose some new syntax, prefixing the type with a `class` keyword:\r\n```python\r\nclass Starship:\r\n    captain: str                      # instance variable without default\r\n    damage: int = 0                   # instance variable with default (stored in class)\r\n    stats: class Dict[str, int] = {}  # class variable with initialization\r\n```\r\nI do have to admit that this is entirely unproven. PEP 484 and mypy currently don't have a way to distinguish between instance and class variables, and it hasn't been a big problem (though I think I've seen a few mypy bug reports related to mypy's inability to tell the difference).\r\n\r\n## Capturing the declared types at runtime\r\n\r\nFor function annotations, the types are captured in the function's `__annotations__` object. It would be an obvious extension of this idea to do the same thing for variable declarations. But where exactly would we store this info? A strawman proposal is to introduce `__annotations__` dictionaries at various levels. At each level, the types would go into the `__annotations__` dict at that same level. Examples:\r\n\r\nGlobal variables\r\n```python\r\nplayers: Dict[str, Player]\r\nprint(__annotations__)\r\n```\r\nThis would print `{'players': Dict[str, Player]}` (where the value is the runtime representation of the type `Dict[str, Player]`).\r\n\r\nClass and instance variables:\r\n```python\r\nclass Starship:\r\n    # Class variables\r\n    hitpoints: class int = 50\r\n    stats: class Dict[str, int] = {}\r\n    # Instance variables\r\n    damage: int = 0\r\n    shield: int = 100\r\n    captain: str  # no initial value\r\nprint(Starship.__annotations__)\r\n```\r\nThis would print a dict with five keys, and corresponding values:\r\n```python\r\n{'hitpoints': ClassVar[int],  # I'm making this up as a runtime representation of \"class int\"\r\n 'stats': ClassVar[Dict[str, int]],\r\n 'damage': int,\r\n 'shield': int,\r\n 'captain': str\r\n}\r\n```\r\n\r\nFinally, locals. Here I think we should not store the types -- the value of having the annotations available locally is just not enough to offset the cost of creating and populating the dictionary on each function call.\r\n\r\nIn fact, I don't even think that the type expression should be evaluated during the function execution. So for example:\r\n```python\r\ndef side_effect():\r\n    print(\"Hello world\")\r\ndef foo():\r\n    a: side_effect()\r\n    a = 12\r\n    return a\r\nfoo()\r\n```\r\nshould not print anything. (A type checker would also complain that `side_effect()` is not a valid type.)\r\n\r\nThis is inconsistent with the behavior of\r\n```python\r\ndef foo(a: side_effect()):\r\n    a = 12\r\n    return a\r\n```\r\nwhich *does* print something (at function definition time). But there's a limit to how much consistency I am prepared to propose. (OTOH for globals and class/instance variables I think that there would be some cool use cases for having the information available.)\r\n\r\n### Effect of presence of `a: <type>`\r\n\r\nThe presence of a local variable declaration without initialization still has an effect: it ensures that the variable is considered to be a local variable, and it is given a \"slot\" as if it was assigned to. So, for example:\r\n```python\r\ndef foo():\r\n    a: int\r\n    print(a)\r\na = 42\r\nfoo()\r\n```\r\nwill raise `UnboundLocalError`, not `NameError`. It's the same as if the code had read\r\n```python\r\ndef foo():\r\n    if False: a = 0\r\n    print(a)\r\n```\r\n\r\n## Instance variables inside methods\r\n\r\nMypy currently supports `# type` comments on assignments to instance variables (and other things). At least for `__init__` (and `__new__`, and functions called from either) this seems useful, in case you prefer a style where instance variables are declared in `__init__` (etc.) rather than in the class body.\r\n\r\nI'd like to support this, at least for cases that obviously refer to instance variables of `self`. In this case we should probably not update `__annotations__`.\r\n\r\n## What about `global` or `nonlocal`?\r\n\r\nWe should not change `global` and `nonlocal`. The reason is that those don't declare new variables, they declare that an existing variable is write-accessible in the current scope. Their type belongs in the scope where they are defined.\r\n\r\n## Redundant declarations\r\n\r\nI propose that the Python compiler should ignore duplicate declarations of the same variable in the same scope. It should also not bother to validate the type expression (other than evaluating it when not in a local scope). It's up to the type checker to complain about this. The following nonsensical snippet should be allowed at runtime:\r\n```python\r\na: 2+2\r\nb: int = 'hello'\r\nif b:\r\n    b: str\r\n    a: str\r\n```"},"comment":{"url":"https://api.github.com/repos/python/typing/issues/comments/240714363","html_url":"https://github.com/python/typing/issues/258#issuecomment-240714363","issue_url":"https://api.github.com/repos/python/typing/issues/258","id":240714363,"user":{"login":"ilevkivskyi","id":12005495,"avatar_url":"https://avatars.githubusercontent.com/u/12005495?v=3","gravatar_id":"","url":"https://api.github.com/users/ilevkivskyi","html_url":"https://github.com/ilevkivskyi","followers_url":"https://api.github.com/users/ilevkivskyi/followers","following_url":"https://api.github.com/users/ilevkivskyi/following{/other_user}","gists_url":"https://api.github.com/users/ilevkivskyi/gists{/gist_id}","starred_url":"https://api.github.com/users/ilevkivskyi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ilevkivskyi/subscriptions","organizations_url":"https://api.github.com/users/ilevkivskyi/orgs","repos_url":"https://api.github.com/users/ilevkivskyi/repos","events_url":"https://api.github.com/users/ilevkivskyi/events{/privacy}","received_events_url":"https://api.github.com/users/ilevkivskyi/received_events","type":"User","site_admin":false},"created_at":"2016-08-18T12:55:59Z","updated_at":"2016-08-18T12:55:59Z","body":"@gvanrossum There is another point about creating ``__annotations__`` dynamically. In such class\r\n```python\r\nclass C:\r\n    if fun():\r\n        x: int = 5\r\n    else:\r\n        print('no way for x')\r\n```\r\n``__annotations__`` could be, or could be not created, depending on runtime. I think that static creation of ``__annotations__`` will be more predictable and easier to debug. So that I am tending more in favour of two-opcode option."}}
{"actor":{"display_login":"ilevkivskyi"}}