按形状缩小联合

Narrow Union by shape

如何按形状缩小Union?我不想使用 isinstance 或手动转换(有很多类型)来检查实际类型。我也不能修改类型定义。

class X:
    title = "1"
class Y:
    name = "2"
class Z:
    name = "3"

for (i, r) in enumerate([X(), Y(), Z()]): # type of r: X | Y | Z

    if hasattr(r, "title"):
        print(r.title) # error
    else:
        print(r.name)  # error

类型检查错误说:

(variable) title: str | Unknown
Cannot access member "title" for type "Y"
  Member "title" is unknown
Cannot access member "title" for type "Z"
  Member "title" is unknown

(variable) name: Unknown | str
Cannot access member "name" for type "X"
  Member "name" is unknown

问题

在某种程度上,Pylance 检查器是正确的。

类型不相关。不会有相关类型的检查错误:

class Base:
    title: str
    name: str

class X(Base):
    title = "1"
class Y(Base):
    name = "2"
class Z(Base):
    name = "3"

for (i, r) in enumerate([X(), Y(), Z()]): # type of r: X | Y | Z

    if hasattr(r, "title"):
        print(r.title) # no error
    else:
        print(r.name) # no error

此外,duck typing不适用,因为成员集不同。如果类型被认为是等价的,就不会出现错误:

class X:
    name: str
    title = "1"
class Y:
    name = "2"
    title: str
class Z:
    name = "3"
    title: str

for (i, r) in enumerate([X(), Y(), Z()]): # type of r: X | Y | Z

    if hasattr(r, "title"):
        print(r.title) # no error
    else:
        print(r.name) # no error

解决方法

虽然您可以欺骗 Pylance 检查器,使其不会抱怨代码,但有几个选项:

选项 1。Suppress 类型检查

class X:
    title = "1"
class Y:
    name = "2"
class Z:
    name = "3"

for (i, r) in enumerate([X(), Y(), Z()]): # type of r: X | Y | Z

    if hasattr(r, "title"):
        print(r.title) # type: ignore
    else:
        print(r.name) # type: ignore

选项 2. 使用动态访问 getattr 而不是直接成员访问

class X:
    title = "1"
class Y:
    name = "2"
class Z:
    name = "3"

for (i, r) in enumerate([X(), Y(), Z()]): # type of r: X | Y | Z

    if hasattr(r, "title"):
        print(getattr(r, "title"))
    else:
        print(getattr(r, "name"))