อะไรคือจุดประสงค์__slots__
ใน Python - โดยเฉพาะอย่างยิ่งเมื่อฉันต้องการใช้และเมื่อไม่ต้องการ?
อะไรคือจุดประสงค์__slots__
ใน Python - โดยเฉพาะอย่างยิ่งเมื่อฉันต้องการใช้และเมื่อไม่ต้องการ?
คำตอบ:
ใน Python อะไรคือจุดประสงค์
__slots__
และอะไรคือกรณีที่เราควรหลีกเลี่ยงสิ่งนี้
คุณลักษณะพิเศษ__slots__
ช่วยให้คุณสามารถระบุแอตทริบิวต์ของอินสแตนซ์ที่คุณคาดว่าอินสแตนซ์ของวัตถุของคุณได้อย่างชัดเจนพร้อมกับผลลัพธ์ที่คาดหวัง:
ประหยัดพื้นที่มาจาก
__dict__
การจัดเก็บการอ้างอิงค่าในช่องแทน__dict__
และการสร้างถ้าคลาสแม่ปฏิเสธพวกเขาและคุณประกาศ__weakref__
__slots__
ข้อแม้เล็ก ๆ คุณควรประกาศเฉพาะช่วงเวลาหนึ่งในต้นไม้ที่มีการสืบทอด ตัวอย่างเช่น:
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__
อย่างชัดเจนถ้าคุณต้องการคุณลักษณะที่
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 ชนิดย่อยได้
แม้ว่าช่องที่ไม่ว่างจะเหมือนกันสำหรับผู้ปกครองหลายคน แต่ก็ไม่สามารถใช้ร่วมกันได้:
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__
มอบหมายด้วยคลาสอื่นที่ไม่มี (และคุณไม่สามารถเพิ่มได้) ยกเว้นว่าเลย์เอาต์สล็อตจะเหมือนกัน (ฉันสนใจที่จะเรียนรู้ว่าใครกำลังทำสิ่งนี้และทำไม)คุณอาจจะสามารถหยอกล้อเพิ่มเติม 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>
ดังนั้นฉันจะเก็บเรื่องนี้ไว้ในใจเพราะมันเป็นปัญหาที่แก้ไขแล้ว
ย่อหน้าแรกคือคำอธิบายสั้น ๆ ครึ่งคำทำนายครึ่งหนึ่ง นี่คือส่วนเดียวที่ตอบคำถามได้จริง
การใช้ที่เหมาะสม
__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__ = []
" ใน ชั้นเรียนของคุณ.
__slots__
คำตอบนี้ควรจะเป็นส่วนหนึ่งของเอกสารอย่างเป็นทางการเกี่ยวกับงูหลาม อย่างจริงจัง! ขอบคุณ!
__slots__
ประมาณหนึ่งปีหลัง: github.com/python/cpython/pull/1819/files
อ้างจาค็อบ Hallen :
การใช้ที่เหมาะสม
__slots__
คือการประหยัดพื้นที่ในวัตถุ แทนที่จะมี dict แบบไดนามิกที่อนุญาตให้เพิ่มคุณลักษณะให้กับวัตถุได้ตลอดเวลามีโครงสร้างแบบสแตติกซึ่งไม่อนุญาตให้มีการเพิ่มหลังจากสร้างแล้ว [การใช้การ__slots__
กำจัดค่าใช้จ่ายหนึ่ง dict สำหรับทุกวัตถุ] แม้ว่าบางครั้งนี่เป็นการเพิ่มประสิทธิภาพที่มีประโยชน์ แต่ก็ไม่จำเป็นอย่างสมบูรณ์หากล่าม Python นั้นมีพลวัตเพียงพอที่จะต้องใช้ dict เมื่อมีการเพิ่มเข้าไป วัตถุ.น่าเสียดายที่มีผลข้างเคียงกับสล็อต พวกเขาเปลี่ยนพฤติกรรมของวัตถุที่มีช่องในลักษณะที่สามารถถูกทารุณกรรมโดยการควบคุมประหลาดและ weenies พิมพ์คงที่ นี่เป็นสิ่งที่ไม่ดีนักเนื่องจากตัวควบคุมที่คลั่งไคล้ควรใช้ metaclasses แบบผิด ๆ และ weenies การพิมพ์แบบคงที่ควรจะใช้นักตกแต่งภายในเนื่องจากใน Python เราควรมีวิธีการทำอะไรที่ชัดเจนเพียงวิธีเดียว
การทำให้ CPython ฉลาดพอที่จะจัดการกับการประหยัดพื้นที่โดยไม่ต้อง
__slots__
มีภารกิจสำคัญซึ่งอาจเป็นสาเหตุว่าทำไมมันไม่อยู่ในรายการการเปลี่ยนแปลงสำหรับ P3k (ยัง)
__slots__
ไม่ได้แก้ไขปัญหาเดียวกับการพิมพ์แบบคงที่ ตัวอย่างเช่นใน C ++ ไม่ใช่การประกาศตัวแปรสมาชิกถูก จำกัด แต่เป็นการกำหนดประเภทที่ไม่ตั้งใจ (และบังคับใช้คอมไพเลอร์) ให้กับตัวแปรนั้น ฉันไม่ยอมให้ใช้__slots__
เพียงแค่สนใจในการสนทนา ขอบคุณ!
คุณต้องการใช้__slots__
ถ้าคุณจะยกตัวอย่างวัตถุจำนวนมาก (หลายแสน) ในชั้นเดียวกัน __slots__
มีอยู่เป็นเครื่องมือเพิ่มประสิทธิภาพหน่วยความจำเท่านั้น
มันมีความท้อแท้อย่างมากที่จะใช้__slots__
สำหรับการสร้างข้อ จำกัด ด้านคุณลักษณะ
การดองวัตถุด้วย__slots__
จะไม่ทำงานกับโปรโตคอลดอง (เก่าที่สุด) เริ่มต้น จำเป็นต้องระบุรุ่นที่ใหม่กว่า
คุณสมบัติการวิปัสสนาอื่นของงูหลามก็อาจได้รับผลกระทบเช่นกัน
วัตถุหลามแต่ละคนมี__dict__
คุณสมบัติที่เป็นพจนานุกรมที่มีคุณลักษณะอื่น ๆ ทั้งหมด เช่นเมื่อคุณพิมพ์self.attr
หลามกำลังทำself.__dict__['attr']
อยู่ ในขณะที่คุณสามารถจินตนาการได้ว่าการใช้พจนานุกรมเพื่อเก็บแอตทริบิวต์นั้นต้องใช้พื้นที่และเวลาเพิ่มเติมในการเข้าถึง
อย่างไรก็ตามเมื่อคุณใช้__slots__
วัตถุใด ๆ ที่สร้างขึ้นสำหรับคลาสนั้นจะไม่มี__dict__
แอตทริบิวต์ การเข้าถึงคุณลักษณะทั้งหมดทำได้โดยตรงผ่านตัวชี้
ดังนั้นหากต้องการโครงสร้างสไตล์ C แทนคลาสที่เต็มเปี่ยมคุณสามารถใช้__slots__
สำหรับการกระชับขนาดของวัตถุและลดเวลาในการเข้าถึงแอ็ตทริบิวต์ ตัวอย่างที่ดีคือคลาส Point ที่มีแอ็ตทริบิวต์ x & y หากคุณจะมีคะแนนมากคุณสามารถลองใช้__slots__
เพื่อประหยัดหน่วยความจำบางส่วน
__slots__
กำหนดไม่เหมือนกับโครงสร้างแบบ C มีชื่อแอตทริบิวต์การแมปพจนานุกรมระดับคลาสสำหรับดัชนีมิฉะนั้นจะไม่สามารถทำสิ่งต่อไปนี้ได้: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
ฉันคิดว่าคำตอบนี้ควรได้รับการชี้แจง (ฉันสามารถทำได้ถ้าคุณต้องการ) นอกจากนี้ผมไม่แน่ใจว่าจะเร็วกว่าinstance.__hidden_attributes[instance.__class__[attrname]]
instance.__dict__[attrname]
นอกเหนือจากคำตอบอื่น ๆ นี่คือตัวอย่างของการใช้__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 เท่าโดยไม่จำเป็นต้องเขียนรหัสดองที่กำหนดเองหากและเมื่อจำเป็น
สล็อตมีประโยชน์อย่างมากสำหรับการเรียกใช้ไลบรารีเพื่อกำจัด "วิธีการส่งชื่อ" เมื่อทำการเรียกใช้ฟังก์ชัน สิ่งนี้ถูกกล่าวถึงในเอกสาร SWIGเอกสารสำหรับไลบรารีประสิทธิภาพสูงที่ต้องการลดค่าใช้จ่ายของฟังก์ชั่นสำหรับฟังก์ชั่นทั่วไปที่เรียกว่าการใช้สล็อตจะเร็วกว่ามาก
ตอนนี้อาจไม่เกี่ยวข้องโดยตรงกับคำถาม OPs มันเกี่ยวข้องกับการสร้างส่วนขยายมากกว่าการใช้ไวยากรณ์ของช่องบนวัตถุ แต่มันจะช่วยเติมเต็มรูปภาพสำหรับการใช้สล็อตและเหตุผลบางอย่างที่อยู่เบื้องหลัง
แอททริบิวของคลาสอินสแตนซ์มี 3 คุณสมบัติ: อินสแตนซ์ชื่อของแอททริบิวต์และค่าของแอททริบิวต์
ในการเข้าถึงแอตทริบิวต์ปกติอินสแตนซ์จะทำหน้าที่เป็นพจนานุกรมและชื่อของแอตทริบิวต์นั้นทำหน้าที่เป็นกุญแจสำคัญในการค้นหาค่าพจนานุกรม
อินสแตนซ์ (คุณลักษณะ) -> ค่า
ในการเข้าถึง __slots__ชื่อของแอ็ตทริบิวต์ทำหน้าที่เป็นพจนานุกรมและอินสแตนซ์ทำหน้าที่เป็นคีย์ในการค้นหาค่าพจนานุกรม
คุณลักษณะ (ตัวอย่าง) -> ค่า
ในรูปแบบฟลายเวทชื่อของแอ็ตทริบิวต์ทำหน้าที่เป็นพจนานุกรมและค่าทำหน้าที่เป็นกุญแจสำคัญในพจนานุกรมนั้นเพื่อค้นหาอินสแตนซ์
คุณลักษณะ (ค่า) -> อินสแตนซ์
__slots__
บ้างที่ควรหลีกเลี่ยง ... "
ตัวอย่างง่ายๆของ__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'
การใช้งานที่ค่อนข้างคลุมเครือ__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
คุณมีหลัก - - __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 คล้ายคลาสไม่มีแอ็ตทริบิวต์ - เป็นเพียงแค่วิธีการที่ใช้กับข้อมูลพื้นฐาน วิธีการสามารถลดลงเป็นวิธีการเรียน ที่จริงแล้วมันอาจจะลดลงเพียงแค่ฟังก์ชั่นที่ทำงานบนอาเรย์ของข้อมูล
__slots__
อะไรได้บ้าง?
__slots__
เป็นทั้งเทคนิคการเพิ่มประสิทธิภาพเพื่อประหยัดหน่วยความจำ __slots__
แสดงให้เห็นถึงประโยชน์เมื่อคุณมีวัตถุมากมายเช่นเดียวกับรูปแบบการออกแบบ Flyweight ทั้งคู่แก้ปัญหาเดียวกัน
__slots__
คำตอบคือจริงๆและเมื่อ Evgeni ชี้ให้เห็นก็สามารถเพิ่มเป็นความคิดที่เรียบง่าย (เช่นคุณสามารถมุ่งเน้นไปที่ความถูกต้องก่อนแล้วจึงเพิ่มประสิทธิภาพ)
คำถามเดิมเป็นเรื่องเกี่ยวกับกรณีการใช้งานทั่วไปไม่เพียง แต่เกี่ยวกับหน่วยความจำ ดังนั้นจึงควรกล่าวถึงที่นี่ว่าคุณจะได้รับประสิทธิภาพที่ดีขึ้นเมื่อยกตัวอย่างวัตถุจำนวนมาก - น่าสนใจเช่นเมื่อแยกเอกสารขนาดใหญ่เป็นวัตถุหรือจากฐานข้อมูล
นี่คือการเปรียบเทียบการสร้างทรีวัตถุที่มีรายการนับล้านรายการโดยใช้สล็อตและไม่มีสล็อต เป็นการอ้างอิงถึงประสิทธิภาพเมื่อใช้ 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
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
ตัวอย่างกับ empy-slot-parent เหตุใดจึงdictproxy
สร้างที่นี่แทนที่จะสร้างขึ้นAttributeError
เพื่อc
?