Каков наилучший способ проверить, печатается ли поле из класса. Необязательно?
Пример кода:
from typing import Optional
import re
from dataclasses import dataclass, fields
@dataclass(frozen=True)
class TestClass:
required_field_1: str
required_field_2: int
optional_field: Optional[str]
def get_all_optional_fields(fields) -> list:
return [field.name for field in fields if __is_optional_field(field)]
def __is_optional_field(field) -> bool:
regex = '^typing.Union\[.*, NoneType\]$'
return re.match(regex, str(field.type)) is not None
print(get_all_optional_fields(fields(TestClass)))
Где fields из dataclasses, я хочу перечислить все поля Optional.
То, что я делаю в данный момент, чтобы решить эту проблему, использует регулярное выражение на основе имени поля, но мне не нравится этот подход. Есть ли лучший способ сделать это?
Поля извлекают информацию для данного типа без необходимости создания объекта.
затем опубликовать его. возможно, его можно оптимизировать
Обновлено с дополнительной информацией сейчас
Обновил пост с рабочим кодом






Optional[X] эквивалентно Union[X, None]. Так что вы могли бы сделать,
import re
from typing import Optional
from dataclasses import dataclass, fields
@dataclass(frozen=True)
class TestClass:
required_field_1: str
required_field_2: int
optional_field: Optional[str]
def get_optional_fields(klass):
class_fields = fields(klass)
for field in class_fields:
if (
hasattr(field.type, "__args__")
and len(field.type.__args__) == 2
and field.type.__args__[-1] is type(None)
):
# Check if exactly two arguments exists and one of them are None type
yield field.name
print(list(get_optional_fields(TestClass)))
Это не будет работать на каждой версии python3. В моей библиотеке есть чеки github.com/ltworf/typedload/blob/master/typedload/…github.com/ltworf/typedload/blob/master/typedload/…
Примечание: typing.Optional[x] — это псевдоним typing.Union[x, None]
.
Теперь можно проверить атрибуты аннотации поля ввода, чтобы проверить, определена ли она как Union[x, None]:
Вы можете прочитать его атрибуты __module__, __args__ и __origin__:
from typing import *
def print_meta_info(x):
print(x.__module__, x.__args__, x.__origin__)
x = Optional[int]
print_meta_info(x) # 'typing', (class Int,), typing.Union
x = Union[int, float]
print_meta_info(x) # 'typing', (class int, class float), typing.Union
x = Iterable[str]
print_meta_info(x) # 'typing', (class int,), typing.Iterable
Вам необходимо выполнить следующие шаги, чтобы определить свою программу проверки:
__module__, __args__ и __origin____module__ должен быть установлен на «ввод». Если нет, аннотация не является объектом, определенным модулем ввода.__origin__ значение равно type.Union__args__ должен быть кортеж с двумя элементами, второй из которых является классом NoneType (type(None))
Если все условия оцениваются как истинные, у вас есть typing.Optional[x]
Вам также может понадобиться знать, что является необязательным классом в аннотации:
x = Optional[int].__args__[0]
print(x) # class int
Я написал библиотеку под названием typedload, которую можно использовать для этого.
Основная цель библиотеки — преобразование в/из json и namedtuple/dataclass/attrs, но поскольку ей нужно было выполнять эти проверки, она предоставляет функции.
Обратите внимание, что разные версии Python изменяют работу внутреннего API ввода, поэтому проверки не будут работать для каждой версии Python.
Моя библиотека обращается к нему внутренне, скрывая детали от пользователя.
Используя его, код выглядит так
from typing import *
a = Optional[int]
from typedload import typechecks
typechecks.is_union(a) and type(None) in typechecks.uniontypes(a)
https://github.com/ltworf/typedload
Конечно, если вам не нужно поддерживать несколько версий Python, вам может не понадобиться зависеть от библиотеки только для этого, но будущие выпуски могут нарушить проверку. Они изменили API даже между второстепенными выпусками.
Для справки: Python 3.8 (впервые выпущенный в октябре 2019 г.) добавил функции get_origin и get_args в модуль typing.
Примеры из документы:
assert get_origin(Dict[str, int]) is dict
assert get_args(Dict[int, str]) == (int, str)
assert get_origin(Union[int, str]) is Union
assert get_args(Union[int, str]) == (int, str)
Это позволит:
def is_optional(field):
return typing.get_origin(field) is Union and \
type(None) in typing.get_args(field)
Вот код совместимости для старых версий Python:
# Python >= 3.8
try:
from typing import Literal, get_args, get_origin
# Compatibility
except ImportError:
get_args = lambda t: getattr(t, '__args__', ()) \
if t is not Generic else Generic
get_origin = lambda t: getattr(t, '__origin__', None)
Другой подход (который работает как на python 3.7, так и на 3.8) заключается в том, чтобы понять, как работает операция set Union:
union([x,y],[y])= union([x],[y]) = union(union([x],[y]),[x,y])
Логика в том, что тип Optional не может быть Optionaler. Хотя вы не можете напрямую знать, является ли type обнуляемым/необязательным, Optional[type] будет таким же, как type, является ли type необязательным, а другое (точнее, Union[type,None]) в противном случае.
Итак, в нашем случае:
Union[SomeType,None] == Union[Union[SomeType,None]]
(первый эквивалентен Optional[SomeType], а второй Optional[Optional[SomeType]]
Это позволяет очень легко проверить значения Optional:
from dataclasses import dataclass, fields
from typing import Optional
@dataclass()
class DC:
x: Optional[str] = None
y: str = "s"
def get_optional_fields(cls):
fields_list = fields(cls)
return [
field.name
for field in fields_list if
field.type == Optional[field.type]
]
if __name__ == '__main__':
print(get_optional_fields(DC())) # ['x']
Изящный. Я думаю, что набор текста должен переопределить __eq__ вот так Optional[T] == Union[T, None].
что такое
fieldsдекларация и ожидаемый результат?