มี 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_success

คำตอบ:


421
other = s or "some default value"

ตกลงจะต้องมีการชี้แจงวิธีorการทำงานของผู้ประกอบการ มันเป็นโอเปอเรเตอร์บูลีนดังนั้นจึงสามารถทำงานในบริบทบูลีน หากค่านั้นไม่ใช่บูลีนค่าเหล่านั้นจะถูกแปลงเป็นบูลีนเพื่อจุดประสงค์ของโอเปอเรเตอร์

โปรดทราบว่าorผู้ประกอบการไม่กลับมาเท่านั้นหรือTrue Falseแต่จะส่งกลับตัวถูกดำเนินการแรกหากตัวถูกดำเนินการแรกประเมินเป็นจริงและจะส่งกลับตัวถูกดำเนินการที่สองหากตัวถูกดำเนินการแรกประเมินเป็นเท็จ

ในกรณีนี้นิพจน์x or yจะคืนค่าxถ้าเป็นTrueหรือหาค่าเป็นจริงเมื่อแปลงเป็นบูลีน yมิฉะนั้นก็จะส่งกลับ สำหรับกรณีส่วนใหญ่สิ่งนี้จะให้บริการเพื่อจุดประสงค์เดียวกันกับตัวดำเนินการ null ที่รวมกันของ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-coalescing

ในความเป็นจริงมันอาจเป็นประโยชน์ที่จะมีผลข้างเคียงของ Python เนื่องจากคุณรู้ว่าค่าใดที่ประเมินว่าเป็นเท็จคุณสามารถใช้สิ่งนี้เพื่อทริกเกอร์ค่าเริ่มต้นโดยไม่ต้องใช้Noneเฉพาะ (เช่นวัตถุข้อผิดพลาด)

ในบางภาษาพฤติกรรมนี้จะเรียกว่าเป็นผู้ประกอบการเอลวิส


3
สิ่งนี้จะทำงานเหมือนเดิมหรือไม่ ฉันหมายความว่ามันจะแตกถ้าsเป็นค่าที่ถูกต้อง แต่ไม่ใช่ความจริง? (ฉันไม่รู้จัก Python ดังนั้นฉันไม่แน่ใจว่าจะใช้แนวคิดของ 'ความจริง')
cHao

9
จำนวน 0, Noneและภาชนะเปล่า (รวมทั้งสตริง) Falseจะถือว่าเป็นเท็จนอกเหนือไปอย่างต่อเนื่อง ทุกสิ่งทุกอย่างส่วนใหญ่ถือว่าเป็นเรื่องจริง ฉันจะบอกว่าอันตรายหลักที่นี่จะเป็นที่คุณจะได้รับค่าจริง แต่ไม่ใช่สายอักขระ แต่นั่นจะไม่เป็นปัญหาในบางโปรแกรม
ใจดี

25
นี้โดยใช้อื่น ๆจะได้รับค่าเริ่มต้นถ้า s คือไม่มีหรือเท็จซึ่งอาจจะไม่สิ่งที่เป็นที่ต้องการ
pafcu

6
มีข้อบกพร่องมากมายที่เกิดจากสิ่งนี้เช่นกัน ตัวอย่างเช่นก่อนหน้า Python 3.5 datetime.time(0)ก็เป็นเท็จเช่นกัน!
Antti Haapala

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()
nurettin

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 อื่น ๆ หรือไม่ แต่การรวมตัวกันใช้รายการอาร์กิวเมนต์โดยพลการดังนั้นนี่จึงควรเป็นคำตอบที่ดีที่สุด
Eric Twilegar

2
glglgl มีคำตอบที่ดีที่สุด ฉันใช้ timeit ในชุดทดสอบขนาดใหญ่และการลดการใช้งานนั้นช้าเกินกว่าจะยอมรับได้หลายบรรทัดสำหรับ / หากเวอร์ชันเร็วที่สุดและการใช้งานครั้งถัดไปก็ช้ากว่าเล็กน้อย รุ่นถัดไปเป็นสิ่งที่ดีที่สุดโดยรวมเมื่อพิจารณาถึงความเรียบง่ายและรัดกุม
Clay

3
@glglgl มีตัวอย่างที่น่าสนใจ น่าเสียดายเนื่องจาก Python ไม่มีชื่อรหัสผ่านรวมตัวกันเช่นนี้ไม่ใช่การลัดวงจร อาร์กิวเมนต์ทั้งหมดได้รับการประเมินก่อนที่รหัสจะทำงาน
user1338062

Consider x()?.y()?.z()
nurettin

5

