组合 2 和 3 代码库中 `__str__` 的类型是什么?

What is the type of `__str__` in a combined 2 and 3 code base?

我正在尝试转换这个 py2 class:

class Example(object):
    def __unicode__(self):
        return unicode(42)   # unicode(self.id)

    def __str__(self):
        return unicode(self).encode('u8')

    __repr__ = __str__

合并 2/3 版本的类型:

import sys
from typing import Text
from builtins import str as text

class Example(object):

    def _as_unicode(self):  # type: () -> Text
        return text(42)

    __unicode__ = _as_unicode

    def _as_bytes(self):  # type: () -> bytes
        return self._as_unicode().encode('utf-8')

    def __str__(self):  # type: () -> str
        if sys.version_info.major == 2:
            return self._as_bytes()      # line 17
        else:
            return self._as_unicode()    # line 19

    __repr__ = __str__

这会产生以下错误:

c:\tmp> py -3 -m  mypy example.py
example.py:17: error: Incompatible return value type (got "bytes", expected "str")

c:\tmp> py -3 -m  mypy example.py -2
example.py:19: error: Incompatible return value type (got "unicode", expected "str")

有没有办法让 mypy 相信 __str__ 的类型是 copacetic? (有没有更好的方法以 2 + 3 兼容的方式编写此代码?)

这里正确的方法实际上不是试图找到某种可以桥接两个 Python 版本的类型,而是让 mypy 了解您的分支将 运行 仅在某些版本上Python.

为此,将 sys.version_info.major == 2 更改为看起来像这样的支票 sys.version_info[0] == 2,例如:

import sys
from typing import Text
from builtins import str as text

class Example(object):

    def _as_unicode(self):  # type: () -> Text
        return text(42)

    def _as_bytes(self):  # type: () -> bytes
        return self._as_unicode().encode('utf-8')

    def __str__(self):  # type: () -> str
        if sys.version_info[0] == 2:
            return self._as_bytes()
        else:
            return self._as_unicode()

    __unicode__ = _as_unicode
    __repr__ = __str__

这最终完全回避了您的问题。例如。由于在 Python 2.7 模式下进行类型检查时 "else" 分支被标记为不可访问,因此 mypy 不会尝试分析该分支,因此不会报告错误。

如果你愿意,你可以更进一步,围绕 __str__ 的整个定义做一个 if-else:

import sys
from typing import Text
from builtins import str as text

class Example(object):

    def _as_unicode(self):  # type: () -> Text
        return text(42)

    def _as_bytes(self):  # type: () -> bytes
        return self._as_unicode().encode('utf-8')

    if sys.version_info[0] == 2:
        __str__ = _as_bytes
    else:
        __str__ = _as_unicode

    __unicode__ = _as_unicode
    __repr__ = __str__

这里有一些 more info on the version and platform checks mypy 支持。

mypy 不能具体理解 sys.version_info.major 形式的事实可能是一种疏忽。您可以尝试在 mypy's issue tracker (though idk how highly this one would be prioritized, given there are easy workarounds), or perhaps try adding support for this yourself by tinkering with the consider_sys_version_info function in mypy/reachability.py.

上提交有关此问题的问题