Являются ли структуры возврата NamedTuple python одним из немногих мест, где следует использовать изменяемые значения по умолчанию? - PullRequest
0 голосов
/ 08 июля 2020

Методы возврата структур из python функций подробно обсуждались в различных сообщениях. Два хороших здесь и здесь .

Однако, если я не пропустил это, ни одно из предлагаемых решений не определяет структуру в том же месте, где установлены ее члены , и вместо этого либо повторяйте список членов при назначении (не DRY), либо полагайтесь на позицию (подвержена ошибкам).

Я ищу способ DRY сделать это как для скорости записи, так и для Избегайте ошибок несовпадения аргументов, часто возникающих при повторении.

В приведенном ниже фрагменте кода показаны три попытки сделать это. Для краткости структура примера содержит только один элемент, но, очевидно, предполагается, что структуры содержат несколько элементов.

Три метода: DRY, встраивающие определение структуры с инициализацией возвращенного экземпляра.

Метод 1 подчеркивает потребность в лучшем способе, но иллюстрирует DRY искомый синтаксис, где структура и то, как она должна быть заполнена (определяется во время выполнения), находятся в одном месте, а именно dict() вызов.

Метод 2 использует typing.NamedTuple и, похоже, работает. Однако для этого используются изменяемые значения по умолчанию.

Метод 3 следует подходу метода 2, используя dataclasses.dataclass вместо typing.NamedTuple. Это не удается, потому что первый явно запрещает изменяемые значения по умолчанию, повышая ValueError: mutable default is not allowed

from collections import namedtuple
from dataclasses import dataclass
from typing import NamedTuple, List, Tuple

# Method 1
def ret_dict(foo_: float, bar_: float) -> Tuple:
    return_ = dict(foo_bar=[foo_, bar_])
    _ = namedtuple('_', return_.keys())
    return _(*return_.values())


# Method 2
def ret_nt(foo_: float, bar_: float) -> 'ReturnType':
    class ReturnType(NamedTuple):
        foo_bar: List[float] = [foo_, bar_]     # Mutable default value allowed
    return ReturnType()


# Method 3
def ret_dc(foo_: float, bar_: float) -> 'ReturnType':
    @dataclass
    class ReturnType:
        foo_bar: List[float] = [foo_, bar_]   # raises ValueError: mutable default is not allowed
    return ReturnType()


def main():
    rt1 = ret_dict(1, 0)
    rt1.foo_bar.append(3)
    rt2 = ret_dict(2, 0)
    print(rt1)
    print(rt2)

    rt1 = ret_nt(1, 0)
    rt1.foo_bar.append(3)   # amending the mutable default does not affect subsequent calls
    rt2 = ret_nt(2, 0)
    print(rt1)
    print(rt2)

    rt1 = ret_dc(1, 0)
    rt1.foo_bar.append(3)  # amending the default does not affect subsequent calls
    rt2 = ret_dc(2, 0)
    print(rt1)
    print(rt2)


if __name__ == "__main__":
    main()

. Возникают следующие вопросы:

Является ли метод 2 разумным подходом pythoni c?

Одна проблема заключается в том, что изменяемые значения по умолчанию являются своего рода табу, особенно для аргументов функций. Мне интересно, подходит ли их использование здесь, учитывая, что прилагаемый код предполагает, что эти NamedTuple значения по умолчанию (и, возможно, все определение ReturnType) оцениваются при каждом вызове функции, в отличие от значений по умолчанию аргументов функции, которые, как мне кажется, являются оценивается только один раз и сохраняется навсегда (отсюда и проблема).

Еще одна проблема заключается в том, что модуль dataclasses, похоже, изо всех сил явно запретил такое использование. Было ли это решение чрезмерно догматичным c в данном случае? или защита от метода 2 оправдана?

Это неэффективно?

Я был бы счастлив, если бы синтаксис метода 2 означал:

1 - Определите ReturnType один раз только на первом проходе

2 - вызовите __init__() с заданной (динамически установленной) инициализацией на каждом проходе

Однако я боюсь, что вместо этого это может означать следующие:

1 - определять ReturnType и его значения по умолчанию на каждом проходе

2 - вызывать __init__() с заданной (динамически установленной) инициализацией на каждом проходе

Должен можно беспокоиться о неэффективности повторного определения chunky ReturnType s на каждом проходе, когда вызов находится в "тайтовом" l oop? Разве эта неэффективность присутствует всякий раз, когда класс определяется внутри функции? Следует ли определять классы внутри функций?

Есть ли (надеюсь, хороший) способ достижения DRY создания экземпляра с использованием нового модуля dataclasses (python 3.7)?

Наконец, есть ли лучший DRY синтаксис определения-создания экземпляра?

1 Ответ

1 голос
/ 08 июля 2020

Однако я боюсь, что вместо этого это может означать следующее:

1 - Определить ReturnType и его значения по умолчанию на каждом проходе

2 - вызвать __init__() с заданная (динамически устанавливаемая) инициализация на каждом проходе

Вот что это значит, и это требует много времени и места. Кроме того, это делает ваши аннотации недействительными - -> 'ReturnType' требует определения ReturnType на уровне модуля. Это также мешает травлению.

Придерживайтесь уровня модуля ReturnType и не используйте изменяемые значения по умолчанию. Или, если все, что вам нужно, это доступ к члену с помощью точечной нотации, и вы действительно не заботитесь о создании значимого типа, просто используйте types.SimpleNamespace:

return types.SimpleNamespace(thing=whatever, other_thing=stuff)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...