การใช้ __slots__?


คำตอบ:


1018

ใน Python อะไรคือจุดประสงค์__slots__และอะไรคือกรณีที่เราควรหลีกเลี่ยงสิ่งนี้

TLDR:

คุณลักษณะพิเศษ__slots__ช่วยให้คุณสามารถระบุแอตทริบิวต์ของอินสแตนซ์ที่คุณคาดว่าอินสแตนซ์ของวัตถุของคุณได้อย่างชัดเจนพร้อมกับผลลัพธ์ที่คาดหวัง:

  1. ได้เร็วขึ้นเข้าถึงคุณลักษณะที่
  2. ประหยัดพื้นที่ในหน่วยความจำ

ประหยัดพื้นที่มาจาก

  1. จัดเก็บการอ้างอิงค่าในสล็อตแทน __dict__การจัดเก็บการอ้างอิงค่าในช่องแทน
  2. การปฏิเสธ__dict__และการสร้างถ้าคลาสแม่ปฏิเสธพวกเขาและคุณประกาศ__weakref____slots__

ด่วน Caveats

ข้อแม้เล็ก ๆ คุณควรประกาศเฉพาะช่วงเวลาหนึ่งในต้นไม้ที่มีการสืบทอด ตัวอย่างเช่น:

class Base:
    __slots__ = 'foo', 'bar'

class Right(Base):
    __slots__ = 'baz', 

class Wrong(Base):
    __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar

Python จะไม่คัดค้านเมื่อคุณทำผิด (อาจเป็นไปได้) ปัญหาอาจไม่ชัดเจน แต่วัตถุของคุณจะใช้พื้นที่มากกว่าที่ควรจะเป็น Python 3.8:

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)

นี่เป็นเพราะตัวบอกสล็อตของ Base มีสล็อตแยกต่างหากจาก Wrong's สิ่งนี้ไม่ควรเกิดขึ้น แต่มันสามารถ:

>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'

ข้อแม้ที่ใหญ่ที่สุดสำหรับหลายมรดก - ไม่สามารถ "รวมชั้นผู้ปกครองที่มีช่องว่าง" ได้

เพื่อรองรับข้อ จำกัด นี้ให้ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด: แยกปัจจัยทั้งหมดยกเว้นนามธรรมอย่างใดอย่างหนึ่งหรือทั้งหมดของผู้ปกครองที่ระดับคอนกรีตของพวกเขาตามลำดับและคลาสคอนกรีตใหม่ของคุณจะได้รับมรดกร่วมกัน - ให้ช่องว่างนามธรรม ห้องสมุดมาตรฐาน)

ดูหัวข้อในการสืบทอดหลายรายการด้านล่างสำหรับตัวอย่าง

ที่ต้องการ:

  • จะมีคุณลักษณะที่มีชื่ออยู่ใน__slots__จริงถูกเก็บไว้ในช่องแทนของชั้นจะต้องสืบทอดมาจาก__dict__object

  • เพื่อป้องกันการสร้าง a __dict__คุณต้องรับช่วงจากobjectและคลาสทั้งหมดในการสืบทอดต้องประกาศ__slots__และไม่มีรายการใดสามารถ'__dict__'เข้าร่วมได้

มีรายละเอียดมากมายหากคุณต้องการอ่านต่อไป

ทำไมต้องใช้ __slots__ : เข้าถึงแอตทริบิวต์ได้เร็วขึ้น

ผู้สร้าง Python, Guido van Rossum กล่าวว่าเขาสร้างขึ้นจริง__slots__เพื่อเข้าถึงแอตทริบิวต์ได้เร็วขึ้น

มันเป็นเรื่องเล็กน้อยที่จะแสดงให้เห็นถึงการเข้าถึงได้เร็วขึ้นอย่างมีนัยสำคัญ:

import timeit

class Foo(object): __slots__ = 'foo',

class Bar(object): pass

slotted = Foo()
not_slotted = Bar()

def get_set_delete_fn(obj):
    def get_set_delete():
        obj.foo = 'foo'
        obj.foo
        del obj.foo
    return get_set_delete

และ

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085

การเข้าถึงแบบ slotted นั้นเร็วกว่าเกือบ 30% ใน Python 3.5 บน Ubuntu

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

ใน Python 2 บน Windows ฉันวัดได้เร็วขึ้นประมาณ 15%

ทำไมต้องใช้ __slots__ : การประหยัดหน่วยความจำ

วัตถุประสงค์อื่นของ __slots__คือเพื่อลดพื้นที่ในหน่วยความจำที่แต่ละอินสแตนซ์ของวัตถุใช้

การมีส่วนร่วมของฉันในเอกสารอธิบายอย่างชัดเจนถึงเหตุผลที่อยู่เบื้องหลัง :

พื้นที่ที่บันทึกไว้โดยใช้__dict__มีความสำคัญมาก

SQLAlchemy มีคุณสมบัติในการประหยัดหน่วยความจำ__slots__มากมาย

