Ответ 1
Передайте аргументы как kwargs из словаря, из которого вы отфильтровываете значения None
:
kwargs = dict(p1='FOO', p2=None)
alpha(**{k: v for k, v in kwargs.items() if v is not None})
Там функция, которая принимает необязательные аргументы.
def alpha(p1="foo", p2="bar"):
print('{0},{1}'.format(p1, p2))
Позвольте мне повторить, что происходит, когда мы используем эту функцию по-разному:
>>> alpha()
foo,bar
>>> alpha("FOO")
FOO,bar
>>> alpha(p2="BAR")
foo,BAR
>>> alpha(p1="FOO", p2=None)
FOO,None
Теперь рассмотрим случай, когда я хочу называть его alpha("FOO", myp2)
а myp2
будет либо содержать значение, которое нужно передать, либо быть None
. Но даже если функция обрабатывает p2=None
, я хочу использовать ее значение по умолчанию "bar"
.
Может быть, это сформулировано смутно, поэтому позвольте мне сказать следующее:
Если
myp2 is None
, вызовитеalpha("FOO")
. Иначе, вызовитеalpha("FOO", myp2)
.
Различие имеет значение, потому что alpha("FOO", None)
имеет другой результат, чем alpha("FOO")
.
Как я могу (но читающе) сделать это различие?
Одна из возможностей, как правило, заключалась бы в том, чтобы проверить отсутствие в alpha
, что будет поощряться, потому что это сделает код более безопасным. Но предположим, что alpha
используется в других местах, где он фактически должен обрабатывать None
как он.
Я бы хотел обработать это на стороне вызывающего абонента.
Одна из возможностей - сделать различие в делах:
if myp2 is None:
alpha("FOO")
else:
alpha("FOO", myp2)
Но это может быстро стать большим количеством кода, когда есть несколько таких аргументов. (экспоненциально, 2 ^ n)
Другая возможность - просто выполнить alpha("FOO", myp2 or "bar")
, но это требует, чтобы мы знали значение по умолчанию. Обычно, я бы, вероятно, пошел с таким подходом, но позже я мог бы изменить значения по умолчанию для alpha
и этот вызов затем должен быть обновлен вручную, чтобы по-прежнему вызывать его с (новым) значением по умолчанию.
Я использую python 3.4, но было бы лучше, если бы ваши ответы могли обеспечить хороший способ работы в любой версии python.
Вопрос технически закончен здесь, но я снова пересказываю какое-то требование, так как первый ответ затушевал:
Я хочу, чтобы поведение alpha
со значениями по умолчанию "foo", "bar"
сохранялось в целом, поэтому (возможно) это не вариант изменения самой alpha
.
В еще нескольких других словах предположим, что alpha
используется где-то еще как alpha("FOO", None)
где ожидается выход FOO,None
.
Передайте аргументы как kwargs из словаря, из которого вы отфильтровываете значения None
:
kwargs = dict(p1='FOO', p2=None)
alpha(**{k: v for k, v in kwargs.items() if v is not None})
хотя ** определенно является языковой функцией, она, безусловно, не создана для решения этой конкретной проблемы. Ваше предложение работает, так же как и мое. Какой из них лучше работает, зависит от остальной части кода OP. Однако по-прежнему нет способа написать
f(x or dont_pass_it_at_all)
- blue_note
Благодаря вашим замечательным ответам, я думал, что попытаюсь сделать именно это:
# gen.py
def callWithNonNoneArgs(f, *args, **kwargs):
kwargsNotNone = {k: v for k, v in kwargs.items() if v is not None}
return f(*args, **kwargsNotNone)
# python interpreter
>>> import gen
>>> def alpha(p1="foo", p2="bar"):
... print('{0},{1}'.format(p1,p2))
...
>>> gen.callWithNonNoneArgs(alpha, p1="FOO", p2=None)
FOO,bar
>>> def beta(ree, p1="foo", p2="bar"):
... print('{0},{1},{2}'.format(ree,p1,p2))
...
>>> beta('hello', p2="world")
hello,foo,world
>>> beta('hello', p2=None)
hello,foo,None
>>> gen.callWithNonNoneArgs(beta, 'hello', p2=None)
hello,foo,bar
Это, вероятно, не идеально, но, похоже, это работает: это функция, которую вы можете вызывать с помощью другой функции и ее аргументов, и она применяет отрицательный ответ, чтобы отфильтровать аргументы, которые являются None
.
Но предположим, что альфа используется в других местах, где он фактически должен обрабатывать None, как он.
Чтобы ответить на эту проблему, у меня, как известно, есть значение None
-like, которое на самом деле не является None
для этой конкретной цели.
_novalue = object()
def alpha(p1=_novalue, p2=_novalue):
if p1 is _novalue:
p1 = "foo"
if p2 is _novalue:
p2 = "bar"
print('{0},{1}'.format(p1, p2))
Теперь аргументы по-прежнему являются необязательными, поэтому вы можете пренебречь тем, чтобы пройти любой из них. И функция обрабатывает None
правильно. Если вы когда-либо хотите явно не передавать аргумент, вы можете передать _novalue
.
>>> alpha(p1="FOO", p2=None)
FOO,None
>>> alpha(p1="FOO")
FOO,bar
>>> alpha(p1="FOO", p2=_novalue)
FOO,bar
и поскольку _novalue
представляет собой специальную добавленную стоимость, созданную для этой прямой цели, любой, кто передает _novalue
, безусловно, намеревается поведение "аргумент по умолчанию", в отличие от того, кто передает None
который мог бы предположить, что значение должно интерпретироваться как литерал None
.
К сожалению, нет способа сделать то, что вы хотите. Даже широко распространенные библиотеки/рамки python используют ваш первый подход. Это дополнительная строка кода, но она вполне читаема.
Не используйте alpha("FOO", myp2 or "bar")
, потому что, как вы сами alpha("FOO", myp2 or "bar")
, он создает ужасную связь, поскольку для alpha("FOO", myp2 or "bar")
функции требуется, чтобы вызывающий пользователь знал подробности о функции.
Что касается работы: вы можете сделать декоратор для вашей функции (используя модуль inspect
), который проверяет переданные ему аргументы. Если один из них - None
, он заменяет значение своим значением по умолчанию.
Вы можете проверить значения по умолчанию через alpha.__defaults__
а затем использовать их вместо None
. Таким образом вы обходите жесткое кодирование значений по умолчанию:
>>> args = [None]
>>> alpha('FOO', *[x if x is not None else y for x, y in zip(args, alpha.__defaults__[1:])])
Я удивлен, что никто не поднял это
def f(p1="foo", p2=None):
p2 = "bar" if p2 is None else p2
print(p1+p2)
Вы назначаете None для p2 в качестве стандартного (или нет, но таким образом, у вас есть настоящий стандарт в одной точке вашего кода) и используйте встроенную if. Имо самый пифонический ответ. Еще одна вещь, которая приходит на ум, - это использовать обертку, но это было бы менее понятным.
EDIT: то, что я, вероятно, сделаю, это использовать манекен в качестве стандартного значения и проверить это. Так что-то вроде этого:
class dummy():
pass
def alpha(p1="foo", p2=dummy()):
if isinstance(p2, dummy):
p2 = "bar"
print("{0},{1}".format(p1, p2))
alpha()
alpha("a","b")
alpha(p2=None)
производит:
foo,bar
a,b
foo,None