#python #inheritance #static-analysis #type-hinting
#python #наследование #статический анализ #подсказка типа
Вопрос:
Рассмотрим следующий код, Child
от которого наследуется мой класс Parent
(класс, полученный из стороннего пакета, над которым я не имею никакого контроля). Parent
имеет встроенный метод set_attributes
, который используется для добавления произвольных атрибутов к экземпляру:
# third-party code, I can't modify this
class Parent:
def __init__(self, a: str) -> None:
self.a = a
def set_attributes(self, **kwargs) -> None:
for k, v in kwargs.items():
setattr(self, k, v)
# my code, I can modify this
class Child(Parent):
def __init__(self, a: str, b: int, c: float) -> None:
super().__init__(a=a)
self.set_attributes(b=b, c=c)
def show_attributes(self):
print(self.a)
print(self.b)
print(self.c)
Это работает нормально, но любой линтер выдаст мне предупреждение о show_attributes
методе, сообщая мне, что self.b
и self.c
не существуют (потому что они определены во время выполнения, когда self.set_attributes(b=b, c=c)
выполняется строка).
Один из способов преодолеть это ограничение — определить заполнитель для атрибутов в Child.__init__
:
from typing import Optional
class Child(Parent):
def __init__(self, a: str, b: int, c: float) -> None:
super().__init__(a=a)
self.b: Optional[int] = None
self.c: Optional[float] = None
self.set_attributes(b=b, c=c)
… но проблема сейчас в том, что self.b
и self.c
на самом деле не должны быть None
значениями — это просто заполнитель. Я не хочу, чтобы мой линтер рассматривал возможность того, что эти значения могут быть None
для остальной части моего кода, потому что их не должно быть.
Другие альтернативы, о которых я думал::
- Удалить
Optional
из подсказки типа — теперь линтер предупреждает меня, чтоself.b
иself.c
не должно бытьNone
:
class Child(Parent):
def __init__(self, a: str, b: int, c: float) -> None:
super().__init__(a=a)
self.b: int = None
self.c: float = None
self.set_attributes(b=b, c=c)
- Используйте значение по умолчанию соответствующего типа: это работает, но кажется банальным и многословным (я чувствую, что это укусит меня позже).:
class Child(Parent):
def __init__(self, a: str, b: int, c: float) -> None:
super().__init__(a=a)
self.b: int = 0
self.c: float = 0.0
self.set_attributes(b=b, c=c)
- введите намек на атрибуты, но не указывайте им значение-заполнитель: это выглядит хорошо, но компоновщик по-прежнему считает, что эти атрибуты не существуют, даже после выполнения
set_attributes
метода:
class Child(Parent):
def __init__(self, a: str, b: int, c: float) -> None:
super().__init__(a=a)
self.b: int
self.c: float
self.set_attributes(b=b, c=c)
Ни одно из этих решений меня не устраивает. Кроме того, ни одно из этих решений не работает, если я решу сохранить гибкость set_attributes
путем добавления **kwargs
Child.__init__
, т.е.:
class Child(Parent):
def __init__(self, a: str, b: int, c: float, **kwargs) -> None:
super().__init__(a=a)
self.b: int
self.c: float
self.set_attributes(b=b, c=c, **kwargs)
Есть ли лучший способ обойти это, или это просто ограничение, к которому я должен привыкнуть?
Комментарии:
1. динамически определяемые атрибуты по своей сути противоречат номинальной типизации.
2. @pavel Я не вижу никакой двусмысленности, они используют
Optional[int]
иOptional[float]
что было бы довольно идиоматичным способом связыванияUnion[None, int]
иUnion[None, float]
.