ในการตรวจสอบสิ่งนี้โดยใช้การแจกจ่าย Anaconda ของ Python 2.7 บน Ubuntu Linux ด้วยguppy.hpy(หรือ heapy) และsys.getsizeofขนาดของอินสแตนซ์ของคลาสที่ไม่มีการ__slots__ประกาศและไม่มีอะไรอื่นคือ 64 ไบต์ แต่นั่นไม่ได้__dict__รวมถึง ขอบคุณ Python สำหรับการประเมินที่ขี้เกียจอีกครั้ง__dict__ดูเหมือนจะไม่ถูกเรียกให้มีอยู่จนกว่าจะมีการอ้างอิง แต่คลาสที่ไม่มีข้อมูลมักจะไร้ประโยชน์ เมื่อถูกเรียกเข้าสู่การมีอยู่ของ__dict__แอตทริบิวต์นั้นจะต้องมีอย่างน้อย 280 ไบต์เพิ่มเติม

ในทางตรงกันข้ามอินสแตนซ์ของชั้นเรียนที่มีการ__slots__ประกาศให้เป็น() (ไม่มีข้อมูล) มีเพียง 16 ไบต์และ 56 ไบต์ทั้งหมดที่มีหนึ่งไอเท็มในสล็อต 64 ด้วยสอง

สำหรับ 64 บิต Python ฉันจะแสดงปริมาณการใช้หน่วยความจำเป็นไบต์ใน Python 2.7 และ 3.6 สำหรับ__slots__และ__dict__(ไม่กำหนดสล็อต) สำหรับแต่ละจุดที่ dict เติบโตใน 3.6 (ยกเว้นแอตทริบิวต์ 0, 1 และ 2):

       Python 2.7             Python 3.6
attrs  __slots__  __dict__*   __slots__  __dict__* | *(no slots defined)
none   16         56 + 272   16         56 + 112 | if __dict__ referenced
one    48         56 + 272    48         56 + 112
two    56         56 + 272    56         56 + 112
six    88         56 + 1040   88         56 + 152
11     128        56 + 1040   128        56 + 240
22     216        56 + 3344   216        56 + 408     
43     384        56 + 3344   384        56 + 752

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

เพื่อความสมบูรณ์ของบันทึกย่อของฉันโปรดทราบว่ามีค่าใช้จ่ายครั้งเดียวต่อสล็อตในเนมสเปซของคลาสที่ 64 ไบต์ใน Python 2 และ 72 ไบต์ใน Python 3 เนื่องจากสล็อตใช้ตัวอธิบายข้อมูลเช่นคุณสมบัติที่เรียกว่า "สมาชิก"

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72

การสาธิตของ__slots__:

หากต้องการปฏิเสธการสร้าง a __dict__คุณต้อง subclass object:

class Base(object): 
    __slots__ = ()

ขณะนี้:

>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'

หรือคลาสย่อยคลาสอื่นที่กำหนด __slots__

class Child(Base):
    __slots__ = ('a',)

และตอนนี้:

c = Child()
c.a = 'a'

แต่:

>>> c.b = 'b'
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'

หากต้องการอนุญาต__dict__การสร้างในขณะที่ subclassing วัตถุ slotted เพียงเพิ่ม'__dict__'ไปที่__slots__(โปรดทราบว่าช่องมีการสั่งซื้อและคุณไม่ควรทำซ้ำช่องที่อยู่ในชั้นผู้ปกครอง):

class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'

และ

>>> swd.__dict__
{'c': 'c'}

หรือคุณไม่จำเป็นต้องประกาศ__slots__ในคลาสย่อยของคุณและคุณจะยังคงใช้สล็อตจากผู้ปกครอง แต่ไม่ จำกัด การสร้าง__dict__:

class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'

และ:

>>> ns.__dict__
{'b': 'b'}

อย่างไรก็ตาม__slots__อาจทำให้เกิดปัญหาสำหรับการสืบทอดหลายรายการ:

class BaseA(object): 
    __slots__ = ('a',)

class BaseB(object): 
    __slots__ = ('b',)

เนื่องจากการสร้างคลาสย่อยจากผู้ปกครองที่มีช่องที่ไม่ว่างทั้งสองล้มเหลว:

>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    class Child(BaseA, BaseB): __slots__ = ()
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

หากคุณพบปัญหานี้คุณสามารถลบออก__slots__จากผู้ปกครองหรือถ้าคุณมีการควบคุมของผู้ปกครองให้พวกเขาช่องว่างหรือ refactor เพื่อ abstractions:

from abc import ABC

class AbstractA(ABC):
    __slots__ = ()

class BaseA(AbstractA): 
    __slots__ = ('a',)

class AbstractB(ABC):
    __slots__ = ()

class BaseB(AbstractB): 
    __slots__ = ('b',)

class Child(AbstractA, AbstractB): 
    __slots__ = ('a', 'b')

c = Child() # no problem!

