|
|
Log in / Subscribe / Register

Wrangling the typing PEPs

By Jake Edge
December 16, 2021

When last we looked in on the great typing PEP debate for Python, back in August, two PEPs were still being discussed as alternatives for handling annotations in the language. The steering council was considering the issue after deferring on a decision for the Python 3.10 release, but the question has been deferred again for Python 3.11. More study is needed and the council is looking for help from the Python community to guide its decision. In the meantime, though, discussion about the deferral has led to the understanding that annotations are not a general-purpose feature, but are only meant for typing information. In addition, there is a growing realization that typing information is effectively becoming mandatory for Python libraries.

Background

Annotations in Python are meant to allow specifying some attribute that gets associated with, originally, function parameters and their return value; eventually that mechanism was extended to variables as well. From the beginning, annotations were used to specify type information for those elements, but the language itself did not require anything other than syntactically correct Python for the value of an annotation; it would turn the value into a Python object that gets stored. Until Python 3.5 in 2015, there was not even a standard on how to specify type information for annotations; that came from the type hints effort.

The annotation information is available at run time in the __annotations__ dictionary for the annotated object, but type hints were largely meant to be used by static type checkers and other tools that never actually consult that dictionary. Instead, those kinds of tools simply parse the Python themselves. But there are libraries that actually use the annotations at run time. There may even be uses of annotations that are not tied to typing information at all, though examples of that are thin on the ground. The Python ecosystem is enormous, however, and the annotations feature was never strictly tied to typing, so Someone Out There surely could be doing their own thing.

PEP 563 ("Postponed Evaluation of Annotations") was accepted in 2017, added as an opt-in feature in Python 3.7, and was set to replace the existing implementation in Python 3.10. The problem with forward references to types that have not yet been defined was the main impetus for the PEP; it deferred the evaluation of annotations by storing them as strings and requiring anyone who wanted to use them as Python objects to call eval() or typing.get_type_hints() to evaluate them. This new behavior was gated by a __future__ import, but was set to become the only available behavior in 3.10.

PEP 563 had further implications beyond just changing how the annotations were handled by the language. In particular, the evaluation of the strings required for run-time uses of the annotation values was done in a different scope than when it was done at compile time, which led to various problems for that use case. So PEP 649 ("Deferred Evaluation Of Annotations Using Descriptors") was a late-breaking proposal that was meant to fix some shortcomings of the earlier PEP. Instead of storing the annotations as strings that need to be evaluated later when they are used, PEP 649 turned them into descriptor functions that would be run once, the first time elements of __annotations__ are accessed. Those functions would effectively operate in the same scope as the existing interpreter uses when it generates the values, so the net result would be generally the same as the existing behavior.

Back in April, given the questions surrounding the feature, the imminent Python 3.10 feature freeze, and the two competing PEPs, the steering council deferred switching the behavior to that of PEP 563. The background here is meant as a capsule summary; the articles linked to (and the links from those) will provide lots more history and details for interested readers.

More recent events

In October, PEP 563 author Łukasz Langa published a lengthy blog post discussing the two PEPs and their strengths and weaknesses. In it he noted that it would be sensible to simply adopt PEP 649 had PEP 563 never come about. Langa found much to like about PEP 649:

Looking at PEP 649 in isolation, it provides a more flexible and elegant solution to the problem. It avoids unparsing annotations from the AST [abstract syntax tree] form back into strings, which is AFAICT [as far as I can tell] unheard of in the world of programming language implementations. It avoids proliferation of strings in annotations which means that in many happy cases the user might be blissfully unaware of them. With PEP 563 they’re user-visible which is suboptimal. Clearly, as long as the PEP can be similarly performant, it represents better engineering.

But, since there is no option to go back in time to before PEP 563 was adopted, that is not the world we live in. If, for example, PEP 563 were to be deprecated in favor of PEP 649, he asked, where would that leave code that is using the new feature and that needs to be compatible with a wide range of Python versions? Depending on whether PEP 649 was added as an opt-in feature (behind a different __future__ import), as the PEP itself suggests, or if it is adopted as the language default, as has also been discussed, there are different kinds of problems for developers using the feature. Langa suggested that maybe a middle course could be found where the one specific case that led to the deferral of PEP 563 as default could use a PEP 649 technique:

