Как лучше всего проверить, печатает ли поле из класса. Опционально?

Пример кода:

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. Что я делаю в данный момент, чтобы решить эту проблему, так это использование регулярных выражений на основе имени поля, но мне не нравится этот подход. Есть ли лучший способ сделать это?

7
Rodrigo Farias Rezino 1 Июл 2019 в 12:08

4 ответа

Лучший ответ

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)))
1
Abdul Niyas P M 1 Июл 2019 в 10:12

Я написал библиотеку с именем 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 даже между второстепенными релизами.

0
LtWorf 1 Июл 2019 в 10:28

Для справки, 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)

Или с запасным вариантом:

def is_optional(field):
    if (sys.version_info.major, sys.version_info.minor) >= (3, 8):
        return typing.get_origin(field.type) is typing.Union and \
            type(None) in typing.get_args(field.type)
    return getattr(field.type, '__origin__', None) is typing.Union and \
        type(None) in getattr(field.type, '__args__', ())
0
Garrett 13 Ноя 2019 в 21:39

Примечание. 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

Вы должны предпринять следующие шаги, чтобы определить свою программу проверки:

  1. Убедитесь, что в аннотации есть ключи __module__, __args__ и __origin__
  2. __module__ должен быть установлен на «печатать». Если нет, аннотация не является объектом, определенным модулем набора
  3. Значение __origin__ равно typing.Union
  4. __args__ должен быть кортежем из 2 элементов, вторым из которых является класс NoneType (type(None))

Если все условия оценены как истинные, вы можете набрать. Дополнительно [x]

Вам также может понадобиться узнать, что такое необязательный класс в аннотации:

x = Optional[int].__args__[0]
print(x) # class int
2
Victor Ruiz 1 Июл 2019 в 10:12