เพิ่ม'__dict__'เพื่อ__slots__รับการมอบหมายแบบไดนามิก:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'

และตอนนี้:

>>> foo = Foo()
>>> foo.boink = 'boink'

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

เมื่อคุณได้รับมรดกจากวัตถุที่ไม่ได้เสียบคุณจะได้รับในประเภทเดียวกันของความหมายเมื่อคุณใช้__slots__- ชื่อที่อยู่ในการ__slots__ชี้ไปที่ค่า slotted ในขณะที่ค่าอื่น ๆ __dict__ที่จะใส่ในอินสแตนซ์ของ

การหลีกเลี่ยง__slots__เพราะคุณต้องการเพิ่มคุณลักษณะได้ทันทีไม่ใช่เหตุผลที่ดี - เพียงเพิ่ม"__dict__"ของคุณ__slots__ถ้าจำเป็น

ในทำนองเดียวกันคุณสามารถเพิ่ม__weakref__การ__slots__อย่างชัดเจนถ้าคุณต้องการคุณลักษณะที่

ตั้งค่าเป็น tuple ที่ว่างเปล่าเมื่อ subclassing namedtuple:

namedtuple builtin สร้างอินสแตนซ์ที่ไม่เปลี่ยนรูปแบบซึ่งมีน้ำหนักเบามาก (โดยทั่วไปขนาดของ tuples) แต่เพื่อให้ได้ประโยชน์คุณต้องทำด้วยตัวเองถ้าคุณซับคลาสเหล่านั้น:

from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
    """MyNT is an immutable and lightweight object"""
    __slots__ = ()

การใช้งาน:

>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'

และพยายามกำหนดแอตทริบิวต์ที่ไม่คาดคิดเกิดขึ้นAttributeErrorเพราะเราป้องกันการสร้าง__dict__:

>>> nt.quux = 'quux'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'

คุณสามารถอนุญาตให้__dict__สร้างโดยออกไป__slots__ = ()แต่คุณไม่สามารถใช้แบบไม่__slots__ย่อยกับ tuple ชนิดย่อยได้

Caveat ที่ใหญ่ที่สุด: หลายมรดก

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

class Foo(object): 
    __slots__ = 'foo', 'bar'
class Bar(object):
    __slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()

>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

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

class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'

คุณไม่จำเป็นต้องมีสล็อต - ดังนั้นหากคุณเพิ่มและลบออกในภายหลังก็ไม่ควรทำให้เกิดปัญหาใด ๆ

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

ในการสาธิตก่อนอื่นเรามาสร้างคลาสที่มีรหัสที่เราต้องการใช้ภายใต้การสืบทอดหลายแบบ

class AbstractBase:
    __slots__ = ()
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'

เราสามารถใช้ข้างต้นได้โดยตรงโดยการสืบทอดและประกาศช่องโฆษณาที่คาดหวัง:

class Foo(AbstractBase):
    __slots__ = 'a', 'b'

แต่เราไม่สนใจเกี่ยวกับเรื่องนี้นั่นคือการถ่ายทอดทางพันธุกรรมเพียงเล็กน้อยเราจำเป็นต้องมีชั้นเรียนอื่นที่เราอาจได้รับมรดกด้วยบางทีอาจจะเป็นเสียงที่ดังมาก:

class AbstractBaseC:
    __slots__ = ()
    @property
    def c(self):
        print('getting c!')
        return self._c
    @c.setter
    def c(self, arg):
        print('setting c!')
        self._c = arg

ตอนนี้ถ้าฐานทั้งสองมีช่องว่างเราไม่สามารถทำด้านล่างได้ (อันที่จริงถ้าเราต้องการเราสามารถให้AbstractBaseช่อง a และ b ที่ไม่ว่างเปล่าและปล่อยให้พวกเขาออกจากการประกาศด้านล่าง - การปล่อยพวกเขาไว้ในนั้นจะผิด):

class Concretion(AbstractBase, AbstractBaseC):
    __slots__ = 'a b _c'.split()

และตอนนี้เรามีฟังก์ชั่นการทำงานจากทั้งสองผ่านการสืบทอดหลายแบบและยังสามารถปฏิเสธ__dict__และการ__weakref__สร้างอินสแตนซ์ได้:

>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'

กรณีอื่น ๆ เพื่อหลีกเลี่ยงช่องเสียบ:

  • หลีกเลี่ยงพวกเขาเมื่อคุณต้องการที่จะดำเนินการ __class__มอบหมายด้วยคลาสอื่นที่ไม่มี (และคุณไม่สามารถเพิ่มได้) ยกเว้นว่าเลย์เอาต์สล็อตจะเหมือนกัน (ฉันสนใจที่จะเรียนรู้ว่าใครกำลังทำสิ่งนี้และทำไม)
  • หลีกเลี่ยงพวกเขาหากคุณต้องการ subclass ตัวแปรความยาวบิวด์อินเช่น long, tuple หรือ str และคุณต้องการเพิ่มแอททริบิว
  • หลีกเลี่ยงพวกเขาหากคุณยืนยันที่จะให้ค่าเริ่มต้นผ่านทางแอตทริบิวต์คลาสสำหรับตัวแปรอินสแตนซ์

