Hướng dẫn __slots__ python - __slots__ python

Trong Python, mục đích của >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0 là gì và những trường hợp người ta nên tránh điều này là gì?

TLDR:

Thuộc tính đặc biệt

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 cho phép bạn nêu rõ các thuộc tính mà bạn mong đợi các thể hiện đối tượng của mình sẽ có, với kết quả dự kiến:

Show
  1. Truy cập thuộc tính nhanh hơn. attribute access.
  2. Tiết kiệm không gian trong bộ nhớ. in memory.

Tiết kiệm không gian là từ

  1. Lưu trữ tài liệu tham khảo giá trị trong các khe thay vì
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    2.
  2. Từ chối tạo
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    2 và
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    4 nếu các lớp phụ huynh từ chối họ và bạn tuyên bố
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    0.

Nhanh chóng cảnh báo

Triển lãm nhỏ, bạn chỉ nên khai báo một khe cụ thể một lần trong cây kế thừa. 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 hiểu sai (có lẽ nó nên), các vấn đề có thể không biểu hiện, nhưng các đối tượng của bạn sẽ chiếm nhiều không gian hơn so với những gì họ nên làm. Python 3.8:

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

Điều này là do bộ mô tả khe của cơ sở có một khe tách biệt với sai. Điều này thường không nên xuất hiện, nhưng nó có thể:

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

Việc lưu ý lớn nhất là cho nhiều kế thừa - không thể kết hợp nhiều "lớp cha với các vị trí không trống".

Để phù hợp với hạn chế này, hãy làm 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 phụ huynh mà lớp cụ thể của họ tương ứng và lớp cụ thể mới của bạn sẽ được thừa hưởng 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 tiêu chuẩn).

Xem phần về nhiều kế thừa dưới đây cho một ví dụ.

Requirements:

  • Để có các thuộc tính có tên trong

    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    0 để thực sự được lưu trữ trong các khe thay vì
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    2, một lớp phải thừa hưởng từ
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    8 (tự động trong Python 3, nhưng phải rõ ràng trong Python 2).

  • Để ngăn chặn việc tạo ra

    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    2, bạn phải thừa hưởng từ
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    8 và tất cả các lớp trong kế thừa phải khai báo
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085
    
    0 và không ai trong số họ có thể có mục nhập
    >>> 0.3664822799983085 / 0.2846834529991611
    1.2873325658284342
    
    2.

Có rất nhiều chi tiết nếu bạn muốn tiếp tục đọc.

Tại sao sử dụng >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0: 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

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 để truy cập thuộc tính nhanh hơn.

Thật tầm thường để chứng minh khả năng truy cập nhanh hơn có ý nghĩa đá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

>>> 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%.

Tại sao sử dụng >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0: Tiết kiệm bộ nhớ

Một mục đích khác của

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 là giảm không gian trong bộ nhớ mà mỗi trường hợp đối tượng chiếm.

Đóng góp của riêng tôi cho tài liệu nêu rõ lý do đằng sau điều này:

Không gian được lưu qua bằng cách sử dụng

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 có thể là đáng kể.

SQLalchemy gán rất nhiều tiết kiệm bộ nhớ cho

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

Để xác minh điều này, sử dụng phân phối Anaconda của Python 2.7 trên Ubuntu Linux, với

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
9 (còn gọi là HEAPY) và
       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
0, kích thước của một thể hiện lớp mà không có
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 được khai báo, và không có gì khác, là 64 byte. Điều đó không bao gồm
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2. Cảm ơn bạn Python vì đã đánh giá lười biếng một lần nữa,
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 rõ ràng không được gọi là 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, thuộc tính
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 là tối thiểu là 280 byte.

Ngược lại, một thể hiện lớp với

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 được tuyên bố là
       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
6 (không có dữ liệu) chỉ là 16 byte và 56 byte tổng số với một mục trong các khe, 64 với hai.

Đối với 64 bit Python, tôi minh họa mức tiêu thụ bộ nhớ trong các byte trong Python 2.7 và 3.6, cho

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 và
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 (không có vị trí nào được xác định) cho mỗi điểm mà dict

       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 dicts nhỏ hơn trong Python 3, chúng ta thấy tỷ lệ

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 độc đáo như thế nào để lưu bộ nhớ của chúng tôi và đó là một lý do chính mà bạn muốn sử dụng
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0.