This approach, while less pure than PEP 649, would entirely avoid the necessity for complex deprecations which I believe would provide a better end user experience. It would solve what made people unhappy while keeping what makes PEP 563 effective (its availability from Python 3.7 and runtime efficiency).

On November 17, Barry Warsaw posted a note to the python-dev mailing list on behalf of the steering council. It described the status of the two PEPs from the perspective of the council, while rendering a verdict (for now):

We have concluded that we lack enough detailed information to make a decision in favor of either PEP. As we see it, adopting either PEP 563 or PEP 649 as the default would be insufficient. They will not fully resolve the existing problems these PEPs intend to fix, will break some existing code, and likely don’t address all the use cases and requirements across the static and dynamic typing constituents. We are also uncertain as to the best migration path from the current state of affairs.

Defer decision on PEP 563 and 649 in 3.11

As such, at this time, the only reasonable path forward that the SC [steering council] sees is to defer the decision in Python 3.11 again, essentially keeping the 3.10 status quo. We know that this is far from ideal, but it’s also the safest path since we can’t clearly make the situation better, and we don’t have confidence that either PEP solves the problems once and for all. Pragmatically, we don’t want to make the situation worse, and we really don’t want to find ourselves back here again in a couple of releases because we overlooked an important requirement for a set of users.

The post included a call for help from the community in order to better understand the requirements for both static and dynamic typing throughout the Python ecosystem. There is also a need to rally "typing enthusiasts to help build consensus with either of the proposed PEPs" or to find an alternative, perhaps along the lines of what Langa suggested. Beyond that, the council is looking for a neutral party who can help shepherd some kind of a solution going forward.

As might be guessed, that set off a longish thread discussing the issues. One thing that was clear, at least from the wording of that announcement, is that the council was only really considering annotations for typing purposes, thus precluding other uses of annotations, by implication anyway. Christopher Barker pointed that out as something that could probably use some clarification:

Annotations can be, and are, used for other things than "typing". I just noticed that PEP 563 apparently deprecated those other uses (well, sort of: "uses for annotations incompatible with the aforementioned PEPs should be considered deprecated"), but if the SC is reconsidering PEP 563, then it would be nice to be clear about whether non-typing uses of annotations are indeed deprecated. If not, then the challenge is to come up with a way forward that not only supports both static and dynamic typing, but also other potentially arbitrary use cases.

He briefly described his use case, which will no longer work with the PEP 563 changes. His code was written after PEP 563 was approved (with the language he quoted), so it may be his "fault" that he used the annotations feature incorrectly. If that is the case, he will be a bit disappointed, but there is a larger issue at hand, he said:

But the fact is that I, among others, have been a bit uncomfortable about the focus on typing in Python for years. But when issues are raised, we have been repeatedly told that typing is, and always will remain, optional. In short, it was made clear that anyone not interested in typing could safely ignore the discussions about it. And thus, a number of PEPs came and went, and those among us that did not choose to involve ourselves in the conversation did not pay attention to the details.

And thus we didn't notice that buried in what seemed like a typing PEP, was, in fact, a [deprecation] of any non-typing uses of an existing Python feature. (also to be fair, the title of the PEP is "Postponed Evaluation of Annotations" -- so should have caught the attention of anyone interested in annotations for any use.

Paul Moore agreed with Barker that more clarity is needed:

It's becoming harder and harder for people not particularly interested in static typing to simply ignore it, and any use of annotations to affect runtime behaviour is in a weird grey area. And as a library author, I'm now finding that I'm getting requests to add typing to my code "for my users" (i.e., using types is no longer just a choice I make for my project, it's an API design issue).

Original intentions

While the non-typing users of annotations have a legitimate complaint, Stephen J. Turnbull said, he believes that the writing has been on the wall for some time. His understanding is that typing information is and always has been "considered the primary use case for annotations" by former benevolent-dictator-for-life (BDFL) Guido van Rossum, and that the council has been following that lead. Greg Ewing remembered things somewhat differently:

[...] the BDFL didn't say that, or at least didn't say it very clearly. It sounded more like type hints were just one of many possible uses, and he encouraged people to experiment. There were even discussions about coming up with a convention to manage conflicting uses of annotations in the same code. That wouldn't have happened if typing were considered the only supported use.

Van Rossum is not sure that his communication was completely clear, but said that at least in his mind annotations were always about typing:

My memory is also hazy, but I'm quite sure that *in my mind* annotations were intended as a compromise between conflicting proposals for *typing*. We didn't have agreement on the syntax or semantics, but we did know we wanted to do something with types eventually. Some folks wanted to enforce types at runtime. Others wanted to use them to generate faster code. Yet others wanted types to be checked by the compiler. The term "gradual typing" wasn't invented (or hadn't reached our community) yet, and offline static type checking wasn't something we had thought of either (I think). But it was clear that typing would have to be optional.

Warsaw remembers things a little differently as well:

My recollection of the history of annotations falls somewhere between Greg’s and Guido’s. Annotations as a feature were inspired by the typing use case (with no decision at the time whether those were to be static or runtime checks), but at the same time allowing for experimentation for other use cases. Over time, annotations-for-typing clearly won the mindset and became the predominant use case.

But Oscar Benjamin pointed to PEP 3107 ("Function Annotations") from 2006, which lists numerous use cases, many of which were not for typing, at least directly. "As I remember it, a decision about the purpose of annotations was *explicitly* not made when they were introduced." Antoine Pitrou concurred: "Annotations were purposefully use case-agnostic, and there was no stated desire to push for one use case or another."

The intent when annotations were added is not necessarily relevant 15 years later, because the language and its users have largely excluded non-typing use cases. Warsaw said that "while all the signs are there for 'annotations are for typing', this has never been explicitly or sufficiently codified". He suggested that a PEP be written to that effect, in order to remove all doubt.

[...] We can lament the non-typing use of annotations, but I think that horse is out of the barn and I don’t know how you would resolve conflicts of use for typing and non-typing annotations. It’s been a slow boil, with no definitive pronouncement, and that needs to be fixed, but I think it's just acknowledging reality. That’s my personal opinion.

Pressure

As Moore noted, there is an increasing effort to push for typing annotations in libraries throughout the Python world. Steve Dower also sees that type checking is not really optional for many projects, which is leading to some people advocating for "annotations everywhere". The "optional" typing feature is heading toward being mandatory, Moore said:

There's a subtle (maybe not so subtle, actually) and increasing pressure on projects to add typing. Often with little or no justification beyond "you should", as if having typing is a sort of "obvious best practice". Sometimes "because it will make it easier for your users who use typing" is given as a justification, but while that's fair, it's also a disturbing gradual pressure for typing to extend everywhere, manifesting by making it feel like not adding typing is somehow "not caring about your users".

Benjamin said that code editors may be behind some of that push. His students sometimes start adding type annotations even though he has deliberately avoided the topic. "I can only presume that some editor is doing this for them or telling them that they need to do this (students often can't tell the difference between editor warnings and actual errors)." He has also seen a push to add annotations to SymPy, some of which are not particularly useful from a typing perspective but make some editors work better:

Some people apparently want to add type hints that look completely useless to me like
    def f() -> Union[MyClass, Any]
As I understand it this does not give any meaningful information to a type checker but it apparently makes vscode work better

Benjamin is not exactly opposed to adding typing information to SymPy, "but it's a huge amount of work that someone would have to do and I don't want to add useless/inaccurate hints temporarily (as some have suggested to do)". Moore agreed, but said "it is awfully tempting to passive-aggressively annotate everything as Any, just to shut people up :-(". Steven D'Aprano took that joke one step further by suggesting setting up a logical conflict between PEP 8 purism and typing activism:

We could update PEP 8 to ban type annotations, then watch as the people who over-zealously apply PEP 8 to everything AND over-zealously insist on adding type annotations to everything have their heads explode.

Benjamin had suggested that he saw more value for type annotations on the internals of SymPy, but Sebastian Rittau said that users generally want this information for the API, which can be supplied from stub files. Those files, which are only consumed by type checkers, editors, and similar tools, can be created and maintained outside of the projects; they contain type information for the APIs of various libraries. He listed several resources for those who are trying to add typing information (or review pull requests that add it), including a documentation hub, "but there is not much to see at the moment".

Moore was glad to see the pointers, but said that "the most critical missing resource is a central set of typing documentation that includes examples, FAQs and best practices as well as reference materials". He continued:

TBH [To be honest], I'd quite happily not use typing if I didn't want to and stay quiet. A lot of the frustration I see being expressed here (including my own) seems to come from the fact that it's so difficult to actually take that sort of "I can ignore it if I don't use it" attitude, whether that's because of community pressure, tool requirements, or whatever.

