Установить аргумент равным значению другого аргумента по умолчанию - PullRequest
1 голос
/ 04 апреля 2019

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

Это разработано как пошаговое руководство с тремя различными решениями проблемы, каждое из которых повышает сложность и надежность. Итак, вперед!

Это для вас, если бы вы сказали, я пытаюсь сделать это:

def my_fun(a, b, c=a):
  return str(a) + str(b) + str(c)

В этом примере, если c не задано, мы бы добавили str (a) в конец. Это простой и простой пример с игрушкой, и я не сомневаюсь, что ваш вариант использования гораздо сложнее. Тем не менее, это не синтаксически правильный Python, и он не будет работать, так как a не определен.

Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'a' is not defined

Однако чаще всего я вижу принятые ответы:

  • «Нет, это невозможно»
  • "Вы должны использовать значение по умолчанию None, а затем проверить, является ли значение None."

Если это звучит как проблема, с которой вы столкнулись, я надеюсь, что смогу помочь!

1 Ответ

2 голосов
/ 04 апреля 2019

Ответ 1:

Решение сверху выглядит так:

def cast_to_string_concat(a, b, c=None):
  c = a if c is None else c

  return str(a) + str(b) + str(c)

Хотя такой подход решит множество потенциальных проблем (и, возможно, ваших)! Я хотел написать функцию, в которой возможный ввод для переменной "c" действительно является синглтоном None, поэтому мне пришлось больше копать.

Чтобы объяснить это далее, вызовите функцию со следующими переменными:

A='A'
B='B'
my_var = None

Урожайность:

cast_to_string_concat(A, B, my_var):
>>>'ABA'

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

cast_to_string_concat(A, B, my_var):
>>> 'ABNone' # simulated and expected outcome

Итак, эта реализация игнорирует третью переменную, даже когда она была объявлена, так что это означает, что функция больше не может определять, была ли определена переменная "c".

Итак, для моего варианта использования значение по умолчанию None не совсем подходит.

Ответы, предлагающие это решение, читайте следующим образом:


Но, если это не сработает, то, возможно, продолжайте читать!


В комментарии в первой ссылке выше упоминается использование _sentinel, определяемое object(), которое исключает использование None и заменяет его на object() с использованием подразумеваемого private sentinel. (Я кратко говорю о другой подобной (но неудачной) попытке в Addendum .)


Ответ 2:

_sentinel = object()
def cast_to_string_concat(a, b, c=_sentinel):
  c = a if c == _sentinel else c

  return str(a) + str(b) + str(c)
A='A'
B='B'
C='C'

cast_to_string_append(A,B,C)
>>> 'ABC'

cast_to_string_concat(A,B)
>>> 'ABA'

Так что это круто! Это правильно обрабатывает вышеупомянутый крайний случай! Убедитесь сами:


A='A'
B='B'
C = None

cast_to_string_concat(A, B, C)
>>> 'ABNone'

Итак, мы закончили, верно? Есть ли вероятный способ, что это может не сработать? Хм ... наверное нет! Но я сказал, что это был ответ из трех частей, так что вперед! ;)