ฉันรู้ว่าคำตอบนี้มี แต่มีตัวเลือกอื่นเมื่อคุณจัดการกับวัตถุ

หากคุณมีวัตถุที่อาจเป็น:

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

คุณสามารถใช้ได้:

obj.get(property_name, value_if_null)

ชอบ:

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

โดยการเพิ่ม{}เป็นค่าเริ่มต้นหาก "ชื่อ" หายไปวัตถุที่ว่างเปล่าจะถูกส่งกลับและส่งผ่านไปยังการรับต่อไป สิ่งนี้คล้ายกับการนำทางที่ปลอดภัยโดยใช้ null ใน C # ซึ่งจะเป็นเช่นobj?.name?.firstนั้น


มีวัตถุทั้งหมดไม่ได้สิ่ง.getนี้ใช้ได้เฉพาะกับวัตถุที่มีลักษณะเหมือน dict
timdiels

ฉันส่งคำตอบแก้ไขเพื่อให้ครอบคลุมgetattr()ยัง
dgw

geton dict ไม่ได้ใช้พารามิเตอร์เริ่มต้นหากค่าเป็น None แต่ใช้พารามิเตอร์เริ่มต้นหากไม่มีค่าอยู่เนื่องจากคีย์ไม่ได้อยู่ใน dict {'a': None}.get('a', 'I do not want None')จะยังคงให้Noneผลลัพธ์
Patrick Mevzek

3

นอกจากคำตอบของ Juliano เกี่ยวกับพฤติกรรมของ "หรือ": มัน "เร็ว"

>>> 1 or 5/0
1

ดังนั้นบางครั้งมันอาจเป็นทางลัดที่มีประโยชน์สำหรับสิ่งต่าง ๆ

object = getCachedVersion() or getFromDB()

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


1

ในกรณีที่คุณต้องการซ้อนการดำเนินการรวมศูนย์มากกว่าหนึ่งอย่างเช่น:

model?.data()?.first()

orนี้ไม่ได้เป็นปัญหาแก้ไขได้อย่างง่ายดายด้วย นอกจากนี้ยังไม่สามารถแก้ไขด้วย.get()ซึ่งต้องใช้ประเภทพจนานุกรมหรือคล้ายกัน (และไม่สามารถซ้อนกันอยู่แล้ว) หรือgetattr()ที่จะโยนข้อยกเว้นเมื่อ NoneType ไม่มีแอตทริบิวต์

จุดเล็ก ๆ ที่เกี่ยวข้องที่พิจารณาเพิ่มการรวมกันเป็นโมฆะกับภาษาคือPEP 505และการอภิปรายที่เกี่ยวข้องกับเอกสารอยู่ในเธรดpython-ideas


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ฟังก์ชั่นตอบคำถามของ OP ได้อย่างถูกต้องและจัดการปัญหา "ผิดพลาด" นอกจากนี้ยังเป็นวิธีที่เร็วและง่ายที่สุดในการอ่าน หากใช้ตรรกะในหลาย ๆ ที่มันเป็นวิธีที่ดีที่สุดที่จะไปอย่างชัดเจน

หากคุณมีปัญหาที่คุณต้องการค้นหาค่าที่ไม่เป็นค่าที่ 1 ใน iterable การตอบสนองของ @ mortehu คือหนทางที่จะไป แต่มันเป็นวิธีการแก้ไขปัญหาที่แตกต่างจาก OP แม้ว่ามันจะสามารถจัดการกับกรณีนั้นได้บางส่วน มันไม่สามารถใช้ iterable และเป็นค่าเริ่มต้น อาร์กิวเมนต์สุดท้ายจะเป็นค่าเริ่มต้นที่ส่งคืน แต่จากนั้นคุณจะไม่ผ่านการทำซ้ำในกรณีนั้นรวมทั้งไม่ชัดเจนว่าอาร์กิวเมนต์สุดท้ายนั้นเป็นค่าเริ่มต้นสำหรับค่า

จากนั้นคุณสามารถทำด้านล่าง แต่ฉันยังคงใช้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
mic

-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) นำเข้าจากภาษาอื่น ๆ รวมถึงตัวแปรต่างๆ (เข้มงวดหรือไม่เข้มงวด!) สิ่งนี้เพิ่มความสับสนเท่านั้น การตรวจสอบ "is none" อย่างชัดเจนเป็นสิ่งที่คุณควรใช้ นอกจากนี้คุณจะไม่ได้รับประโยชน์จากความหมายสั้น ๆ ใด ๆ ที่ผู้ให้บริการสามารถทำได้เมื่อคุณใช้การเรียกใช้ฟังก์ชัน
spookylukey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.