Chỉ để hoàn thành các ghi chú của tôi, lưu ý rằng có chi phí một lần cho mỗi khe trong không gian tên của lớp là 64 byte trong Python 2 và 72 byte trong Python 3, vì các khe sử dụng mô tả dữ liệu như thuộc tính, được gọi là "thành viên".

>>> Foo.foo

>>> type(Foo.foo)

>>> getsizeof(Foo.foo)
72

Trình diễn >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0:

Để từ chối việc tạo ra một

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2, bạn phải phân lớp
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
8. Mọi thứ đều phân lớp
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
8 trong Python 3, nhưng trong Python 2, bạn phải rõ ràng:

class Base(object): 
    __slots__ = ()

now:

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

Hoặc lớp con khác xác định

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

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

và bây giờ:

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

but:

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

Để cho phép tạo

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 trong khi phân lớp các đối tượng có rãnh, chỉ cần thêm
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
2 vào
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 (lưu ý rằng các khe đượ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 mẹ):

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

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

Truy cập có rãnh nhanh hơn gần 30% trong Python 3,5 trên Ubuntu.

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

And:

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

Tuy nhiên,

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 có thể gây ra vấn đề cho nhiều kế thừa:

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

Bởi vì tạo ra một lớp con từ cha mẹ với cả hai khe không trống thất bại:

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

Nếu bạn gặp phải vấn đề này, bạn có thể loại bỏ

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 khỏi cha mẹ hoặc nếu bạn có quyền kiểm soát cha mẹ, hãy cho họ các khe trống hoặc tái cấu trúc để trừu tượng hóa:

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

Thêm >>> 0.3664822799983085 / 0.2846834529991611 1.2873325658284342 2 vào >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0 để nhận gán động:

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

và bây giờ:

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

Vì vậy, với

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
2 trong các khe cắm, chúng tôi mất một số lợi ích kích thước với mặt trái của việc gán động và vẫn có các khe cho những cái tên 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ẽ nhận được cùng một loại ngữ nghĩa khi bạn sử dụng

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 - tên nằm trong
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 điểm đến các giá trị có rãnh, trong khi bất kỳ giá trị nào khác được đặt trong phiên bản
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2.

Tránh

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 vì bạn muốn có thể thêm các thuộc tính khi đang bay thực sự không phải là một lý do chính đáng - chỉ cần thêm
>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
  File "", line 1, in 
    b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
0 vào
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 của bạn nếu điều này là bắt buộc.

Bạn có thể tương tự thêm

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
4 vào
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 rõ ràng nếu bạn cần tính năng đó.

Được đặt thành Tuple trống khi phân lớp con có tênTuple:

