क्या कारण है [*] समग्र करने के लिए?


136

स्पष्ट रूप से list(a)समग्र नहीं है, [x for x in a]कुछ बिंदुओं पर समग्र, और हर समय[*a] समग्र ?

N = 100 तक आकार देता है

यहाँ 0 से 12 तक के आकार और तीन तरीकों के लिए बाइट्स में परिणामी आकार दिए गए हैं:

0 56 56 56
1 64 88 88
2 72 88 96
3 80 88 104
4 88 88 112
5 96 120 120
6 104 120 128
7 112 120 136
8 120 120 152
9 128 184 184
10 136 184 192
11 144 184 200
12 152 184 208

इस तरह की गणना की गई, जो कि प्रतिकृति पर पुन: प्रयोज्य है । पायथन 3. 8 का उपयोग करके :

from sys import getsizeof

for n in range(13):
    a = [None] * n
    print(n, getsizeof(list(a)),
             getsizeof([x for x in a]),
             getsizeof([*a]))

यह कैसे काम करता है? [*a]समग्र कैसे करता है ? दरअसल, दिए गए इनपुट से परिणाम सूची बनाने के लिए किस तंत्र का उपयोग किया जाता है? क्या यह एक पुनरावृत्ति का उपयोग करता है aऔर कुछ का उपयोग करता है list.append? सोर्स कोड कहां है?

( डेटा और कोड के साथ कोलाब जो छवियों का उत्पादन करते हैं।)

छोटे n में ज़ूमिंग:

N = 40 तक आकार देता है

बड़ा n करने के लिए बाहर ज़ूमिंग:

N = 1000 तक आकार देता है


1
Fwiw, आपके परीक्षण के मामलों को बढ़ाते हुए ऐसा प्रतीत होता है कि सूची की समझ एक लूप लिखने के रूप में व्यवहार करती है और प्रत्येक आइटम को सूची में जोड़ [*a]देती है , जबकि extendएक खाली सूची का उपयोग करते हुए व्यवहार करता प्रतीत होता है ।
jdehesa

4
यह प्रत्येक के लिए उत्पन्न बाइट कोड को देखने में मदद कर सकता है। list(a)पूरी तरह से सी में काम करता है; यह आंतरिक बफर नोड को नोड द्वारा आवंटित कर सकता है क्योंकि यह इससे अधिक पुनरावृत्ति करता है a[x for x in a]बस LIST_APPENDएक बहुत का उपयोग करता है, इसलिए यह सामान्य "एक सामान्य सूची के आवश्यक" पैटर्न को थोड़ा, फिर से व्यवस्थित करता है। [*a]का उपयोग करता है BUILD_LIST_UNPACK, जो ... मुझे नहीं पता कि वह क्या करता है, इसके अलावा अन्य सभी समय से अधिक आवंटित करते हैं :)
chepner

2
इसके अलावा, पायथन 3.7 में, यह प्रतीत होता है list(a)और [*a]समान हैं, और दोनों समग्र रूप से तुलना करते हैं [x for x in a], इसलिए ... sys.getsizeofयहां उपयोग करने के लिए सही उपकरण नहीं हो सकता है।
छप्पन

7
@chepner मुझे लगता sys.getsizeofहै कि सही उपकरण है, यह सिर्फ दिखाता है कि list(a)समग्र रूप से उपयोग किया जाता है। वास्तव में व्हाट्स न्यू इन पाइथन 3.8 का उल्लेख है: "सूची निर्माणकर्ता समग्र [...] नहीं करता है"
स्टीफन पोचमैन 16

5
@chepner: यह 3.8 में तय किया गया बग था ; कंस्ट्रक्टर समग्र को नहीं माना जाता है।
शैडो रेंजर

जवाबों:


81

[*a] आंतरिक रूप से C के बराबर कर रहा है :

  1. एक नया, खाली करें list
  2. कॉल newlist.extend(a)
  3. लौटता है list

तो अगर आप अपने परीक्षण का विस्तार करने के लिए:

from sys import getsizeof

for n in range(13):
    a = [None] * n
    l = []
    l.extend(a)
    print(n, getsizeof(list(a)),
             getsizeof([x for x in a]),
             getsizeof([*a]),
             getsizeof(l))

इसे ऑनलाइन आज़माएं!

आप के लिए परिणाम देखेंगे getsizeof([*a])और l = []; l.extend(a); getsizeof(l)समान हैं।

यह आमतौर पर सही काम है; जब extendआप आमतौर पर बाद में और अधिक जोड़ने की उम्मीद कर रहे हैं, और इसी तरह सामान्यीकृत अनपैकिंग के लिए, यह माना जाता है कि एक के बाद एक कई चीजें जोड़ी जाएंगी। [*a]सामान्य मामला नहीं है; पायथन मान लेता है कि इसमें कई आइटम या पुनरावृत्तियाँ जोड़ी जा रही हैं list( [*a, b, c, *d]), इसलिए समग्र स्थिति आम मामले में काम बचाती है।

इसके विपरीत, एक listएकल से निर्मित, पुनरावृत्त चलने योग्य (के साथ list()) उपयोग के दौरान बढ़ या सिकुड़ नहीं सकता है, और अन्यथा सिद्ध होने तक समग्र रूप से समयपूर्व है; पायथन ने हाल ही में एक बग को तय किया है जिसने ज्ञात आकार के साथ इनपुट के लिए भी कंस्ट्रक्टर को समग्र बना दिया है

के रूप में listcomprehensions, वे प्रभावी रूप दोहराया के बराबर कर रहे appendहै, तो आप सामान्य overallocation विकास पैटर्न के अंतिम परिणाम जब एक समय में कोई तत्व जोड़ देख रहे हैं।

स्पष्ट होने के लिए, यह कोई भी भाषा गारंटी नहीं है। यह सिर्फ यह है कि सीपीथॉन इसे कैसे लागू करता है। पायथन भाषा की कल्पना आम तौर पर विशिष्ट विकास पैटर्न के साथ असंबद्ध होती है list(एक तरफ से अंत में amortized O(1) appends और pops की गारंटी से)। जैसा कि टिप्पणियों में कहा गया है, विशिष्ट कार्यान्वयन फिर से 3.9 में बदल जाता है; हालांकि यह प्रभावित नहीं करेगा [*a], यह अन्य मामलों को प्रभावित कर सकता है जहां " tupleव्यक्तिगत वस्तुओं का एक अस्थायी निर्माण" हुआ करता था और फिर "के extendसाथ tupleअब के कई अनुप्रयोग बन जाते हैं LIST_APPEND, जो समग्र रूप से घटित होने और गणना में जाने पर संख्या बदल सकते हैं।"


4
@StefanPochmann: मैंने पहले कोड पढ़ा होगा (यही वजह है कि मुझे यह पहले से ही पता था)। यह बाइट कोड हैंडलर हैBUILD_LIST_UNPACK , यह _PyList_Extendकॉलिंग के सी समतुल्य के रूप में उपयोग करता है extend(बस सीधे, बजाय विधि खोज के)। उन्होंने इसे tupleअनपैकिंग के साथ निर्माण के लिए रास्तों से जोड़ा ; tupleएस टुकड़ा निर्माण के लिए अच्छी तरह से समग्र नहीं करते हैं, इसलिए वे हमेशा एक list(समग्र से लाभ के लिए) के लिए अनपैक करते हैं , और tupleजब वह अनुरोध किया गया था तब अंत में परिवर्तित हो जाते हैं।
शैडो रेंजर

4
नोट यह है कि जाहिरा तौर पर 3.9 में आने वाले बदलाव , जहां निर्माण अलग bytecodes साथ किया जाता है ( BUILD_LIST, LIST_EXTENDअनपैक करने के लिए प्रत्येक बात के लिए, LIST_APPENDपूरे निर्माण से पहले ढेर पर एक आइटम के लिए), सब कुछ लोड करने के बजाय list(यह अनुमति देता है एक एकल बाइट कोड अनुदेश के साथ संकलक को लागू करने की तरह ऑप्टिमाइज़ेशन करने के लिए है कि सभी एक-में-शिक्षा की अनुमति नहीं दी, [*a, b, *c]के रूप में LIST_EXTEND, LIST_APPEND, LIST_EXTENDw / ओ रैप करने के लिए की आवश्यकता होगी, bएक एक- में tupleकी आवश्यकताओं को पूरा करने के लिए BUILD_LIST_UNPACK)।
शैडो रेंजर

18

क्या होता है की पूरी तस्वीर , अन्य उत्तरों और टिप्पणियों पर निर्माण (विशेष रूप से शैडरंगर का उत्तर , जो यह भी बताता है कि ऐसा क्यों किया गया है)।

Disassembling से पता चलता है कि BUILD_LIST_UNPACKइस्तेमाल किया जाता है:

>>> import dis
>>> dis.dis('[*a]')
  1           0 LOAD_NAME                0 (a)
              2 BUILD_LIST_UNPACK        1
              4 RETURN_VALUE

इसे संभाला गया हैceval.c , जो एक खाली सूची बनाता है और इसे (साथ a) विस्तारित करता है :

        case TARGET(BUILD_LIST_UNPACK): {
            ...
            PyObject *sum = PyList_New(0);
              ...
                none_val = _PyList_Extend((PyListObject *)sum, PEEK(i));

_PyList_Extend उपयोग करता है list_extend :

_PyList_Extend(PyListObject *self, PyObject *iterable)
{
    return list_extend(self, iterable);
}

जो आकार के योग के साथ कहता हैlist_resize :

list_extend(PyListObject *self, PyObject *iterable)
    ...
        n = PySequence_Fast_GET_SIZE(iterable);
        ...
        m = Py_SIZE(self);
        ...
        if (list_resize(self, m + n) < 0) {

और वह overallocates इस प्रकार है:

list_resize(PyListObject *self, Py_ssize_t newsize)
{
  ...
    new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);

आइए चेक करते हैं। उपरोक्त सूत्र के साथ स्पॉट की अपेक्षित संख्या की गणना करें, और अपेक्षित बाइट के आकार को 8 से गुणा करके (जैसा कि मैं यहां 64-बिट पायथन का उपयोग कर रहा हूं) की गणना करें और एक खाली सूची के बाइट के आकार (यानी, एक सूची ऑब्जेक्ट के स्थिर ओवरहेड) को जोड़ दें। :

from sys import getsizeof
for n in range(13):
    a = [None] * n
    expected_spots = n + (n >> 3) + (3 if n < 9 else 6)
    expected_bytesize = getsizeof([]) + expected_spots * 8
    real_bytesize = getsizeof([*a])
    print(n,
          expected_bytesize,
          real_bytesize,
          real_bytesize == expected_bytesize)

आउटपुट:

0 80 56 False
1 88 88 True
2 96 96 True
3 104 104 True
4 112 112 True
5 120 120 True
6 128 128 True
7 136 136 True
8 152 152 True
9 184 184 True
10 192 192 True
11 200 200 True
12 208 208 True

मैचों को छोड़कर n = 0, जो list_extendवास्तव में शॉर्टकट हैं , इसलिए वास्तव में वह मैच भी:

        if (n == 0) {
            ...
            Py_RETURN_NONE;
        }
        ...
        if (list_resize(self, m + n) < 0) {

8

ये CPython दुभाषिया का कार्यान्वयन विवरण होने जा रहे हैं, और इसलिए अन्य दुभाषियों के अनुरूप नहीं हो सकते हैं।

उसने कहा, आप देख सकते हैं कि समझ और list(a)व्यवहार यहाँ कहाँ आते हैं:

https://github.com/python/cpython/blob/master/Objects/listobject.c#L36

विशेष रूप से समझ के लिए:

 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
...

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

उन लाइनों के ठीक नीचे, वहाँ है list_preallocate_exactजो कॉल करते समय उपयोग किया जाता है list(a)


1
[*a]एक बार में व्यक्तिगत तत्वों को एक में जोड़ना नहीं है। यह अपना खुद का समर्पित बायटेकोड है, जो बल्क इंसर्शन करता है extend
शैडो रेंजर

मुझे पता है - मुझे लगता है कि मैं उस पर पर्याप्त खुदाई नहीं किया था। पर अनुभाग निकाल दिया[*a]
रैंडी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.