Rittau said that the documentation hub was meant to eventually have the kinds of information Moore is looking for. Rittau also noted that the typeshed project may help provide a stepping stone:

Providing high quality stubs and the best user experience is not easy. But I believe that referring people to typeshed can help. While we of course prefer high quality stubs or type annotations shipped with the package in question, typeshed can provide a fairly low barrier of entry for projects that don't have the resources to maintain type annotations themselves. It can also be used as an "incubator", where stubs are created and improved iteratively, until they are deemed ready for inclusion in an upstream package.

The future

Those are valuable resources, obviously, but they do tend to reinforce the message that the future of Python is typed. The pressure that library developers are feeling is real and likely to increase as more and more tools—and developers—come to depend on the availability of typing annotations. While they are optional from a language perspective, they are rapidly becoming mandatory from a community and ecosystem perspective. That is precisely what the "typing-suspicious crowd" (as Turnbull called them) has been worried about and it has come to pass—or soon will.

Meanwhile, though, it seems clear that anyone using annotations for non-typing purposes should be figuring out some other way to accomplish their goals. But the resolution of the two PEPs does not seem any closer at this point. Neither Langa or PEP 649 author Larry Hastings seem inclined to change their PEPs, at least yet, and the hoped-for PEP shepherd has not appeared either (publicly, anyway).

Given that there are 16 months or so before Python 3.12 feature freeze, it might be guessed that situation will have worked itself out by then. The contours of the problem are clearer, and some of the extraneous pieces have been removed from consideration, which should hopefully clear the way for a consensus to emerge. Since "practicality beats purity", according to The Zen of Python, something like what Langa has proposed may well be the "winner". It would seem that 2022 will provide more opportunities to finally put this issue to bed.


Index entries for this article
PythonAnnotations
PythonPython Enhancement Proposals (PEP)/PEP 563
PythonPython Enhancement Proposals (PEP)/PEP 649


to post comments

Greenspun's tenth rule

