问题:是否有Python等效于C#null-coalescing运算符?

在C#中,有一个null-coalescing运算符(写为??),允许在赋值期间轻松(简短)进行null检查:

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

有python等效项吗?

我知道我可以做到:

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

但是,还有没有更短的方法(我不需要重复s)?

In C# there’s a null-coalescing operator (written as ??) that allows for easy (short) null checking during assignment:

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

Is there a python equivalent?

I know that I can do:

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

But is there an even shorter way (where I don’t need to repeat s)?


回答 0

other = s or "some default value"

好的,必须弄清楚or操作员的工作方式。它是一个布尔运算符,因此可以在布尔上下文中工作。如果值不是布尔值,则出于运算符的目的,它们将转换为布尔值。

请注意,or运算符不会仅返回TrueFalse。相反,如果第一个操作数的计算结果为true,则返回第一个操作数;如果第一个操作数的计算结果为false,则返回第二个操作数。

在这种情况下,如果表达式为true ,则x or y返回x它,True或者当转换为boolean时,表达式为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(只要您的类未定义member __nonzero__()__len__()),则使用与null-coalescing运算符相同的语义是安全的。

实际上,拥有Python的这种副作用甚至可能是有用的。由于您知道哪些值的计算结果为false,因此可以使用它来触发默认值,而无需None专门使用(例如,错误对象)。

在某些语言中,此行为称为Elvis运算符

other = s or "some default value"

Ok, it must be clarified how the or operator works. It is a boolean operator, so it works in a boolean context. If the values are not boolean, they are converted to boolean for the purposes of the operator.

Note that the or operator does not return only True or False. Instead, it returns the first operand if the first operand evaluates to true, and it returns the second operand if the first operand evaluates to false.

In this case, the expression x or y returns x if it is True or evaluates to true when converted to boolean. Otherwise, it returns y. For most cases, this will serve for the very same purpose of C♯’s null-coalescing operator, but keep in mind:

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

If you use your variable s to hold something that is either a reference to the instance of a class or None (as long as your class does not define members __nonzero__() and __len__()), it is secure to use the same semantics as the null-coalescing operator.

In fact, it may even be useful to have this side-effect of Python. Since you know what values evaluates to false, you can use this to trigger the default value without using None specifically (an error object, for example).

In some languages this behavior is referred to as the Elvis operator.


回答 1

严格来说

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

Strictly,

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

Otherwise, s = False will become "default value", which may not be what was intended.

If you want to make this shorter, try:

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

other = notNone(s, "default value")

回答 2

这是一个将返回第一个参数not的函数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

Here’s a function that will return the first argument that isn’t 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() might needlessly iterate over all the arguments even if the first argument is not None, so you can also use this version:

def coalesce(*arg):
  for el in arg:
    if el is not None:
      return el
  return None

回答 3

我知道已经解决了,但是在处理对象时还有另一种选择。

如果您的对象可能是:

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

您可以使用:

obj.get(property_name, value_if_null)

喜欢:

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

通过添加{}作为默认值,如果缺少“名称”,则返回一个空对象并将其传递到下一个get。这类似于C#中的null-safe-navigation,就像obj?.name?.first

I realize this is answered, but there is another option when you’re dealing with objects.

If you have an object that might be:

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

You can use:

obj.get(property_name, value_if_null)

Like:

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

By adding {} as the default value, if “name” is missing, an empty object is returned and passed through to the next get. This is similar to null-safe-navigation in C#, which would be like obj?.name?.first.


回答 4

除了朱利诺(Juliano)有关“或”行为的答案:“快速”

>>> 1 or 5/0
1

因此有时对于某些事情,它可能是一个有用的捷径

object = getCachedVersion() or getFromDB()

In addition to Juliano’s answer about behavior of “or”: it’s “fast”

>>> 1 or 5/0
1

So sometimes it’s might be a useful shortcut for things like

object = getCachedVersion() or getFromDB()

回答 5

对于单个值,除了@Bothwells答案(我更喜欢)之外,为了对函数返回值进行空值检查,您可以使用新的walrus-operator(自python3.8起):

def test():
    return

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

因此,test无需对函数进行两次评估(如中的所示a = 2 if test() is None else test()

Addionally to @Bothwells answer (which I prefer) for single values, in order to null-checking assingment of function return values, you can use new walrus-operator (since python3.8):

def test():
    return

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

Thus, test function does not need to be evaluated two times (as in a = 2 if test() is None else test())


回答 6

如果您需要嵌套多个null合并操作,例如:

model?.data()?.first()

这不是用容易解决的问题or。它也不能解决.get()需要字典类型或类似类型(无论如何都不能嵌套)的问题,或者getattr()当NoneType没有属性时会抛出异常。

考虑向该语言添加空合并的相关点是PEP 505,与该文档相关的讨论在python-ideas线程中。

In case you need to nest more than one null coalescing operation such as:

model?.data()?.first()

This is not a problem easily solved with or. It also cannot be solved with .get() which requires a dictionary type or similar (and cannot be nested anyway) or getattr() which will throw an exception when NoneType doesn’t have the attribute.

The relevant pip considering adding null coalescing to the language is PEP 505 and the discussion relevant to the document is in the python-ideas thread.


回答 7

关于@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)

使用Python 2.7在Mac i7 @ 2.7Ghz上的结果

>>> %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函数可以正确回答OP的问题并处理“虚假”问题。它也是最快,最容易阅读的。如果将逻辑应用于很多地方,显然这是最好的方法。

如果您有一个问题想要在迭代中找到第一个非空值,那么@mortehu的响应就是解决方法。但这是与OP 不同的解决方案,尽管它可以部分解决这种情况。它不能采用可迭代的AND默认值。最后一个参数将是返回的默认值,但在这种情况下,您将不会传递可迭代的值,而且也不清楚最后一个参数是否是value的默认值。

然后,您可以在下面做,但我仍将使用not_null单值用例。

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

Regarding answers by @Hugh Bothwell, @mortehu and @glglgl.

Setup Dataset for testing

import random

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

Define implementations

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)

Make test function

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

Results on mac i7 @2.7Ghz using 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

Clearly the not_none function answers the OP’s question correctly and handles the “falsy” problem. It is also the fastest and easiest to read. If applying the logic in many places, it is clearly the best way to go.

If you have a problem where you want to find the 1st non-null value in a iterable, then @mortehu’s response is the way to go. But it is a solution to a different problem than OP, although it can partially handle that case. It cannot take an iterable AND a default value. The last argument would be the default value returned, but then you wouldn’t be passing in an iterable in that case as well as it isn’t explicit that the last argument is a default to value.

You could then do below, but I’d still use not_null for the single value use case.

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

回答 8

对于像我这样偶然发现此问题的可行解决方案的人,当变量可能未定义时,我得到的最接近的是:

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

请注意,在检查全局变量时需要一个字符串,但是之后在检查值时将使用实际变量。

有关变量存在的更多信息: 如何检查变量是否存在?

For those like me that stumbled here looking for a viable solution to this issue, when the variable might be undefined, the closest i got is:

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

Note that a string is needed when checking in globals, but afterwards the actual variable is used when checking for value.

More on variable existence: How do I check if a variable exists?


回答 9

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。

希望这可以帮助

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

if you cannot find the name inside the dictionary, it will return the default_value, if the name exist then it will add any existing value with 1.

hope this can help


回答 10

我发现下面的两个功能在处理许多可变测试用例时非常有用。

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

The two functions below I have found to be very useful when dealing with many variable testing cases.

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。