ideas for type hints for variable: beyond comments

mypy currently inspects the comment on the line of first assignment for the variables to be type hinted. It is logical that at some time python language will add support to allow these type hints to move from comments to the code as has happened for 'def' signatures. One logical syntax would be to move from i = 1 # Infer type int for i to i:int = 1 # no comment needed, but does not look attractive The first question that arises is 'is the type inference legal for the additional uses. Having a 'second use' flagged by warning or error by either an external typechecker or even the language itself could pick up on accidental reuse of a name, but in practice accidentally creating a new variable through a typo can be more common. In python today the first use is the same as every other, so this change just does not feel comfortable. The other question is 'what about globals and nonlocals?'. Currently globals and nonlocals need a 'global' or 'nonlocal' statement to allow assignment, but what if these values are not assigned in scope? What if we allowed global i:int or nonlocal i:int and even local i:int Permitting a new keyword 'local' to me might bring far more symmetry between different cases. It would also allow type hinting to be collected near the function definition and keep the type hinting clear of the main code. Use of the 'local' keyword in the global namespace could indicate a value not accessible in other namespaces. Personally I would like to go even further and allow some syntax to allow (or disable) flagging the use of new variables without type hinting as possible typos I have a syntax in mind, but the idea is the discussion point, not the specific syntax. Possibly what is here already is too much of a change of direction to consider for ideas already in progress?

On Tue, Sep 1, 2015 at 8:24 PM, Ian <ian.team.python@gmail.com> wrote:
Potential problem: Function annotations are supported all the way back to Python 3.0, but any new syntax would be 3.6+ only. That's going to severely limit its value for quite some time. That doesn't mean new syntax can't be added (otherwise none ever would), but the bar is that much higher - you'll need an extremely compelling justification.
Not sure what you're talking about here. If they're not assigned in this scope, then presumably they have the same value they had from some other scope. You shouldn't need to declare that "len" is a function, inside every function that calls it. Any type hints should go where it's assigned, and nowhere else.
Hey, if you want C, you know where to find it :)
Use of the 'local' keyword in the global namespace could indicate a value not accessible in other namespaces.
I'm not sure what "not accessible" would mean. If someone imports your module, s/he gains access to all your globals. Do you mean that it's "not intended for external access" (normally notated with a single leading underscore)? Or is this a new feature - some way of preventing other modules from using these? That might be useful, but that's a completely separate proposal.
If you're serious about wanting all your variables to be declared, then I think you want a language other than Python. There are such languages around (and maybe even compiling to Python byte-code, I'm not sure), but Python isn't built that way. Type hinting is NOT variable declaration, and never will be. (Though that's famous last words, I know, and I'm not the BDFL or even anywhere close to that. If someone pulls up this email in ten years and laughs in my face, so be it. It'd not be the first time I've been utterly confidently wrong!) ChrisA

On Tue, Sep 01, 2015 at 09:19:29PM +1000, Chris Angelico wrote:
PEP 484 says: "No first-class syntax support for explicitly marking variables as being of a specific type is added by this PEP. To help with type inference in complex cases, a comment of the following format may be used: ..." https://www.python.org/dev/peps/pep-0484/ I recall that in the discussions prior to the PEP, I got the strong impression that Guido was open to the concept of annotating variables in principle, but didn't think it was very important (for the most part, the type checker should be able to infer the variable type), and he didn't want to delay the PEP for the sake of agreement on a variable declaration syntax when a simple comment will do the job. So in principle, if we agree that type declarations for variables should look like (let's say) `str s = some_function(arg)` then the syntax may be added in the future, but it's a low priority.
But they will be assigned in the scope, otherwise there's no need to declare them global. def spam(*args): global eggs eggs = len(args) process(something, eggs) That's a case where the type-checker should be able to infer that eggs will be an int. But what if the type inference engine cannot work that out? The developer may choose to add a hint. eggs = len(args) # type:int will work according to PEP 484 (although, I guess that's a quality of implementation issue for the actual type checker). Or we could steal syntax from some other language and make it "official" that type checkers have to look at this: eggs:int # (Pascal, Swift, Ada, F#, Scala) int eggs # (Java, C, Perl6) eggs int # (Go) eggs as int # (RealBasic) Hence, for example: global eggs:int cheese:int, ham:str = 23, "foo" A big question would be, what runtime effect (if any) would this have? If the default Python compiler ignored the type hint at both compile-time and run-time, it would be hard to justify making it syntax. But perhaps the current namespace could get a magic variable __annotations__ = {name: hint} similar to the __annotations__ attribute of functions. Again, the default compiler would simply record the annotation and ignore it, the same as for functions, leaving any actual type-checking to third-party tools. [...]
Use of the 'local' keyword in the global namespace could indicate a value not accessible in other namespaces.
That won't work without a *major* change to Python's design. Currently, module namespaces are regular dicts, and there is no way to prevent others from looking up names in that dict/namespace. If you (Ian) want to change that, you should raise it as a completely separate PEP. -- Steve

On Tue, Sep 1, 2015 at 11:03 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Right, it's low priority, and a non-backward-compatible one. Backporting typing.py to any 3.x Python will make all the annotations "succeed" (given that success, at run time, doesn't require any sort of actual checking); it's not possible to backport a syntax change. It's like using 'yield from' for coroutines - it instantly stops you from running on anything older than 3.3. Maybe that'll be worthwhile, but the complaint that "comments are ugly" isn't enough justification IMO. If there were some serious run-time value for these annotations, then I could see more reason for adding them. At the moment, though, I'm distinctly -1. ChrisA

