Чи є еквівалент Python оператора C # null-coalescing?


301

У C # є оператор, що зв'язує нуль (пишеться як ??), що дозволяє легко (коротко) перевіряти нуль під час призначення:

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

Чи є еквівалент пітона?

Я знаю, що можу:

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

Але чи існує ще коротший шлях (де мені не потрібно повторюватись s)?


14
??Оператор пропонується в якості PEP 505 .
200_успіх

Відповіді:


421
other = s or "some default value"

Гаразд, слід уточнити, як orпрацює оператор. Це логічний оператор, тому він працює в булевому контексті. Якщо значення не булеві, вони перетворюються на булеві для цілей оператора.

Зауважте, що orоператор не повертає тільки 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__()), безпечно використовувати ту саму семантику, що і оператор, що поєднує null.

Насправді, може бути навіть корисним цей побічний ефект від Python. Оскільки ви знаєте, які значення оцінюються як хибні, ви можете використовувати це для запуску значення за замовчуванням, не використовуючи Noneконкретно (наприклад, об'єкт помилки).

У деяких мовах така поведінка називається оператором Елвіса .


3
Чи буде це працювати так само? Я маю на увазі, чи зламається вона, якщо sце дійсне значення, але не є правдою? (Я не знаю Python, тому я не впевнений, чи застосовується поняття "truthy".)
cHao

9
NoneОкрім константи, число 0, та порожні контейнери (включаючи рядки) вважаються помилковими False. Більшість всього іншого вважається правдою. Я б сказав, що головна небезпека полягає в тому, що ви отримаєте справжнє, але не рядкове значення, але це не буде проблемою в деяких програмах.
kindall

25
Використовуючи це інше, ви отримаєте значення за замовчуванням, якщо s - None або False , що може бути не тим, що потрібно.
pafcu

6
Існує багато незрозумілих помилок, викликаних цим. Наприклад, до Python 3.5, datetime.time(0)також було хибним!
Антті Хаапала

19
Це погано. Рекомендую додати повідомлення про його підводні камені. І рекомендую не використовувати його.
Mateen Ulhaq

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

23
def coalesce(*arg): return next((a for a in arg if a is not None), None)робить те саме, що і ваш останній приклад в одному рядку.
glglgl

2
Я розумію, що люди хочуть пояснити, якщо інакше sytnax тощо, але coalesce приймає довільний аргумент, так що це справді повинна бути головною відповіддю.
Ерік Твілегар

2
glglgl має найкращу відповідь. Я використовував timeit на великому тестовому масиві, і зменшення реалізації неприйнятно повільно, багаторядкова версія для / якщо версія найшвидша, а наступна реалізація дуже відстає. Наступна версія є найкращою в цілому, якщо враховувати простоту та стислість.
глина

3
@glglgl має цікавий фрагмент. На жаль, тому що Python не має пропускного імені, подібне до цього не є коротким замиканням; всі аргументи оцінюються до запуску коду.
користувач1338062

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") 

Додавши {}як значення за замовчуванням, якщо "ім'я" відсутнє, порожній об'єкт повертається і передається до наступного отримання. Це схоже на безпечну навігацію в C #, як це було б obj?.name?.first.


Не у всіх об’єктів є .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()

17
Термін, який ви шукаєте, - це "коротке замикання".
jpmc26

1

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

def test():
    return

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

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


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.7Ghz за допомогою 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 функція відповідає на питання ОП правильно і вирішує проблему "хибної". Це також найшвидший і найпростіший для читання. Якщо застосовувати логіку в багатьох місцях, це, очевидно, найкращий шлях.

Якщо у вас є проблема, коли ви хочете знайти перше ненулеве значення в ітерабелі, то відповідь @ mortehu - це шлях. Але це рішення іншої проблеми, ніж ОП, хоча він може частково впоратися з цією справою. Він не може приймати ітерабельне І значення за замовчуванням. Останнім аргументом було б повернене значення за замовчуванням, але тоді ви не переходите в ітерабельний в такому випадку, а також не явно, що останній аргумент є значенням за замовчуванням.

Потім ви можете зробити це нижче, але я все одно використовую not_nullдля випадку з використанням одного значення.

def coalesce(*args, **kwargs):
    default = kwargs.get('default')
    return next((a for a in arg if a is not None), default)

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

якщо ви не можете знайти ім’я всередині словника, воно поверне значення default_value, якщо ім'я існує, то воно додасть будь-яке існуюче значення з 1.

сподіваюся, що це може допомогти


1
Привіт, Ласкаво просимо до Stack Overflow. Яку нову інформацію додає ваша відповідь, яка вже не була охоплена наявними відповідями? Дивіться, наприклад, відповідь @ Craig
Gricey

-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), імпортованих з інших мов, плюс з варіантами (суворі або нестриктні!). Це лише додає плутанини. Явні чеки "немає" - це те, що ви повинні використовувати. Крім того, ви не отримаєте користі від будь-якої короткої семантики, яку можуть робити оператори, коли ви використовуєте виклик функції.
spookylukey
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.