วิธี pythonic ในการหลีกเลี่ยงพารามิเตอร์เริ่มต้นที่เป็นรายการว่างคืออะไร?


119

บางครั้งดูเหมือนเป็นธรรมชาติที่จะมีพารามิเตอร์เริ่มต้นซึ่งเป็นรายการว่าง แต่งูใหญ่ให้พฤติกรรมที่ไม่คาดคิดในสถานการณ์เหล่านี้

ตัวอย่างเช่นฉันมีฟังก์ชัน:

def my_func(working_list = []):
    working_list.append("a")
    print(working_list)

ครั้งแรกที่เรียกว่าค่าเริ่มต้นจะใช้งานได้ แต่การโทรหลังจากนั้นจะอัปเดตรายการที่มีอยู่ (โดยมี "a" แต่ละสาย) และพิมพ์เวอร์ชันที่อัปเดต

ดังนั้นอะไรคือวิธี pythonic ในการรับพฤติกรรมที่ฉันต้องการ (รายการใหม่ในการโทรแต่ละครั้ง)?


17
หากใครสนใจว่าเหตุใดจึงเกิดขึ้นโปรดดูที่effbot.org/zone/default-values.htm
Ryan Haining

พฤติกรรมเดียวกันนี้เกิดขึ้นกับชุดแม้ว่าคุณจะต้องการตัวอย่างที่ซับซ้อนกว่านี้เล็กน้อยเพื่อให้มันแสดงเป็นบั๊ก
abeboparebop

เมื่อลิงก์ตายไปฉันขอชี้ให้ชัดเจนว่านี่เป็นพฤติกรรมที่ต้องการ ตัวแปรเริ่มต้นจะได้รับการประเมินที่นิยามฟังก์ชัน (ซึ่งเกิดขึ้นในครั้งแรกที่เรียก) และไม่ใช่ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน ดังนั้นหากคุณกลายพันธุ์อาร์กิวเมนต์ดีฟอลต์ที่ไม่สามารถเปลี่ยนแปลงได้การเรียกใช้ฟังก์ชันที่ตามมาจะสามารถใช้ได้เฉพาะอ็อบเจ็กต์ที่กลายพันธุ์เท่านั้น
Moritz

คำตอบ:


152
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    working_list.append("a")
    print(working_list)

เอกสารบอกว่าคุณควรใช้Noneเป็นค่าเริ่มต้นและทดสอบอย่างชัดเจนในเนื้อหาของฟังก์ชัน


1
จะดีกว่าไหมที่จะพูดว่า: if working_list == ไม่มี: หรือถ้า working_list: ??
John Mulder

2
นี่เป็นวิธีที่แนะนำใน python แม้ว่าฉันจะไม่ชอบเพราะมันน่าเกลียดก็ตาม ฉันจะบอกว่าแนวทางปฏิบัติที่ดีที่สุดคือ "if working_list is None"
e-satis

21
วิธีที่ต้องการในตัวอย่างนี้คือการพูดว่า: if working_list is None ผู้เรียกอาจใช้อ็อบเจ็กต์ที่เหมือนรายการว่างกับการผนวกที่กำหนดเอง
tzot

5
Mohit Ranka: ระวังว่าไม่ working_list เป็น True หากความยาวเป็น 0 สิ่งนี้นำไปสู่พฤติกรรมที่ไม่สอดคล้องกัน: หากฟังก์ชันได้รับรายการที่มีองค์ประกอบบางอย่างอยู่ผู้โทรจะมีการอัปเดตรายการของเขาและหากรายการว่างเปล่า จะไม่ถูกแตะต้อง
vincent

1
@PatrickT เครื่องมือที่เหมาะสมขึ้นอยู่กับกรณี - เป็น varargs ฟังก์ชั่นมากแตกต่างจากที่ใช้เวลา (อุปกรณ์เสริม) อาร์กิวเมนต์รายการ สถานการณ์ที่คุณต้องเลือกระหว่างนั้นเกิดขึ้นน้อยกว่าที่คุณคิด Varargs นั้นยอดเยี่ยมเมื่อจำนวนอาร์กิวเมนต์เปลี่ยนไป แต่จะได้รับการแก้ไขเมื่อรหัสเป็น WRITTEN เช่นเดียวกับตัวอย่างของคุณ หากเป็นตัวแปรรันไทม์หรือคุณต้องการเรียกf()รายการคุณจะต้องเรียกf(*l)สิ่งที่เป็นขั้นต้น ที่แย่กว่านั้นการใช้งานmate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])จะประสบความสำเร็จด้วย varargs def mate(birds=[], bees=[]):ดีมากถ้ามัน
FeRD

26

คำตอบที่มีอยู่ได้ให้คำตอบโดยตรงตามที่ขอ อย่างไรก็ตามเนื่องจากนี่เป็นข้อผิดพลาดทั่วไปสำหรับโปรแกรมเมอร์ Python ใหม่จึงควรเพิ่มคำอธิบายว่าเหตุใด python จึงทำงานในลักษณะนี้ซึ่งสรุปไว้อย่างดีใน " the Hitchhikers Guide to Python " ว่า " Mutable Default Arguments ": http: // docs .python-guide.org / th / ล่าสุด / writing / gotchas /

คำพูด: " อาร์กิวเมนต์เริ่มต้นของ Python จะได้รับการประเมินหนึ่งครั้งเมื่อมีการกำหนดฟังก์ชันไม่ใช่ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน (เช่นเดียวกับที่กล่าวคือ Ruby) ซึ่งหมายความว่าหากคุณใช้อาร์กิวเมนต์เริ่มต้นที่ไม่แน่นอนและกลายพันธุ์คุณจะมี กลายพันธุ์วัตถุนั้นสำหรับการเรียกใช้ฟังก์ชันในอนาคตทั้งหมดด้วย "

ตัวอย่างโค้ดที่จะใช้:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

13

ไม่ใช่ว่าจะมีความสำคัญในกรณีนี้ แต่คุณสามารถใช้เอกลักษณ์ของวัตถุเพื่อทดสอบว่าไม่มี:

if working_list is None: working_list = []

คุณยังสามารถใช้ประโยชน์จากวิธีการที่ตัวดำเนินการบูลีนหรือกำหนดไว้ใน python:

working_list = working_list or []

แม้ว่าสิ่งนี้จะทำงานโดยไม่คาดคิดหากผู้โทรให้รายการว่าง (ซึ่งนับว่าเป็นเท็จ) เป็น working_list และคาดว่าฟังก์ชันของคุณจะแก้ไขรายการที่เขาให้ไว้


10

หากเจตนาของฟังก์ชันคือการแก้ไขพารามิเตอร์ที่ส่งผ่านให้working_listดูคำตอบของ HenryR (= ไม่มีให้ตรวจสอบว่าไม่มีภายใน)

แต่ถ้าคุณไม่ได้ตั้งใจที่จะกลายพันธุ์อาร์กิวเมนต์เพียงแค่ใช้มันเป็นจุดเริ่มต้นของรายการคุณก็สามารถคัดลอกได้:

def myFunc(starting_list = []):
    starting_list = list(starting_list)
    starting_list.append("a")
    print starting_list

(หรือในกรณีง่ายๆนี้print starting_list + ["a"]แต่ฉันเดาว่านั่นเป็นเพียงตัวอย่างของเล่น)

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

  • หากคุณทำจากนิสัย C ของ "อาร์กิวเมนต์เอาต์พุต" นั่นไม่จำเป็นอย่างยิ่ง - คุณสามารถคืนค่าหลายค่าเป็นทูเปิลได้เสมอ

  • หากคุณทำเช่นนี้เพื่อสร้างรายการผลลัพธ์ที่ยาวได้อย่างมีประสิทธิภาพโดยไม่ต้องสร้างรายการกลางให้ลองเขียนเป็นตัวสร้างและใช้result_list.extend(myFunc())เมื่อคุณเรียกมัน วิธีนี้การประชุมการโทรของคุณยังคงสะอาดมาก

รูปแบบหนึ่งที่มักเกิดการกลายพันธุ์ของอาร์กิวเมนต์ที่เป็นทางเลือกคืออาร์กิวเมนต์"บันทึก" ที่ซ่อนอยู่ในฟังก์ชันเรียกซ้ำ:

def depth_first_walk_graph(graph, node, _visited=None):
    if _visited is None:
        _visited = set()  # create memo once in top-level call

    if node in _visited:
        return
    _visited.add(node)
    for neighbour in graph[node]:
        depth_first_walk_graph(graph, neighbour, _visited)

3

ฉันอาจจะนอกเรื่อง แต่จำไว้ว่าถ้าคุณเพียงต้องการที่จะผ่านจำนวนตัวแปรของการขัดแย้งวิธี pythonic คือการผ่าน tuple หรือพจนานุกรม*args **kargsสิ่งเหล่านี้เป็นทางเลือกและดีกว่าไวยากรณ์myFunc([1, 2, 3])เหล่านี้เป็นตัวเลือกและจะดีกว่าไวยากรณ์

หากคุณต้องการส่ง tuple:

def myFunc(arg1, *args):
  print args
  w = []
  w += args
  print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]

หากคุณต้องการส่งผ่านพจนานุกรม:

def myFunc(arg1, **kargs):
   print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}

0

มีคำตอบที่ดีและถูกต้องอยู่แล้ว ฉันแค่อยากให้ไวยากรณ์อื่นเขียนสิ่งที่คุณต้องการทำซึ่งฉันพบว่าสวยงามกว่าเมื่อคุณต้องการสร้างคลาสด้วยรายการว่างเริ่มต้น:

class Node(object):
    def __init__(self, _id, val, parents=None, children=None):
        self.id = _id
        self.val = val
        self.parents = parents if parents is not None else []
        self.children = children if children is not None else []

ตัวอย่างข้อมูลนี้ใช้ประโยชน์จากไวยากรณ์ตัวดำเนินการ if else ฉันชอบมันเป็นพิเศษเพราะมันเป็นซับในตัวเล็ก ๆ ที่ไม่มีเครื่องหมายโคลอน ฯลฯ เกี่ยวข้องและมันเกือบจะอ่านเหมือนประโยคภาษาอังกฤษทั่วไป :)

ในกรณีของคุณคุณสามารถเขียน

def myFunc(working_list=None):
    working_list = [] if working_list is None else working_list
    working_list.append("a")
    print working_list

-3

ฉันใช้คลาสส่วนขยาย UCSC Python for programmer

ซึ่งเป็นจริงของ: def Fn (data = []):

a) เป็นความคิดที่ดีเพื่อให้รายการข้อมูลของคุณเริ่มว่างเปล่าทุกครั้งที่โทร

b) เป็นความคิดที่ดีเพื่อให้การเรียกใช้ฟังก์ชันที่ไม่มีอาร์กิวเมนต์ใด ๆ ในการโทรจะได้รับรายการว่างเป็นข้อมูล

c) เป็นแนวคิดที่สมเหตุสมผลตราบเท่าที่ข้อมูลของคุณเป็นรายการสตริง

d) เป็นความคิดที่ไม่ดีเนื่องจากค่าเริ่มต้น [] จะสะสมข้อมูลและค่าเริ่มต้น [] จะเปลี่ยนไปพร้อมกับการโทรที่ตามมา

ตอบ:

d) เป็นความคิดที่ไม่ดีเนื่องจากค่าเริ่มต้น [] จะสะสมข้อมูลและค่าเริ่มต้น [] จะเปลี่ยนไปพร้อมกับการโทรที่ตามมา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.