สำรวจและทำความเข้าใจ Python ผ่านตัวอย่างข้อมูลที่น่าประหลาดใจ
การแปล: จีน 中文 | ภาษาเวียดนาม Tiếng Viết | ภาษาสเปนภาษาสเปน | เกาหลี เกาหลี | รัสเซีย Русский | เยอรมัน เยอรมัน | เพิ่มคำแปล
โหมดอื่นๆ: เว็บไซต์เชิงโต้ตอบ | สมุดบันทึกแบบโต้ตอบ
Python เป็นภาษาโปรแกรมระดับสูงที่ได้รับการออกแบบอย่างสวยงามและใช้ล่าม ทำให้เรามีคุณสมบัติมากมายเพื่อความสะดวกสบายของโปรแกรมเมอร์ แต่บางครั้งผลลัพธ์ของตัวอย่าง Python อาจไม่ชัดเจนตั้งแต่แรกเห็น
นี่เป็นโปรเจ็กต์สนุกๆ ที่พยายามอธิบายสิ่งที่เกิดขึ้นภายใต้ประทุนสำหรับตัวอย่างโค้ดที่ขัดกับสัญชาตญาณและฟีเจอร์ที่ไม่ค่อยมีใครรู้จักใน Python
แม้ว่าตัวอย่างบางส่วนที่คุณเห็นด้านล่างอาจไม่ใช่ WTF ในความหมายที่แท้จริงที่สุด แต่ตัวอย่างเหล่านี้จะเปิดเผยส่วนที่น่าสนใจบางส่วนของ Python ที่คุณอาจไม่รู้ ฉันพบว่ามันเป็นวิธีที่ดีในการเรียนรู้ภายในของภาษาการเขียนโปรแกรม และฉันเชื่อว่าคุณก็จะพบว่ามันน่าสนใจเช่นกัน
หากคุณเป็นโปรแกรมเมอร์ Python ที่มีประสบการณ์ คุณอาจถือเป็นความท้าทายในการทำให้โปรแกรมส่วนใหญ่ถูกต้องในครั้งแรก คุณอาจเคยมีประสบการณ์บางอย่างมาก่อนแล้ว และฉันอาจจะสามารถรื้อฟื้นความทรงจำเก่า ๆ อันแสนหวานของคุณ! -
PS: หากคุณเป็นผู้อ่านที่กลับมา คุณสามารถเรียนรู้เกี่ยวกับการแก้ไขใหม่ได้ที่นี่ (ตัวอย่างที่มีเครื่องหมายดอกจันคือตัวอย่างที่เพิ่มเข้ามาในการแก้ไขหลักครั้งล่าสุด)
เอาล่ะเราไป...
is
ตัวดำเนินการis not ...
ไม่ใช่ is (not ...)
del
ปากแข็งgoto
แต่ทำไม?+=
เร็วขึ้นdict
ช้าลง *dict
s *ตัวอย่างทั้งหมดมีโครงสร้างดังนี้:
▶ ฉายาแฟนซีบางอย่าง
# Set up the code. # Preparation for the magic...เอาต์พุต (เวอร์ชัน Python):
> >> triggering_statement Some unexpected output(ไม่บังคับ): หนึ่งบรรทัดที่อธิบายเอาต์พุตที่ไม่คาดคิด
คำอธิบาย:
- คำอธิบายโดยย่อเกี่ยวกับสิ่งที่เกิดขึ้นและเหตุใดจึงเกิดขึ้น
# Set up code # More examples for further clarification (if necessary)เอาต์พุต (เวอร์ชัน Python):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
หมายเหตุ: ตัวอย่างทั้งหมดได้รับการทดสอบบนล่ามแบบโต้ตอบ Python 3.5.2 และควรใช้ได้กับเวอร์ชัน Python ทั้งหมด เว้นแต่จะระบุไว้อย่างชัดเจนก่อนเอาต์พุต
วิธีที่ดีในการใช้ตัวอย่างเหล่านี้ให้เกิดประโยชน์สูงสุดในความคิดของฉันคือการอ่านตามลำดับและสำหรับทุกตัวอย่าง:
ด้วยเหตุผลบางประการ ตัวดำเนินการ "Walrus" ของ Python 3.8 ( :=
) จึงได้รับความนิยมอย่างมาก ลองตรวจสอบดู
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2.
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
ทบทวนตัวดำเนินการวอลรัสด่วน
ตัวดำเนินการ Walrus ( :=
) เปิดตัวใน Python 3.8 ซึ่งจะมีประโยชน์ในสถานการณ์ที่คุณต้องการกำหนดค่าให้กับตัวแปรภายในนิพจน์
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
เอาท์พุต (> 3.8):
5
5
5
สิ่งนี้จะบันทึกโค้ดหนึ่งบรรทัด และป้องกันการเรียกใช้ some_func
สองครั้งโดยปริยาย
"นิพจน์การกำหนด" ที่ไม่อยู่ในวงเล็บ (การใช้ตัวดำเนินการ walrus) ถูกจำกัดที่ระดับบนสุด ด้วยเหตุนี้ SyntaxError
ในคำสั่ง a := "wtf_walrus"
ของตัวอย่างแรก การใส่วงเล็บทำงานได้ตามที่คาดไว้และมอบหมายให้ a
ตามปกติ ไม่อนุญาตให้ใส่วงเล็บของนิพจน์ที่มีตัวดำเนินการ =
ดังนั้นข้อผิดพลาดทางไวยากรณ์ใน (a, b = 6, 9)
ไวยากรณ์ของตัวดำเนินการ Walrus อยู่ในรูปแบบ NAME:= expr
โดยที่ NAME
เป็นตัวระบุที่ถูกต้อง และ expr
คือนิพจน์ที่ถูกต้อง ดังนั้นจึงไม่รองรับการบรรจุและแกะบรรจุภัณฑ์ซ้ำได้ ซึ่งหมายความว่า
(a := 6, 9)
เทียบเท่ากับ ((a := 6), 9)
และท้ายที่สุด (a, 9)
(โดยที่ค่า a
s คือ 6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
ในทำนองเดียวกัน (a, b := 16, 19)
เทียบเท่ากับ (a, (b := 16), 19)
ซึ่งไม่มีอะไรนอกจาก 3 สิ่งอันดับ
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
เอาต์พุต (< Python3.7 )
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
มันสมเหตุสมผลใช่ไหม?
'wtf'
จะถูกฝึกงาน แต่ ''.join(['w', 't', 'f'])
จะไม่ถูกฝึกงาน)'wtf!'
ไม่ได้ถูกฝึกงาน เนื่องจาก !
- สามารถดูการใช้ CPython ของกฎนี้ได้ ที่นี่ a
และ b
ถูกตั้งค่าเป็น "wtf!"
ในบรรทัดเดียวกัน ตัวแปล Python จะสร้างออบเจ็กต์ใหม่ จากนั้นอ้างอิงตัวแปรตัวที่สองพร้อมกัน ถ้าทำแยกบรรทัดก็ไม่รู้ "รู้" ว่ามี "wtf!"
อยู่แล้ว! เป็นวัตถุ (เพราะ "wtf!"
ไม่ได้ถูกฝึกงานโดยปริยายตามข้อเท็จจริงที่กล่าวถึงข้างต้น) เป็นการเพิ่มประสิทธิภาพเวลาคอมไพล์ การเพิ่มประสิทธิภาพนี้ใช้ไม่ได้กับ CPython เวอร์ชัน 3.7.x (ตรวจสอบปัญหานี้เพื่อดูการสนทนาเพิ่มเติม)a, b = "wtf!", "wtf!"
เป็นคำสั่งเดียว ในขณะที่ a = "wtf!"; b = "wtf!"
เป็นสองคำสั่งในบรรทัดเดียว สิ่งนี้อธิบายว่าทำไมตัวตนถึงแตกต่างกันใน a = "wtf!"; b = "wtf!"
และอธิบายด้วยว่าเหตุใดจึงเหมือนกันเมื่อเรียกใช้ใน some_file.py
'a'*20
จะถูกแทนที่ด้วย 'aaaaaaaaaaaaaaaaaaaa'
ในระหว่างการคอมไพล์เพื่อบันทึกรอบนาฬิกาสองสามรอบระหว่างรันไทม์ การพับอย่างต่อเนื่องเกิดขึ้นเฉพาะกับสตริงที่มีความยาวน้อยกว่า 21 เท่านั้น (เพราะเหตุใด ลองนึกภาพขนาดของไฟล์ .pyc
ที่สร้างขึ้นจากนิพจน์ 'a'*10**10
) นี่คือแหล่งที่มาของการนำไปปฏิบัติสำหรับสิ่งเดียวกัน > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> True is False == False
False
> >> False is False is False
True
> >> 1 > 0 < 1
True
> >> ( 1 > 0 ) < 1
False
> >> 1 > ( 0 < 1 )
False
ตามhttps://docs.python.org/3/reference/expressions.html#comparisons
อย่างเป็นทางการ ถ้า a, b, c, ..., y, z เป็นนิพจน์ และ op1, op2, ..., opN เป็นตัวดำเนินการเปรียบเทียบ ดังนั้น op1 b op2 c ... y opN z จะเท่ากับ op1 b และ b op2 c และ ... y opN z ยกเว้นว่าแต่ละนิพจน์จะได้รับการประเมินมากที่สุดหนึ่งครั้ง
แม้ว่าพฤติกรรมดังกล่าวอาจดูงี่เง่าสำหรับคุณในตัวอย่างข้างต้น แต่มันก็ยอดเยี่ยมมากกับข้อความอย่าง a == b == c
และ 0 <= x <= 100
False is False is False
เทียบเท่ากับ (False is False) and (False is False)
True is False == False
เทียบเท่ากับ (True is False) and (False == False)
และเนื่องจากส่วนแรกของคำสั่ง ( True is False
) ประเมินเป็น False
นิพจน์โดยรวมจึงประเมินเป็น False
1 > 0 < 1
เทียบเท่ากับ (1 > 0) and (0 < 1)
ซึ่งประเมินเป็น True
(1 > 0) < 1
เทียบเท่ากับ True < 1
และ > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
ประเมินเป็น False
is
ตัวดำเนินการต่อไปนี้เป็นตัวอย่างที่มีชื่อเสียงมากที่ปรากฏอยู่ทั่วอินเทอร์เน็ต
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3. เอาท์พุต
> >> a , b = 257 , 257
> >> a is b
True
เอาต์พุต (โดยเฉพาะ Python 3.7.x)
> >> a , b = 257 , 257
> >> a is b
False
ความแตกต่างระหว่าง is
และ ==
is
ตัวดำเนินการตรวจสอบว่าตัวถูกดำเนินการทั้งสองอ้างถึงวัตถุเดียวกันหรือไม่ (เช่น จะตรวจสอบว่าตัวถูกดำเนินการตรงกันหรือไม่)==
เปอเรเตอร์เปรียบเทียบค่าของทั้งโอเปอเรเตอร์และตรวจสอบว่ามีค่าเท่ากันหรือไม่is
ความเท่าเทียมกันในการอ้างอิง และ ==
คือความเท่าเทียมกันของค่า ตัวอย่างเพื่อเคลียร์สิ่งต่าง ๆ > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
เป็นวัตถุที่มีอยู่ แต่ 257
ไม่ใช่
เมื่อคุณเริ่มต้น python ตัวเลขตั้งแต่ -5
ถึง 256
จะถูกจัดสรร ตัวเลขเหล่านี้มีการใช้กันมาก ดังนั้นจึงควรเตรียมตัวเลขเหล่านี้ให้พร้อม
อ้างอิงจาก https://docs.python.org/3/c-api/long.html
การใช้งานปัจจุบันจะเก็บอาร์เรย์ของอ็อบเจ็กต์จำนวนเต็มสำหรับจำนวนเต็มทั้งหมดระหว่าง -5 ถึง 256 เมื่อคุณสร้าง int ในช่วงนั้น คุณเพียงแค่ได้รับการอ้างอิงกลับไปยังอ็อบเจ็กต์ที่มีอยู่ ดังนั้นจึงควรเป็นไปได้ที่จะเปลี่ยนค่า 1 ฉันสงสัยว่าพฤติกรรมของ Python ในกรณีนี้คือไม่ได้กำหนดไว้ -
> >> id ( 256 )
10922528
> >> a = 256
> >> b = 256
> >> id ( a )
10922528
> >> id ( b )
10922528
> >> id ( 257 )
140084850247312
> >> x = 257
> >> y = 257
> >> id ( x )
140084850247440
> >> id ( y )
140084850247344
ที่นี่ล่ามไม่ฉลาดพอที่จะดำเนินการ y = 257
เพื่อรับรู้ว่าเราได้สร้างจำนวนเต็มของค่า 257,
แล้ว ดังนั้นมันจึงสร้างวัตถุอื่นในหน่วยความจำต่อไป
การเพิ่มประสิทธิภาพที่คล้ายกันนี้ใช้กับวัตถุ ที่ไม่เปลี่ยนรูป อื่นๆ เช่น สิ่งอันดับว่างเช่นกัน เนื่องจากรายการไม่แน่นอน นั่นเป็นสาเหตุที่ [] is []
จะส่งกลับ False
และ () is ()
จะส่งกลับ True
นี่เป็นการอธิบายตัวอย่างที่สองของเรา เรามาต่อกันที่อันที่สามกันดีกว่า
ทั้ง a
และ b
อ้างถึงวัตถุเดียวกันเมื่อเตรียมใช้งานด้วยค่าเดียวกันในบรรทัดเดียวกัน
เอาท์พุต
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
เมื่อ a และ b ถูกตั้งค่าเป็น 257
ในบรรทัดเดียวกัน ตัวแปล Python จะสร้างอ็อบเจ็กต์ใหม่ จากนั้นอ้างอิงตัวแปรตัวที่สองพร้อมกัน หากคุณแยกบรรทัดกัน มันจะไม่รู้ว่ามี 257
เป็นอ็อบเจ็กต์อยู่แล้ว
เป็นการเพิ่มประสิทธิภาพคอมไพเลอร์และใช้กับสภาพแวดล้อมแบบโต้ตอบโดยเฉพาะ เมื่อคุณป้อนสองบรรทัดในล่ามสด บรรทัดเหล่านั้นจะถูกรวบรวมแยกกัน ดังนั้นจึงมีการปรับปรุงแยกกัน หากคุณลองตัวอย่างนี้ในไฟล์ .py
คุณจะไม่เห็นลักษณะการทำงานเหมือนเดิม เนื่องจากไฟล์จะถูกคอมไพล์ทั้งหมดในครั้งเดียว การเพิ่มประสิทธิภาพนี้ไม่ได้จำกัดอยู่เพียงจำนวนเต็ม แต่จะใช้ได้กับประเภทข้อมูลที่ไม่เปลี่ยนรูปอื่นๆ เช่น สตริง (ตรวจสอบ "ตัวอย่างสตริงที่ยุ่งยาก") และลอยเช่นกัน
> >> a , b = 257.0 , 257.0
> >> a is b
True
เหตุใดจึงใช้ไม่ได้กับ Python 3.7 เหตุผลที่เป็นนามธรรมคือเนื่องจากการเพิ่มประสิทธิภาพคอมไพเลอร์ดังกล่าวเป็นการใช้งานเฉพาะ (เช่น อาจเปลี่ยนแปลงไปตามเวอร์ชัน ระบบปฏิบัติการ ฯลฯ) ฉันยังคงทราบว่าการเปลี่ยนแปลงการใช้งานที่แน่นอนใดที่ทำให้เกิดปัญหา คุณสามารถตรวจสอบปัญหานี้เพื่อรับการอัปเดตได้
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
เอาท์พุท:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
แล้วทำไม Python ถึงแพร่หลายไปทั่ว?
ความเป็นเอกลักษณ์ของคีย์ในพจนานุกรม Python นั้นมาจาก ความเท่าเทียมกัน ไม่ใช่เอกลักษณ์ ดังนั้นแม้ว่า 5
, 5.0
และ 5 + 0j
จะเป็นวัตถุที่แตกต่างกันประเภทที่แตกต่างกัน เนื่องจากพวกมันเท่ากัน พวกมันจึงไม่สามารถอยู่ใน dict
เดียวกันได้ (หรือ set
) ทันทีที่คุณแทรกรายการใดรายการหนึ่ง การพยายามค้นหาคีย์ที่แตกต่างแต่เทียบเท่าจะประสบความสำเร็จด้วยค่าที่แมปดั้งเดิม (แทนที่จะล้มเหลวด้วย KeyError
):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
สิ่งนี้ใช้เมื่อตั้งค่ารายการด้วย ดังนั้นเมื่อคุณทำ some_dict[5] = "Python"
Python จะค้นหารายการที่มีอยู่ด้วยคีย์ที่เทียบเท่า 5.0 -> "Ruby"
เขียนทับค่าของมันในตำแหน่งและปล่อยคีย์ดั้งเดิมไว้ตามลำพัง
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
แล้วเราจะอัพเดตคีย์เป็น 5
(แทน 5.0
) ได้อย่างไร? เราไม่สามารถทำการอัปเดตนี้ได้จริง ๆ แต่สิ่งที่เราทำได้คือลบคีย์ก่อน ( del some_dict[5.0]
) จากนั้นตั้งค่า ( some_dict[5]
) เพื่อรับจำนวนเต็ม 5
เป็นคีย์แทนที่จะลอย 5.0
แม้ว่าสิ่งนี้ควรจะจำเป็นในบางกรณีก็ตาม
Python ค้นหา 5
ในพจนานุกรมที่มี 5.0
ได้อย่างไร Python ทำสิ่งนี้ในเวลาคงที่โดยไม่ต้องสแกนทุกรายการโดยใช้ฟังก์ชันแฮช เมื่อ Python ค้นหาคีย์ foo
ใน dict ก่อนอื่นมันจะคำนวณ hash(foo)
(ซึ่งทำงานในเวลาคงที่) เนื่องจากใน Python วัตถุที่เปรียบเทียบเท่ากันจะต้องมีค่าแฮชเดียวกันด้วย (เอกสารที่นี่) 5
, 5.0
และ 5 + 0j
จะต้องมีค่าแฮชเท่ากัน
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
หมายเหตุ: ค่าผกผันไม่จำเป็นต้องเป็นจริง: ออบเจ็กต์ที่มีค่าแฮชเท่ากันอาจไม่เท่ากัน (ซึ่งทำให้เกิดสิ่งที่เรียกว่าการชนกันของแฮช และลดประสิทธิภาพการทำงานตามเวลาคงที่ที่การแฮชมักจะให้)
class WTF :
pass
เอาท์พุท:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
เมื่อ id
ถูกเรียก Python จะสร้างอ็อบเจ็กต์คลาส WTF
และส่งผ่านไปยังฟังก์ชัน id
ฟังก์ชัน id
รับ id
(ตำแหน่งหน่วยความจำ) และโยนวัตถุออกไป วัตถุถูกทำลาย
เมื่อเราทำเช่นนี้สองครั้งติดต่อกัน Python จะจัดสรรตำแหน่งหน่วยความจำเดียวกันให้กับอ็อบเจ็กต์ที่สองนี้เช่นกัน เนื่องจาก (ใน CPython) id
ใช้ตำแหน่งหน่วยความจำเป็น id วัตถุ รหัสของทั้งสองวัตถุจึงเหมือนกัน
ดังนั้น ID ของวัตถุจะไม่ซ้ำกันตลอดอายุของวัตถุเท่านั้น หลังจากที่วัตถุถูกทำลายหรือก่อนที่จะถูกสร้างขึ้น สิ่งอื่นสามารถมีรหัสเดียวกันได้
แต่เหตุใดตัวดำเนินการ is
จึงประเมินเป็น False
มาดูตัวอย่างนี้กัน
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
เอาท์พุท:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
ดังที่คุณอาจสังเกตเห็น ลำดับที่วัตถุถูกทำลายคือสิ่งที่สร้างความแตกต่างทั้งหมดที่นี่
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
เอาท์พุต
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
เกิดอะไรขึ้นที่นี่?
สาเหตุที่ความเสมอภาคแบบอกรรมกริยาไม่อยู่ใน dictionary
, ordered_dict
และ another_ordered_dict
เป็นเพราะวิธีการ __eq__
ถูกนำมาใช้ในคลาส OrderedDict
จากเอกสาร
การทดสอบความเท่าเทียมกันระหว่างออบเจ็กต์ OrderedDict นั้นไวต่อคำสั่งซื้อและนำไปใช้เป็น
list(od1.items())==list(od2.items())
การทดสอบความเท่าเทียมกันระหว่างออบเจ็กต์OrderedDict
และออบเจ็กต์การแม็ปอื่นๆ จะไม่คำนึงถึงลำดับเหมือนพจนานุกรมทั่วไป
เหตุผลของพฤติกรรมที่เท่าเทียมกันก็คือ ช่วยให้สามารถแทนที่ออบเจ็กต์ OrderedDict
ได้โดยตรงทุกที่ที่ใช้พจนานุกรมทั่วไป
โอเค แล้วเหตุใดการเปลี่ยนแปลงลำดับจึงส่งผลต่อความยาวของชุด set
เจ็กต์ที่สร้างขึ้น คำตอบคือการขาดความเท่าเทียมกันแบบอกรรมกริยาเท่านั้น เนื่องจากชุดต่างๆ เป็นคอลเลกชันขององค์ประกอบที่ไม่ซ้ำกัน "ไม่เรียงลำดับ" ลำดับที่แทรกองค์ประกอบจึงไม่สำคัญ แต่ในกรณีนี้มันไม่สำคัญ เรามาแตกประเด็นกันสักหน่อย
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
ดังนั้นความไม่สอดคล้องกันจึงเกิดจากการที่ another_ordered_dict in another_set
เป็น False
เนื่องจาก ordered_dict
มีอยู่แล้วใน another_set
และดังที่สังเกตไว้ก่อนหน้านี้ ordered_dict == another_ordered_dict
is False
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
เอาท์พุท:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
return
, break
หรือ continue
ถูกดำเนินการในชุดคำ try
ของคำสั่ง "try…finally" ส่วนคำสั่ง finally
ก็จะถูกดำเนินการเมื่อทางออกเช่นกันreturn
ล่าสุดที่ดำเนินการ เนื่องจากคำสั่ง finally
จะถูกดำเนินการเสมอ ดังนั้นคำสั่ง return
ที่ถูกดำเนินการในคำสั่ง finally
จึงจะเป็นคำสั่งสุดท้ายที่ถูกดำเนินการเสมอreturn
หรือ break
ข้อยกเว้นที่บันทึกไว้ชั่วคราวจะถูกยกเลิก some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
เอาท์พุท:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
คำสั่ง for
ถูกกำหนดไว้ในไวยากรณ์ Python ดังนี้:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
โดยที่ exprlist
คือเป้าหมายการมอบหมาย ซึ่งหมายความว่าค่าที่เทียบเท่าของ {exprlist} = {next_value}
จะ ถูกดำเนินการสำหรับแต่ละรายการ ในการวนซ้ำ ตัวอย่างที่น่าสนใจที่แสดงให้เห็นสิ่งนี้:
for i in range ( 4 ):
print ( i )
i = 10
เอาท์พุท:
0
1
2
3
คุณคาดหวังว่าลูปจะทำงานเพียงครั้งเดียวหรือไม่?
คำอธิบาย:
i = 10
จะไม่ส่งผลต่อการวนซ้ำของลูป เนื่องจากวิธีการทำงานของลูปใน Python ก่อนที่จะเริ่มการวนซ้ำทุกครั้ง รายการถัดไปที่ตัววนซ้ำ ( range(4)
ในกรณีนี้) จะถูกแตกออกและกำหนดตัวแปรรายการเป้าหมาย ( i
ในกรณีนี้) ฟังก์ชัน enumerate(some_string)
ให้ค่าใหม่ i
(ตัวนับขึ้นไป) และอักขระจาก some_string
ในการวนซ้ำแต่ละครั้ง จากนั้นจะตั้งค่าคีย์ (เพิ่งกำหนด) i
ของพจนานุกรม some_dict
ให้เป็นอักขระนั้น การคลายลูปสามารถทำได้ง่ายขึ้นดังนี้:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
เอาท์พุท:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
เอาท์พุท:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
เอาท์พุท:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
ในนิพจน์ตัวสร้าง in
clause จะถูกประเมิน ณ เวลาประกาศ แต่ส่วนคำสั่งแบบมีเงื่อนไขจะถูกประเมิน ณ รันไทม์
ดังนั้นก่อนรันไทม์ array
จะถูกกำหนดให้กับรายการอีกครั้ง [2, 8, 22]
และเนื่องจากจาก 1
, 8
และ 15
มีเพียงการนับของ 8
เท่านั้นที่มากกว่า 0
ตัวสร้างจะให้ผลเพียง 8
เท่านั้น
ความแตกต่างในเอาต์พุตของ g1
และ g2
ในส่วนที่สองเกิดจากการที่ตัวแปร array_1
และ array_2
ถูกกำหนดค่าใหม่
ในกรณีแรก array_1
ถูกผูกไว้กับอ็อบเจ็กต์ใหม่ [1,2,3,4,5]
และเนื่องจาก in
clause ได้รับการประเมิน ณ เวลาประกาศ มันจึงยังคงอ้างถึงอ็อบเจ็กต์เก่า [1,2,3,4]
(ซึ่งไม่ถูกทำลาย)
ในกรณีที่สอง การกำหนดสไลซ์ให้กับ array_2
จะอัพเดตออบเจ็กต์เก่าเดียวกัน [1,2,3,4]
เป็น [1,2,3,4,5]
ดังนั้นทั้ง g2
และ array_2
ยังคงมีการอ้างอิงถึงวัตถุเดียวกัน (ซึ่งขณะนี้ได้รับการอัปเดตเป็น [1,2,3,4,5]
)
โอเค ตามตรรกะที่กล่าวถึงไปแล้ว ไม่ควรมีค่าของ list(gen)
ในตัวอย่างที่สาม [11, 21, 31, 12, 22, 32, 13, 23, 33]
? (เพราะ array_3
และ array_4
จะทำงานเหมือนกับ array_1
) สาเหตุที่ค่า (เท่านั้น) array_4
ได้รับการอัปเดตอธิบายไว้ใน PEP-289
เฉพาะนิพจน์ภายนอกสุดเท่านั้นที่จะได้รับการประเมินทันที ส่วนนิพจน์อื่นๆ จะถูกเลื่อนออกไปจนกว่าตัวสร้างจะทำงาน
is not ...
ไม่ใช่ is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
เป็นตัวดำเนินการไบนารีตัวเดียว และมีพฤติกรรมที่แตกต่างไปจากการใช้ is
และ not
แยกออกจากกันis not
ประเมินเป็น False
หากตัวแปรในด้านใดด้านหนึ่งของตัวดำเนินการชี้ไปที่วัตถุเดียวกันและ True
มิฉะนั้น(not None)
ประเมินค่าเป็น True
เนื่องจากค่า None
เป็น False
ในบริบทบูลีน ดังนั้นนิพจน์จึงกลายเป็น 'something' is True
# Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
เอาท์พุท:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
เราไม่ได้กำหนด "X"
สามตัวใช่ไหม?
เมื่อเราเริ่มต้นตัวแปร row
การแสดงภาพนี้จะอธิบายสิ่งที่เกิดขึ้นในหน่วยความจำ
และเมื่อ board
เริ่มต้นโดยการคูณ row
นี่คือสิ่งที่เกิดขึ้นภายในหน่วยความจำ (แต่ละองค์ประกอบ board[0]
, board[1]
และ board[2]
เป็นการอ้างอิงถึงรายการเดียวกันที่อ้างอิงโดย row
)
เราสามารถหลีกเลี่ยงสถานการณ์นี้ได้ที่นี่โดยไม่ใช้ตัวแปร row
เพื่อสร้าง board
(ถามในฉบับนี้).
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
เอาต์พุต (เวอร์ชัน Python):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
ค่าของ x
แตกต่างกันในการวนซ้ำทุกครั้งก่อนที่จะผนวก some_func
เข้ากับ funcs
แต่ฟังก์ชันทั้งหมดส่งคืน 6 เมื่อได้รับการประเมินหลังจากการวนซ้ำเสร็จสมบูรณ์
> >> powers_of_x = [ lambda x : x ** i for i in range ( 10 )]
> >> [ f ( 2 ) for f in powers_of_x ]
[ 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 ]
x
ในบริบทโดยรอบ แทนที่จะใช้ค่า x
ณ เวลาที่สร้างฟังก์ชัน ดังนั้นฟังก์ชันทั้งหมดจึงใช้ค่าล่าสุดที่กำหนดให้กับตัวแปรในการคำนวณ เราจะเห็นว่ามันใช้ x
จากบริบทโดยรอบ (เช่น ไม่ใช่ ตัวแปรท้องถิ่น) กับ: > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
เนื่องจาก x
เป็นค่าโกลบอล เราสามารถเปลี่ยนค่าที่ funcs
จะค้นหาและส่งกลับโดยการอัปเดต x
:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
ณ เวลานั้น funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
เอาท์พุท:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
ไม่ได้ใช้ x
ในขอบเขตส่วนกลางอีกต่อไป:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
แล้วคลาสพื้นฐาน "ขั้นสูงสุด" คืออะไร? มีความสับสนมากกว่านั้น
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
เป็น metaclass ใน Pythonobject
ใน Python ซึ่งรวมถึงคลาสและวัตถุ (อินสแตนซ์)type
คือ metaclass ของ class object
และทุกคลาส (รวมถึง type
) ได้รับการสืบทอดโดยตรงหรือโดยอ้อมจาก object
object
และ type
ความสับสนในตัวอย่างข้างต้นเกิดขึ้นเนื่องจากเรากำลังคิดถึงความสัมพันธ์เหล่านี้ ( issubclass
และ isinstance
) ในแง่ของคลาส Python ความสัมพันธ์ระหว่าง object
และ type
ไม่สามารถทำซ้ำในหลามบริสุทธิ์ เพื่อให้แม่นยำยิ่งขึ้นความสัมพันธ์ต่อไปนี้ไม่สามารถทำซ้ำใน Python ล้วนๆ ได้object
และ type
(ทั้งที่เป็นอินสแตนซ์ของกันและกัน) มีอยู่ใน Python เนื่องจาก "การโกง" ในระดับการใช้งานเอาท์พุท:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
ความสัมพันธ์ของคลาสย่อยนั้นคาดว่าจะเป็นแบบสกรรมกริยาใช่ไหม? (เช่น ถ้า A
เป็นคลาสย่อยของ B
และ B
เป็นคลาสย่อยของ C
แล้ว A
ก็ควรเป็น คลาสย่อยของ C
)
__subclasscheck__
ของตนเองโดยพลการในเมตาคลาสissubclass(cls, Hashable)
ถูกเรียก มันจะค้นหาเมธอด non-Falsey " __hash__
" ใน cls
หรืออะไรก็ตามที่สืบทอดมาobject
สามารถแฮชได้ แต่ list
ไม่สามารถแฮชได้ จึงเป็นการทำลายความสัมพันธ์ระหว่างการถ่ายทอด class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
เอาท์พุท:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
การเข้าถึง classm
สองครั้ง เราได้วัตถุเท่ากัน แต่ไม่ เหมือนกัน ใช่ไหม มาดูกันว่าเกิดอะไรขึ้นกับอินสแตนซ์ของ SomeClass
:
o1 = SomeClass ()
o2 = SomeClass ()
เอาท์พุท:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
การเข้าถึง classm
หรือ method
สองครั้ง สร้างวัตถุที่เท่ากันแต่ไม่ เหมือนกัน สำหรับอินสแตนซ์เดียวกันของ SomeClass
self
เป็นอาร์กิวเมนต์แรก แม้ว่าจะไม่ส่งผ่านอย่างชัดเจนก็ตาม) > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
ไม่เคยเป็นความจริง การเข้าถึงฟังก์ชันเป็นคุณลักษณะของคลาส (ตรงข้ามกับอินสแตนซ์) ไม่ได้สร้างเมธอด อย่างไรก็ตาม ดังนั้น SomeClass.method is SomeClass.method
นั้นเป็นความจริง > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
แปลงฟังก์ชันให้เป็นวิธีการเรียน เมธอดคลาสเป็นตัวอธิบายที่เมื่อเข้าถึงแล้ว ให้สร้างอ็อบเจ็กต์เมธอดซึ่งผูก คลาส (ประเภท) ของอ็อบเจ็กต์ แทนที่จะเป็นอ็อบเจ็กต์นั้นเอง > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
s จะสร้างเมธอดเมื่อเข้าถึงเป็นแอตทริบิวต์ของคลาสด้วย (ซึ่งในกรณีนี้จะผูกคลาส ไม่ใช่กับประเภทของคลาสนั้น) ดังนั้น SomeClass.classm is SomeClass.classm
เป็นเท็จ > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
จึงเป็นความจริง แม้ว่าจะไม่ใช่วัตถุเดียวกันในหน่วยความจำก็ตามstaticmethod
แปลงฟังก์ชันให้เป็น descriptor "no-op" ซึ่งจะส่งคืนฟังก์ชันตามที่เป็นอยู่ ไม่เคยมีการสร้างอ็อบเจ็กต์เมธอด ดังนั้นการเปรียบเทียบกับ is
จึงเป็นความจริง > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
ได้ไม่ดี CPython 3.7 แก้ไขได้โดยการแนะนำ opcodes ใหม่ที่เกี่ยวข้องกับวิธีการเรียกโดยไม่ต้องสร้างวัตถุวิธีการชั่วคราว ใช้เฉพาะเมื่อมีการเรียกใช้ฟังก์ชันที่เข้าถึงจริงเท่านั้น ดังนั้นตัวอย่างข้อมูลที่นี่จะไม่ได้รับผลกระทบ และยังคงสร้างวิธีการ :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
เหตุใดจึงมีการเปลี่ยนแปลง True-False นี้
การใช้งานฟังก์ชั่น all
จะเทียบเท่ากับ
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
คืนค่า True
เนื่องจากค่าที่ทำซ้ำได้นั้นว่างเปล่า
all([[]])
คืนค่า False
เนื่องจากอาร์เรย์ที่ส่งผ่านมีองค์ประกอบเดียว []
และใน python รายการว่างถือเป็นเท็จ
all([[[]]])
และรูปแบบการเรียกซ้ำที่สูงกว่าจะเป็น True
เสมอ เนื่องจากองค์ประกอบเดี่ยวของอาร์เรย์ที่ส่งผ่าน ( [[...]]
) ไม่ว่างเปล่าอีกต่อไป และรายการที่มีค่าเป็นความจริง
เอาท์พุต (< 3.6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
เอาท์พุท:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
) แบ็กสแลชจะผ่านตัวเองไปพร้อมกับลักษณะการทำงานของการหลีกอักขระต่อไปนี้ > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
) แบ็กสแลชหนีจากเครื่องหมายคำพูดต่อท้าย ปล่อยให้ parser โดยไม่มีเครื่องหมายคำพูดยุติ (ด้วยเหตุนี้ SyntaxError
) นั่นเป็นสาเหตุที่เครื่องหมายแบ็กสแลชไม่ทำงานที่ส่วนท้ายของสตริงดิบ x = True
y = False
เอาท์พุท:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
มีลำดับความสำคัญสูงกว่าตัวดำเนิน not
ใน Pythonnot x == y
จึงเทียบเท่ากับ not (x == y)
ซึ่งเทียบเท่ากับ not (True == False)
ในที่สุดก็ประเมิน True
x == not y
ทำให้เกิด SyntaxError
เนื่องจากสามารถคิดได้ว่าเทียบเท่ากับ (x == not) y
และไม่ใช่ x == (not y)
ซึ่งคุณอาจคาดหวังตั้งแต่แรกเห็นnot
จะเป็นส่วนหนึ่งของตัวดำเนินการ not in
(เนื่องจากทั้ง ==
และ not in
ตัวดำเนินการมีลำดับความสำคัญเท่ากัน) แต่หลังจากไม่พบโทเค็น in
ที่ตามหลังโทเค็น not
จะทำให้เกิด SyntaxError
เอาท์พุท:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
และ """
ยังเป็นตัวคั่นสตริงใน Python ซึ่งทำให้เกิด SyntaxError เนื่องจากล่าม Python คาดหวังว่าเครื่องหมายคำพูดสามตัวจะสิ้นสุดเป็นตัวคั่นในขณะที่สแกนตัวอักษรสตริงเครื่องหมายคำพูดสามตัวที่พบในปัจจุบัน1.
# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [ False , 1.0 , "some_string" , 3 , True , [], False ]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list :
if isinstance ( item , int ):
integers_found_so_far += 1
elif isinstance ( item , bool ):
booleans_found_so_far += 1
เอาท์พุท:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
เอาท์พุต (< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
เป็นคลาสย่อยของ int
ใน Python
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
ดังนั้น True
และ False
จึงเป็นอินสแตนซ์ของ int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
ค่าจำนวนเต็มของ True
คือ 1
และค่าของ False
คือ 0
> >> int ( True )
1
> >> int ( False )
0
ดูคำตอบ StackOverflow นี้สำหรับเหตุผลที่อยู่เบื้องหลัง
ในตอนแรก Python ไม่เคยมีประเภท bool
(คนใช้ 0 สำหรับค่าเท็จ และค่าที่ไม่ใช่ศูนย์ เช่น 1 สำหรับค่าจริง) True
, False
, และประเภท bool
ถูกเพิ่มเข้ามาในเวอร์ชัน 2.x แต่สำหรับความเข้ากันได้แบบย้อนหลัง ไม่สามารถสร้างค่าคงที่ True
และ False
ได้ พวกมันเป็นเพียงตัวแปรในตัว และเป็นไปได้ที่จะกำหนดพวกมันใหม่
Python 3 เข้ากันไม่ได้แบบย้อนหลัง ในที่สุดปัญหาก็ได้รับการแก้ไขแล้ว ดังนั้นตัวอย่างสุดท้ายจึงใช้งานไม่ได้กับ Python 3.x!
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
เอาท์พุท:
> >> A . x , B . x , C . x
( 1 , 1 , 1 )
>> > B . x = 2
>> > A . x , B . x , C . x
( 1 , 2 , 1 )
>> > A . x = 3
>> > A . x , B . x , C . x # C.x changed, but B.x didn't
( 3 , 2 , 3 )
>> > a = A ()
>> > a . x , A . x
( 3 , 3 )
>> > a . x + = 1
>> > a . x , A . x
( 4 , 3 )
2.
class SomeClass :
some_var = 15
some_list = [ 5 ]
another_list = [ 5 ]
def __init__ ( self , x ):
self . some_var = x + 1
self . some_list = self . some_list + [ x ]
self . another_list += [ x ]
เอาท์พุท:
> >> some_obj = SomeClass ( 420 )
> >> some_obj . some_list
[ 5 , 420 ]
> >> some_obj . another_list
[ 5 , 420 ]
> >> another_obj = SomeClass ( 111 )
> >> another_obj . some_list
[ 5 , 111 ]
> >> another_obj . another_list
[ 5 , 420 , 111 ]
> >> another_obj . another_list is SomeClass . another_list
True
> >> another_obj . another_list is some_obj . another_list
True
+=
แก้ไขวัตถุที่ไม่แน่นอนโดยไม่ต้องสร้างวัตถุใหม่ ดังนั้นการเปลี่ยนแอตทริบิวต์ของอินสแตนซ์หนึ่งจะส่งผลต่ออินสแตนซ์อื่นและแอตทริบิวต์ของคลาสด้วย some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
เอาท์พุต (<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
ของ CPython ในเครื่องกำเนิดและความเข้าใจyield
อยู่ในรายการความเข้าใจอีกต่อไป และจะส่ง SyntaxError
1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
เอาท์พุต (> 3.3):
> >> list ( some_func ( 3 ))
[]
"wtf"
ไปไหน? เป็นเพราะผลพิเศษบางประการของ yield from
? มาตรวจสอบกันว่า
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
เอาท์พุท:
> >> list ( some_func ( 3 ))
[]
ผลลัพธ์เดียวกัน นี่ก็ใช้งานไม่ได้เช่นกัน
return
พร้อมค่าภายในตัวสร้างได้ (ดู PEP380) เอกสารอย่างเป็นทางการบอกว่า"...
return expr
ในเครื่องกำเนิดไฟฟ้าทำให้StopIteration(expr)
เพิ่มขึ้นเมื่อออกจากเครื่องกำเนิด"
ในกรณีของ some_func(3)
StopIteration
จะถูกยกขึ้นที่จุดเริ่มต้นเนื่องจากคำสั่ง return
ข้อยกเว้น StopIteration
จะถูกตรวจจับโดยอัตโนมัติภายใน wrapper list(...)
และ for
loop ดังนั้น ตัวอย่างสองรายการข้างต้นจึงส่งผลให้รายการว่างเปล่า
ในการรับ ["wtf"]
จากตัวสร้าง some_func
เราจำเป็นต้องจับข้อยกเว้น StopIteration
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
a = float ( 'inf' )
b = float ( 'nan' )
c = float ( '-iNf' ) # These strings are case-insensitive
d = float ( 'nan' )
เอาท์พุท:
> >> a
inf
> >> b
nan
> >> c
- inf
> >> float ( 'some_other_string' )
ValueError : could not convert string to float : some_other_string
> >> a == - c # inf==inf
True
> >> None == None # None == None
True
> >> b == d # but nan!=nan
False
> >> 50 / a
0.0
> >> a / a
nan
> >> 23 + b
nan
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
และ 'nan'
เป็นสตริงพิเศษ (ไม่คำนึงถึงขนาดตัวพิมพ์) ซึ่งเมื่อระบุอย่างชัดเจนถึงประเภท typecast-ed ถึง float
จะใช้เพื่อแสดงค่าทางคณิตศาสตร์ "อนันต์" และ "ไม่ใช่ตัวเลข" ตามลำดับ
เนื่องจากตามมาตรฐาน IEEE NaN != NaN
การเชื่อฟังกฎนี้จะทำลายสมมติฐานการสะท้อนกลับขององค์ประกอบคอลเลกชันใน Python เช่นหาก x
เป็นส่วนหนึ่งของคอลเลกชันที่เหมือน list
การใช้งานเช่นการเปรียบเทียบจะขึ้นอยู่กับสมมติฐานที่ว่า x == x
เนื่องจากสมมติฐานนี้ ข้อมูลประจำตัวจะถูกเปรียบเทียบก่อน (เนื่องจากเร็วกว่า) ในขณะที่เปรียบเทียบสององค์ประกอบ และค่าจะถูกเปรียบเทียบเมื่อข้อมูลประจำตัวไม่ตรงกันเท่านั้น ตัวอย่างต่อไปนี้จะทำให้สิ่งต่าง ๆ ชัดเจนยิ่งขึ้น
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
เนื่องจากเอกลักษณ์ของ x
และ y
แตกต่างกัน ค่าจึงถูกนำมาพิจารณา ซึ่งก็แตกต่างกันเช่นกัน ดังนั้นการเปรียบเทียบจึงคืนค่า False
ในครั้งนี้
อ่านที่น่าสนใจ: การสะท้อนกลับ และเสาหลักอื่นๆ ของอารยธรรม
นี่อาจดูเล็กน้อยหากคุณรู้ว่าการอ้างอิงทำงานอย่างไรใน Python
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
เอาท์พุท:
> >> some_tuple [ 2 ] = "change this"
TypeError : 'tuple' object does not support item assignment
> >> another_tuple [ 2 ]. append ( 1000 ) #This throws no error
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 ])
> >> another_tuple [ 2 ] += [ 99 , 999 ]
TypeError : 'tuple' object does not support item assignment
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 , 99 , 999 ])
แต่ฉันคิดว่าสิ่งอันดับไม่เปลี่ยนรูป ...
อ้างอิงจาก https://docs.python.org/3/reference/datamodel.html
ลำดับที่ไม่เปลี่ยนรูป ออบเจ็กต์ประเภทลำดับที่ไม่เปลี่ยนรูปไม่สามารถเปลี่ยนแปลงได้เมื่อถูกสร้างขึ้น (หากวัตถุมีการอ้างอิงไปยังวัตถุอื่น วัตถุอื่น ๆ เหล่านี้อาจไม่แน่นอนและอาจได้รับการแก้ไข อย่างไรก็ตาม คอลเลกชันของวัตถุที่อ้างอิงโดยตรงโดยวัตถุที่ไม่เปลี่ยนรูปจะไม่สามารถเปลี่ยนแปลงได้)
+=
ตัวดำเนินการเปลี่ยนรายการแทนที่ การกำหนดรายการไม่ทำงาน แต่เมื่อเกิดข้อยกเว้น รายการดังกล่าวได้ถูกเปลี่ยนแปลงแล้ว
นอกจากนี้ยังมีคำอธิบายใน Python FAQ อย่างเป็นทางการ
e = 7
try :
raise Exception ()
except Exception as e :
pass
เอาต์พุต (Python 2.x):
> >> print ( e )
# prints nothing
เอาต์พุต (Python 3.x):
> >> print ( e )
NameError : name 'e' is not defined
ที่มา: https://docs.python.org/3/reference/compound_stmts.html#ยกเว้น
เมื่อมีการกำหนดข้อยกเว้นเพื่อใช้ as
เป้าหมาย ข้อยกเว้นนั้นจะถูกล้างที่ส่วนท้ายของส่วนคำสั่ง except
นี่เหมือนกับว่า
except E as N :
foo
ถูกแปลเป็น
except E as N :
try :
foo
finally :
del N
ซึ่งหมายความว่าจะต้องกำหนดข้อยกเว้นให้กับชื่ออื่นเพื่อให้สามารถอ้างถึงได้หลังจากส่วนคำสั่งยกเว้น ข้อยกเว้นจะถูกล้างออกไป เนื่องจากเมื่อมีการติดตามย้อนกลับ จะสร้างวงจรการอ้างอิงด้วยสแต็กเฟรม ซึ่งทำให้คนในพื้นที่ทั้งหมดในเฟรมนั้นยังคงอยู่จนกว่าการรวบรวมขยะครั้งถัดไปจะเกิดขึ้น
ส่วนคำสั่งไม่มีการกำหนดขอบเขตใน Python ทุกสิ่งในตัวอย่างอยู่ในขอบเขตเดียวกัน และตัวแปร e
ถูกลบออกเนื่องจากการดำเนินการของส่วนคำสั่ง except
เช่นเดียวกันไม่ใช่กรณีที่มีฟังก์ชั่นที่มีช่องด้านในแยกต่างหาก ตัวอย่างด้านล่างแสดงสิ่งนี้:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
เอาท์พุท:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
ใน Python 2.x ชื่อตัวแปร e
จะได้รับการกำหนดให้กับอินสแตนซ์ Exception()
ดังนั้นเมื่อคุณพยายามพิมพ์มันจะไม่พิมพ์อะไรเลย
เอาต์พุต (Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
เอาท์พุท:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
ทั้งวัตถุ s
และสตริง "s"
แฮชเป็นค่าเดียวกันเพราะ SomeClass
สืบทอดวิธี __hash__
ของคลาส str
SomeClass("s") == "s"
ประเมินเป็น True
เพราะ SomeClass
ยังสืบทอดวิธี __eq__
จากคลาส str
เนื่องจากทั้งสองวัตถุแฮชเป็นค่าเดียวกันและเท่ากันพวกเขาจึงถูกแสดงโดยคีย์เดียวกันในพจนานุกรม
สำหรับพฤติกรรมที่ต้องการเราสามารถกำหนดวิธี __eq__
ใน SomeClass
ใหม่ได้
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
เอาท์พุท:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
เอาท์พุท:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
คำสั่งที่ได้รับมอบหมายประเมินรายการนิพจน์ (โปรดจำไว้ว่านี่อาจเป็นนิพจน์เดียวหรือรายการที่คั่นด้วยเครื่องหมายจุลภาคซึ่งหลังให้ tuple) และกำหนดวัตถุที่เป็นผลลัพธ์เดียวให้กับแต่ละรายการเป้าหมายจากซ้ายไปขวา
+
in (target_list "=")+
หมายความว่าอาจมีรายการเป้าหมาย อย่างน้อยหนึ่ง รายการ ในกรณีนี้รายการเป้าหมายคือ a, b
และ a[b]
(หมายเหตุรายการนิพจน์เป็นรายการที่แน่นอนซึ่งในกรณีของเราคือ {}, 5
)
หลังจากประเมินรายการนิพจน์ค่าของมันจะถูกบรรจุไว้ในรายการเป้าหมายจาก ซ้ายไปขวา ดังนั้นในกรณีของเราก่อนอื่น {}, 5
tuple ถูกแกะกล่องออกเป็น a, b
และตอนนี้เรามี a = {}
และ b = 5
ตอนนี้ a
ถูกกำหนดให้กับ {}
ซึ่งเป็นวัตถุที่ไม่แน่นอน
รายการเป้าหมายที่สองคือ a[b]
(คุณอาจคาดหวังว่าสิ่งนี้จะเกิดข้อผิดพลาดเพราะทั้ง a
และ b
ไม่ได้ถูกกำหนดไว้ในงบมาก่อน แต่โปรดจำไว้ว่าเราเพิ่งมอบหมาย a
ถึง {}
และ b
ถึง 5
)
ตอนนี้เรากำลังตั้งค่าคีย์ 5
ในพจนานุกรมเป็น tuple ({}, 5)
การสร้างการอ้างอิงแบบวงกลม ( {...}
ในเอาต์พุตหมายถึงวัตถุเดียวกับที่ a
อ้างอิงอยู่แล้ว) อีกตัวอย่างที่ง่ายกว่าของการอ้างอิงแบบวงกลมอาจเป็น
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
กรณีที่คล้ายกันในตัวอย่างของเรา ( a[b][0]
เป็นวัตถุเดียวกันกับ a
)
ดังนั้นเพื่อสรุปคุณสามารถแบ่งตัวอย่างลงไปได้
a , b = {}, 5
a [ b ] = a , b
และการอ้างอิงแบบวงกลมสามารถพิสูจน์ได้โดยความจริงที่ว่า a[b][0]
เป็นวัตถุเดียวกันกับ a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
เอาท์พุท:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
การโทรไปยัง int()
นี้ใช้งานได้ดีใน Python 3.10.6 และเพิ่ม ValueError ใน Python 3.10.8 โปรดทราบว่า Python ยังคงสามารถทำงานกับจำนวนเต็มขนาดใหญ่ได้ ข้อผิดพลาดจะเกิดขึ้นเฉพาะเมื่อแปลงระหว่างจำนวนเต็มและสตริง
โชคดีที่คุณสามารถเพิ่มขีด จำกัด สำหรับจำนวนตัวเลขที่อนุญาตเมื่อคุณคาดว่าการดำเนินการจะเกิน ในการทำเช่นนี้คุณสามารถใช้อย่างใดอย่างหนึ่งต่อไปนี้:
ตรวจสอบเอกสารสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการเปลี่ยนขีด จำกัด เริ่มต้นหากคุณคาดว่ารหัสของคุณจะเกินค่านี้
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
เอาต์พุต (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
ใช่มันทำงานเป็นเวลา แปด ครั้งและหยุด
RuntimeError: dictionary keys changed during iteration
หากคุณพยายามทำสิ่งนี้del
ที่ดื้อรั้น class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
เอาท์พุท: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
PHEW ถูกลบในที่สุด คุณอาจเดาได้ว่าสิ่งที่บันทึกไว้ __del__
ถูกเรียกในความพยายามครั้งแรกของเราในการลบ x
มาเพิ่มการบิดให้มากขึ้นในตัวอย่าง
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
โอเคตอนนี้มันถูกลบ?
del x
ไม่ได้โทรโดยตรง x.__del__()
del x
Python จะลบชื่อ x
ออกจากขอบเขตปัจจุบันและการลดลงโดย 1 จำนวนการอ้างอิงของวัตถุ x
ที่อ้างอิง __del__()
เรียกว่าเมื่อจำนวนการอ้างอิงของวัตถุถึงศูนย์__del__()
ไม่ได้ถูกเรียกเพราะคำสั่งก่อนหน้า ( >>> y
) ในล่ามอินเทอร์แอคทีฟสร้างการอ้างอิงอื่นไปยังวัตถุเดียวกัน (โดยเฉพาะตัวแปร _
Magic ซึ่งอ้างอิงค่า None
การแสดงออกของ Repl) ดังนั้นจึงป้องกันการนับจำนวนการอ้างอิงจากการเข้าถึงศูนย์เมื่อพบ del y
globals
(หรือจริง ๆ แล้วการดำเนินการอะไรก็ตามที่จะไม่มีผลลัพธ์ None
) ทำให้ _
อ้างอิงผลลัพธ์ใหม่ลดการอ้างอิงที่มีอยู่ ตอนนี้จำนวนการอ้างอิงถึง 0 และเราสามารถเห็น "ลบ!" ถูกพิมพ์ (ในที่สุด!)1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
เอาท์พุท:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
เมื่อคุณทำการมอบหมายให้ตัวแปรในขอบเขตมันจะกลายเป็นท้องถิ่นตามขอบเขตนั้น ดังนั้น a
กลายเป็นท้องถิ่นตามขอบเขตของ another_func
แต่ก็ไม่ได้เริ่มต้นก่อนหน้านี้ในขอบเขตเดียวกันซึ่งทำให้เกิดข้อผิดพลาด
ในการปรับเปลี่ยนตัวแปรขอบเขตด้านนอก a
ใน another_func
เราต้องใช้คำหลัก global
def another_func ()
global a
a += 1
return a
เอาท์พุท:
> >> another_func ()
2
ใน another_closure_func
a
กลายเป็นท้องถิ่นตามขอบเขตของ another_inner_func
แต่มันไม่ได้เริ่มต้นก่อนหน้านี้ในขอบเขตเดียวกันซึ่งเป็นสาเหตุที่ทำให้เกิดข้อผิดพลาด
ในการแก้ไขตัวแปรขอบเขตด้านนอก a
ใน another_inner_func
ให้ใช้คำหลัก nonlocal
คำสั่ง nonlocal ใช้เพื่ออ้างถึงตัวแปรที่กำหนดไว้ในขอบเขตภายนอกที่ใกล้ที่สุด (ไม่รวมทั่วโลก)
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
เอาท์พุท:
> >> another_func ()
2
คำหลัก global
และ nonlocal
บอกล่าม Python ที่จะไม่ประกาศตัวแปรใหม่และค้นหาในขอบเขตด้านนอกที่สอดคล้องกัน
อ่านคำแนะนำสั้น ๆ แต่ยอดเยี่ยมในการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีการทำงานของเนมสเปซและขอบเขตการทำงานใน Python
list_1 = [ 1 , 2 , 3 , 4 ]
list_2 = [ 1 , 2 , 3 , 4 ]
list_3 = [ 1 , 2 , 3 , 4 ]
list_4 = [ 1 , 2 , 3 , 4 ]
for idx , item in enumerate ( list_1 ):
del item
for idx , item in enumerate ( list_2 ):
list_2 . remove ( item )
for idx , item in enumerate ( list_3 [:]):
list_3 . remove ( item )
for idx , item in enumerate ( list_4 ):
list_4 . pop ( idx )
เอาท์พุท:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
คุณเดาได้ไหมว่าทำไมเอาต์พุตคือ [2, 4]
?
ไม่ใช่ความคิดที่ดีที่จะเปลี่ยนวัตถุที่คุณทำซ้ำ วิธีที่ถูกต้องในการทำเช่นนั้นคือการทำซ้ำสำเนาของวัตถุแทนและ list_3[:]
ทำเช่นนั้น
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
ความแตกต่างระหว่าง del
, remove
และ pop
:
del var_name
เพียงแค่ลบการเชื่อมโยงของ var_name
ออกจากเนมสเปซท้องถิ่นหรือทั่วโลก (นั่นคือสาเหตุที่ list_1
ไม่ได้รับผลกระทบ)remove
ลบค่าการจับคู่แรกไม่ใช่ดัชนีเฉพาะเพิ่ม ValueError
หากไม่พบค่าpop
จะลบองค์ประกอบที่ดัชนีเฉพาะและส่งคืนเพิ่ม IndexError
หากมีการระบุดัชนีที่ไม่ถูกต้อง ทำไมเอาต์พุตคือ [2, 4]
?
1
จาก list_2
หรือ list_4
เนื้อหาของรายการคือ [2, 3, 4]
องค์ประกอบที่เหลือจะถูกเลื่อนลงเช่น 2
คือดัชนี 0 และ 3
อยู่ที่ดัชนี 1 เนื่องจากการทำซ้ำครั้งต่อไปจะดูดัชนี 1 (ซึ่งเป็น 3
) ทั้ง 2
จะถูกข้ามไปทั้งหมด สิ่งที่คล้ายกันจะเกิดขึ้นกับทุกองค์ประกอบทางเลือกในลำดับรายการ > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
องค์ประกอบ 3
ไปจากรายการ numbers
ที่ไหน?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
โดยเรียกใช้ฟังก์ชัน next
ไปบนพวกเขาและหยุดเมื่อใดก็ตามที่การทำซ้ำใด ๆ หมดไปresult
จะถูกยกเลิก นั่นคือสิ่งที่เกิดขึ้นกับ 3
ใน numbers_iter
zip
คือ > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
1.
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
เอาท์พุท:
6 : for x inside loop
6 : x in global
แต่ x
ไม่เคยถูกกำหนดไว้นอกขอบเขตของการวนซ้ำ ...
2.
# This time let's initialize x first
x = - 1
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
เอาท์พุท:
6 : for x inside loop
6 : x in global
3.
เอาต์พุต (Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
เอาต์พุต (Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
ใน Python สำหรับลูปใช้ขอบเขตที่พวกเขามีอยู่และปล่อยให้วนรอบตัวแปรที่กำหนดไว้เบื้องหลัง นอกจากนี้ยังใช้ถ้าเรากำหนดตัวแปรสำหรับลูปอย่างชัดเจนในเนมสเปซทั่วโลกก่อน ในกรณีนี้มันจะ rebind ตัวแปรที่มีอยู่
ความแตกต่างในผลลัพธ์ของ Python 2.x และ Python 3.x ล่ามสำหรับตัวอย่างความเข้าใจในรายการสามารถอธิบายได้โดยการเปลี่ยนแปลงที่บันทึกไว้ในสิ่งใหม่ใน Python 3.0 Changelog:
"รายการความเข้าใจไม่รองรับรูปแบบวากยสัมพันธ์อีกต่อไป
[... for var in item1, item2, ...]
. ใช้[... for var in (item1, item2, ...)]
แทนนอกจากนี้โปรดทราบว่ารายการนั้น ความเข้าใจมีความหมายที่แตกต่างกัน: พวกเขาอยู่ใกล้กับน้ำตาลวากยสัมพันธ์สำหรับการแสดงออกของเครื่องกำเนิดไฟฟ้าภายในตัวสร้างlist()
และโดยเฉพาะอย่างยิ่งตัวแปรควบคุมลูปจะไม่รั่วไหลออกไปในขอบเขตโดยรอบอีกต่อไป "
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
เอาท์พุท:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
อาร์กิวเมนต์ที่ไม่แน่นอนเริ่มต้นของฟังก์ชั่นใน Python ไม่ได้เริ่มต้นจริงๆทุกครั้งที่คุณเรียกฟังก์ชั่น แต่ค่าที่กำหนดเมื่อเร็ว ๆ นี้ใช้เป็นค่าเริ่มต้น เมื่อเราผ่านไปอย่างชัดเจน []
ไปยัง some_func
เป็นอาร์กิวเมนต์ค่าเริ่มต้นของตัวแปร default_arg
ไม่ได้ใช้ดังนั้นฟังก์ชันที่ส่งคืนตามที่คาดไว้
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
เอาท์พุท:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
การปฏิบัติทั่วไปเพื่อหลีกเลี่ยงข้อบกพร่องเนื่องจากอาร์กิวเมนต์ที่ไม่แน่นอนคือการกำหนด None
เป็นค่าเริ่มต้นและตรวจสอบในภายหลังว่าค่าใด ๆ ถูกส่งผ่านไปยังฟังก์ชันที่สอดคล้องกับอาร์กิวเมนต์นั้นหรือไม่ ตัวอย่าง:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
some_list = [ 1 , 2 , 3 ]
try :
# This should raise an ``IndexError``
print ( some_list [ 4 ])
except IndexError , ValueError :
print ( "Caught!" )
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except IndexError , ValueError :
print ( "Caught again!" )
เอาต์พุต (Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
เอาต์พุต (Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
ในการเพิ่มข้อยกเว้นหลายข้อในประโยคยกเว้นคุณต้องส่งผ่านเป็น tuple เป็นวงเล็บเป็นอาร์กิวเมนต์แรก อาร์กิวเมนต์ที่สองเป็นชื่อทางเลือกซึ่งเมื่อให้มาจะผูกอินสแตนซ์ข้อยกเว้นที่ได้รับการยกขึ้น ตัวอย่าง,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
เอาต์พุต (Python 2.x):
Caught again!
list.remove(x): x not in list
เอาต์พุต (Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
การแยกข้อยกเว้นจากตัวแปรที่มีเครื่องหมายจุลภาคเลิกใช้และไม่ทำงานใน Python 3; วิธีที่ถูกต้องคือใช้ as
ตัวอย่าง,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
เอาท์พุท:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
เอาท์พุท:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 ]
2.
a = [ 1 , 2 , 3 , 4 ]
b = a
a += [ 5 , 6 , 7 , 8 ]
เอาท์พุท:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
ไม่ได้ทำในลักษณะเดียวกับ a = a + b
คลาส อาจ ใช้ตัวดำเนินการ op=
แตกต่างกันและแสดงรายการสิ่งนี้
นิพจน์ a = a + [5,6,7,8]
สร้างรายการใหม่และตั้งค่า a
อ้างอิงของรายการใหม่นั้นโดยไม่เปลี่ยนแปลง b
นิพจน์ a += [5,6,7,8]
ถูกแมปกับฟังก์ชั่น "ขยาย" ที่ทำงานในรายการเช่น a
และ b
ยังคงชี้ไปที่รายการเดียวกันกับที่ได้รับการแก้ไขในสถานที่
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
เอาท์พุท:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
เอาต์พุต (Python 2.x):
> >> SomeClass . y [ 0 ]
17
เอาต์พุต (Python 3.x):
> >> SomeClass . y [ 0 ]
5
มาใช้ฟังก์ชั่นไร้เดียงสาเพื่อรับองค์ประกอบกลางของรายการ:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
ดูเหมือนว่า Python จะปัดเศษ 2.5 ถึง 2
round()
ใช้การปัดเศษของนายธนาคารโดยที่. 5 เศษส่วนจะถูกปัดเศษเป็น หมายเลข ที่ใกล้ที่สุด: > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
ส่งคืน 1 เท่านั้นเนื่องจากดัชนีเป็น round(0.5) - 1 = 0 - 1 = -1
, ส่งคืนองค์ประกอบสุดท้ายในรายการฉันไม่ได้พบกับประสบการณ์คนเดียวกับ Pythonist จนถึงวันที่ที่ไม่ได้เจอกับสถานการณ์ต่อไปนี้อย่างน้อยหนึ่งสถานการณ์
1.
x , y = ( 0 , 1 ) if True else None , None
เอาท์พุท:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
t = ( 'one' , 'two' )
for i in t :
print ( i )
t = ( 'one' )
for i in t :
print ( i )
t = ()
print ( t )
เอาท์พุท:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
เอาท์พุต
> >> len ( ten_words_list )
9
4. ไม่ได้ยืนยันอย่างแรงพอ
a = "python"
b = "javascript"
เอาท์พุท:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
some_list = [ 1 , 2 , 3 ]
some_dict = {
"key_1" : 1 ,
"key_2" : 2 ,
"key_3" : 3
}
some_list = some_list . append ( 4 )
some_dict = some_dict . update ({ "key_4" : 4 })
เอาท์พุท:
> >> print ( some_list )
None
> >> print ( some_dict )
None
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
เอาท์พุท:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
สำหรับ 1 คำสั่งที่ถูกต้องสำหรับพฤติกรรมที่คาดหวังคือ x, y = (0, 1) if True else (None, None)
สำหรับ 2 คำสั่งที่ถูกต้องสำหรับพฤติกรรมที่คาดหวังคือ t = ('one',)
หรือ t = 'one',
(จุลภาคที่หายไป) มิฉะนั้นล่ามจะถือว่าเป็น t
และวนซ้ำ str
ละครโดยตัวละคร
()
เป็นโทเค็นพิเศษและหมายถึง tuple
ที่ว่างเปล่า
ใน 3 อย่างที่คุณอาจคิดออกมาแล้วมีเครื่องหมายจุลภาคที่หายไปหลังจากองค์ประกอบที่ 5 ( "that"
) ในรายการ ดังนั้นโดยการเชื่อมต่อที่แท้จริงของสตริงโดยปริยาย
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
ไม่มีการรวบรวม AssertionError
ในตัวอย่างที่ 4 เพราะแทนที่จะยืนยันการแสดงออกของแต่ละบุคคล a == b
เรากำลังยืนยัน tuple ทั้งหมด ตัวอย่างต่อไปนี้จะล้างสิ่งต่างๆ
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
สำหรับตัวอย่างที่ห้าวิธีการส่วนใหญ่ที่แก้ไขรายการของวัตถุลำดับ/การแมปเช่น list.append
, dict.update
, list.sort
ฯลฯ แก้ไขวัตถุในสถานที่และส่ง None
เหตุผลที่อยู่เบื้องหลังสิ่งนี้คือการปรับปรุงประสิทธิภาพโดยการหลีกเลี่ยงการทำสำเนาของวัตถุหากการดำเนินการสามารถทำได้ในสถานที่ (อ้างอิงจากที่นี่)
สุดท้ายควรมีความชัดเจนเป็นธรรมวัตถุที่ไม่แน่นอน (เช่น list
) สามารถเปลี่ยนแปลงได้ในฟังก์ชั่นและการมอบหมายใหม่ของการเปลี่ยนรูปแบบไม่เปลี่ยนรูป ( a -= 1
) ไม่ได้เป็นการเปลี่ยนแปลงของค่า
การตระหนักถึง nitpicks เหล่านี้สามารถช่วยคุณประหยัดเวลาในการดีบักในระยะยาว
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
แต่ตามเอกสารหากไม่ได้ระบุ SEP หรือ
None
การใช้อัลกอริทึมการแยกที่แตกต่างกัน: การรันของช่องว่างติดต่อกันต่อเนื่องถือเป็นตัวคั่นเดี่ยวและผลลัพธ์จะไม่มีสตริงว่างเปล่าในตอนเริ่มต้นหรือสิ้นสุดหากสตริงมีช่องว่างนำหรือตามท้าย ดังนั้นการแยกสตริงเปล่าหรือสตริงที่ประกอบด้วยช่องว่างเพียงช่องว่างที่ไม่มีตัวคั่นไม่มีการส่งคืน[]
หากได้รับ SEP ตัวคั่นที่ต่อเนื่องกันจะไม่ถูกจัดกลุ่มเข้าด้วยกันและถือว่ามีการกำหนดสตริงที่ว่างเปล่า (ตัวอย่างเช่น'1,,2'.split(',')
ส่งคืน['1', '', '2']
) การแยกสตริงเปล่าที่มีตัวคั่นที่ระบุจะส่งคืน['']
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
เอาท์พุต
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
มักจะแนะนำให้ไม่ใช้การนำเข้าไวด์การ์ด เหตุผลแรกที่ชัดเจนสำหรับเรื่องนี้คือในการนำเข้าไวลด์การ์ดชื่อที่มีขีดล่างชั้นนำไม่ได้นำเข้า สิ่งนี้อาจนำไปสู่ข้อผิดพลาดในระหว่างการรันไทม์
หากเราใช้ from ... import a, b, c
ไวยากรณ์, NameError
ด้านบนจะไม่เกิดขึ้น
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
หากคุณต้องการใช้การนำเข้าไวด์การ์ดจริงๆคุณจะต้องกำหนดรายการ __all__
ในโมดูลของคุณที่จะมีรายการวัตถุสาธารณะที่จะพร้อมใช้งานเมื่อเรานำเข้าไวด์การ์ด
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
เอาท์พุต
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
วิธี sorted
จะส่งคืนรายการเสมอและการเปรียบเทียบรายการและ tuples จะส่งคืน False
ใน Python เสมอ
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
ซึ่งแตกต่างจาก sorted
วิธี reversed
จะส่งคืนตัววนซ้ำ ทำไม เนื่องจากการเรียงลำดับต้องมีการแก้ไขในสถานที่หรือใช้คอนเทนเนอร์พิเศษ (รายการ) ในขณะที่การย้อนกลับสามารถทำงานได้โดยการวนซ้ำจากดัชนีสุดท้ายเป็นครั้งแรก
ดังนั้นในระหว่างการเปรียบเทียบ sorted(y) == sorted(y)
การโทรครั้งแรกไปยัง sorted()
จะกินตัววนซ้ำ y
และการโทรครั้งต่อไปจะส่งคืนรายการที่ว่างเปล่า
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
from datetime import datetime
midnight = datetime ( 2018 , 1 , 1 , 0 , 0 )
midnight_time = midnight . time ()
noon = datetime ( 2018 , 1 , 1 , 12 , 0 )
noon_time = noon . time ()
if midnight_time :
print ( "Time at midnight is" , midnight_time )
if noon_time :
print ( "Time at noon is" , noon_time )
เอาต์พุต (<3.5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
เวลาเที่ยงคืนไม่ได้พิมพ์
ก่อน Python 3.5 ค่าบูลีนสำหรับ datetime.time
วัตถุเวลาได้รับการพิจารณาว่าเป็น False
หากเป็นตัวแทนเที่ยงคืนใน UTC มันเป็นข้อผิดพลาดที่เกิดขึ้นได้ง่ายเมื่อใช้ if obj:
ไวยากรณ์เพื่อตรวจสอบว่า obj
นั้นเป็นโมฆะหรือเทียบเท่ากับ "ว่าง"
ส่วนนี้มีสิ่งที่ไม่ค่อยมีใครรู้จักและน่าสนใจเกี่ยวกับ Python ที่ผู้เริ่มต้นอย่างฉันส่วนใหญ่ไม่ทราบ (ไม่ใช่อีกต่อไป)
คุณไปที่นี่
import antigravity
เอาท์พุท: SSHH ... มันเป็นความลับสุดยอด
antigravity
เป็นหนึ่งในไข่อีสเตอร์เพียงไม่กี่ตัวที่ปล่อยออกมาโดยนักพัฒนา Pythonimport antigravity
เปิดเว็บเบราว์เซอร์ที่ชี้ไปที่การ์ตูน XKCD คลาสสิกเกี่ยวกับ Pythongoto
แต่ทำไม? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
เอาต์พุต (Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
ใน Python เวอร์ชันทำงานได้ประกาศว่าเป็นเรื่องตลกของ April Fool ในวันที่ 1 เมษายน 2004goto
ถึงไม่อยู่ใน Pythonหากคุณเป็นหนึ่งในคนที่ไม่ชอบใช้ช่องว่างใน Python เพื่อแสดงถึงขอบเขตคุณสามารถใช้ C-Style {} ได้โดยการนำเข้า
from __future__ import braces
เอาท์พุท:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
เหล็กจัดฟัน? ไม่มีทาง! หากคุณคิดว่าน่าผิดหวังให้ใช้ Java โอเคสิ่งที่น่าประหลาดใจอีกอย่างหนึ่งคุณสามารถค้นหาว่า SyntaxError
เพิ่มขึ้นในรหัสโมดูล __future__
?
__future__
จะใช้เพื่อให้คุณสมบัติจาก Python รุ่นในอนาคต อย่างไรก็ตาม "อนาคต" ในบริบทเฉพาะนี้เป็นเรื่องน่าขันfuture.c
future.c
ก่อนที่จะถือว่าเป็นคำสั่งนำเข้าปกติเอาต์พุต (Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
ไปแล้ว.
สิ่งนี้เกี่ยวข้องกับ PEP-401 ที่วางจำหน่ายเมื่อวันที่ 1 เมษายน 2552 (ตอนนี้คุณรู้แล้วว่ามันหมายถึงอะไร)
อ้างจาก PEP-401
ได้รับการยอมรับว่า! = ตัวดำเนินการไม่เท่าเทียมกันใน Python 3.0 นั้นเป็นความผิดพลาดที่น่ากลัว
มีหลายสิ่งหลายอย่างที่ลุงแบร์รี่ต้องมีส่วนร่วมใน Pep; คุณสามารถอ่านได้ที่นี่
มันทำงานได้ดีในสภาพแวดล้อมแบบโต้ตอบ แต่จะเพิ่ม SyntaxError
เมื่อคุณเรียกใช้ผ่านไฟล์ Python (ดูปัญหานี้) อย่างไรก็ตามคุณสามารถสรุปคำสั่งภายใน eval
หรือ compile
เพื่อให้มันทำงานได้
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
เดี๋ยวก่อน นี่ คืออะไร? this
คือความรัก❤
เอาท์พุท:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
มันคือเซนแห่ง Python!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
ใน Python เป็นไข่อีสเตอร์สำหรับ Zen of Python (PEP 20)love is not True or False; love is love
แดกดัน แต่เป็นการอธิบายตนเอง (ถ้าไม่โปรดดูตัวอย่างที่เกี่ยวข้องกับ is
และ is not
ผู้ให้บริการ) ประโยค else
สำหรับลูป ตัวอย่างทั่วไปหนึ่งตัวอย่างอาจเป็น:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
เอาท์พุท:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
ประโยค else
ในการจัดการข้อยกเว้น ตัวอย่าง
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
เอาท์พุท:
Try block executed successfully ...
else
หลังจากลูปถูกดำเนินการเฉพาะเมื่อไม่มี break
อย่างชัดเจนหลังจากการวนซ้ำทั้งหมด คุณสามารถคิดว่ามันเป็นประโยค "nobreak"else
หลังจากบล็อก try
เรียกอีกอย่าง else
ว่า def some_func ():
Ellipsis
เอาท์พุต
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
เป็นวัตถุในตัวทั่วโลกซึ่งเทียบเท่ากับ ...
> >> ...
Ellipsis
pass
) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
ของเราจึงเป็นอาร์เรย์อาร์เรย์ของอาร์เรย์ สมมติว่าเราต้องการพิมพ์องค์ประกอบที่สอง (ดัชนี 1
) ของอาร์เรย์ด้านในสุดทั้งหมดเราสามารถใช้จุดไข่ปลาเพื่อข้ามมิติทั้งหมดก่อนหน้านี้ > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
หรือ Tuple[str, ...]
)))การสะกดคำนั้นมีจุดประสงค์ กรุณาอย่าส่งแพตช์สำหรับสิ่งนี้
เอาต์พุต (Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
คือ "-10⁵ x π" ใน Python 3 ในขณะที่ "-10⁵ xe" ใน Python 21.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
เอาท์พุท:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
เอาท์พุท:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
ทำไม Yo()._Yo__honey
ทำงาน?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
เอาท์พุท:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(ขีดล่างสองเท่าหรือที่รู้จักกันในชื่อ "dunder") และไม่จบลงด้วยการตอกย้ำมากกว่าหนึ่งการต่อท้ายโดยการเพิ่ม _NameOfTheClass
ด้านหน้า__honey
ในตัวอย่างแรกเราต้องผนวก _Yo
ไปที่ด้านหน้าซึ่งจะป้องกันความขัดแย้งด้วยแอตทริบิวต์ชื่อเดียวกันที่กำหนดไว้ในชั้นเรียนอื่น ๆ__variable
ในคำสั่ง return __variable
ถูก mangled to _A__variable
ซึ่งเป็นชื่อของตัวแปรที่เราประกาศในขอบเขตด้านนอกเอาท์พุท:
> >> value = 11
> >> valuе = 32
> >> value
11
wut?
หมายเหตุ: วิธีที่ง่ายที่สุดในการทำซ้ำสิ่งนี้คือการคัดลอกข้อความจากตัวอย่างด้านบนและวางลงในไฟล์/เชลล์ของคุณ
ตัวละครที่ไม่ใช่ตะวันตกบางตัวมีลักษณะเหมือนตัวอักษรในตัวอักษรภาษาอังกฤษ แต่ถือว่าแตกต่างกันโดยล่าม
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
ฟังก์ชั่น ord()
ในตัวส่งคืนจุดรหัส Unicode ของอักขระและตำแหน่งรหัสที่แตกต่างกันของ cyrillic 'e' และละติน 'e' แสดงให้เห็นถึงพฤติกรรมของตัวอย่างข้างต้น
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
เอาท์พุท:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
รางวัลโนเบลอยู่ที่ไหน
energy_send
จะไม่ถูกส่งคืนดังนั้นพื้นที่หน่วยความจำจึงมีอิสระที่จะจัดสรรใหม่numpy.empty()
ส่งคืนสล็อตหน่วยความจำฟรีถัดไปโดยไม่ต้องปรับเปลี่ยนใหม่ จุดหน่วยความจำนี้เกิดขึ้นเป็นแบบเดียวกับที่เพิ่งเป็นอิสระ (โดยปกติ แต่ไม่เสมอไป) def square ( x ):
"""
A simple function to calculate the square of a number by addition.
"""
sum_so_far = 0
for counter in range ( x ):
sum_so_far = sum_so_far + x
return sum_so_far
เอาต์พุต (Python 2.x):
> >> square ( 10 )
10
ไม่ควรเป็น 100?
หมายเหตุ: หากคุณไม่สามารถทำสิ่งนี้ซ้ำได้ลองเรียกใช้ไฟล์ mixed_tabs_and_spaces.py ผ่านเชลล์
อย่าผสมแท็บและช่องว่าง! อักขระที่เพิ่งกลับมาก่อนหน้านี้คือ "แท็บ" และรหัสถูกเยื้องด้วยหลาย "4 ช่องว่าง" ในที่อื่นในตัวอย่าง
นี่คือวิธีที่ Python จัดการแท็บ:
ก่อนอื่นแท็บจะถูกแทนที่ (จากซ้ายไปขวา) โดยช่องว่างหนึ่งถึงแปดช่องเช่นจำนวนอักขระทั้งหมดถึงและรวมถึงการเปลี่ยนเป็นหลายแปด <... >
ดังนั้นแท็บ "แท็บ" ที่ฟังก์ชั่นสุดท้ายของ square
จะถูกแทนที่ด้วยแปดช่องว่างและเข้าสู่วง
Python 3 ใจดีพอที่จะโยนข้อผิดพลาดสำหรับกรณีดังกล่าวโดยอัตโนมัติ
เอาต์พุต (Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
เร็วขึ้น # using "+", three strings:
> >> timeit . timeit ( "s1 = s1 + s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.25748300552368164
# using "+=", three strings:
> >> timeit . timeit ( "s1 += s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.012188911437988281
+=
เร็วกว่า +
สำหรับการเชื่อมต่อมากกว่าสองสตริงเนื่องจากสตริงแรก (ตัวอย่าง s1
สำหรับ s1 += s2 + s3
) จะไม่ถูกทำลายในขณะที่คำนวณสตริงที่สมบูรณ์ def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"xyz"
assert len ( s ) == 3 * iters
def add_string_with_format ( iters ):
fs = "{}" * iters
s = fs . format ( * ([ "xyz" ] * iters ))
assert len ( s ) == 3 * iters
def add_string_with_join ( iters ):
l = []
for i in range ( iters ):
l . append ( "xyz" )
s = "" . join ( l )
assert len ( s ) == 3 * iters
def convert_list_to_string ( l , iters ):
s = "" . join ( l )
assert len ( s ) == 3 * iters
เอาท์พุท:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
มาเพิ่มจำนวนการวนซ้ำด้วยปัจจัย 10
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ TimeIt หรือ %TimeIt บนลิงค์เหล่านี้ พวกเขาจะใช้ในการวัดเวลาดำเนินการของชิ้นส่วนรหัส
อย่าใช้ +
สำหรับการสร้างสายยาว - ใน Python, str
ไม่เปลี่ยนรูปดังนั้นสตริงซ้ายและขวาจะต้องถูกคัดลอกไปยังสตริงใหม่สำหรับการเชื่อมต่อทุกคู่ หากคุณเชื่อมต่อสี่สายยาว 10 คุณจะคัดลอก (10+10)+((10+10) +10)+(((10+10) +10) +10) = 90 อักขระแทนที่จะเป็นเพียง 40 ตัวอักษร สิ่งต่าง ๆ แย่ลงอย่างสม่ำเสมอเมื่อจำนวนและขนาดของสตริงเพิ่มขึ้น (เป็นธรรมกับเวลาดำเนินการของฟังก์ชัน add_bytes_with_plus
)
ดังนั้นจึงควรใช้ .format.
หรือ %
ไวยากรณ์ (อย่างไรก็ตามจะช้ากว่า +
เล็กน้อยสำหรับสตริงสั้นมาก)
หรือดีกว่าถ้าคุณมีเนื้อหาอยู่ในรูปแบบของวัตถุที่ทำซ้ำแล้วให้ใช้ ''.join(iterable_object)
ซึ่งเร็วกว่ามาก
ซึ่งแตกต่างจาก add_bytes_with_plus
เนื่องจาก +=
การปรับให้เหมาะสมที่กล่าวถึงในตัวอย่างก่อนหน้านี้ add_string_with_plus
ไม่แสดงการเพิ่มขึ้นของเวลาในการดำเนินการเป็นกำลังสอง หากคำสั่งเป็น s = s + "x" + "y" + "z"
แทน s += "xyz"
การเพิ่มขึ้นจะเป็นกำลังสอง
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
มีหลายวิธีในการจัดรูปแบบและสร้างสตริงยักษ์ค่อนข้างตรงกันข้ามกับเซนของ Python ตามที่
ควรมีหนึ่ง-และควรใช้วิธีเดียวเท่านั้นที่จะทำ
dict
คำสั่ง * some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
เอาท์พุท:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
เหตุใดการค้นหาแบบเดียวกันจึงช้าลง?
str
, int
, วัตถุใด ๆ ... ) และเป็นพิเศษสำหรับกรณีทั่วไปของพจนานุกรมที่ประกอบด้วยคีย์ str
-onlylookdict_unicode
ในแหล่งที่มาของ Cpython) รู้คีย์ที่มีอยู่ทั้งหมด ( __eq__
ถึงคีย์ค้นหา) เป็นสตริงและใช้การเปรียบเทียบสตริงที่เร็วขึ้นdict
ถูกเข้าถึงด้วยคีย์ที่ไม่ใช่ str
มันได้รับการแก้ไขเพื่อให้การค้นหาในอนาคตใช้ฟังก์ชั่นทั่วไปdict
เฉพาะและคีย์ไม่จำเป็นต้องมีอยู่ในพจนานุกรม นั่นเป็นเหตุผลว่าทำไมการพยายามค้นหาที่ล้มเหลวมีผลเหมือนกันdict
s * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
เอาท์พุท: (Python 3.8, Python 3 รุ่นอื่น ๆ อาจแตกต่างกันเล็กน้อย)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
ลองอีกครั้ง ... ในล่ามใหม่:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
อะไรทำให้พจนานุกรมเหล่านั้นป่อง? และทำไมวัตถุที่สร้างขึ้นใหม่จึงป่องเช่นกัน?
__init__
ของอินสแตนซ์ที่สร้างขึ้นครั้งแรก "Unshare") หากมีหลายอินสแตนซ์เมื่อมีการปรับขนาดเกิดขึ้นการแชร์คีย์จะถูกปิดใช้งานสำหรับอินสแตนซ์ในอนาคตทั้งหมดของคลาสเดียวกัน: Cpython ไม่สามารถบอกได้ว่าอินสแตนซ์ของคุณใช้แอตทริบิวต์ชุดเดียวกันอีกต่อไปและตัดสินใจที่จะประกันตัวในการพยายามแบ่งปัน กุญแจ__init__
ของคุณ! join()
เป็นการดำเนินการสตริงแทนการดำเนินการในรายการ (เรียงลำดับของการตอบโต้ที่ใช้งานง่ายในการใช้งานครั้งแรก)
คำอธิบาย: หาก join()
เป็นวิธีการในสตริงมันสามารถทำงานได้กับการทำซ้ำ (รายการ, tuple, iterators) หากเป็นวิธีการในรายการมันจะต้องดำเนินการแยกต่างหากโดยทุกประเภท นอกจากนี้ยังไม่สมเหตุสมผลที่จะใส่วิธีการเฉพาะสตริงใน API list
Object ทั่วไป
คำสั่งที่ดูแปลก ๆ แต่ถูกต้องตามความหมาย:
[] = ()
list
คำ tuple
ที่ถูกต้องตามความหมาย'a'[0][0][0][0][0]
ก็ถูกต้องตามความหมายเช่นกันเพราะ Python ไม่มีชนิดข้อมูลอักขระเช่นภาษาอื่น ๆ ที่แยกออกจาก C ดังนั้นการเลือกอักขระตัวเดียวจากสตริงจะส่งคืน สตริงตัวละครเดี่ยว3 --0-- 5 == 8
และ --5 == 5
เป็นคำสั่งที่ถูกต้องตามความหมายและประเมินเป็น True
ระบุว่า a
คือตัวเลข, ++a
และ --a
เป็นทั้งคำสั่ง Python ที่ถูกต้อง แต่ไม่ได้ทำแบบเดียวกับเมื่อเทียบกับข้อความที่คล้ายกันในภาษาเช่น C, C ++ หรือ Java
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
คำอธิบาย:
++
ใน Python Grammar จริงๆแล้วมันเป็นตัวดำเนินการสอง +
++a
Parses As +(+a)
ซึ่งแปลเป็น a
ในทำนองเดียวกันเอาต์พุตของคำสั่ง --a
สามารถพิสูจน์ได้คุณต้องระวังผู้ให้บริการ Walrus ใน Python แต่คุณเคยได้ยินเกี่ยวกับ ผู้ให้บริการ Space-Invader หรือ ไม่?
> >> a = 42
> >> a -= - 1
> >> a
43
มันถูกใช้เป็นตัวดำเนินการเพิ่มขึ้นอีกทางเลือกหนึ่งพร้อมกับอีกตัวหนึ่ง
> >> a += + 1
> >> a
> >> 44
คำอธิบาย: การเล่นตลกนี้มาจากทวีตของ Raymond Hettinger ตัวดำเนินการ Space Invader เป็นเพียง malformatted a -= (-1)
ซึ่งเทียบเท่ากับ a = a - (- 1)
คล้ายกับกรณี a += (+ 1)
Python มีผู้ดำเนินการสนทนาที่ไม่มีเอกสาร
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
คำอธิบาย: หากคุณแทนที่ False
และ True
โดย 0 และ 1 และทำคณิตศาสตร์ตารางความจริงจะเทียบเท่ากับผู้ดำเนินการสนทนา (แหล่งที่มา)
เนื่องจากเรากำลังพูดถึงผู้ให้บริการนอกจากนี้ยังมีผู้ให้บริการ @
สำหรับการคูณเมทริกซ์ (ไม่ต้องกังวลคราวนี้เป็นของจริง)
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
คำอธิบาย: ผู้ประกอบการ @
ถูกเพิ่มเข้ามาใน Python 3.5 ทำให้ชุมชนวิทยาศาสตร์อยู่ในใจ วัตถุใด ๆ สามารถโอเวอร์โหลด __matmul__
วิธีเวทย์มนตร์เพื่อกำหนดพฤติกรรมสำหรับตัวดำเนินการนี้
จาก Python 3.8 เป็นต้นไปคุณสามารถใช้ไวยากรณ์ F-string ทั่วไปเช่น f'{some_var=}
สำหรับการดีบักอย่างรวดเร็ว ตัวอย่าง,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python ใช้ 2 ไบต์สำหรับที่เก็บข้อมูลตัวแปรท้องถิ่นในฟังก์ชั่น ในทางทฤษฎีหมายความว่ามีเพียง 65536 ตัวแปรเท่านั้นที่สามารถกำหนดได้ในฟังก์ชั่น อย่างไรก็ตาม Python มีวิธีแก้ปัญหาที่มีประโยชน์ในตัวที่สามารถใช้ในการจัดเก็บชื่อตัวแปรมากกว่า 2^16 รหัสต่อไปนี้แสดงให้เห็นว่าเกิดอะไรขึ้นในสแต็กเมื่อมีการกำหนดตัวแปรท้องถิ่นมากกว่า 65536 ตัวแปร (คำเตือน: รหัสนี้พิมพ์ประมาณ 2^18 บรรทัดของข้อความดังนั้นเตรียมพร้อม!):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
เธรด Python หลายตัวจะไม่เรียกใช้ รหัส Python ของคุณพร้อมกัน (ใช่คุณได้ยินถูกต้อง!) มันอาจดูง่ายที่จะวางไข่หลายเธรดและปล่อยให้พวกเขาเรียกใช้รหัส Python ของคุณพร้อมกัน แต่เนื่องจากล็อคล็อคล่ามทั่วโลกใน Python สิ่งที่คุณทำคือการทำให้เธรดของคุณทำงานในเทิร์นหลักเดียวกัน เธรด Python นั้นดีสำหรับงานที่ถูกผูกไว้กับ IO แต่เพื่อให้บรรลุการขนานที่แท้จริงใน Python สำหรับงานที่ถูกผูกไว้กับ CPU คุณอาจต้องการใช้โมดูล Multiprocessing Python
บางครั้งวิธี print
อาจไม่พิมพ์ค่าทันที ตัวอย่างเช่น,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
สิ่งนี้จะพิมพ์ wtfpython
หลังจาก 3 วินาทีเนื่องจากอาร์กิวเมนต์ end
เนื่องจากบัฟเฟอร์เอาท์พุทถูกล้างออกหลังจากพบ n
หรือเมื่อโปรแกรมเสร็จสิ้นการดำเนินการ เราสามารถบังคับบัฟเฟอร์ให้ล้างออกโดยผ่านการ flush=True
รายการการหั่นด้วยดัชนีนอกขอบเขตไม่มีข้อผิดพลาด
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
การหั่นการทำซ้ำไม่ได้สร้างวัตถุใหม่เสมอไป ตัวอย่างเช่น,
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
ส่งคืน 123456789
ใน Python 3 ใน Python อักขระทศนิยมรวมถึงตัวละครหลักและตัวละครทั้งหมดที่สามารถใช้ในการสร้างหมายเลขทศนิยม-รดิกซ์เช่น U+0660 นี่คือเรื่องราวที่น่าสนใจที่เกี่ยวข้องกับพฤติกรรมของ Python นี้
คุณสามารถแยกตัวอักษรตัวเลขด้วยขีดเส้นใต้ (เพื่อการอ่านที่ดีขึ้น) จาก Python 3 เป็นต้นไป
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
นี่คือการใช้วิธี count
โดยประมาณซึ่งจะทำให้สิ่งต่าง ๆ ชัดเจนขึ้น
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
พฤติกรรมเกิดจากการจับคู่ของ substring ( ''
) ที่มีความยาว 0 ในสตริงต้นฉบับ
สองสามวิธีที่คุณสามารถมีส่วนร่วมใน WTFPython
โปรดดูรายละเอียดเพิ่มเติม อย่าลังเลที่จะสร้างปัญหาใหม่เพื่อหารือเกี่ยวกับสิ่งต่าง ๆ
PS: โปรดอย่าติดต่อกับคำขอย้อนกลับจะไม่มีการเพิ่มลิงก์เว้นแต่ว่าพวกเขาเกี่ยวข้องกับโครงการอย่างมาก
ความคิดและการออกแบบสำหรับคอลเล็กชั่นนี้ได้รับแรงบันดาลใจจากโครงการ WTFJS ที่ยอดเยี่ยมของ Denys Dovhan การสนับสนุนอย่างล้นหลามโดย Pythonistas ทำให้มันมีรูปร่างที่อยู่ในตอนนี้
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
ทวิตเตอร์ | Linkedin | เฟสบุ๊ค
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
That's all folks! For upcoming content like this, you can add your email here.