Bản dựng được đặt tên là các trường hợp bất biến rất nhẹ (về cơ bản là kích thước của các 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:

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

usage:

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

Và cố gắng gán một thuộc tính bất ngờ làm tăng

>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
  File "", line 1, in 
    b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
4 vì chúng tôi đã ngăn chặn việc tạo ra
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2:

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

Bạn có thể cho phép tạo

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 bằng cách bỏ
>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
  File "", line 1, in 
    b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
7, nhưng bạn không thể sử dụng
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 không trống với các loại phụ của tuple.

Hãy cẩn thận nhất: Nhiều kế thừa

Ngay cả khi các khe không trống là giống nhau đối với nhiều cha mẹ, chúng không thể được sử dụng cùng nhau:

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

Sử dụng

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 trống trong cha mẹ dường như cung cấp sự linh hoạt nhất, cho phép trẻ chọn ngăn chặn hoặc cho phép (bằng cách thêm
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
2 để nhận gán động, xem phần trên) Việc tạo ra
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2:allowing the child to choose to prevent or allow (by adding
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
2 to get dynamic assignment, see section above) the creation of a
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2
:

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

Bạn không cần phải có khe - vì vậy nếu bạn thêm chúng và loại bỏ chúng sau, nó không gây ra bất kỳ vấn đề nào.

Đi ra ngoài ở đây: Nếu bạn sáng tác các bản phối hoặc sử dụng các lớp cơ sở trừu tượng, không có ý định được khởi tạo, một

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 trống ở những phụ huynh đó dường như là cách tốt nhất để đi về tính linh hoạt cho các lớp con.: If you're composing mixins or using abstract base classes, which aren't intended to be instantiated, an empty
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 in those parents seems to be the best way to go in terms of flexibility for subclassers.

Để 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 trong nhiều kế thừa

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

Chúng tôi có thể sử dụng trực tiếp các điều trên bằng cách kế thừa và khai báo các vị trí dự kiến:

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

Nhưng chúng tôi không quan tâm đến điều đó, đó là sự thừa kế nhỏ, chúng tôi cần một lớp khác mà chúng tôi cũng có thể kế thừa, có thể với một thuộc tính ồn ào:

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

Bây giờ nếu cả hai căn cứ đều có vị trí không trống, chúng tôi không thể làm dưới đây. .

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
0

Và bây giờ chúng ta có chức năng từ cả thông qua nhiều kế thừa và vẫn có thể từ chối

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 và
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
4 Instantiation:

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
1

Các trường hợp khác để tránh các khe:

  • Tránh chúng khi bạn muốn thực hiện gán
    >>> from sys import getsizeof
    >>> getsizeof(Right()), getsizeof(Wrong())
    (56, 72)
    
    06 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ố cục khe giống hệt nhau. (Tôi rất thích tìm hiểu ai đang làm điều này và tại sao.)
  • Tránh chúng nếu bạn muốn phân lớp độ dài thay đổi được xây dựng như dài, tuple hoặc str và bạn muốn thêm các thuộc tính cho chúng.
  • Tránh chúng nếu bạn khăng khăng cung cấp các giá trị mặc định thông qua các thuộc tính lớp cho các biến.

Bạn có thể trêu chọc các cảnh báo thêm từ phần còn lại của tài liệu

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 (tài liệu 3.7 Dev là hiện tại nhất), mà tôi đã có những đóng góp đáng kể gần đây.

Phê bình các câu trả lời khác

Các câu trả lời hàng đầu hiện tại trích dẫn thông tin lỗi thời và khá léo tay và bỏ lỡ dấu ấn theo một số cách quan trọng.

Không "chỉ sử dụng >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0 khi khởi tạo nhiều đối tượng"

Tôi trích dẫn:

"Bạn sẽ muốn sử dụng

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 nếu bạn định khởi tạo rất nhiều (hàng trăm, ngàn) đối tượng của cùng một lớp."

Các lớp cơ sở trừu tượng, ví dụ, từ mô -đun

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
10, không được khởi tạo, nhưng
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 được khai báo cho chúng.

Why?

Nếu người dùng muốn từ chối tạo

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 hoặc
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
4, những điều đó không có sẵn trong các lớp mẹ.

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 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 để tái sử dụng, 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ị.

>>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 0 không phá vỡ Pickling

Khi ngâm một đối tượng có rãnh, bạn có thể thấy nó phàn nàn với một

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
16 gây hiểu lầm:

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
2

Điều này thực sự không chính xác. Thông báo này xuất phát từ giao thức lâu đời nhất, là mặc định. Bạn có thể chọn giao thức mới nhất với đối số

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
17. Trong Python 2.7, điều này sẽ là
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
18 (được giới thiệu trong 2.3) và trong 3.6 là
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
19.

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
3

Trong Python 2.7:

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
4

trong Python 3.6

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
5

Vì vậy, tôi sẽ ghi nhớ điều này, vì nó là một vấn đề được giải quyết.

Phê bình về (cho đến ngày 2 tháng 10 năm 2016) đã chấp nhận câu trả lời

Đ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

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0 là tiết kiệm không gian trong các đối tượng. Thay vì có một chế độ động cho phép thêm các 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 tiết kiệm chi phí của một dict cho mọi đối tượng sử dụng các khe

Nửa thứ hai là suy nghĩ mong muốn, và tắt dấu:

Mặc dù điều này đôi khi 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 đủ động để nó chỉ yêu cầu dict 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

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 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á mức và bỏ lỡ lý do thực tế để tránh

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
0. Dưới đây không phải là một lý do thực sự để tránh các vị trí (vì lý do thực tế, 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ởi những kẻ lập dị kiểm soát và gõ tĩnh.

Sau đó, nó tiếp tục thảo luận về những 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 với

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

Đoạn thứ ba là suy nghĩ mong muốn hơn. Cùng nhau, chủ yếu là nội dung ngoài thị trường mà người trả lời thậm chí không có tác giả và đóng góp cho đạn dược cho các nhà phê bình của trang web.

Bằng chứng sử dụng bộ nhớ

Tạo một số đối tượng bình thường và các đối tượng có rãnh:

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
6

Khởi tạo một triệu trong số họ:

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
7

Kiểm tra với

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

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
8

Truy cập các đối tượng thông thường và

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 của chúng và kiểm tra lại:

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
9

Đ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, thêm không gian sẽ tự động thêm vào các phiên bản để hấp dẫn

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
2 và
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
27. . "đến lớp của bạn.