Для полноты картины давайте представим, что наша программа работает в пространстве, где каждый возможный сценарий действительно возможен. (Возможно, это не обоснованное предположение, но я предполагаю, что можно получить значение _sentinel с достаточным количеством информации об архитектуре компьютера и реализации выбора объекта. Поэтому, если вы хотите, давайте предположим, что действительно возможно, и давайте представим, что мы решили проверить эту гипотезу, ссылающуюся на _sentinel, как определено выше.


_sentinel = object()
def cast_to_string_concat(a, b, c=_sentinel):
  c = a if c == _sentinel else c

  return str(a) + str(b) + str(c)
A='A'
B='B'
S = _sentinel

cast_to_string_append(A,B,S)
>>> 'ABA'

Подожди минутку! Я ввел три аргумента, поэтому я должен увидеть объединение строк из трех из них вместе!


* очередь на въезд в страну непредвиденных последствий *

Я имею в виду, не совсем. Ответ: "Это ничтожно малая территория !!" или его род полностью оправдан.

И это чувство верно! В этом случае (и, вероятно, в большинстве случаев) об этом действительно не стоит беспокоиться!

Но если стоит о чем-то беспокоиться или если вы просто хотите получить математическое удовлетворение от устранения всех крайних случаев, о которых вы знаете ... вперед!


Хорошо, после этого длинного упражнения мы вернулись!

Напомним, что цель состоит в том, чтобы написать функцию, которая потенциально может иметь n входы, и только если одна переменная не указана - тогда вы скопируете другую переменную в положение i.

Вместо определения переменной по умолчанию, что если мы изменим подход, чтобы разрешить произвольное количество переменных?

Так что, если вы ищете решение, которое не ставит под угрозу потенциальные входы, где допустимый ввод может быть либо None, object(), либо _sentinel ... тогда (и только тогда), вв этот момент я думаю, что мое решение будет полезнымИсточником вдохновения для этой техники послужила вторая часть ответа Джона Клементса .


Ответ 3:

Мое решение этой проблемы - изменить имяэтой функции и оберните эту функцию функцией предыдущего соглашения об именах, но вместо переменных мы используем *args.Затем вы определяете исходную функцию в локальной области (с новым именем) и допускаете только те несколько возможностей, которые вам нужны.

По шагам:

  1. Переименуйте функцию во что-то похожее
  2. Удалите настройки по умолчанию для вашего необязательного параметра
  3. Начните создавать новую функцию чуть выше и вставьте исходную функцию в.
 def cast_to_string_concat(*args):
Определите арность вашей функции - (я нашел это слово в моем поиске ... это число параметров, переданных в данную функцию) Использовать регистроператор внутри, который определяет, если вы ввели правильное количество переменных, и скорректируйте соответственно!
def cast_to_string_append(*args):

    def string_append(a, b, c):
        # this is the original function, it is only called within the wrapper
        return str(a) + str(b) + str(c)

    if len(args) == 2:
        # if two arguments, then set the third to be the first
        return string_append(*args, args[0])

    elif len(args) == 3:
        # if three arguments, then call the function as written
        return string_append(*args)

    else:
        raise Exception(f'Function: cast_to_string_append() accepts two or three arguments, and you entered {len(args)}.')

# instantiation

A='A'
B='B'
C='C'
D='D'

_sentinel = object()
S = _sentinel

N = None
""" Answer 3 Testing """

# two variables

cast_to_string_append(A,B)

>>> 'ABA'


# three variables

cast_to_string_append(A,B,C)

>>> 'ABC'


# three variables, one is _sentinel

cast_to_string_append(A,B,S)

>>>'AB<object object at 0x10c56f560>'


# three variables, one is None

cast_to_string_append(A,B,N)

>>>'ABNone'


# one variable

cast_to_string_append(A)

>>>Traceback (most recent call last):
>>>  File "<input>", line 1, in <module>
>>>  File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 1.

# four variables

cast_to_string_append(A,B,C,D)

>>>Traceback (most recent call last):
>>>  File "<input>", line 1, in <module>
>>>  File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 4.


# ten variables

cast_to_string_append(0,1,2,3,4,5,6,7,8,9)

>>>Traceback (most recent call last):
>>>  File "<input>", line 1, in <module>
>>>  File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 10.


# no variables

cast_to_string_append()

>>>Traceback (most recent call last):
>>>  File "<input>", line 1, in <module>
>>>  File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 0.

""" End Answer 3 Testing """

Итак, в итоге:

  • Ответ 1 - самый простой ответ и работает для большинства случаев.
def cast_to_string_concat(a, b, c=None):
  c = a if c is None else c

  return str(a) + str(b) + str(c)
  • Ответ 2 - используйте, если Noneфактически не означает пустой параметр, переключаясь на object() через _sentinel.
_sentinel = object()
def cast_to_string_concat(a, b, c=_sentinel):
  c = a if c == _sentinel else c

  return str(a) + str(b) + str(c)
  • Ответ 3 ищет общее решение с использованием оболочкифункционирует с произвольной точностью, используя *args, и обрабатывает допустимые случаи внутри:
def cast_to_string_append(*args):

    def string_append(a, b, c):
        # this is the original function, it is only called within the wrapper
        return str(a) + str(b) + str(c)

    if len(args) == 2:
        # if two arguments, then set the third to be the first
        return string_append(*args, args[0])

    elif len(args) == 3:
        # if three arguments, then call the function as written
        return string_append(*args)

    else:
        raise Exception(f'Function: cast_to_string_append() accepts two or three arguments, and you entered {len(args)}.')

Как сказал мой любимый профессор информатики доктор Джеймс Кейн, сторонник высочайшей безопасности и полноты,«Вычисление является контекстуальным [sic]», поэтому всегда используйте то, что работает для вы!Но для меня я буду использовать Вариант 3;)

Спасибо за чтение!

-Spencer


Приложение: Следующие три ссылки предлагают использовать некоторое подобие класса или оператора импорта, но я решил не идти по этому пути.Если это похоже на то, что вы ищете, попробуйте!

Для ответов, использующих класс, прочитайте их:

Упражнение, оставленное читателю:

Отказываясь от этой техники, вы можете прямо утверждать c=object(), однако, честно говоря, я не получил такой способ работать на меня.Мое расследование показывает, что c == object() это False, а str(c) == str(object()) также False, и поэтому я использую реализацию из Martin Pieters .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...