Câu trả lời:
Trong Python, mục đích của
__slots__
nó là gì và những trường hợp nào người ta nên tránh điều này?
Thuộc tính đặc biệt __slots__
cho phép bạn nêu rõ các thuộc tính thể hiện mà bạn mong muốn các đối tượng của bạn có, với kết quả mong đợi:
Tiết kiệm không gian là từ
__dict__
.__dict__
và __weakref__
tạo nếu lớp cha mẹ từ chối chúng và bạn khai báo __slots__
.Hãy cẩn thận, bạn chỉ nên khai báo một vị trí cụ thể một lần trong cây thừa kế. Ví dụ:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python không phản đối khi bạn gặp lỗi này (có lẽ nên như vậy), các vấn đề có thể không biểu hiện khác, nhưng các đối tượng của bạn sẽ chiếm nhiều không gian hơn so với bình thường. Con trăn 3,8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Điều này là do bộ mô tả vị trí của Base có một vị trí tách biệt với phần sai. Điều này thường không xuất hiện, nhưng nó có thể:
>>> 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'
Sự cảnh báo lớn nhất là dành cho nhiều kế thừa - nhiều "lớp cha với các khe không trống" không thể được kết hợp.
Để phù hợp với hạn chế này, hãy thực hiện theo các thực tiễn tốt nhất: Yếu tố tất cả trừ một hoặc tất cả sự trừu tượng của cha mẹ mà lớp cụ thể của chúng tương ứng và lớp bê tông mới của bạn sẽ kế thừa từ - đưa ra các khe trống trừu tượng (giống như các lớp cơ sở trừu tượng trong thư viện chuẩn).
Xem phần về nhiều kế thừa dưới đây để biết ví dụ.
Để có các thuộc tính được đặt tên __slots__
thực sự được lưu trữ trong các vị trí thay vì a __dict__
, một lớp phải kế thừa từ đó object
.
Để ngăn chặn việc tạo a __dict__
, bạn phải kế thừa object
và tất cả các lớp trong thừa kế phải khai báo __slots__
và không ai trong số chúng có thể có một '__dict__'
mục.
Có rất nhiều chi tiết nếu bạn muốn tiếp tục đọc.
__slots__
: Truy cập thuộc tính nhanh hơn.Người tạo ra Python, Guido van Rossum, nói rằng ông thực sự đã tạo ra __slots__
để truy cập thuộc tính nhanh hơn.
Thật là tầm thường khi chứng minh truy cập nhanh hơn đáng kể:
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
và
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Truy cập có rãnh nhanh hơn gần 30% trong Python 3.5 trên Ubuntu.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
Trong Python 2 trên Windows tôi đã đo nó nhanh hơn khoảng 15%.
__slots__
: Tiết kiệm bộ nhớMột mục đích khác __slots__
là để giảm dung lượng trong bộ nhớ mà mỗi đối tượng chiếm.
Đóng góp của riêng tôi vào tài liệu nêu rõ lý do đằng sau điều này :
Không gian lưu trên sử dụng
__dict__
có thể là đáng kể.
SQLAlchemy quy rất nhiều bộ nhớ tiết kiệm __slots__
.
Để xác minh điều này, bằng cách sử dụng bản phân phối Anaconda của Python 2.7 trên Ubuntu Linux, với guppy.hpy
(hay còn gọi là heapy) và sys.getsizeof
, kích thước của một thể hiện lớp mà không được __slots__
khai báo, và không có gì khác, là 64 byte. Điều đó không bao gồm __dict__
. Cảm ơn Python vì đã đánh giá lười biếng một lần nữa, __dict__
rõ ràng là không được gọi vào sự tồn tại cho đến khi nó được tham chiếu, nhưng các lớp không có dữ liệu thường là vô dụng. Khi được gọi vào sự tồn tại, __dict__
thuộc tính tối thiểu là 280 byte bổ sung.
Ngược lại, một thể hiện lớp với __slots__
khai báo là ()
(không có dữ liệu) chỉ có 16 byte và 56 byte tổng với một mục trong các khe, 64 với hai.
Đối với Python 64 bit, tôi minh họa mức tiêu thụ bộ nhớ theo byte trong Python 2.7 và 3.6, cho __slots__
và __dict__
(không có vị trí được xác định) cho mỗi điểm mà dict tăng lên trong 3.6 (ngoại trừ các thuộc tính 0, 1 và 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
Vì vậy, mặc dù có các đoạn nhỏ hơn trong Python 3, chúng tôi thấy __slots__
các trường hợp độc đáo để tiết kiệm bộ nhớ cho chúng tôi và đó là một lý do chính mà bạn muốn sử dụng __slots__
.
Chỉ để hoàn thiện các ghi chú của tôi, lưu ý rằng có một chi phí một lần cho mỗi vị trí trong không gian tên của lớp là 64 byte trong Python 2 và 72 byte trong Python 3, bởi vì các vị trí sử dụng các mô tả dữ liệu như các thuộc tính, được gọi là "thành viên".
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Để từ chối việc tạo a __dict__
, bạn phải phân lớp object
:
class Base(object):
__slots__ = ()
hiện nay:
>>> 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'
Hoặc phân lớp một lớp khác định nghĩa __slots__
class Child(Base):
__slots__ = ('a',)
và bây giờ:
c = Child()
c.a = 'a'
nhưng:
>>> 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'
Để cho phép __dict__
tạo trong khi phân lớp các đối tượng có rãnh, chỉ cần thêm '__dict__'
vào __slots__
(lưu ý rằng các vị trí được đặt hàng và bạn không nên lặp lại các vị trí đã có trong các lớp cha):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
và
>>> swd.__dict__
{'c': 'c'}
Hoặc thậm chí bạn không cần phải khai báo __slots__
trong lớp con của mình và bạn vẫn sẽ sử dụng các vị trí từ cha mẹ, nhưng không hạn chế việc tạo __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
Và:
>>> ns.__dict__
{'b': 'b'}
Tuy nhiên, __slots__
có thể gây ra sự cố cho nhiều kế thừa:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Bởi vì việc tạo một lớp con từ cha mẹ với cả hai vị trí không trống: đều thất bại:
>>> 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
Nếu bạn gặp phải vấn đề này, Bạn có thể xóa __slots__
khỏi cha mẹ hoặc nếu bạn có quyền kiểm soát của cha mẹ, cung cấp cho họ các vị trí trống hoặc cấu trúc lại để trừu tượng hóa:
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__'
vào __slots__
để nhận nhiệm vụ động:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
và bây giờ:
>>> foo = Foo()
>>> foo.boink = 'boink'
Vì vậy, '__dict__'
trong các vị trí, chúng tôi mất một số lợi ích về kích thước với mặt trái của việc gán động và vẫn có các vị trí cho tên mà chúng tôi mong đợi.
Khi bạn kế thừa từ một đối tượng không có rãnh, bạn sẽ có cùng một loại ngữ nghĩa khi bạn sử dụng __slots__
- các tên được đặt trong __slots__
các giá trị có rãnh, trong khi bất kỳ giá trị nào khác được đặt trong trường hợp __dict__
.
Tránh __slots__
bởi vì bạn muốn có thể thêm các thuộc tính một cách nhanh chóng thực sự không phải là một lý do chính đáng - chỉ cần thêm "__dict__"
vào của bạn __slots__
nếu điều này là bắt buộc.
Bạn có thể thêm __weakref__
vào __slots__
một cách rõ ràng nếu bạn cần tính năng đó.
Nội dung được đặt tên tạo ra các trường hợp bất biến rất nhẹ (về cơ bản, kích thước của bộ dữ liệu) nhưng để có được lợi ích, bạn cần phải tự làm nếu bạn phân lớp chúng:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
sử dụng:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
Và cố gắng gán một thuộc tính bất ngờ làm tăng AttributeError
vì chúng tôi đã ngăn chặn việc tạo __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Bạn có thể cho phép __dict__
tạo bằng cách bỏ đi __slots__ = ()
, nhưng bạn không thể sử dụng không trống __slots__
với các kiểu con của tuple.
Ngay cả khi các vị trí không trống giống nhau cho nhiều cha mẹ, chúng không thể được sử dụng cùng nhau:
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
Việc sử dụng một khoảng trống __slots__
trong cha mẹ dường như mang lại sự linh hoạt nhất, cho phép trẻ lựa chọn ngăn chặn hoặc cho phép (bằng cách thêm '__dict__'
để có được sự phân công động, xem phần bên trên) việc tạo ra một__dict__
:
class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Bạn không cần phải có vị trí - vì vậy nếu bạn thêm chúng và xóa chúng sau, nó sẽ không gây ra vấn đề gì.
Đi ra ngoài một chi ở đây : Nếu bạn đang soạn các mixin hoặc sử dụng các lớp cơ sở trừu tượng , không có ý định khởi tạo, thì một khoảng trống __slots__
trong các bậc cha mẹ đó dường như là cách tốt nhất để đạt được sự linh hoạt cho các lớp con.
Để chứng minh, trước tiên, hãy tạo một lớp có mã mà chúng tôi muốn sử dụng theo nhiều kế thừa
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)})'
Chúng tôi có thể sử dụng trực tiếp ở trên bằng cách kế thừa và khai báo các vị trí dự kiến:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Nhưng chúng tôi không quan tâm đến điều đó, đó là sự thừa kế đơn lẻ tầm thường, chúng tôi cần một lớp khác mà chúng tôi cũng có thể thừa hưởng, có thể với một thuộc tính ồn ào:
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
Bây giờ nếu cả hai căn cứ đều có các khe không trống, chúng tôi không thể thực hiện các thao tác dưới đây. (Trên thực tế, nếu chúng ta muốn, chúng ta có thể đã đưa ra AbstractBase
các vị trí không trống a và b, và để chúng ra khỏi tuyên bố dưới đây - để chúng vào sẽ là sai):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
Và bây giờ chúng tôi có chức năng từ cả hai thông qua nhiều kế thừa, và vẫn có thể từ chối __dict__
và __weakref__
khởi tạo:
>>> 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__
gán với một lớp khác không có chúng (và bạn không thể thêm chúng) trừ khi bố trí vị trí giống hệt nhau. (Tôi rất thích tìm hiểu ai đang làm việc này và tại sao.)Bạn có thể trêu chọc thêm từ phần còn lại của __slots__
tài liệu (tài liệu 3.7 dev là mới nhất) , mà tôi đã có những đóng góp đáng kể gần đây.
Các câu trả lời hàng đầu hiện nay trích dẫn thông tin lỗi thời và khá gợn sóng và bỏ lỡ dấu hiệu trong một số cách quan trọng.
__slots__
khi khởi tạo nhiều đối tượng"Tôi trích dẫn:
"Bạn sẽ muốn sử dụng
__slots__
nếu bạn định khởi tạo rất nhiều (hàng trăm, hàng ngàn) đối tượng cùng loại."
Các lớp cơ sở trừu tượng, ví dụ, từ collections
mô-đun, không được khởi tạo, nhưng __slots__
được khai báo cho chúng.
Tại sao?
Nếu người dùng muốn từ chối __dict__
hoặc __weakref__
tạo, những thứ đó không được có sẵn trong các lớp cha.
__slots__
góp phần tái sử dụng khi tạo giao diện hoặc mixin.
Đúng là nhiều người dùng Python không viết để sử dụng lại, nhưng khi bạn có, tùy chọn từ chối sử dụng không gian không cần thiết là có giá trị.
__slots__
không phá vỡ dưa chuaKhi tẩy một vật có rãnh, bạn có thể thấy nó phàn nàn với sự sai lệch TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
Điều này thực sự không chính xác. Thông báo này đến từ giao thức cũ nhất, là mặc định. Bạn có thể chọn giao thức mới nhất với -1
đối số. Trong Python 2.7, điều này sẽ là 2
(được giới thiệu trong 2.3) và trong 3.6 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
trong Python 2.7:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
trong Python 3.6
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Vì vậy, tôi sẽ ghi nhớ điều này, vì nó là một vấn đề được giải quyết.
Đoạn đầu tiên là một nửa giải thích ngắn, một nửa dự đoán. Đây là phần duy nhất thực sự trả lời câu hỏi
Việc sử dụng đúng
__slots__
là để tiết kiệm không gian trong các đối tượng. Thay vì có một lệnh động cho phép thêm thuộc tính vào các đối tượng bất cứ lúc nào, có một cấu trúc tĩnh không cho phép bổ sung sau khi tạo. Điều này giúp tiết kiệm chi phí của một dict cho mọi đối tượng sử dụng vị trí
Nửa thứ hai là mơ tưởng, và đánh dấu:
Mặc dù điều này đôi khi là một tối ưu hóa hữu ích, nhưng nó sẽ hoàn toàn không cần thiết nếu trình thông dịch Python đủ năng động để nó chỉ yêu cầu lệnh khi thực sự có bổ sung cho đối tượng.
Python thực sự làm một cái gì đó tương tự như thế này, chỉ tạo ra __dict__
khi nó được truy cập, nhưng việc tạo ra nhiều đối tượng không có dữ liệu là khá vô lý.
Đoạn thứ hai quá đơn giản và bỏ lỡ các lý do thực tế để tránh __slots__
. Dưới đây không phải là lý do thực sự để tránh các vị trí (vì lý do thực tế , hãy xem phần còn lại của câu trả lời của tôi ở trên.):
Chúng thay đổi hành vi của các đối tượng có các vị trí theo cách có thể bị lạm dụng bằng cách kiểm soát các quái vật và các ký hiệu gõ tĩnh.
Sau đó, tiếp tục thảo luận về các cách khác để thực hiện mục tiêu đồi trụy đó với Python, không thảo luận bất cứ điều gì để làm __slots__
.
Đoạn thứ ba là mơ tưởng nhiều hơn. Cùng với nhau, chủ yếu là nội dung không chính xác mà người trả lời thậm chí không phải là tác giả và đóng góp đạn dược cho các nhà phê bình của trang web.
Tạo một số đối tượng bình thường và các đối tượng có rãnh:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Khởi tạo một triệu trong số họ:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Kiểm tra với 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
...
Truy cập các đối tượng thông thường và của họ __dict__
và kiểm tra lại:
>>> 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
...
Điều này phù hợp với lịch sử của Python, từ các loại và lớp thống nhất trong Python 2.2
Nếu bạn phân lớp một loại tích hợp, không gian thêm sẽ tự động được thêm vào các thể hiện để chứa
__dict__
và__weakrefs__
. (__dict__
Mặc dù không được khởi tạo cho đến khi bạn sử dụng nó, vì vậy bạn không nên lo lắng về không gian bị chiếm bởi một từ điển trống cho mỗi phiên bản bạn tạo.) Nếu bạn không cần thêm dung lượng này, bạn có thể thêm cụm từ "__slots__ = []
" vào lớp của bạn.
__slots__
. Nghiêm túc! Cảm ơn bạn!
__slots__
khoảng một năm trước: github.com/python/cpython/pull/1819/files
Trích dẫn Jacob Hallen :
Việc sử dụng đúng
__slots__
là để tiết kiệm không gian trong các đối tượng. Thay vì có một lệnh động cho phép thêm thuộc tính vào các đối tượng bất cứ lúc nào, có một cấu trúc tĩnh không cho phép bổ sung sau khi tạo. [Việc sử dụng này__slots__
giúp loại bỏ chi phí của một dict cho mọi đối tượng.] Mặc dù đôi khi đây là một tối ưu hóa hữu ích, nhưng sẽ hoàn toàn không cần thiết nếu trình thông dịch Python đủ năng động để nó chỉ yêu cầu dict khi thực sự có bổ sung cho vật.Thật không may, có một tác dụng phụ cho khe. Chúng thay đổi hành vi của các đối tượng có các vị trí theo cách có thể bị lạm dụng bằng cách kiểm soát các quái vật và các ký hiệu gõ tĩnh. Điều này là xấu, bởi vì những kẻ thích kiểm soát nên lạm dụng các siêu dữ liệu và các thuật ngữ gõ tĩnh nên lạm dụng các trình trang trí, vì trong Python, chỉ nên có một cách rõ ràng để làm một cái gì đó.
Làm cho CPython đủ thông minh để xử lý tiết kiệm không gian mà không phải
__slots__
là một công việc chính, đó có thể là lý do tại sao nó không nằm trong danh sách các thay đổi cho P3k (chưa).
__slots__
không giải quyết các vấn đề tương tự như gõ tĩnh. Ví dụ, trong C ++, nó không phải là khai báo của một biến thành viên đang bị hạn chế, đó là việc gán một loại ngoài ý muốn (và trình biên dịch được thi hành) cho biến đó. Tôi không bỏ qua việc sử dụng __slots__
, chỉ quan tâm đến cuộc trò chuyện. Cảm ơn!
Bạn sẽ muốn sử dụng __slots__
nếu bạn định khởi tạo rất nhiều (hàng trăm, hàng nghìn) đối tượng cùng loại. __slots__
chỉ tồn tại như một công cụ tối ưu hóa bộ nhớ.
Nó không được khuyến khích sử dụng __slots__
để hạn chế việc tạo thuộc tính.
Pickling đối tượng __slots__
không hoạt động với giao thức pickle mặc định (cũ nhất); nó là cần thiết để xác định một phiên bản mới hơn.
Một số tính năng hướng nội khác của trăn cũng có thể bị ảnh hưởng xấu.
Mỗi đối tượng python có một __dict__
thuộc tính là một từ điển chứa tất cả các thuộc tính khác. ví dụ như khi bạn gõ self.attr
python đang thực sự làm self.__dict__['attr']
. Như bạn có thể tưởng tượng, việc sử dụng một từ điển để lưu trữ thuộc tính cần thêm không gian và thời gian để truy cập nó.
Tuy nhiên, khi bạn sử dụng __slots__
, bất kỳ đối tượng nào được tạo cho lớp đó sẽ không có __dict__
thuộc tính. Thay vào đó, tất cả các truy cập thuộc tính được thực hiện trực tiếp thông qua con trỏ.
Vì vậy, nếu muốn cấu trúc kiểu C thay vì lớp đầy đủ, bạn có thể sử dụng __slots__
để nén kích thước của các đối tượng và giảm thời gian truy cập thuộc tính. Một ví dụ điển hình là lớp Point chứa các thuộc tính x & y. Nếu bạn sẽ có rất nhiều điểm, bạn có thể thử sử dụng __slots__
để bảo tồn một số bộ nhớ.
__slots__
được định nghĩa không giống như cấu trúc kiểu C. Có một tên thuộc tính ánh xạ từ điển cấp lớp cho các chỉ mục, nếu không thì điều sau đây là không thể: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Tôi thực sự nghĩ rằng câu trả lời này cần được làm rõ (tôi có thể làm điều đó nếu bạn muốn). Ngoài ra, tôi không chắc instance.__hidden_attributes[instance.__class__[attrname]]
là nhanh hơn instance.__dict__[attrname]
.
Ngoài các câu trả lời khác, đây là một ví dụ về việc sử dụng __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']
Vì vậy, để thực hiện __slots__
, nó chỉ cần thêm một dòng (và biến lớp của bạn thành một lớp kiểu mới nếu nó chưa có). Bằng cách này, bạn có thể giảm dung lượng bộ nhớ của các lớp đó xuống 5 lần , với chi phí phải viết mã dưa chua tùy chỉnh, nếu và khi điều đó trở nên cần thiết.
Slots rất hữu ích cho các cuộc gọi thư viện để loại bỏ "công văn phương thức được đặt tên" khi thực hiện các cuộc gọi hàm. Điều này được đề cập trong tài liệu SWIG . Đối với các thư viện hiệu suất cao muốn giảm chi phí chức năng cho các chức năng thường được gọi là sử dụng vị trí thì nhanh hơn nhiều.
Bây giờ điều này có thể không liên quan trực tiếp đến câu hỏi OP. Nó liên quan nhiều hơn đến việc xây dựng các tiện ích mở rộng hơn là sử dụng cú pháp vị trí trên một đối tượng. Nhưng nó giúp hoàn thành bức tranh cho việc sử dụng các vị trí và một số lý do đằng sau chúng.
Một thuộc tính của một thể hiện lớp có 3 thuộc tính: thể hiện, tên của thuộc tính và giá trị của thuộc tính.
Trong truy cập thuộc tính thông thường , thể hiện hoạt động như một từ điển và tên của thuộc tính đóng vai trò là khóa trong từ điển tìm kiếm giá trị.
thể hiện (thuộc tính) -> giá trị
Trong truy cập __slots__ , tên của thuộc tính đóng vai trò là từ điển và thể hiện đóng vai trò là khóa trong từ điển tìm kiếm giá trị.
thuộc tính (ví dụ) -> giá trị
Trong mẫu fly trọng , tên của thuộc tính đóng vai trò là từ điển và giá trị đóng vai trò là khóa trong từ điển đó tìm kiếm thể hiện.
thuộc tính (giá trị) -> thể hiện
__slots__
?
Một ví dụ rất đơn giản về __slot__
thuộc tính.
__slots__
Nếu tôi không có __slot__
thuộc tính trong lớp, tôi có thể thêm thuộc tính mới vào đối tượng của mình.
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}
Nếu bạn nhìn vào ví dụ trên, bạn có thể thấy obj1 và obj2 có các thuộc tính x và y riêng và python cũng đã tạo một dict
thuộc tính cho từng đối tượng ( obj1 và obj2 ).
Giả sử nếu lớp Kiểm tra của tôi có hàng ngàn đối tượng như vậy? Tạo một thuộc tính bổ sung dict
cho mỗi đối tượng sẽ gây ra nhiều chi phí (bộ nhớ, khả năng tính toán, v.v.) trong mã của tôi.
__slots__
Bây giờ trong ví dụ sau đây, lớp Test của tôi chứa __slots__
thuộc tính. Bây giờ tôi không thể thêm thuộc tính mới vào các đối tượng của mình (ngoại trừ thuộc tính x
) và python không tạo dict
thuộc tính nữa. Điều này giúp loại bỏ chi phí cho từng đối tượng, có thể trở nên quan trọng nếu bạn có nhiều đối tượng.
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'
Một cách sử dụng hơi khó hiểu khác __slots__
là thêm các thuộc tính vào proxy đối tượng từ gói ProxyTypes, trước đây là một phần của dự án PEAK. Nó ObjectWrapper
cho phép bạn ủy quyền một đối tượng khác, nhưng chặn tất cả các tương tác với đối tượng ủy quyền. Nó không được sử dụng phổ biến (và không hỗ trợ Python 3), nhưng chúng tôi đã sử dụng nó để triển khai trình bao chặn chặn an toàn luồng xung quanh việc triển khai async dựa trên cơn lốc xoáy, trả lại tất cả quyền truy cập vào đối tượng được ủy quyền thông qua ioloop, sử dụng an toàn luồng concurrent.Future
các đối tượng để đồng bộ hóa và trả về kết quả.
Theo mặc định, bất kỳ quyền truy cập thuộc tính nào vào đối tượng proxy sẽ cung cấp cho bạn kết quả từ đối tượng proxy. Nếu bạn cần thêm một thuộc tính trên đối tượng proxy, __slots__
có thể được sử dụng.
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
Bạn có - về cơ bản - không sử dụng cho __slots__
.
Đối với thời gian mà bạn nghĩ rằng bạn có thể cần __slots__
, bạn thực sự muốn sử dụng các mẫu thiết kế Nhẹ hoặc Flykg . Đây là những trường hợp khi bạn không còn muốn sử dụng các đối tượng Python thuần túy. Thay vào đó, bạn muốn một trình bao bọc giống như đối tượng Python xung quanh một mảng, struct hoặc numpy mảng.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
Trình bao bọc giống như lớp không có thuộc tính - nó chỉ cung cấp các phương thức hoạt động trên dữ liệu cơ bản. Các phương thức có thể được giảm xuống các phương thức lớp. Thật vậy, nó có thể được giảm xuống chỉ còn các chức năng hoạt động trên mảng dữ liệu cơ bản.
__slots__
gì?
__slots__
là cả hai kỹ thuật tối ưu hóa để tiết kiệm bộ nhớ. __slots__
cho thấy lợi ích khi bạn có nhiều đối tượng cũng như mẫu thiết kế Flykg. Cả hai giải quyết cùng một vấn đề.
__slots__
thực sự là câu trả lời, và như Evgeni chỉ ra, nó có thể được thêm vào như một suy nghĩ đơn giản (ví dụ: bạn có thể tập trung vào tính chính xác trước, sau đó thêm hiệu suất).
Câu hỏi ban đầu là về các trường hợp sử dụng chung không chỉ về bộ nhớ. Vì vậy, cần đề cập ở đây rằng bạn cũng có hiệu suất tốt hơn khi khởi tạo một lượng lớn đối tượng - thú vị, ví dụ như khi phân tích tài liệu lớn thành đối tượng hoặc từ cơ sở dữ liệu.
Dưới đây là so sánh về việc tạo các cây đối tượng với một triệu mục, sử dụng các vị trí và không có vị trí. Như một tài liệu tham khảo cũng là hiệu suất khi sử dụng các ký tự đơn giản cho cây (Py2.7.10 trên 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
Các lớp kiểm tra (nhận dạng, appart từ các vị trí):
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)
mã kiểm tra, chế độ dài dòng:
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')
ví dụ với cha mẹ empy-slot-slot. Tại sao ở đây mộtdictproxy
tạo ra thay vì tăng mộtAttributeError
choc
?