คุณอาจจะสามารถหยอกล้อเพิ่มเติม caveats จากส่วนที่เหลือของ__slots__ เอกสาร (เอกสาร 3.7 dev เป็นปัจจุบันมากที่สุด)ซึ่งฉันได้มีส่วนร่วมที่สำคัญล่าสุด

คำวิจารณ์ของคำตอบอื่น ๆ

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

อย่า "ใช้__slots__เมื่อสร้างวัตถุจำนวนมากเท่านั้น"

ฉันพูด:

"คุณต้องการใช้__slots__ถ้าคุณจะยกตัวอย่างวัตถุจำนวนมาก (หลายแสน) ในชั้นเดียวกัน"

บทคัดย่อ Base Classes เช่นจากcollectionsโมดูลนั้นยังไม่ได้สร้างอินสแตนซ์ แต่__slots__จะถูกประกาศสำหรับพวกมัน

ทำไม?

หากผู้ใช้ต้องการที่จะปฏิเสธ__dict__หรือ__weakref__สร้างสิ่งเหล่านั้นจะต้องไม่สามารถใช้ได้ในชั้นผู้ปกครอง

__slots__ ก่อให้เกิดการใช้ซ้ำได้เมื่อสร้างส่วนต่อประสานหรือมิกซ์อิน

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

__slots__ ไม่ทำลายดอง

เมื่อทำการดองวัตถุที่มีช่องเสียบคุณอาจพบว่ามันบ่นว่ามีการเข้าใจผิดTypeError:

>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled

สิ่งนี้ไม่ถูกต้องจริงๆ ข้อความนี้มาจากโปรโตคอลที่เก่าที่สุดซึ่งเป็นค่าเริ่มต้น คุณสามารถเลือกโปรโตคอลล่าสุดด้วย-1อาร์กิวเมนต์ ในหลาม 2.7 นี้จะเป็น2(ซึ่งเป็นที่รู้จักใน 2.3) และใน 3.6 4มันเป็น

>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>

ใน Python 2.7:

>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>

ใน Python 3.6

>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>

ดังนั้นฉันจะเก็บเรื่องนี้ไว้ในใจเพราะมันเป็นปัญหาที่แก้ไขแล้ว

คำติชมของคำตอบที่ยอมรับ (จนถึง 2 ตุลาคม 2016)

ย่อหน้าแรกคือคำอธิบายสั้น ๆ ครึ่งคำทำนายครึ่งหนึ่ง นี่คือส่วนเดียวที่ตอบคำถามได้จริง

การใช้ที่เหมาะสม__slots__คือการประหยัดพื้นที่ในวัตถุ แทนที่จะมี dict แบบไดนามิกที่อนุญาตให้เพิ่มคุณลักษณะให้กับวัตถุได้ตลอดเวลามีโครงสร้างแบบสแตติกซึ่งไม่อนุญาตให้มีการเพิ่มหลังจากสร้างแล้ว สิ่งนี้จะบันทึกโอเวอร์เฮดของหนึ่ง dict สำหรับทุกวัตถุที่ใช้สล็อต

ช่วงครึ่งปีหลังเป็นความคิดที่ปรารถนาและปิดเครื่องหมาย:

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

Python ทำสิ่งที่คล้ายกันจริง ๆ เพียงสร้าง__dict__เมื่อเข้าถึง แต่สร้างวัตถุจำนวนมากที่ไม่มีข้อมูลค่อนข้างไร้สาระ

ที่สอง oversimplifies __slots__วรรคเฉียงเหตุผลที่แท้จริงที่ควรหลีกเลี่ยง ด้านล่างไม่ใช่เหตุผลที่แท้จริงในการหลีกเลี่ยงช่องเสียบ (สำหรับเหตุผลที่แท้จริงโปรดดูคำตอบที่เหลือของฉันด้านบน):

พวกเขาเปลี่ยนพฤติกรรมของวัตถุที่มีช่องในลักษณะที่สามารถถูกทารุณกรรมโดยการควบคุมประหลาดและ weenies พิมพ์คงที่

จากนั้นจะหารือเกี่ยวกับวิธีอื่น ๆ ในการบรรลุเป้าหมายที่ผิดปกติกับ Python โดยไม่พูดถึงสิ่งที่ต้องทำ __slots__ของการบรรลุเป้าหมายที่ขี้อ้อนกับงูหลามไม่ได้พูดคุยอะไรกับ

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

หลักฐานการใช้หน่วยความจำ

สร้างวัตถุปกติและวัตถุ slotted:

>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()

ยกตัวอย่างล้านของพวกเขา:

>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]

ตรวจสอบด้วยguppy.hpy().heap():