On 1 September 2015 at 23:03, Steven D'Aprano <steve@pearwood.info> wrote:
The main case where it's potentially useful is when we want to initialise a variable to None, but constrain permitted rebindings (from a typechecker's perspective) to a particular type. When we initialise a variable to an actual value, then type inference can usually handle it. Using the typing module as it exists today, I believe this should work for that purpose (although I haven't actually tried it with mypy or any other typechecker): from typing import TypeVar, Generic, Optional T = TypeVar("T") class Var(Generic[T]): def __new__(cls, value:Optional[T] = None) -> Optional[T]: return None i = Var[int]() Unless I've misunderstood the likely outcome of type inference completely, the value of i here will be None, but it's inferred type would be Optional[int]. At runtime, you could still rebind "i" to whatever you want, but a typechecker would complain if it was to anything other than None or an integer. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Sep 1, 2015 at 8:24 PM, Ian <ian.team.python@gmail.com> wrote:
Potential problem: Function annotations are supported all the way back to Python 3.0, but any new syntax would be 3.6+ only. That's going to severely limit its value for quite some time. That doesn't mean new syntax can't be added (otherwise none ever would), but the bar is that much higher - you'll need an extremely compelling justification.
Not sure what you're talking about here. If they're not assigned in this scope, then presumably they have the same value they had from some other scope. You shouldn't need to declare that "len" is a function, inside every function that calls it. Any type hints should go where it's assigned, and nowhere else.
Hey, if you want C, you know where to find it :)
Use of the 'local' keyword in the global namespace could indicate a value not accessible in other namespaces.
I'm not sure what "not accessible" would mean. If someone imports your module, s/he gains access to all your globals. Do you mean that it's "not intended for external access" (normally notated with a single leading underscore)? Or is this a new feature - some way of preventing other modules from using these? That might be useful, but that's a completely separate proposal.
If you're serious about wanting all your variables to be declared, then I think you want a language other than Python. There are such languages around (and maybe even compiling to Python byte-code, I'm not sure), but Python isn't built that way. Type hinting is NOT variable declaration, and never will be. (Though that's famous last words, I know, and I'm not the BDFL or even anywhere close to that. If someone pulls up this email in ten years and laughs in my face, so be it. It'd not be the first time I've been utterly confidently wrong!) ChrisA

On Tue, Sep 01, 2015 at 09:19:29PM +1000, Chris Angelico wrote:
PEP 484 says: "No first-class syntax support for explicitly marking variables as being of a specific type is added by this PEP. To help with type inference in complex cases, a comment of the following format may be used: ..." https://www.python.org/dev/peps/pep-0484/ I recall that in the discussions prior to the PEP, I got the strong impression that Guido was open to the concept of annotating variables in principle, but didn't think it was very important (for the most part, the type checker should be able to infer the variable type), and he didn't want to delay the PEP for the sake of agreement on a variable declaration syntax when a simple comment will do the job. So in principle, if we agree that type declarations for variables should look like (let's say) `str s = some_function(arg)` then the syntax may be added in the future, but it's a low priority.
But they will be assigned in the scope, otherwise there's no need to declare them global. def spam(*args): global eggs eggs = len(args) process(something, eggs) That's a case where the type-checker should be able to infer that eggs will be an int. But what if the type inference engine cannot work that out? The developer may choose to add a hint. eggs = len(args) # type:int will work according to PEP 484 (although, I guess that's a quality of implementation issue for the actual type checker). Or we could steal syntax from some other language and make it "official" that type checkers have to look at this: eggs:int # (Pascal, Swift, Ada, F#, Scala) int eggs # (Java, C, Perl6) eggs int # (Go) eggs as int # (RealBasic) Hence, for example: global eggs:int cheese:int, ham:str = 23, "foo" A big question would be, what runtime effect (if any) would this have? If the default Python compiler ignored the type hint at both compile-time and run-time, it would be hard to justify making it syntax. But perhaps the current namespace could get a magic variable __annotations__ = {name: hint} similar to the __annotations__ attribute of functions. Again, the default compiler would simply record the annotation and ignore it, the same as for functions, leaving any actual type-checking to third-party tools. [...]
Use of the 'local' keyword in the global namespace could indicate a value not accessible in other namespaces.
That won't work without a *major* change to Python's design. Currently, module namespaces are regular dicts, and there is no way to prevent others from looking up names in that dict/namespace. If you (Ian) want to change that, you should raise it as a completely separate PEP. -- Steve

On Tue, Sep 1, 2015 at 11:03 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Right, it's low priority, and a non-backward-compatible one. Backporting typing.py to any 3.x Python will make all the annotations "succeed" (given that success, at run time, doesn't require any sort of actual checking); it's not possible to backport a syntax change. It's like using 'yield from' for coroutines - it instantly stops you from running on anything older than 3.3. Maybe that'll be worthwhile, but the complaint that "comments are ugly" isn't enough justification IMO. If there were some serious run-time value for these annotations, then I could see more reason for adding them. At the moment, though, I'm distinctly -1. ChrisA

On 1 September 2015 at 23:03, Steven D'Aprano <steve@pearwood.info> wrote:
The main case where it's potentially useful is when we want to initialise a variable to None, but constrain permitted rebindings (from a typechecker's perspective) to a particular type. When we initialise a variable to an actual value, then type inference can usually handle it. Using the typing module as it exists today, I believe this should work for that purpose (although I haven't actually tried it with mypy or any other typechecker): from typing import TypeVar, Generic, Optional T = TypeVar("T") class Var(Generic[T]): def __new__(cls, value:Optional[T] = None) -> Optional[T]: return None i = Var[int]() Unless I've misunderstood the likely outcome of type inference completely, the value of i here will be None, but it's inferred type would be Optional[int]. At runtime, you could still rebind "i" to whatever you want, but a typechecker would complain if it was to anything other than None or an integer. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (4)
-
Chris Angelico
-
Ian
-
Nick Coghlan
-
Steven D'Aprano