[Python-ideas] Proposal to extend PEP 484 (gradual typing) to support Python 2.7

Spencer Brown spencerb21 at live.com
Sat Jan 23 03:14:58 EST 2016


 
> On 23 Jan 2016, at 7:41 AM, Andrew Barnert via Python-ideas <python-ideas at python.org> wrote:
> 
> The runtime types are a little weird here as well.
> 
> In 3.x, open returns different types depending on the value, rather than the type, of its inputs. Also, TextIOBase is a subclass of IOBase, even though it isn't a subtype in the LSP sense, so you have to test isinstance(IOBase) and not isinstance(TextIOBase) to know that read() is going to return bytes. That's all a little wonky, but not impossible to deal with.
> 
> In 2.x, most file-like objects--including file itself, which open returns--don't satisfy either ABC, and most of them can return either type from read.
> 
> Having a different function for open-binary instead of a mode flag would solve this, but it seems a little late to be adding that now. You'd have to go through all your 2.x code and change every open to one of the two new functions just to statically type your code, and then change it again for 3.x. Plus, you'd need to do the same thing not just for the builtin open, but for every library that provides an open-like method.
> 
> Maybe this special case is special enough that static type checkers just have to deal with it specially? When the mode flag is a literal, process it; when it's forwarded from another function, it may be possible to get the type from there; otherwise, everything is just unicode|bytes and the type checker can't know any more unless you explicitly tell it (by annotating the variable the result of open is stored in).

Instead of special-casing open() specifically, adding a 'Literal' class would solve this issue (although only in a stub file): 

@overload
def open(mode: Literal['rb', 'wb', 'ab']) -> BufferedIOBase: ...
@overload
def open(mode: Literal['rt', 'wt', 'at']) -> TextIOBase: ...

Literal[a,b,c] == Union[Literal[a], Literal[b], Literal[c]] for convenience purposes. To avoid repetition,  func(arg: Literal='value') could be made equivalent to func(arg: Literal['value']='value').

Typecheckers should just treat this the same as the type of the value, but for cases where it knows the value (literals or aliases) check the value too. (Either by comparison for core types, or just by identity. That allows use of object() sentinel values or Enum members.)


More information about the Python-ideas mailing list