How to narrow Union
by shape? I don't want to check actual types with isinstance
or manual casting (there are a lot of types). Also I can't modify type definitions.
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
Type check error says:
(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
In a way Pylance checker is correct.
The types are unrelated. There would be no check errors for related types:
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
Also, duck typing is not applicable here since the set of members is different. There would be no errors if types were considered equivalent:
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
Although you can trick the Pylance checker so it won't complain about the code, there are a couple of options:
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
getattr
instead of direct member accessclass 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"))