Есть ли в Python эквивалент нуль-сливающегося оператора C #?

302

В C # есть оператор слияния нуля (записанный как ??), который позволяет легко (коротко) проверять нуль во время присваивания:

string s = null;
var other = s ?? "some default value";

Есть ли эквивалент в Python?

Я знаю, что я могу сделать:

s = None
other = s if s else "some default value"

Но есть ли еще более короткий путь (где мне не нужно повторяться s)?

Клаус Бысков Педерсен
источник
14
??Оператор предлагается в качестве PEP 505 .
200_success

Ответы:

422
other = s or "some default value"

Хорошо, нужно уточнить, как orработает оператор. Это логический оператор, поэтому он работает в логическом контексте. Если значения не являются логическими, они преобразуются в логические для целей оператора.

Обратите внимание, что orоператор не возвращает только Trueили False. Вместо этого он возвращает первый операнд, если первый операнд оценивается как true, и возвращает второй операнд, если первый операнд оценивается как false.

В этом случае выражение x or yвозвращается, xесли оно равно Trueили оценивается как истинное при преобразовании в логическое значение. В противном случае он возвращается y. В большинстве случаев это будет служить той же цели, что и оператор C♯ с нулевым слиянием, но имейте ввиду:

42    or "something"    # returns 42
0     or "something"    # returns "something"
None  or "something"    # returns "something"
False or "something"    # returns "something"
""    or "something"    # returns "something"

Если вы используете свою переменную sдля хранения чего-либо, что является либо ссылкой на экземпляр класса, либо None(если ваш класс не определяет членов __nonzero__()и __len__()), можно безопасно использовать ту же семантику, что и оператор слияния нулей.

На самом деле, может быть даже полезно иметь этот побочный эффект Python. Поскольку вы знаете, какие значения оцениваются как ложные, вы можете использовать это для запуска значения по умолчанию, не используя Noneспециально (например, объект ошибки).

В некоторых языках это поведение называется оператором Элвиса .

Жулиано
источник
3
Будет ли это работать так же? Я имею в виду, сломается ли оно, если sэто допустимое значение, но не правдивое? (Я не знаю Python, поэтому я не уверен, применима ли концепция «правдивого».)
cHao
9
Число 0 Noneи пустые контейнеры (включая строки) считаются ложными, в дополнение к константе False. Большинство всего остального считается правдой. Я бы сказал, что главной опасностью здесь будет то, что вы получите истинное, но не строковое значение, но это не будет проблемой в некоторых программах.
любезно
25
Использование этого другого получит значение по умолчанию, если s равно None или False , что может быть не тем, что нужно.
Pafcu
6
Есть много неясных ошибок, вызванных этим также. Например, до Python 3.5,datetime.time(0) тоже был фальшивый!
Антти Хаапала
19
Это плохо. Я рекомендую добавить уведомление о его подводных камнях. И рекомендую не использовать его.
Матин Улхак
61

Строго,

other = s if s is not None else "default value"

Иначе s = Falseстанет "default value", что может не соответствовать тому, что было задумано.

Если вы хотите сделать это короче, попробуйте:

def notNone(s,d):
    if s is None:
        return d
    else:
        return s

other = notNone(s, "default value")
Хью Ботвелл
источник
1
Consider x()?.y()?.z()
нуреттин
40

Вот функция, которая будет возвращать первый аргумент, который не является None:

def coalesce(*arg):
  return reduce(lambda x, y: x if x is not None else y, arg)

# Prints "banana"
print coalesce(None, "banana", "phone", None)

reduce() может излишне перебирать все аргументы, даже если первый аргумент не None , поэтому вы также можете использовать эту версию:

def coalesce(*arg):
  for el in arg:
    if el is not None:
      return el
  return None
mortehu
источник
23
def coalesce(*arg): return next((a for a in arg if a is not None), None)делает так же, как ваш последний пример в одной строке.
glglgl
2
Я понимаю, что люди хотят объяснить, если еще sytnax и т. Д., Но coalesce принимает произвольный список аргументов, так что это действительно лучший ответ.
Эрик Твилегар
2
У glglgl есть лучший ответ. Я использовал timeit в большом тестовом массиве, и реализация Reduce неприемлемо медленная, многострочная для / if версия самая быстрая, а следующая реализация немного отстает. Следующая версия является лучшей в целом, учитывая простоту и лаконичность.
глина
3
У @glglgl есть интересный фрагмент. К сожалению, из-за того, что Python не имеет передаваемого имени, объединение, как это, не является коротким замыканием; все аргументы оцениваются до запуска кода.
user1338062
Consider x()?.y()?.z()
Нуреттин
5

Я понимаю, что это ответ, но есть другой вариант, когда вы имеете дело с объектами.

Если у вас есть объект, который может быть:

{
   name: {
      first: "John",
      last: "Doe"
   }
}

Ты можешь использовать:

obj.get(property_name, value_if_null)

Подобно:

obj.get("name", {}).get("first", "Name is missing") 

При добавлении {}в качестве значения по умолчанию, если «имя» отсутствует, пустой объект возвращается и передается следующему get. Это похоже на null-safe-navigation в C #, что было бы похоже obj?.name?.first.

Craig
источник
Не все объекты имеют .get, это работает только для объектов,
похожих
Я отправляю редактирование ответа, чтобы покрыть getattr()также.
DGW
geton dict не использует параметр по умолчанию, если значение равно None, но использует параметр по умолчанию, если значение не существует, поскольку ключ отсутствует в dict. {'a': None}.get('a', 'I do not want None')все равно даст вам Noneв результате.
Патрик Мевзек
3

