Проблема:
Я работаю над библиотекой, например, с поддержкой типа UInt5, типа Int33 и т. д. Библиотека немного сложнее, чем эта, но для примера можно создать тип UInt12.
def makeUInt(size:int) -> type[UInt]:
class NewUInt(UInt):
# Do stuff with size
NewUInt.__name__ = f"UInt{size}"
return NewUInt
UInt12 = makeUInt(12)
an_example_number = UInt12(508)
Функция IntelliSense в моей IDE (VS Code) распознает тип an_example_number как UInt, а не UInt12.
Проблема:
Я не ожидаю, что динамически объявленные типы будут обнаружены с помощью подсказок типов. Однако я четко указал UInt12 в качестве псевдонима типа, и фактически, если я создаю подкласс вместо псевдонима типа, перейдя
def makeUInt(size:int) -> type[UInt]:
class NewUInt(UInt):
# Do stuff with size
NewUInt.__name__ = f"UInt{size}"
return NewUInt
class UInt12(makeUInt(12)): pass
an_example_number = UInt12(508)
все работает так, как задумано, поэтому очевидно, что на каком-то уровне динамическое объявление можно привести к тому, что понимает IDE.
Например, гипотетически я мог бы сделать так, чтобы UInt вел реестр созданных классов и не позволял UInt12(makeUInt(12)) фактически создавать подклассы. Однако это, очевидно, не идеальный обходной путь.
Спросите:
Как я могу (желательно в Python 3.8) сохранить преимущество динамического создания типов и при этом заставить IDE понимать мою предпочтительную номенклатуру для экземпляров этих типов?
Конечный вариант использования заключается в том, что я хочу явно предоставить определенные типы, не переобъявляя каждый раз # Делать что-то с информацией о размере, чтобы общие типы, такие как UInt16, UInt32 и т. д., могли быть объявлены в моей библиотеке и получать подсказки, тогда как более редкие такие типы, как UInt13, будут объявляться пользователями по мере необходимости и не обязательно получать подсказки.
Задняя часть коробки
def makeUInt(size:int) -> type[UInt]:
class NewUInt(UInt):
# Do stuff with size
NewUInt.__name__ = f"UInt{size}"
return NewUInt
UInt12 = makeUInt(12)
an_example_number = UInt12(508)
Я хотел, чтобы an_example_number отображался как UInt12 с помощью подсказки типа. Он отображается как UInt.
Да, хотя речь явно не об этом. Редактирование соответственно; спасибо за улов @anatolyg
типизация пытается передать «статическую» типизацию динамическому языку, а фабрики классов во время выполнения для произвольных типов настолько динамичны, насколько это возможно. Всегда будут пробелы. Тем не менее, похоже, что можно избежать приведения (typing.cast
) каждого возврата makeUnit
к уникальному типу.
@jsbueno python UInt8 = _UIntStructPackedTypeFactory(8, 'B'); typing.cast(type[UInt8], UInt8); a_uint8 = UInt8(0);
Выдает предупреждение «переменная не разрешена в выражении типа» и не сообщает IntelliSense о наведении курсора на то, что a_uint8 является UInt8, а не UInt. Любопытно, python UInt8: TypeAlias = _UIntStructPackedTypeFactory(8, 'B'); typing.cast(type[UInt8], UInt8); a_uint8 = UInt8(0);
Выдает «Вызов выражения не разрешен в выражении типа», но анализатор типов это делает см. UInt8 для a_uint8 там.
Как вы уже видели, в вашем случае, вероятно, будет лучше работать явное создание подклассов.
Вместо создания функции фабрики классов определите общее поведение всех производных классов в базовом классе и сделайте так, чтобы его особенности зависели от переменных класса, определенных подклассами.
В примере, похожем на ваш, это может выглядеть так:
class UInt:
size: int # to be defined by subclasses
def __init__(self, value: int):
if value >= 2 ** self.size:
raise ValueError("Too big")
self._value = value
def __repr__(self):
return f"{self.__class__.__name__}({self._value})"
class UInt12(UInt):
size = 12
an_example_number = UInt12(508)
print(an_example_number) # UInt12(508)
Это была моя первоначальная установка, но у меня 1. в настоящее время есть абстрактные неудовлетворенные методы класса, которые конкретизируются в фабричном процессе (которые зависят от предоставленных значений), и 2. надеялся, что моя реализация будет следовать тому, что, как я ожидал, будут использовать пользователи. -the-fly (объявление типов в DataClasses является довольно заметной особенностью библиотеки, и использование фабрики имеет для нее гораздо больше смысла, чем предыдущее явное объявление класса). Однако в конечном итоге я могу прибегнуть к этому.
Я принял этот ответ. Однако, если кто-нибудь предложит решение, которое сделает свойства, доступные только для чтения, которые необходимо переопределить, более удобными, мне бы хотелось знать!
Есть ли у вас
return
в вашемmakeUInt
коде?