简体   繁体   中英

Provide OrderedSet[int] like types using stub file without modifying ordered-set library

I've contributed type hints for ordered-set library. The problem is that despite I have the following lines in ordered_set.pyi file:

from typing import MutableSet, TypeVar, Sequence

T = TypeVar('T')

class OrderedSet(MutableSet[T], Sequence[T]):
    ...

I cannot write:

IntOrderedSet = OrderedSet[int]

in my code, Python raises:

TypeError: 'ABCMeta' object is not subscriptable

It happens because in the ordered_set.py OrderedSet is defined as:

from collections.abc import MutableSet, Sequence

class OrderedSet(MutableSet, Sequence):
    ...

I made a PR with commit that changes OrderedSet class to inherit from typing classes , but ordered-set owner refused to accept it, because as he said:

Importing typing inside the code adds an install-time dependency and a run-time cost to the code. ordered_set has gotten by as a single module with no dependencies for 6 years. Someone could decide they want nothing to do with setuptools and just drop ordered_set on their PYTHONPATH, and it would still work. I'd hate to lose that over types.

Is there a way, to support OrderedSet[int] like types without modifying the library ordered_set.py file?

The only workaround that I'm aware of is to define any custom type aliases such that they exist only at type-check time, and never at run-time.

In order to do this, you would need to:

  1. Define any type aliases inside if typing.TYPE_CHECKING: blocks. The typing.TYPE_CHECKING block is always false at runtime, and treated as being true by type checkers.
  2. Make sure you either have all of your type hints be strings -- or, if you're using Python 3.7, add from __future__ import annotations , which does that automatically. That is, avoid having to ever evaluate type hints in the first place.

So for example, in your specific setup, you could contribute stubs for the OrderedSet library somewhere then write your code to look like this (assume Python 3.7+):

from __future__ import annotations
from typing import TYPE_CHECKING
from ordered_set import OrderedSet

# Basically equivalent to `if False`
if TYPE_CHECKING:
    IntOrderedSet = OrderedSet[Int]

def expects_int_ordered_set(x: IntOrderedSet) -> None:
    # blah

some_ordered_set: IntOrderedSet = OrderedSet()

Or, if you're using Python 3.6 or lower:

from typing import TYPE_CHECKING
from ordered_set import OrderedSet

if TYPE_CHECKING:
    IntOrderedSet = OrderedSet[Int]

def expects_int_ordered_set(x: 'IntOrderedSet') -> None:
    # blah

some_ordered_set: 'IntOrderedSet' = OrderedSet()

If you're ok with lying a little, we could dispense with the string stuff and define IntOrderedSet to be slightly different things at type-checking time vs runtime. For example:

from typing import TYPE_CHECKING
from ordered_set import OrderedSet

if TYPE_CHECKING:
    IntOrderedSet = OrderedSet[Int]
else:
    IntOrderedSet = OrderedSet

def expects_int_ordered_set(x: IntOrderedSet) -> None:
    # blah

some_ordered_set = IntOrderedSet()

Make sure to be careful when you're doing this though -- the type checker won't check anything in the 'else' block/won't check to make sure whatever you're doing there is consistent with what's in the if TYPE_CHECKING block.

The final solution would be to just not define an IntOrderedSet type in the first place. This lets us skip hack 1 while only needing to use hack 2 (which isn't so much a hack -- it'll be default behavior in Python in a few years).

So for example, we could do this:

from __future__ import annotations
from ordered_set import OrderedSet

def expects_int_ordered_set(x: OrderedSet[int]) -> None:
    # blah

some_ordered_set: OrderedSet[int] = OrderedSet()

Depending on context, it may be the case that I don't even need to add an annotation to that last variable declaration. Here, we do, but if we were defining that variable inside of a function, it's possible type checkers like mypy would automatically infer the correct type for us based on how we end up using that variable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM