Python อาร์กิวเมนต์เริ่มต้นที่ไม่แน่นอน: ทำไม


20

ฉันรู้ว่าข้อโต้แย้งเริ่มต้นจะถูกสร้างขึ้นในเวลาเริ่มต้นฟังก์ชั่นและไม่ได้ทุกครั้งที่ฟังก์ชั่นที่เรียกว่า ดูรหัสต่อไปนี้:

def ook (item, lst=[]):
    lst.append(item)
    print 'ook', lst

def eek (item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    print 'eek', lst

max = 3
for x in xrange(max):
    ook(x)

for x in xrange(max):
    eek(x)

สิ่งที่ฉันไม่ได้รับคือสาเหตุที่ทำให้เกิดสิ่งนี้ขึ้น พฤติกรรมนี้มีประโยชน์อย่างไรในการเริ่มต้นในแต่ละครั้งที่โทร?


มีการกล่าวถึงในรายละเอียดที่น่าอัศจรรย์เกี่ยวกับ Stack Overflow: stackoverflow.com/q/1132941/5419599
Wildcard

คำตอบ:


14

ฉันคิดว่าเหตุผลก็คือการนำความเรียบง่ายมาใช้ ให้ฉันทำอย่างละเอียด

ค่าเริ่มต้นของฟังก์ชั่นคือการแสดงออกที่คุณต้องประเมิน ในกรณีของคุณมันคือการแสดงออกที่เรียบง่ายที่ไม่ขึ้นอยู่กับการปิด แต่ก็สามารถเป็นสิ่งที่มีตัวแปรฟรี def ook(item, lst = something.defaultList())- ถ้าคุณต้องการออกแบบ Python คุณจะมีทางเลือก - คุณประเมินมันหนึ่งครั้งเมื่อมีการกำหนดฟังก์ชั่นหรือทุกครั้งที่เรียกใช้ฟังก์ชัน Python เลือกตัวแรก (ไม่เหมือน Ruby ซึ่งไปกับตัวเลือกที่สอง)

มีประโยชน์บางอย่างสำหรับสิ่งนี้

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

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


3
จุดที่น่าสนใจ - แม้ว่าโดยทั่วไปแล้วจะเป็นหลักการของความประหลาดใจอย่างน้อยกับ Python บางสิ่งนั้นเรียบง่ายในแง่ของความซับซ้อนแบบเป็นทางการ แต่ไม่ชัดเจนและน่าประหลาดใจและฉันคิดว่าสิ่งนี้นับ
Steve314

1
สิ่งที่เกี่ยวกับความประหลาดใจน้อยที่สุดที่นี่คือ: ถ้าคุณมีความหมายในการประเมินทุกครั้งที่โทรคุณจะได้รับข้อผิดพลาดหากการปิดการเปลี่ยนแปลงระหว่างสองสายฟังก์ชั่น (ซึ่งค่อนข้างเป็นไปได้) สิ่งนี้อาจน่าแปลกใจมากขึ้นเมื่อเทียบกับการรู้ว่ามีการประเมินหนึ่งครั้ง แน่นอนหนึ่งสามารถยืนยันว่าเมื่อคุณมาจากภาษาอื่น ๆ คุณยกเว้นความหมายในการประเมินในแต่ละโทรและที่เป็นความประหลาดใจ แต่คุณสามารถเห็นว่ามันไปทั้งสองวิธี :)
สเตฟาน Kanev

จุดดีเกี่ยวกับขอบเขต
0xc0de

ฉันคิดว่าขอบเขตนั้นเป็นบิตที่สำคัญกว่า เนื่องจากคุณไม่ได้ จำกัด ค่าคงที่สำหรับค่าเริ่มต้นคุณอาจต้องการตัวแปรที่ไม่อยู่ในขอบเขตที่ไซต์การโทร
Mark Ransom

5

วิธีหนึ่งที่จะนำมาใช้คือการที่lst.append(item) ไม่ได้กลายพันธุ์lstพารามิเตอร์ lstยังคงอ้างอิงรายการเดียวกัน เป็นเพียงเนื้อหาของรายการนั้นถูกเปลี่ยนแปลง

โดยทั่วไปแล้ว Python ไม่มี (ที่ฉันจำได้) ตัวแปรคงที่หรือไม่เปลี่ยนรูปแบบใด ๆ - แต่มันมีบางประเภทคงที่และไม่เปลี่ยนรูป คุณไม่สามารถแก้ไขค่าจำนวนเต็มคุณสามารถแทนที่ได้เท่านั้น แต่คุณสามารถแก้ไขเนื้อหาของรายการโดยไม่ต้องแทนที่

เช่นเดียวกับจำนวนเต็มคุณไม่สามารถแก้ไขการอ้างอิงคุณสามารถแทนที่ได้เท่านั้น แต่คุณสามารถแก้ไขเนื้อหาของวัตถุที่ถูกอ้างอิงได้

สำหรับการสร้างวัตถุเริ่มต้นหนึ่งครั้งฉันคิดว่าส่วนใหญ่เป็นการเพิ่มประสิทธิภาพเพื่อประหยัดค่าใช้จ่ายในการสร้างวัตถุและขยะ


+1 แน่นอน สิ่งสำคัญคือต้องเข้าใจเลเยอร์ของการเปลี่ยนทิศทาง - ตัวแปรนั้นไม่ใช่ค่า แทนมันอ้างอิงค่า ในการเปลี่ยนตัวแปรคุณสามารถสลับค่าหรือกลายพันธุ์ได้ (หากเป็นตัวแปร)
Joonas Pulakka

เมื่อต้องเผชิญกับสิ่งยุ่งยากที่เกี่ยวข้องกับตัวแปรในหลามฉันพบว่ามีประโยชน์ในการพิจารณา "=" เป็น "ตัวดำเนินการผูกชื่อ" ชื่อนั้นจะเด้งกลับมาเสมอไม่ว่าสิ่งที่เราจะเชื่อมโยงกับมันจะเป็นสิ่งใหม่
StarWeaver

4

พฤติกรรมนี้มีประโยชน์อย่างไรในการเริ่มต้นในแต่ละครั้งที่โทร?

มันช่วยให้คุณเลือกพฤติกรรมที่คุณต้องการตามที่คุณแสดงในตัวอย่างของคุณ ดังนั้นหากคุณต้องการที่อาร์กิวเมนต์เริ่มต้นคือไม่เปลี่ยนรูปคุณใช้ค่าไม่เปลี่ยนรูปเช่นหรือNone หากคุณต้องการที่จะทำให้ไม่แน่นอนอาร์กิวเมนต์ค่าเริ่มต้นคุณใช้สิ่งที่ไม่แน่นอนเช่น1 []เป็นเพียงความยืดหยุ่นแม้ว่าเป็นที่ยอมรับก็สามารถกัดหากคุณไม่ทราบ


2

ฉันคิดว่าคำตอบที่แท้จริงคือ: Python ถูกเขียนเป็นภาษาที่ใช้ในการดำเนินการและมีการใช้งานเฉพาะด้านหลังจากใช้งานจริง สิ่งที่คุณกำลังมองหาคือการตั้งค่าพารามิเตอร์ให้เป็นการปิดและการปิดใน Python จะถูกทำเพียงครึ่งเดียว สำหรับหลักฐานการทดลองนี้:

a = []
for i in range(3):
    a.append(lambda: i)
print [ f() for f in a ]

ซึ่งให้[2, 2, 2]ที่ที่คุณคาดหวังว่าการปิดที่แท้จริงในการผลิต[0, 1, 2]ที่คุณคาดว่าจะปิดที่แท้จริงในการผลิต

มีหลายสิ่งหลายอย่างที่ฉันต้องการถ้า Python มีความสามารถในการตัดพารามิเตอร์ที่เป็นค่าเริ่มต้นในการปิด ตัวอย่างเช่น:

def foo(a, b=a.b):
    ...

จะมีประโยชน์อย่างยิ่ง แต่ "a" ไม่อยู่ในขอบเขตที่นิยามฟังก์ชันดังนั้นคุณไม่สามารถทำเช่นนั้นและต้องทำ clunky แทน:

def foo(a, b=None):
    if b is None:
        b = a.b

ซึ่งเกือบจะเหมือนกัน ... เกือบ



1

ประโยชน์มากมายคือการบันทึก นี่คือตัวอย่างมาตรฐาน:

def fibmem(a, cache={0:1,1:1}):
    if a in cache: return cache[a]
    res = fib(a-1, cache) + fib(a-2, cache)
    cache[a] = res
    return res

และสำหรับการเปรียบเทียบ:

def fib(a):
    if a == 0 or a == 1: return 1
    return fib(a-1) + fib(a-2)

การวัดเวลาใน ipython:

In [43]: %time print(fibmem(33))
5702887
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 200 µs

In [43]: %time print(fib(33))
5702887
CPU times: user 1.44 s, sys: 15.6 ms, total: 1.45 s
Wall time: 1.43 s

0

มันเกิดขึ้นเนื่องจากการคอมไพล์ใน Python นั้นดำเนินการโดยการรันโค้ดอธิบาย

ถ้าใครบอกว่า

def f(x = {}):
    ....

จะค่อนข้างชัดเจนว่าคุณต้องการอาร์เรย์ใหม่ในแต่ละครั้ง

แต่ถ้าฉันพูดว่า:

list_of_all = {}
def create(stuff, x = list_of_all):
    ...

ที่นี่ฉันจะเดาว่าฉันต้องการสร้างสิ่งต่าง ๆ ลงในรายการต่าง ๆ และมี catch-all แบบโกลบอลเดียวเมื่อฉันไม่ได้ระบุรายการ

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

Python จะเรียกใช้งานโค้ดดังกล่าว list_of_all ตัวแปรได้รับการกำหนดวัตถุไว้แล้วดังนั้นวัตถุนั้นจะถูกส่งผ่านโดยการอ้างอิงไปยังรหัสที่เป็นค่าเริ่มต้น x ในลักษณะเดียวกับที่การเรียกใช้ฟังก์ชันใด ๆ จะได้รับการอ้างอิงไปยังวัตถุท้องถิ่นที่ชื่อที่นี่

หากเราต้องการแยกชื่อที่ไม่มีชื่อออกจากเคสที่มีชื่อนั่นจะเกี่ยวข้องกับรหัสในการรวบรวมการเรียกใช้งานการมอบหมายในวิธีที่แตกต่างอย่างมีนัยสำคัญมากกว่าที่จะถูกเรียกใช้ในเวลาทำงาน ดังนั้นเราไม่ได้ทำกรณีพิเศษ


-5

สิ่งนี้เกิดขึ้นเพราะฟังก์ชั่นใน Python เป็นวัตถุชั้นหนึ่ง :

ค่าพารามิเตอร์เริ่มต้นจะได้รับการประเมินเมื่อมีการดำเนินการกำหนดฟังก์ชัน นี่หมายความว่านิพจน์ได้รับการประเมินหนึ่งครั้งเมื่อมีการกำหนดฟังก์ชันและใช้ค่า“ คำนวณล่วงหน้า” เหมือนกันสำหรับการโทรแต่ละครั้ง

มันอธิบายต่อไปว่าการแก้ไขค่าพารามิเตอร์จะแก้ไขค่าเริ่มต้นสำหรับการโทรครั้งต่อไปและวิธีการแก้ปัญหาอย่างง่าย ๆ ในการใช้ None เป็นค่าเริ่มต้นพร้อมการทดสอบอย่างชัดเจนในส่วนของฟังก์ชั่นการทำงานนั้นเป็นสิ่งที่จำเป็น

ซึ่งหมายความว่า def foo(l=[])จะกลายเป็นอินสแตนซ์ของฟังก์ชั่นนั้นเมื่อถูกเรียกและถูกนำกลับมาใช้ใหม่สำหรับการโทรเพิ่มเติม คิดว่าพารามิเตอร์ฟังก์ชั่นแตกต่างจากคุณลักษณะของวัตถุ

โปรอาจรวมถึงการใช้ประโยชน์จากสิ่งนี้เพื่อให้ชั้นเรียนมีตัวแปรคงที่เช่น C ดังนั้นจึงเป็นการดีที่สุดที่จะประกาศค่าเริ่มต้นไม่มีและเริ่มต้นได้ตามต้องการ:

class Foo(object):
    def bar(self, l=None):
        if not l:
            l = []
        l.append(5)
        return l

f = Foo()
print(f.bar())
print(f.bar())

g = Foo()
print(g.bar())
print(g.bar())

อัตราผลตอบแทน:

[5] [5] [5] [5]

แทนสิ่งที่ไม่คาดคิด:

[5] [5, 5] [5, 5, 5] [5, 5, 5, 5]


5
ไม่คุณสามารถกำหนดฟังก์ชั่น (ชั้นหนึ่งหรือไม่) แตกต่างกันเพื่อประเมินการแสดงออกของอาร์กิวเมนต์เริ่มต้นอีกครั้งสำหรับการโทรแต่ละครั้ง และทุกอย่างหลังจากนั้นคือประมาณ 90% ของคำตอบนั้นอยู่ข้างคำถาม -1

1
ดังนั้นแบ่งปันความรู้เกี่ยวกับวิธีการกำหนดฟังก์ชั่นนี้เพื่อประเมิน ARG เริ่มต้นสำหรับการโทรแต่ละครั้งฉันต้องการทราบวิธีที่ง่ายกว่าPython Docs ที่แนะนำ
กลับรายการ

2
ในระดับการออกแบบภาษาฉันหมายถึง นิยามภาษา Python ระบุว่าอาร์กิวเมนต์เริ่มต้นได้รับการปฏิบัติในลักษณะที่เป็นอยู่ มันสามารถระบุได้อย่างเท่าเทียมกันว่าอาร์กิวเมนต์เริ่มต้นจะได้รับการปฏิบัติด้วยวิธีอื่น IOW คุณกำลังตอบว่า "นั่นคือสิ่งที่เป็น" คำถาม "ทำไมสิ่งต่าง ๆ ในแบบที่พวกเขาเป็น"

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