มีการใช้รายการ Python อย่างไร


183

มันเป็นรายการที่เชื่อมโยงอาร์เรย์หรือไม่ ฉันค้นหาไปรอบ ๆ และพบเพียงคนที่คาดเดา ความรู้ C ของฉันไม่ดีพอที่จะดูซอร์สโค้ด

คำตอบ:


58

มันเป็นอาร์เรย์แบบไดนามิก ข้อพิสูจน์ในทางปฏิบัติ: การจัดทำดัชนีต้องใช้เวลา (แน่นอนว่ามีความแตกต่างน้อยมาก (0.0013 ecsecs!)) ในเวลาเดียวกันโดยไม่คำนึงถึงดัชนี:

...>python -m timeit --setup="x = [None]*1000" "x[500]"
10000000 loops, best of 3: 0.0579 usec per loop

...>python -m timeit --setup="x = [None]*1000" "x[0]"
10000000 loops, best of 3: 0.0566 usec per loop

ฉันจะประหลาดใจถ้า IronPython หรือ Jython ใช้รายการที่เชื่อมโยง - พวกเขาจะทำลายประสิทธิภาพของห้องสมุดที่ใช้กันอย่างแพร่หลายจำนวนมากที่สร้างขึ้นบนสมมติฐานที่ว่ารายการนั้นเป็นอาร์เรย์แบบไดนามิก


1
@Ralf: ฉันรู้ว่าซีพียูของฉัน (ฮาร์ดแวร์อื่น ๆ ส่วนใหญ่เช่นกัน) เป็นรุ่นเก่าและช้า - ในด้านสว่างฉันสามารถสันนิษฐานได้ว่ารหัสที่ทำงานเร็วพอสำหรับฉันนั้นเร็วพอสำหรับผู้ใช้ทั้งหมด: D

88
@delnan: -1 "หลักฐานการปฏิบัติ" ของคุณเป็นเรื่องไร้สาระเช่นเดียวกับ 6 upvotes ประมาณ 98% ของเวลาถูกดำเนินการx=[None]*1000โดยปล่อยให้การวัดความแตกต่างของการเข้าถึงรายการที่เป็นไปได้ค่อนข้างไม่แน่นอน คุณต้องแยกการกำหนดค่าเริ่มต้น:-s "x=[None]*100" "x[0]"
John Machin

26
แสดงให้เห็นว่ามันไม่ใช่การนำไปปฏิบัติที่ไร้เหตุผลของรายการที่เชื่อมโยง ไม่แสดงให้เห็นอย่างชัดเจนว่าเป็นอาร์เรย์
Michael Mior

6
คุณสามารถอ่านเกี่ยวกับเรื่องนี้ได้ที่นี่: docs.python.org/2/faq/design.html#how-are-lists-implemented
CCoder

3
มีโครงสร้างมากกว่าแค่ลิสต์และอาเรย์ที่เชื่อมโยงกันหมดเวลาไม่มีประโยชน์ในการตัดสินใจระหว่างพวกมัน
Ross Hemsley

236

รหัส C นั้นค่อนข้างเรียบง่าย การขยายแมโครหนึ่งรายการและตัดการแสดงความคิดเห็นที่ไม่เกี่ยวข้องออกไปโครงสร้างพื้นฐานจะอยู่ในlistobject.hซึ่งจะกำหนดรายการเป็น:

typedef struct {
    PyObject_HEAD
    Py_ssize_t ob_size;

    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     */
    Py_ssize_t allocated;
} PyListObject;

PyObject_HEADมีจำนวนการอ้างอิงและตัวระบุชนิด มันเป็นเวกเตอร์ / อาร์เรย์ที่ overallocates listobject.cรหัสสำหรับการปรับขนาดเช่นอาร์เรย์เมื่อมันเต็มอยู่ใน มันไม่ได้เพิ่มอาร์เรย์เป็นสองเท่า แต่เพิ่มขึ้นโดยการจัดสรร

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;

ต่อความจุในแต่ละครั้งที่newsizeมีขนาดที่ร้องขอ (ไม่จำเป็นต้องเป็นallocated + 1เพราะคุณสามารถextendใช้องค์ประกอบตามจำนวนที่กำหนดแทนการใช้งานappendทีละชิ้น)

เห็นแล้วยังหลามคำถามที่พบบ่อย


6
ดังนั้นเมื่อวนซ้ำรายการไพ ธ อนจะช้าเท่ากับรายการที่เชื่อมโยงเพราะทุกรายการเป็นเพียงตัวชี้ดังนั้นองค์ประกอบส่วนใหญ่น่าจะทำให้แคชพลาด
Kr0e

9
@ Kr0e: ไม่ใช่ว่าองค์ประกอบที่ตามมาเป็นวัตถุเดียวกันจริง ๆ :) แต่ถ้าคุณต้องการโครงสร้างข้อมูลที่เล็กกว่า / แคชเป็นมิตรarrayโมดูลหรือ NumPy จะเป็นที่ต้องการ
Fred Foo

@ Kr0e ฉันจะไม่บอกว่าการวนซ้ำรายการช้ากว่ารายการที่เชื่อมโยง แต่การวนซ้ำค่าของรายการที่เชื่อมโยงนั้นช้ากว่ารายการที่เชื่อมโยงโดยที่ caveat ที่ Fred พูดถึง ตัวอย่างเช่นการวนซ้ำรายการเพื่อคัดลอกไปยังรายการอื่นควรเร็วกว่ารายการที่ลิงก์
Ganea Dan Andrei

35

ใน CPython รายการคืออาร์เรย์ของพอยน์เตอร์ การใช้งานอื่น ๆ ของ Python อาจเลือกที่จะเก็บไว้ในรูปแบบที่แตกต่างกัน


32

นี่ขึ้นอยู่กับการใช้งาน แต่ IIRC:

  • CPython ใช้อาร์เรย์ของพอยน์เตอร์
  • Jython ใช้ ArrayList
  • เห็นได้ชัดว่า IronPython ใช้อาเรย์ด้วย คุณสามารถเรียกดูซอร์สโค้ดเพื่อค้นหา

ดังนั้นพวกเขาทั้งหมดมีการเข้าถึงแบบสุ่ม O (1)


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

2
@sepp ฉันเชื่อว่ารายการใน Python เป็นเพียงการสั่งซื้อคอลเล็กชัน ข้อกำหนดการใช้งานและ / หรือประสิทธิภาพของการใช้งานดังกล่าวไม่ได้ระบุไว้อย่างชัดเจน
NullUserException

6
@ sppe2k: เนื่องจาก Python ไม่มีข้อกำหนดมาตรฐานหรือเป็นทางการ (แม้ว่าจะมีเอกสารบางส่วนที่บอกว่า "... รับประกันว่าจะเป็น ... ") คุณจึงไม่แน่ใจ 100% เหมือนในสิ่งนี้ รับประกันด้วยกระดาษบางส่วน " แต่เนื่องจากO(1)การทำดัชนีรายชื่อเป็นข้อสันนิษฐานที่ค่อนข้างธรรมดาและใช้ได้จริงไม่มีการใช้งานใดที่กล้าทำลาย

@ พอลมันไม่ได้พูดอะไรเกี่ยวกับการใช้งานพื้นฐานของรายการที่ควรทำ
NullUserException

มันไม่ได้เกิดขึ้นเพื่อระบุเวลาทำงานขนาดใหญ่ของสิ่งต่าง ๆ ข้อกำหนดภาษาไวยากรณ์ไม่ได้แปลว่าสิ่งเดียวกันกับรายละเอียดการใช้งาน แต่มันก็เกิดขึ้นบ่อยครั้ง
Paul McMillan

26

ผมจะแนะนำบทความ Laurent Luce ของ "งูใหญ่การดำเนินรายการ" มีประโยชน์จริง ๆ สำหรับฉันเพราะผู้เขียนอธิบายวิธีการนำรายการไปใช้ใน CPython และใช้ไดอะแกรมที่ยอดเยี่ยมสำหรับจุดประสงค์นี้

รายการโครงสร้าง C วัตถุ

วัตถุรายการใน CPython จะถูกแทนด้วยโครงสร้าง C ต่อไปนี้ ob_itemเป็นรายการของตัวชี้ไปยังองค์ประกอบรายการ จัดสรรคือจำนวนของสล็อตที่จัดสรรในหน่วยความจำ

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

สิ่งสำคัญคือต้องสังเกตความแตกต่างระหว่างสล็อตที่จัดสรรและขนาดของรายการ len(l)ขนาดของรายการเป็นเช่นเดียวกับ จำนวนของสล็อตที่จัดสรรคือสิ่งที่ถูกจัดสรรในหน่วยความจำ บ่อยครั้งที่คุณจะเห็นว่าการจัดสรรสามารถมากกว่าขนาด นี่คือการหลีกเลี่ยงการโทรreallocทุกครั้งที่มีองค์ประกอบใหม่ถูกผนวกเข้ากับรายการ

...

ผนวก

l.append(1)เราผนวกจำนวนเต็มเพื่อรายการนี้: เกิดอะไรขึ้น?
ป้อนคำอธิบายรูปภาพที่นี่

l.append(2)เรายังคงได้โดยการเพิ่มองค์ประกอบหนึ่งเพิ่มเติมได้ที่: list_resizeถูกเรียกด้วย n + 1 = 2 แต่เนื่องจากขนาดที่จัดสรรคือ 4 จึงไม่จำเป็นต้องจัดสรรหน่วยความจำเพิ่มเติม สิ่งเดียวกันเกิดขึ้นเมื่อเราเพิ่ม 2 l.append(3)จำนวนเต็มเพิ่มเติมได้ที่: l.append(4), แผนภาพต่อไปนี้แสดงสิ่งที่เรามีจนถึงตอนนี้

ป้อนคำอธิบายรูปภาพที่นี่

...

แทรก

ลองใส่จำนวนเต็มใหม่ (5) ที่ตำแหน่ง 1: l.insert(1,5)และดูสิ่งที่เกิดขึ้นภายในป้อนคำอธิบายรูปภาพที่นี่

...

ป๊อปอัพ

เมื่อคุณป๊อปองค์ประกอบสุดท้าย: l.pop(), listpop()เรียกว่า list_resizeเรียกว่าข้างในlistpop()และถ้าขนาดใหม่น้อยกว่าครึ่งหนึ่งของขนาดที่จัดสรรแล้วรายการจะหดตัวป้อนคำอธิบายรูปภาพที่นี่

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

คุณสามารถสังเกตได้ว่าช่อง 3 และ 4 ยังคงชี้ไปที่จำนวนเต็มบางส่วน แต่สิ่งสำคัญคือขนาดของรายการซึ่งตอนนี้เป็น 3ป้อนคำอธิบายรูปภาพที่นี่

...

นำl.remove(5)วัตถุของรายการงูใหญ่มีวิธีการในการลบองค์ประกอบที่เฉพาะเจาะจง: ป้อนคำอธิบายรูปภาพที่นี่


ขอบคุณฉันเข้าใจส่วนลิงก์ของรายการมากขึ้นแล้ว รายการหลามเป็นไม่ได้aggregation compositionฉันหวังว่าจะมีรายการขององค์ประกอบเช่นกัน
shuva

22

อ้างอิงถึงเอกสาร ,

รายการของ Python เป็นอาร์เรย์ที่มีความยาวผันแปรได้จริงไม่ใช่รายการที่เชื่อมโยงแบบ Lisp


5

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

เพื่อให้เข้าใจว่าทำไมวิธีดังกล่าวจึงถูกตัดทอน O (1) โดยไม่สูญเสียความคิดทั่วไปเราได้ใส่องค์ประกอบ = 2 ^ n และตอนนี้เราต้องเพิ่มตารางของเราเป็นสองเท่าของขนาด 2 ^ (n + 1) นั่นหมายความว่าเรากำลังดำเนินการ 2 ^ (n + 1) สำเนาล่าสุดเราดำเนินการ 2 ^ n ก่อนหน้านั้นเราทำ 2 ^ (n-1) ... จนถึง 8,4,2,1 ตอนนี้ถ้าเราเพิ่มสิ่งเหล่านี้เราจะได้ 1 + 2 + 4 + 8 + ... + 2 ^ (n + 1) = 2 ^ (n + 2) - 1 <4 * 2 ^ n = O (2 ^ n) = O (a) การแทรกทั้งหมด (เช่น O (1) เวลาที่ตัดจำหน่าย) นอกจากนี้ควรสังเกตว่าถ้าตารางอนุญาตให้ลบการหดตัวของตารางจะต้องทำที่ปัจจัยอื่น (เช่น 3x)


เท่าที่ฉันเข้าใจไม่มีการคัดลอกองค์ประกอบเก่า ๆ มีการจัดสรรพื้นที่มากขึ้น แต่พื้นที่ใหม่ไม่ได้ต่อเนื่องกับพื้นที่ที่มีการใช้งานไปแล้วและมีการแทรกองค์ประกอบใหม่ที่จะถูกคัดลอกไปยังพื้นที่ใหม่เท่านั้น โปรดแก้ไขฉันหากฉันผิด
Tushar Vazirani

1

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

list_one = [1,2,3,4]

my_list = list_one

#my_list: [1,2,3,4]

my_list.append("new")

#my_list: [1,2,3,4,'new']
#list_one: [1,2,3,4,'new']

เราผนวกmy_listแต่รายการหลักของเรามีการเปลี่ยนแปลง รายการของค่าเฉลี่ยนั้นไม่ได้ถูกกำหนดให้เป็นรายการสำเนาที่ได้รับมอบหมายเป็นการอ้างอิง


0

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

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