>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1000000  49 64000000  64  64000000  64 __main__.Foo
     1     169   0 16281480  16  80281480  80 list
     2 1000000  49 16000000  16  96281480  97 __main__.Bar
     3   12284   1   987472   1  97268952  97 str
...

เข้าถึงวัตถุปกติและวัตถุ__dict__และตรวจสอบอีกครั้ง:

>>> for f in foos:
...     f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
 Index  Count   %      Size    % Cumulative  % Kind (class / dict of class)
     0 1000000  33 280000000  74 280000000  74 dict of __main__.Foo
     1 1000000  33  64000000  17 344000000  91 __main__.Foo
     2     169   0  16281480   4 360281480  95 list
     3 1000000  33  16000000   4 376281480  99 __main__.Bar
     4   12284   0    987472   0 377268952  99 str
...

สิ่งนี้สอดคล้องกับประวัติของ Python จากUnifying ประเภทและคลาสใน Python 2.2

หากคุณ subclass ประเภทในตัวพื้นที่พิเศษจะถูกเพิ่มลงในอินสแตนซ์ที่จะรองรับ__dict__และ__weakrefs__โดยอัตโนมัติ ( __dict__ไม่ถูกเตรียมใช้งานจนกว่าคุณจะใช้มันดังนั้นคุณไม่ควรกังวลเกี่ยวกับพื้นที่ว่างของพจนานุกรมว่างสำหรับแต่ละอินสแตนซ์ที่คุณสร้าง) หากคุณไม่ต้องการพื้นที่พิเศษนี้คุณสามารถเพิ่มวลี " __slots__ = []" ใน ชั้นเรียนของคุณ.


14
ว้าวหนึ่งคำตอบจากนรก - ขอบคุณ! อย่างไรก็ตามฉันไม่ได้จับclass Child(BaseA, BaseB): __slots__ = ('a', 'b')ตัวอย่างกับ empy-slot-parent เหตุใดจึงdictproxyสร้างที่นี่แทนที่จะสร้างขึ้นAttributeErrorเพื่อc?
Skandix

@Skandix ขอบคุณที่นำความผิดพลาดนี้มาให้ความสนใจของฉันมันกลับกลายเป็นว่า c ไม่ใช่การสร้างอินสแตนซ์ฉันอาจลืมว่าฉันแก้ไขส่วนนั้นเมื่อฉันบันทึกลงในประวัติโพสต์ มันอาจจะถูกจับได้เร็วกว่านี้ถ้าฉันทำสิ่งที่ถูกต้องและทำให้รหัสถูกคัดลอกได้มากขึ้น ... ขอบคุณอีกครั้ง!
Aaron Hall

38
__slots__คำตอบนี้ควรจะเป็นส่วนหนึ่งของเอกสารอย่างเป็นทางการเกี่ยวกับงูหลาม อย่างจริงจัง! ขอบคุณ!
NightElfik

13
@NightElfik เชื่อหรือไม่ฉันสนับสนุน Python docs __slots__ประมาณหนึ่งปีหลัง: github.com/python/cpython/pull/1819/files
Aaron Hall

คำตอบที่มีรายละเอียดดีเลิศ ฉันมีคำถามหนึ่งข้อ: เราควรใช้สล็อตเป็นค่าเริ่มต้นเว้นแต่การใช้งานจะกระทบกับหนึ่งใน caveats หรือเป็นบางสิ่งที่ต้องพิจารณาหากคุณรู้ว่าคุณกำลังต่อสู้เพื่อความเร็ว / หน่วยความจำ เพื่อให้เป็นอีกวิธีหนึ่งคุณควรกระตุ้นให้ newbie เรียนรู้เกี่ยวกับพวกเขาและใช้พวกเขาตั้งแต่ต้น?
Freethebees

265

อ้างจาค็อบ Hallen :

การใช้ที่เหมาะสม__slots__คือการประหยัดพื้นที่ในวัตถุ แทนที่จะมี dict แบบไดนามิกที่อนุญาตให้เพิ่มคุณลักษณะให้กับวัตถุได้ตลอดเวลามีโครงสร้างแบบสแตติกซึ่งไม่อนุญาตให้มีการเพิ่มหลังจากสร้างแล้ว [การใช้การ__slots__กำจัดค่าใช้จ่ายหนึ่ง dict สำหรับทุกวัตถุ] แม้ว่าบางครั้งนี่เป็นการเพิ่มประสิทธิภาพที่มีประโยชน์ แต่ก็ไม่จำเป็นอย่างสมบูรณ์หากล่าม Python นั้นมีพลวัตเพียงพอที่จะต้องใช้ dict เมื่อมีการเพิ่มเข้าไป วัตถุ.

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

การทำให้ CPython ฉลาดพอที่จะจัดการกับการประหยัดพื้นที่โดยไม่ต้อง__slots__มีภารกิจสำคัญซึ่งอาจเป็นสาเหตุว่าทำไมมันไม่อยู่ในรายการการเปลี่ยนแปลงสำหรับ P3k (ยัง)


