mypy Error TypeVar with Value Restriction and Unio

2019-07-14 18:35发布

So, the following example is obviously contrived but I tried to keep some verisimilitude to my actual situation. Now that I've whittled this down, I am sure I am missing something obvious. Consider a couple of types and a restricted Union:

from typing import Union, TypeVar, Optional, Generic, overload

class Foo:
    def __init__(self, x: int)-> None:
        self.x = x
    def frobnicate(self) -> 'Foo':
        return Foo((self.x + 42) // 42)

class Bar:
    def __init__(self, y: int) -> None:
        self.y = y
    def frobnicate(self) -> 'Bar':
        return Bar(self.y + 88)

MyType = TypeVar('MyType', Foo, Bar)
class Container(Generic[MyType]):
    val: Optional[MyType]
    def __init__(self, val: Optional[MyType]=None) -> None:
        self.val = val

def transmogrify(arg: Optional[MyType]) -> Optional[MyType]:
    if arg is None:
        return None
    else:
        return arg.frobnicate()

def extract_stuff(x: Optional[int], cont: Container[MyType]) -> Optional[MyType]:
    result: Optional[MyType]
    if x is None:
        result = None
    elif x == 88 or x == 42:
        result = transmogrify(cont.val)
    else:
        result = cont.val
    return result

When I try to type-check this with mypy, I get the following errors:

mcve3.py:32: error: Value of type variable "MyType" of "transmogrify" cannot be "Optional[Foo]"
mcve3.py:32: error: Value of type variable "MyType" of "transmogrify" cannot be "Optional[Bar]"

I cannot make sense of this. I suspect it is a problem of the many nested unions? Note, in my actual code, I am using a custom singleton enum Null, so wherever you see Optional[Something] it's actually Union[Something, Null], but I don't think that makes a difference.

Now, if I remove the Optional, i.e. Union, it all plays nice:

from typing import Union, TypeVar, Optional, Generic, overload

class Foo:
    def __init__(self, x: int)-> None:
        self.x = x
    def frobnicate(self) -> 'Foo':
        return Foo((self.x + 42) // 42)

class Bar:
    def __init__(self, y: int) -> None:
        self.y = y
    def frobnicate(self) -> 'Bar':
        return Bar(self.y + 88)

MyType = TypeVar('MyType', Foo, Bar)
class Container(Generic[MyType]):
    val: MyType
    def __init__(self, val: MyType) -> None:
        self.val = val

def transmogrify(arg: MyType) -> MyType:
    if arg is None:
        return None
    else:
        return arg.frobnicate()

def extract_stuff(x: int, cont: Container[MyType]) -> MyType:
    if x is None:
        return None
    elif x == 88 or x == 42:
        return transmogrify(cont.val)
    else:
        return cont.val

What am I missing about Union's here?

Note, I've tried abstracting out a base-class, and having Foo and Bar derive from an abstract base class class MyType(metaclass=abc.Meta), but a very similar error pops up.

Edit to Add:

(py37) Juans-MBP: juan$ mypy --version
mypy 0.620

1条回答
欢心
2楼-- · 2019-07-14 19:06

This seems to be a bug that was fixed somewhat recently in mypy. I was able to repro the problem in your first snippet by using mypy 0.630 but was unable to repro using both mypy 0.641 and the latest version of mypy on master.

I very loosely suspect the bug was fixed by https://github.com/python/mypy/pull/5699, but don't know for certain (and don't feel like checking, tbh).

You can monitor mypy's blog if you'd like to be notified of future releases to avoid similar situations in the future. New releases are made roughly every 6 weeks to two months or so. -- the next release is slated to come out in roughly two weeks or so from time of writing.

查看更多
登录 后发表回答