В дополнение к ответу Джулиано о поведении «или»: это «быстро»

>>> 1 or 5/0
1

Так что иногда это может быть полезным ярлыком для таких вещей, как

object = getCachedVersion() or getFromDB()
Orca
источник
17
Вы ищете термин «короткие замыкания».
jpmc26
1

В дополнение к ответу @Bothwells (который я предпочитаю) для отдельных значений, для проверки нулевых значений, возвращаемых функцией, вы можете использовать новый оператор walrus (начиная с python3.8):

def test():
    return

a = 2 if (x:= test()) is None else x

Таким образом, testфункцию не нужно оценивать два раза (как в a = 2 if test() is None else test())

Henhuy
источник
1

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

model?.data()?.first()

Это не легко решаемая проблема or. Это также не может быть решено, для .get()которого требуется тип словаря или аналог (и не может быть вложен в любом случае) илиgetattr() который вызовет исключение, когда NoneType не имеет атрибута.

Соответствующий пункт, рассматривающий добавление нулевого слияния в язык, - это PEP 505, и обсуждение, относящееся к документу, находится в ветке идей python .

Нуреттин
источник
0

Что касается ответов @Hugh Bothwell, @mortehu и @glglgl.

Настройка набора данных для тестирования

import random

dataset = [random.randint(0,15) if random.random() > .6 else None for i in range(1000)]

Определить реализации

def not_none(x, y=None):
    if x is None:
        return y
    return x

def coalesce1(*arg):
  return reduce(lambda x, y: x if x is not None else y, arg)

def coalesce2(*args):
    return next((i for i in args if i is not None), None)

Сделать тестовую функцию

def test_func(dataset, func):
    default = 1
    for i in dataset:
        func(i, default)

Результаты на Mac i7 @ 2,7 ГГц с использованием Python 2,7

>>> %timeit test_func(dataset, not_none)
1000 loops, best of 3: 224 µs per loop

>>> %timeit test_func(dataset, coalesce1)
1000 loops, best of 3: 471 µs per loop

>>> %timeit test_func(dataset, coalesce2)
1000 loops, best of 3: 782 µs per loop

Очевидно, что not_none функция отвечает на вопрос ОП правильно и обрабатывает проблему «ложных». Это также самый быстрый и легкий для чтения. Если применять логику во многих местах, это явно лучший путь.

Если у вас есть проблема, когда вы хотите найти 1-е ненулевое значение в итерируемом, то ответ @ mortehu - это путь. Но это решение другой проблемы, нежели OP, хотя он может частично справиться с этим делом. Он не может принимать итерируемое И значение по умолчанию. Последним аргументом будет возвращаемое значение по умолчанию, но тогда в этом случае вы не передадите итерируемое, так как не очевидно, что последний аргумент является значением по умолчанию.

Затем вы могли бы сделать это ниже, но я бы все еще использовал not_nullдля варианта использования одно значение.

def coalesce(*args, **kwargs):
    default = kwargs.get('default')
    return next((a for a in arg if a is not None), default)
Debo
источник
0

Для таких, как я, которые наткнулись здесь на поиски жизнеспособного решения этой проблемы, когда переменная может быть неопределенной, самое близкое, что я получил, это:

if 'variablename' in globals() and ((variablename or False) == True):
  print('variable exists and it\'s true')
else:
  print('variable doesn\'t exist, or it\'s false')

Обратите внимание, что при проверке глобальных переменных требуется строка, но впоследствии при проверке значения используется фактическая переменная.

Подробнее о существовании переменных: как проверить, существует ли переменная?

Итака
источник
1
(variablename or False) == Trueтак же, какvariablename == True
микрофон
-2
Python has a get function that its very useful to return a value of an existent key, if the key exist;
if not it will return a default value.

def main():
    names = ['Jack','Maria','Betsy','James','Jack']
    names_repeated = dict()
    default_value = 0

    for find_name in names:
        names_repeated[find_name] = names_repeated.get(find_name, default_value) + 1

если вы не можете найти имя внутри словаря, он вернет значение по умолчанию, если имя существует, то будет добавлено любое существующее значение с 1.

надеюсь, это поможет

Angelo
источник
1
Привет, добро пожаловать в Stack Overflow. Какую новую информацию добавляет ваш ответ, который не был охвачен существующими ответами? См. Ответ @ Крейга, например
Грайси
-6

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

def nz(value, none_value, strict=True):
    ''' This function is named after an old VBA function. It returns a default
        value if the passed in value is None. If strict is False it will
        treat an empty string as None as well.

        example:
        x = None
        nz(x,"hello")
        --> "hello"
        nz(x,"")
        --> ""
        y = ""   
        nz(y,"hello")
        --> ""
        nz(y,"hello", False)
        --> "hello" '''

    if value is None and strict:
        return_val = none_value
    elif strict and value is not None:
        return_val = value
    elif not strict and not is_not_null(value):
        return_val = none_value
    else:
        return_val = value
    return return_val 

def is_not_null(value):
    ''' test for None and empty string '''
    return value is not None and len(str(value)) > 0
Майк Стабиле
источник
5
Подобные вещи добавляют целую кучу немного другой терминологии (например, «null» и «nz», ни одна из которых ничего не значит в контексте Python), импортированные из других языков, плюс с вариантами (строгими или не строгими!). Это только добавляет путаницы. Явные проверки "is None" - это то, что вы должны использовать. Кроме того, вы не получите никакой выгоды от краткой семантики, которую могут использовать операторы при использовании вызова функции.
spookylukey