Posted Dec 16, 2021 1:29 UTC (Thu) by ejr (subscriber, #51652) [Link]

I would be interested in a layout of benefits and drawbacks when compared to other optional type annotation systems like Common Lisp. Also with heavily-inferenced typing systems.

Wrangling the typing PEPs

Posted Dec 16, 2021 9:16 UTC (Thu) by taladar (subscriber, #68407) [Link] (2 responses)

It seems to me that dynamically and/or weakly typed languages like Python are struggling a bit in adding optional typing in general.

There are probably a few reasons for this, some technical and some social.

On the one hand it is just hard to add a type system later to an API designed without one since a lot of the APIs in dynamic languages are a bit 'sloppy' from a typed perspective, often accepting different types for the same parameter and/or returning different types (and null/nil like values). This makes it hard to make a high quality typed API the way it would look like in a language that had static types from the start.

Obviously having types on just some functions and not others does not exactly help either.

The fact that optional typing annotations are not enforced in all situations in some of the optional typing languages also means defensive programming code and unit tests meant to check the same thing as the type system would in a statically typed language can not be removed and add a dual maintenance burden.

The social issue I see is that a lot of the people who really know how static typing works and who care about a good static type system have moved on to languages that suit their tastes better, leaving a high percentage of people who do not like static type systems to please for the dynamically typed language community to deal with.

At the same time the languages lose relevance as it becomes more and more clear that the whole 'sufficiently disciplined programmer' does not exist, especially not whole teams of them or even a whole language ecosystem or at least all the people working on your project and all of your project's dependencies.

Replacing type checks with unit tests also means a lot more work for inferior results (e.g. how do you test that no combination of parameters ever yields a different return type).

This means the existing code bases have a higher maintenance burden but most likely fewer maintainers highly motivated to adjust the API to be a great typed API.

Wrangling the typing PEPs

Posted Dec 17, 2021 1:12 UTC (Fri) by jkingweb (subscriber, #113039) [Link] (1 responses)

> It seems to me that dynamically and/or weakly typed languages like Python are struggling a bit in adding optional typing in general.

The introduction of a type system in PHP has been a great success. It has been gradual, optional, and backwards compatible across heterogeneous programs—and essentially universally adopted.

Perhaps this is one of the few times PHP has something to teach the wider community.

Wrangling the typing PEPs

Posted Dec 17, 2021 12:26 UTC (Fri) by mathstuf (subscriber, #69389) [Link]

> Perhaps this is one of the few times PHP has something to teach the wider community.

There's always been things to learn. This is just one of the times where it's not following a "eh…maybe let's not do it *that* way" pattern. ;)

Wrangling the typing PEPs

Posted Dec 16, 2021 9:50 UTC (Thu) by LtWorf (subscriber, #124958) [Link] (13 responses)

Well people knew that PEP 563 was going to break my library (I had complained), but didn't seem to care until they got complaints from someone making a very similar library (obviously a worse one, but with a million more users) complained.

So I'm a bit surprised now about the interest for those not using annotation in the approved way.

Anyway for library writers… yes I prefer if libraries have (meaningful) annotations… but I miss a bit the duck typing days.

I've seen libraries that enforce filenames to be "str" with a check, just to then call the python open function which would have gladly accepted pathlib.Path and bytes as well.

So hints are nice, but unnecessarily restricting to one type just means the users have to do unneeded type conversions just to make the linters (or the runtime checks) happy.

Wrangling the typing PEPs

Posted Dec 16, 2021 14:12 UTC (Thu) by ballombe (subscriber, #9523) [Link] (10 responses)

> I've seen libraries that enforce filenames to be "str" with a check, just to then call the python open function which would have gladly accepted pathlib.Path and bytes as well

This is a misuse of the type system.

Wrangling the typing PEPs

Posted Dec 16, 2021 15:13 UTC (Thu) by mb (subscriber, #50428) [Link] (8 responses)

How do I spell out a correct type annotation for a filesystem path?

Does Python have the possibility to specify a Procotol as "type" instead of nailing down the type to some specific ones? Similar to Traits in Rust. There's List, which means object with list procotol, as far as I understand it. But is there something similar for paths? Can the developer create new such types for his own protocols?

Wrangling the typing PEPs

Posted Dec 16, 2021 15:48 UTC (Thu) by gdiscry (subscriber, #91125) [Link] (5 responses)

A correct annotation would use something like Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] or Union[AnyStr, os.PathLike[AnyStr]].

Like mentioned in the article, the typeshed project is a good resource if one needs some inspiration, in this case os.fspath() or similar and the type aliases defined by typeshed could be useful.

Wrangling the typing PEPs

Posted Dec 16, 2021 18:20 UTC (Thu) by atnot (guest, #124910) [Link] (4 responses)

It would be really nice if python could figure out something comparable to e.g. Rust's Into/From there.

Basically the way it works is that you can declare a parameter to be say, Into<MyType>. This means you may pass any value on which .into::<MyType>() can be called to convert it. From<MyType> being the reverse.

Python kind of already has this by convention for a few types like __bytes__, __bool__, __iter__, __str__ (which is also overloaded for other uses), __int__, __float__, etc. but it's not really widely used or extensibile.

Wrangling the typing PEPs

Posted Dec 16, 2021 21:44 UTC (Thu) by NYKevin (subscriber, #129325) [Link] (3 responses)

This is because Python doesn't have a standard "into" method. Instead, the canonical way to convert type X into type Y is to call Y's constructor on an instance of X (just like in C++, except that Python never implicitly calls a constructor when the types don't match - so it's like C++ if all single-argument constructors were explicit). But in Python, the values which are acceptable to Y's constructor are annotated on Y's constructor (or more commonly, on its __init__ method, which is technically not a constructor, but whatever), not on X, so there's simply no obvious way to statically annotate "anything that Y can accept as a constructor argument." Perhaps there should be - but ideally, we would generalize this to support all functions, not just __init__, so that you could instead write "anything that is acceptable to the foo function as an argument," with Y.__init__ being one possible value of "the foo function."

If you are writing Y yourself, you can fix this by using whatever annotation you used on Y's constructor, and you can even set up a convenient TypeAlias for it if desired.

Wrangling the typing PEPs

Posted Dec 16, 2021 23:23 UTC (Thu) by atnot (guest, #124910) [Link] (2 responses)

It's my fault for not explaining this better, but Rusts's Into/From system is quite a bit more capable than that. In a python constructors, the set of things that can be converted is defined only in the constructor. With Into/From, it can be implemented on any type, even foreign ones.

So for example, you could have Into<String> implemented for Paths, or From<Path> implemented for Strings, it works exactly the same either way. There is no common way to do this in python right now. Although it could probably be built from two existing features:

- The constructor/dunder pair I mentioned previously. Functions like float() will attempt to convert the object into bytes using predefined conversions. If that fails it will ask the object to convert itself using __float__.
- Operators, where a + b will attempt to call a.__add__(b) and if that fails b.__radd__(a). Similarly, it could call a.__into__(MyType) and MyType.__from__(a) or even MyType(a) as you mentioned.

Wrangling the typing PEPs

Posted Dec 17, 2021 6:34 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)

You can't just make up new dunder methods. Standard dunder methods are recognized by the Python compiler and (ultimately) converted into struct fields at the C level. Strictly speaking, if a dunder method is not recognized, it will be left alone, but a future version of Python might decide to use that name, and if it does, you get no backcompat guarantees (see https://docs.python.org/3/reference/lexical_analysis.html...). You don't even get a DeprecationWarning, either, it will just silently break one day.

However, classes are first class in Python, so you can absolutely do this already without using dunder names. Just write a.into(MyType), and it will pass the class object. Now the only hard part is convincing everyone that .into() should be spelled like that (more common is .as_foo() where the type is hard-coded into the method name).

Wrangling the typing PEPs

Posted Dec 17, 2021 10:28 UTC (Fri) by atnot (guest, #124910) [Link]

Yes, that is my point.

Wrangling the typing PEPs

Posted Dec 16, 2021 17:50 UTC (Thu) by anselm (subscriber, #2796) [Link]

Does Python have the possibility to specify a Procotol as "type" instead of nailing down the type to some specific ones?

Yes, this is explained in PEP-544, which deals with “duck typing”.

There's List, which means object with list procotol, as far as I understand it. But is there something similar for paths?

The os module implements PathLike as an “abstract base class” for classes that implement path-like behaviour (such as pathlib.Path). Basically this means that such classes support the __fspath__() method, which is supposed to return a representation of the object that is a str suitable to use as a file system path. If you wanted to say that a function accepts a parameter that is either a str to begin with, or a PathLike object, you could define it as

from os import fspath, PathLike

def do_something_with_path(path: str | PathLike) -> …:
    …
    f = open(fspath(path))
    …
(where os.fspath(s) will return s outright if s is of type str, or else s.__fspath__()). Of course you could also define a type variable like
PathOrStr = TypeVar('PathOrStr', str, PathLike)
and then simply use this as
def do_something_with_path(path: PathOrStr) -> ...:
    …
Can the developer create new such types for his own protocols?

Absolutely. See here.

Wrangling the typing PEPs

Posted Dec 16, 2021 22:48 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

List absolutely does not mean "object with list protocol." That's spelled collections.abc.MutableSequence (see this table: https://docs.python.org/3/library/collections.abc.html#co...). As of Python 3.9, typing.List is just a deprecated alias for list. Previously, it was "a list that can have type parameters," but now that functionality has been moved into the builtin.

(Strictly speaking, it is possible to create an object with the mutable sequence protocol which is not a subclass of MutableSequence, but this is significantly more work and there's no obvious benefit to doing so, so I would like to believe that nobody would do it.)

Wrangling the typing PEPs

Posted Dec 16, 2021 16:20 UTC (Thu) by smurf (subscriber, #17840) [Link]

> This is a misuse of the type system.

It is just plain stupid. Why do some people write useless code like that?

Wrangling the typing PEPs

Posted Dec 16, 2021 21:11 UTC (Thu) by pj (subscriber, #4506) [Link] (1 responses)

> I've seen libraries that enforce filenames to be "str" with a check, just to then call the python open function which would have gladly accepted pathlib.Path and bytes as well.

I feel like the correct way to do this would be to explicitly infer the type. Which meand we need some way to refer to 'the type that open()' takes as its first argument, but that's somewhat complicated to obtain:

```
import inspect

def mywrapper(filename: list(inspect.signature(open).parameters.values())[0].annotation):
...
```

...doesn't exactly flow from the fingertips. And I'm not sure if the if the value to the annotation is valid as an annotation itself. *sigh* IWBNI there was like an `infer(open, 1)`.

Wrangling the typing PEPs

Posted Dec 17, 2021 6:39 UTC (Fri) by NYKevin (subscriber, #129325) [Link]

Runtime type checkers will probably be perfectly happy with that, but I am not aware of a single static type checker that actually evals the annotations in a full Python environment for you.

Wrangling the typing PEPs

Posted Dec 16, 2021 9:58 UTC (Thu) by LtWorf (subscriber, #124958) [Link] (2 responses)

Maybe interesting to the readers but a function like

def f(i: Type[T]) -> T: ...

that basically is a factory, returning an instance of the passed type, will not work in all cases with mypy.

A List[int] will work fine, but a Tuple[int] won't work. In general from that definition mypy will understand "Any" or error out.

https://github.com/python/mypy/issues/9003

Wrangling the typing PEPs

Posted Dec 16, 2021 22:35 UTC (Thu) by NYKevin (subscriber, #129325) [Link] (1 responses)

In general, type[T] is only supposed to match reified types (types that you can pass as the second argument to isinstance()), which neither of those examples is. The intention is that it represents a runtime type object - but at runtime, list[int] is mostly useless because it does not actually check its constructor arguments (i.e. list[int](['x', 'y', 'z']) is perfectly happy to give you a list of strings), so you might as well just use list instead, or so the thinking went when they were designing this feature.

However, some people want to use parameterized types as a convenient way of representing the schema of some structured data which you e.g. want to unserialize. This is not what type[T] was designed to do, but that does not make it an invalid use case for type[T]. Unfortunately, allowing such parameterized types to inhabit type[T] now would create backcompat issues, because mypy would need to flag this function as possibly unsafe:

T = TypeVar('T', bound=SomeGenericClass)

def foo(a: object, b: type[T]) -> TypeGuard[T]:
    if not isinstance(a, b):
        return False
    # Do additional checks here...

(Specifically, if b is a parameterized type object, this throws a TypeError at runtime - but mypy considers it OK because type[T] is not allowed to match such types.)

Wrangling the typing PEPs

Posted Dec 17, 2021 7:05 UTC (Fri) by LtWorf (subscriber, #124958) [Link]

It's not like mypy ever had any regard for consistency.

For example running the same mypy version with the same identical flags, but using different python versions will give different errors in a way that it might be impossible to use both on the same codebase.

Also in general every new version of mypy will require code changes to pass on a codebase. Especially if you enable the error on the # type: ignore comments, so as it gets better and requires less and less of them they need to be removed.

Missing the point of loose languages

Posted Dec 17, 2021 1:38 UTC (Fri) by rdeforest (guest, #153619) [Link] (7 responses)

I've only read the intro paragraph of the article and the first comment, so this may be redundant or already refuted elsewhere, BUT!

Languages like Python, PHP and JavaScript gained enormous popularity BECAUSE the users didn't have to deal with types. The barriers to entry were low and the things type information help with didn't matter enough. Adding type information to these languages after they've become popular misses the point of how they got popular in the first place. I would even posit that it objectively worsens them. The eventual of triumph of TypeScript over JavaScript would refute my claim, if it happens.

I would suggest as an alternative to extending existing dynamic languages, that those seeking these features instead choose languages built around them. But those languages (Haskel, I'm looking at you) don't have the enormous communities and the network effects they bring...

In other words, Richard P Gabriel was right all along: "Worse Is Better".

Missing the point of loose languages

Posted Dec 17, 2021 15:56 UTC (Fri) by tnoo (subscriber, #20427) [Link] (3 responses)

Exactly my feeling!

Python was (still mostly is) so clean and at the same time powerful enough for all my use cases (data analysis, glue language for numerical codes). And just perfect for teaching introductory classes for scientists.

I highly welcome a generic type annotation system that codes like Cython make use of. But imposing a mandatory rigid typing system is the wrong approach. If strong static typing is needed, there is a big choice of other languages out there (go, rust, C++, Java, Haskell, ....ML).

But of course, this also comes with a high cost. Besides the need for compilation, for C++ we have the Design Patterns (Gang of Four) with Facade, Adapter, Decorator patterns etc that are not necessary in Python because of Duck-Typing.

For me Python is getting less and less attractive if more line noise is added, and simple code is becoming unreadable. From a comment above

def foo(a: object, b: type[T]) -> TypeGuard[T]:

does not look and feel like Python anymore. Why not use Haskell right away?

Missing the point of loose languages

Posted Dec 17, 2021 16:57 UTC (Fri) by mpr22 (subscriber, #60784) [Link] (1 responses)

> Why not use Haskell right away?

My understanding is that a lot of people who love static typing etc. still bounce off of Haskell.

(Partly because a lot of the literature around Haskell has a reputation for using terminology that even many CS graduates are unfamiliar with.)

Missing the point of loose languages

Posted Dec 19, 2021 10:15 UTC (Sun) by smurf (subscriber, #17840) [Link]

Haskell is one of these "you need to wrap your head around these new concepts, otherwise you'll feel like bashing your noggin against a brick wall is the saner option when you actually start coding in it" languages.

That takes more up-front time and effort than most people want to spend. Plus everybody else in your team needs to do the same thing.

I was looking into using Elm (a Haskell-ish language for client-side web programming; transpiles to Javascript of course) recently. Great idea, but where do I get the month that's not in the calendar from, so that I can be up to speed on it?

Missing the point of loose languages

Posted Dec 17, 2021 19:11 UTC (Fri) by Wol (subscriber, #4433) [Link]

> I highly welcome a generic type annotation system that codes like Cython make use of. But imposing a mandatory rigid typing system is the wrong approach. If strong static typing is needed, there is a big choice of other languages out there (go, rust, C++, Java, Haskell, ....ML).

The problem with my favourite language is everything is of type string (or variant). The advantage of this same language is that everything is of type variant. It would be lovely if I could just restrict things by having the default as variant, but options like "integer", "number", and stuff like that available ...

(Said favourite language being DataBASIC, btw)

Cheers,
Wol

Missing the point of loose languages

Posted Dec 21, 2021 18:55 UTC (Tue) by tbelaire (subscriber, #141140) [Link]

I hear you, but then I had to deal with upgrading some email lib from 2 to 3 which was mixing bytes and strings and it was so much easier to do in stages by first annotating the functions with types, and fixing mistakes, then doing the py3 upgrade.

Until I started annotating with types, I was dealing with "Oh, that's a bytes regex, doesn't work on str", on line xxx. And other errors, where as being careful and making sure the encoding/decoding happened exactly once, and using the types to check that across all the functions and not playing wack-a-mole with errors is so much nicer.

And when I'm working with pandas, I do want to know if I have a DF with only one column vs a Series, as the operations are different, but it's just `df['col'] ` vs `df[['col']]` to get each one. (I think). So I already need to check and keep the types separate, why not ask the compiler for help?

Missing the point of loose languages

Posted Dec 27, 2021 8:26 UTC (Mon) by NAR (subscriber, #1313) [Link] (1 responses)

Erlang is one language where the type system was added later. In my experience it's most useful for documentation - now there's a standard way to specify the type of the arguments in an API function. There's a tool (dialyzer) that checks for type errors: I worked on a project where the type annotations were added on later, about 95% of the errors it found were in dead code (so not covered by unit tests - one such example was a branch that only executed if the current date was before 2000, and we were adding the type specs in 2010). If there is a decent test coverage, there's little (but not zero!) value the type system can add.

Missing the point of loose languages

Posted Dec 27, 2021 9:59 UTC (Mon) by smurf (subscriber, #17840) [Link]

IMHO the best thing about explicit typing is documentation. I can see what a variable/parameter is supposed to be without checking external and possibly-out-of-date docs (including broken comments right above the function in question …).

A decent set of test cases is great for checking that the expected cases work as expected. The problem is that some [ft]ools insist on tests for each and every unexpected case, even if logically impossible to reach, while the real problems (i.e. those conditions you didn't even think of being remotely possible when you wrote the code) slip under the radar.

Wrangling the typing PEPs

Posted Dec 18, 2021 0:46 UTC (Sat) by smitty_one_each (subscriber, #28989) [Link]

> Those are valuable resources, obviously, but they do tend to reinforce the message that the future of Python is typed.

I don't see a two-tier ecosystem consisting of:
1. Traditional python for scripting/exploratory work
2. Typed libraries for medium-large projects
...as a bad thing.

As with the 2-to-3 transition, there is just a turbulent crossover period. Welcome to reality.

Maybe it's a big enough deal to call it python 4 when codified and formalized.


Copyright © 2021, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds