Python Listesi Nasıl Uygulanır?


Yanıtlar:


58

Bu bir var dinamik dizisi . Pratik kanıt: Endeksleme, (elbette son derece küçük farklarla (0.0013 µsec!)) İndekse bakılmaksızın aynı zamanı alır:

...>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 veya Jython bağlantılı listeler kullansaydı, listelerin dinamik diziler olduğu varsayımına dayanan birçok yaygın olarak kullanılan kütüphanenin performansını bozarlardı.


1
@Ralf: CPU'mun (bu konuda da diğer birçok donanımın) eski ve köpek yavaş olduğunu biliyorum - parlak tarafta, benim için yeterince hızlı çalışan kodun tüm kullanıcılar için yeterince hızlı olduğunu varsayabilirim: D

88
@delnan: -1 6 pratik oy gibi "pratik kanıt" da saçmalık. Zamanın yaklaşık% 98'i x=[None]*1000, herhangi bir olası liste erişim farkının ölçümünü kesin olmayan bir şekilde bırakarak yapılır. Başlatma işlemini ayırmanız gerekir:-s "x=[None]*100" "x[0]"
John Machin

26
Bağlantılı listenin naif bir uygulaması olmadığını gösterir. Kesinlikle bir dizi olduğunu göstermez.
Michael Mior


3
Sadece bağlantılı liste ve diziden çok daha fazla yapı var, zamanlama aralarında karar vermek için pratik bir kullanım değildir.
Ross Hemsley

236

C kodu aslında oldukça basit. Bir makroyu genişletmek ve bazı alakasız yorumları budamak, temel yapı, listobject.hbir listeyi şu şekilde tanımlar:

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_HEADbir başvuru sayısı ve bir tür tanımlayıcısı içerir. Yani, aşırı konumlanan bir vektör / dizi. Dolduğunda böyle bir diziyi yeniden boyutlandırma kodu listobject.c. Aslında diziyi iki katına çıkarmaz, ancak ayırarak büyür

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

her seferinde kapasiteye göre newsize, istenen boyutun nerede olduğu ( tek tek değil, rastgele sayıda elemanla allocated + 1yapabileceğiniz için değil ).extendappend

Ayrıca bkz. Python SSS .


6
Bu nedenle, python listeleri üzerinden yineleme yaparken, bağlantılı listeler kadar yavaştır, çünkü her giriş sadece bir işaretçi olduğundan, her öğe büyük olasılıkla bir önbellek kaçırmasına neden olur.
Kr0e

9
@ Kr0e: sonraki öğeler aslında aynı nesne ise değil :) Ancak daha küçük / önbellek dostu veri yapılarına ihtiyacınız varsa, arraymodül veya NumPy tercih edilmelidir.
Fred Foo

@ Kr0e Liste üzerinde yineleme yapmanın bağlantılı listeler kadar yavaş olduğunu söyleyemem, ancak bağlı listelerin değerleri üzerinde yineleme yapmanın Fred'in bahsettiği uyarı ile bağlantılı bir liste kadar yavaş olduğunu söyleyebilirim . Örneğin, listeyi başka bir listeye kopyalamak için yineleme işlemi, bağlantılı listeden daha hızlı olmalıdır.
Ganea Dan Andrei

35

CPython'da listeler işaretçi dizileridir. Python'un diğer uygulamaları bunları farklı şekillerde saklamayı seçebilir.


32

Bu uygulamaya bağlıdır, ancak IIRC:

  • CPython bir dizi işaretçi kullanır
  • Jython bir ArrayList
  • IronPython görünüşe göre bir dizi kullanıyor. Öğrenmek için kaynak koduna göz atabilirsiniz .

Böylece hepsinin O (1) rasgele erişimi vardır.


1
Python yorumlayıcısında olduğu gibi, bağlı listeler halinde listeler uygulayan uygulama, python dilinin geçerli bir uygulaması olabilir mi? Başka bir deyişle: O (1) listelere rastgele erişim garanti edilmez mi? Bu, uygulama ayrıntılarına güvenmeden verimli kod yazmayı imkansız kılmaz mı?
sepp2k

2
@sepp Python listeleri sadece sipariş koleksiyonları olduğuna inanıyorum; söz konusu uygulamanın uygulanması ve / veya performans gereksinimleri açıkça belirtilmemiştir
NullUserException

6
@ sppe2k: Python gerçekten standart veya resmi bir spesifikasyona sahip olmadığından ("... garantili ..." diyen bazı belgeler olsa da), "this" bölümünde olduğu gibi% 100 emin olamazsınız. msgstr "bir parça kağıtla garantilidir". Ancak O(1)liste indeksleme oldukça yaygın ve geçerli bir varsayım olduğundan, hiçbir uygulama bunu kırmaya cesaret edemez.

@Paul Listelerin altında yatan uygulamanın nasıl yapılması gerektiği hakkında hiçbir şey söylemiyor.
NullUserException

Sadece şeylerin büyük O çalışma süresini belirtmek olmaz. Dil sözdizimi belirtimi mutlaka uygulama ayrıntılarıyla aynı anlama gelmez, genellikle böyle olur.
Paul McMillan

26

Laurent Luce'ın "Python listesinin uygulanması" başlıklı makalesini öneririm . Benim için gerçekten yararlı oldu çünkü yazar listenin CPython'da nasıl uygulandığını açıklıyor ve bu amaçla mükemmel diyagramlar kullanıyor.

Liste nesnesi C yapısı

CPython'daki bir liste nesnesi aşağıdaki C yapısıyla temsil edilir. ob_itemliste öğelerine işaretçilerin bir listesidir. ayrılan bellekte ayrılan yuva sayısıdır.

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

Ayrılan yuvalar ile listenin boyutu arasındaki farkın fark edilmesi önemlidir. Bir listenin boyutu ile aynıdır len(l). Tahsis edilen yuvaların sayısı, hafızada tahsis edilen yuva sayısıdır. Genellikle, tahsis edilen boyuttan daha büyük olabilir. Bu, realloclisteye her yeni öğe eklendiğinde çağrı yapılmasını önlemek içindir .

...

ekleme

Biz listeye bir tamsayı ekleyin: l.append(1). Ne oluyor?
resim açıklamasını buraya girin

Biz bir daha eleman ekleyerek devam: l.append(2). list_resizen + 1 = 2 ile çağrılır, ancak ayrılan boyut 4 olduğundan daha fazla bellek ayırmaya gerek yoktur. Aynı şey 2 tam sayı daha eklediğimizde olur: l.append(3), l.append(4). Aşağıdaki şemada şu ana kadar sahip olduklarımız gösterilmektedir.

resim açıklamasını buraya girin

...

Ekle

Şimdi 1 konumuna yeni bir tamsayı (5) ekleyelim: l.insert(1,5)ve dahili olarak neler olduğuna bakalım.resim açıklamasını buraya girin

...

Pop

Eğer son öğe pop zaman: l.pop(), listpop()denir. list_resizeiçeride çağrılır listpop()ve yeni boyut ayrılan boyutun yarısından daha azsa, liste küçülür.resim açıklamasını buraya girin

Yuva 4'ün hala tamsayıyı gösterdiğini gözlemleyebilirsiniz, ancak önemli olan şu anda 4 olan listenin boyutudur. Bir öğe daha açalım. Burada list_resize(), boyut - 1 = 4 - 1 = 3 ayrılan yuvaların yarısından daha azdır, bu nedenle liste 6 yuvaya küçültülür ve listenin yeni boyutu artık 3'tür.

Yuva 3 ve 4'ün hala bazı tamsayıları işaret ettiğini gözlemleyebilirsiniz, ancak önemli olan şu anda 3 olan listenin boyutudur.resim açıklamasını buraya girin

...

Kaldır Python liste nesnesi belirli bir öğe kaldırmak için bir yöntem vardır: l.remove(5).resim açıklamasını buraya girin


Teşekkürler, şimdi listenin bağlantı kısmını daha iyi anlıyorum . Python listesi bir aggregationdeğil composition. Ben de bir kompozisyon listesi olsaydı.
shuva

22

Belgelere göre ,

Python'un listeleri gerçekten değişken uzunluklu dizilerdir, Lisp tarzı bağlantılı listeler değildir.


5

Diğerlerinin yukarıda belirttiği gibi, listeler (oldukça büyük olduğunda) sabit bir alan tahsis edilerek ve bu alanın doldurulması gerekiyorsa, daha büyük bir alan tahsis edilerek ve öğelerin üzerine kopyalanarak uygulanır.

Yöntemin neden genelliği kaybetmeden O (1) itfa edildiğini anlamak için, bir = 2 ^ n öğesi eklediğimizi ve şimdi masamızı 2 ^ (n + 1) boyutuna ikiye katlamamız gerektiğini varsayın. Bu şu anda 2 ^ (n + 1) işlem yaptığımız anlamına geliyor. Son kopya, 2 ^ n işlem yaptık. Ondan önce 2 ^ (n-1) yaptık ... 8,4,2,1'e kadar. Şimdi, bunları toplarsak, 1 + 2 + 4 + 8 + ... + 2 ^ (n + 1) = 2 ^ (n + 2) - 1 <4 * 2 ^ n = O (2 ^ n) = O (a) toplam eklemeler (yani O (1) itfa edilmiş süre). Ayrıca, tablonun silmelere izin vermesi durumunda tablonun büzülmesinin farklı bir faktörde (örneğin 3x) yapılması gerektiğine dikkat edilmelidir.


Anladığım kadarıyla, eski öğelerin kopyalanması yok. Daha fazla alan ayrılır, ancak yeni alan, kullanılmakta olan alanla bitişik değildir ve yalnızca eklenecek daha yeni öğeler yeni alana kopyalanır. Yanlışım varsa lütfen düzelt.
Tushar Vazirani

1

Python'daki bir liste, birden çok değeri saklayabileceğiniz bir dizi gibi bir şeydir. Liste değiştirilebilir, yani değiştirebilirsin. Bilmeniz gereken en önemli şey, bir liste oluşturduğumuzda Python otomatik olarak bu liste değişkeni için bir başvuru_kimliği oluşturur. Başka bir değişken atayarak değiştirirseniz, ana liste değişecektir. Bir örnekle deneyelim:

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']

Ekliyoruz my_listama ana listemiz değişti. Bu ortalamanın listesi referans olarak bir kopya listesi ataması olarak atanmadı.


0

CPython listesinde dinamik dizi olarak uygulanır ve bu nedenle o zaman eklediğimizde sadece bir makro eklenmez, ancak her zaman yeni alan eklenmemesi için biraz daha fazla alan ayrılır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.