86
ฉันต้องการดูรายละเอียดเพิ่มเติมเกี่ยวกับ "การพิมพ์แบบคงที่" / จุดตกแต่งภายใน การอ้างถึงบุคคลที่สามที่ไม่มีอยู่จะไม่ช่วยเหลือ __slots__ไม่ได้แก้ไขปัญหาเดียวกับการพิมพ์แบบคงที่ ตัวอย่างเช่นใน C ++ ไม่ใช่การประกาศตัวแปรสมาชิกถูก จำกัด แต่เป็นการกำหนดประเภทที่ไม่ตั้งใจ (และบังคับใช้คอมไพเลอร์) ให้กับตัวแปรนั้น ฉันไม่ยอมให้ใช้__slots__เพียงแค่สนใจในการสนทนา ขอบคุณ!
hiwaylon

126

คุณต้องการใช้__slots__ถ้าคุณจะยกตัวอย่างวัตถุจำนวนมาก (หลายแสน) ในชั้นเดียวกัน __slots__มีอยู่เป็นเครื่องมือเพิ่มประสิทธิภาพหน่วยความจำเท่านั้น

มันมีความท้อแท้อย่างมากที่จะใช้__slots__สำหรับการสร้างข้อ จำกัด ด้านคุณลักษณะ

การดองวัตถุด้วย__slots__จะไม่ทำงานกับโปรโตคอลดอง (เก่าที่สุด) เริ่มต้น จำเป็นต้องระบุรุ่นที่ใหม่กว่า

คุณสมบัติการวิปัสสนาอื่นของงูหลามก็อาจได้รับผลกระทบเช่นกัน


10
ฉันสาธิตการใช้วัตถุที่มีช่องเสียบในคำตอบของฉันและพูดถึงส่วนแรกของคำตอบของคุณ
Aaron Hall

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

61

วัตถุหลามแต่ละคนมี__dict__คุณสมบัติที่เป็นพจนานุกรมที่มีคุณลักษณะอื่น ๆ ทั้งหมด เช่นเมื่อคุณพิมพ์self.attrหลามกำลังทำself.__dict__['attr']อยู่ ในขณะที่คุณสามารถจินตนาการได้ว่าการใช้พจนานุกรมเพื่อเก็บแอตทริบิวต์นั้นต้องใช้พื้นที่และเวลาเพิ่มเติมในการเข้าถึง

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

ดังนั้นหากต้องการโครงสร้างสไตล์ C แทนคลาสที่เต็มเปี่ยมคุณสามารถใช้__slots__สำหรับการกระชับขนาดของวัตถุและลดเวลาในการเข้าถึงแอ็ตทริบิวต์ ตัวอย่างที่ดีคือคลาส Point ที่มีแอ็ตทริบิวต์ x & y หากคุณจะมีคะแนนมากคุณสามารถลองใช้__slots__เพื่อประหยัดหน่วยความจำบางส่วน


10
ไม่อินสแตนซ์ของคลาสที่มีการ__slots__กำหนดไม่เหมือนกับโครงสร้างแบบ C มีชื่อแอตทริบิวต์การแมปพจนานุกรมระดับคลาสสำหรับดัชนีมิฉะนั้นจะไม่สามารถทำสิ่งต่อไปนี้ได้: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)ฉันคิดว่าคำตอบนี้ควรได้รับการชี้แจง (ฉันสามารถทำได้ถ้าคุณต้องการ) นอกจากนี้ผมไม่แน่ใจว่าจะเร็วกว่าinstance.__hidden_attributes[instance.__class__[attrname]] instance.__dict__[attrname]
tzot

22

นอกเหนือจากคำตอบอื่น ๆ นี่คือตัวอย่างของการใช้__slots__:

>>> class Test(object):   #Must be new-style class!
...  __slots__ = ['x', 'y']
... 
>>> pt = Test()
>>> dir(pt)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', 
 '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', 
 '__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
>>> pt.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']

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


11

สล็อตมีประโยชน์อย่างมากสำหรับการเรียกใช้ไลบรารีเพื่อกำจัด "วิธีการส่งชื่อ" เมื่อทำการเรียกใช้ฟังก์ชัน สิ่งนี้ถูกกล่าวถึงในเอกสาร SWIGเอกสารสำหรับไลบรารีประสิทธิภาพสูงที่ต้องการลดค่าใช้จ่ายของฟังก์ชั่นสำหรับฟังก์ชั่นทั่วไปที่เรียกว่าการใช้สล็อตจะเร็วกว่ามาก

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


7

แอททริบิวของคลาสอินสแตนซ์มี 3 คุณสมบัติ: อินสแตนซ์ชื่อของแอททริบิวต์และค่าของแอททริบิวต์

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

อินสแตนซ์ (คุณลักษณะ) -> ค่า

ในการเข้าถึง __slots__ชื่อของแอ็ตทริบิวต์ทำหน้าที่เป็นพจนานุกรมและอินสแตนซ์ทำหน้าที่เป็นคีย์ในการค้นหาค่าพจนานุกรม

คุณลักษณะ (ตัวอย่าง) -> ค่า

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

คุณลักษณะ (ค่า) -> อินสแตนซ์


นี่เป็นส่วนแบ่งที่ดีและจะไม่เหมาะสมกับความคิดเห็นของหนึ่งในคำตอบที่แนะนำ flyweights แต่มันก็ไม่ใช่คำตอบที่สมบูรณ์สำหรับคำถามนั้น โดยเฉพาะอย่างยิ่ง (ในบริบทของคำถาม): ทำไม Flyweight และ "กรณีใด__slots__บ้างที่ควรหลีกเลี่ยง ... "
Merlyn Morgan-Graham

@ Merlyn Morgan-Graham มันทำหน้าที่เป็นแนวทางในการเลือก: การเข้าถึงปกติ __slots__ หรือฟลายเวท
Dmitry Rubanovich

3

ตัวอย่างง่ายๆของ__slot__แอตทริบิวต์

ปัญหา: ไม่ __slots__

ถ้าฉันไม่มี__slot__คุณสมบัติในชั้นเรียนของฉันฉันสามารถเพิ่มคุณสมบัติใหม่ให้กับวัตถุของฉัน

class Test:
    pass

obj1=Test()
obj2=Test()

print(obj1.__dict__)  #--> {}
obj1.x=12
print(obj1.__dict__)  # --> {'x': 12}
obj1.y=20
print(obj1.__dict__)  # --> {'x': 12, 'y': 20}

obj2.x=99
print(obj2.__dict__)  # --> {'x': 99}

หากคุณดูตัวอย่างข้างต้นคุณจะเห็นว่าobj1และobj2มีแอตทริบิวต์xและyของตัวเองและหลามได้สร้างdictแอตทริบิวต์สำหรับแต่ละวัตถุ ( obj1และobj2 )

สมมติว่าการทดสอบในชั้นเรียนของฉันมีวัตถุหลายพันอย่าง? การสร้างแอตทริบิวต์เพิ่มเติมdictสำหรับแต่ละวัตถุจะทำให้เกิดโอเวอร์เฮดจำนวนมาก (หน่วยความจำกำลังการคำนวณเป็นต้น) ในรหัสของฉัน

วิธีแก้ปัญหา: ด้วย __slots__

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

class Test:
    __slots__=("x")

obj1=Test()
obj2=Test()
obj1.x=12
print(obj1.x)  # --> 12
obj2.x=99
print(obj2.x)  # --> 99

obj1.y=28
print(obj1.y)  # --> AttributeError: 'Test' object has no attribute 'y'

2

การใช้งานที่ค่อนข้างคลุมเครือ__slots__คือการเพิ่มคุณสมบัติให้กับวัตถุพร็อกซีจากแพ็คเกจ ProxyTypes ซึ่งก่อนหน้านี้เป็นส่วนหนึ่งของโครงการ PEAK มันObjectWrapperช่วยให้คุณสามารถพร็อกซีวัตถุอื่น แต่สกัดกั้นการโต้ตอบทั้งหมดกับวัตถุพร็อกซี มันไม่ได้ใช้กันมาก (และไม่มีการสนับสนุน Python 3) แต่เราได้ใช้มันในการสร้าง wrapper การปิดกั้นการบล็อกแบบปลอดภัยรอบ ๆ การใช้งาน async บนพื้นฐานของพายุทอร์นาโดที่ตีกลับการเข้าถึงวัตถุพร็อกซีconcurrent.Futureวัตถุที่จะประสานและส่งคืนผลลัพธ์

โดยค่าเริ่มต้นการเข้าถึงคุณลักษณะใด ๆ ไปยังวัตถุพร็อกซีจะให้ผลลัพธ์จากวัตถุพร็อกซี หากคุณต้องการเพิ่มคุณสมบัติในวัตถุพร็อกซี่__slots__สามารถนำมาใช้

from peak.util.proxies import ObjectWrapper

class Original(object):
    def __init__(self):
        self.name = 'The Original'

class ProxyOriginal(ObjectWrapper):

    __slots__ = ['proxy_name']

    def __init__(self, subject, proxy_name):
        # proxy_info attributed added directly to the
        # Original instance, not the ProxyOriginal instance
        self.proxy_info = 'You are proxied by {}'.format(proxy_name)

        # proxy_name added to ProxyOriginal instance, since it is
        # defined in __slots__
        self.proxy_name = proxy_name

        super(ProxyOriginal, self).__init__(subject)

if __name__ == "__main__":
    original = Original()
    proxy = ProxyOriginal(original, 'Proxy Overlord')

    # Both statements print "The Original"
    print "original.name: ", original.name
    print "proxy.name: ", proxy.name

    # Both statements below print 
    # "You are proxied by Proxy Overlord", since the ProxyOriginal
    # __init__ sets it to the original object 
    print "original.proxy_info: ", original.proxy_info
    print "proxy.proxy_info: ", proxy.proxy_info

    # prints "Proxy Overlord"
    print "proxy.proxy_name: ", proxy.proxy_name
    # Raises AttributeError since proxy_name is only set on 
    # the proxy object
    print "original.proxy_name: ", proxy.proxy_name

1

คุณมีหลัก - - __slots__ไม่มีการใช้งานสำหรับ

สำหรับเวลาที่คุณคิดว่าคุณอาจต้องการ__slots__คุณจริง ๆ แล้วต้องการใช้รูปแบบการออกแบบLightweightหรือFlyweight นี่เป็นกรณีที่เมื่อคุณไม่ต้องการใช้วัตถุ Python หมดจดอีกต่อไป แต่คุณต้องการให้ wrapper คล้ายวัตถุของ Python ล้อมรอบอาร์เรย์ struct หรือ numpy array

class Flyweight(object):

    def get(self, theData, index):
        return theData[index]

    def set(self, theData, index, value):
        theData[index]= value

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


17
Flyweight ทำ__slots__อะไรได้บ้าง?
oefe

3
@oefe: ฉันไม่ได้รับคำถามของคุณอย่างแน่นอน ฉันสามารถพูดคำตอบของฉันถ้ามันช่วย "เมื่อคุณคิดว่าคุณอาจต้องการช่องคุณต้องการใช้ ... รูปแบบการออกแบบ Flyweight" นั่นคือสิ่งที่ฟลายเวทจะทำอย่างไรกับช่อง คุณมีคำถามที่เฉพาะเจาะจงมากกว่านี้หรือไม่?
S.Lott

21
@oefe: Flyweight และ__slots__เป็นทั้งเทคนิคการเพิ่มประสิทธิภาพเพื่อประหยัดหน่วยความจำ __slots__แสดงให้เห็นถึงประโยชน์เมื่อคุณมีวัตถุมากมายเช่นเดียวกับรูปแบบการออกแบบ Flyweight ทั้งคู่แก้ปัญหาเดียวกัน
jfs

7
มีการเปรียบเทียบระหว่างการใช้สล็อตกับการใช้ Flyweight เกี่ยวกับการใช้หน่วยความจำและความเร็วหรือไม่?
kontulai

8
แม้ว่า Flyweight จะมีประโยชน์ในบางบริบทเชื่อหรือไม่คำตอบของ "ฉันจะลดการใช้หน่วยความจำใน Python ได้อย่างไรเมื่อฉันสร้างวัตถุ zillion" ไม่เสมอ "อย่าใช้ Python สำหรับวัตถุ zillion ของคุณ" บางครั้ง__slots__คำตอบคือจริงๆและเมื่อ Evgeni ชี้ให้เห็นก็สามารถเพิ่มเป็นความคิดที่เรียบง่าย (เช่นคุณสามารถมุ่งเน้นไปที่ความถูกต้องก่อนแล้วจึงเพิ่มประสิทธิภาพ)
Patrick Maupin

0

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

นี่คือการเปรียบเทียบการสร้างทรีวัตถุที่มีรายการนับล้านรายการโดยใช้สล็อตและไม่มีสล็อต เป็นการอ้างอิงถึงประสิทธิภาพเมื่อใช้ dicts ธรรมดาสำหรับต้นไม้ (Py2.7.10 บน OSX):

********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict

คลาสทดสอบ (ident, appart จาก slot):

class Element(object):
    __slots__ = ['_typ', 'id', 'parent', 'childs']
    def __init__(self, typ, id, parent=None):
        self._typ = typ
        self.id = id
        self.childs = []
        if parent:
            self.parent = parent
            parent.childs.append(self)

class ElementNoSlots(object): (same, w/o slots)

testcode โหมด verbose:

na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
    print '*' * 10, 'RUN', i, '*' * 10
    # tree with slot and no slot:
    for cls in Element, ElementNoSlots:
        t1 = time.time()
        root = cls('root', 'root')
        for i in xrange(na):
            ela = cls(typ='a', id=i, parent=root)
            for j in xrange(nb):
                elb = cls(typ='b', id=(i, j), parent=ela)
                for k in xrange(nc):
                    elc = cls(typ='c', id=(i, j, k), parent=elb)
        to =  time.time() - t1
        print to, cls
        del root

    # ref: tree with dicts only:
    t1 = time.time()
    droot = {'childs': []}
    for i in xrange(na):
        ela =  {'typ': 'a', id: i, 'childs': []}
        droot['childs'].append(ela)
        for j in xrange(nb):
            elb =  {'typ': 'b', id: (i, j), 'childs': []}
            ela['childs'].append(elb)
            for k in xrange(nc):
                elc =  {'typ': 'c', id: (i, j, k), 'childs': []}
                elb['childs'].append(elc)
    td = time.time() - t1
    print td, 'dict'
    del droot
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.