__slots__의 사용법?


762

__slots__파이썬 의 목적은 무엇입니까 ? 특히 사용하고 싶을 때와 그렇지 않을 때와 관련하여 무엇입니까?

답변:


1018

파이썬에서, 이것의 목적은 __slots__무엇이며 이것을 피해야 할 경우는 무엇입니까?

TLDR :

특수 속성을 __slots__사용하면 객체 인스턴스에있을 것으로 예상되는 인스턴스 속성을 예상 결과와 함께 명시 적으로 지정할 수 있습니다.

  1. 더 빠른 속성 액세스.
  2. 메모리 공간 절약 .

공간 절약은

  1. 대신 슬롯 값 참조를 저장 __dict__.
  2. 부모 클래스가 거부하고 선언하면 거부 __dict____weakref__생성 __slots__.

빠른 경고

작은 경고, 상속 트리에서 특정 슬롯을 한 번만 선언해야합니다. 예를 들면 다음과 같습니다.

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

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

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

파이썬은 당신이 이것을 잘못 받았을 때 반대하지 않으며 (아마도 그렇게해야합니다) 문제가 다르게 나타나지 않을 수도 있지만 객체가 그렇지 않은 경우보다 더 많은 공간을 차지합니다. 파이썬 3.8 :

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

베이스의 슬롯 디스크립터에 잘못된 슬롯과 별도의 슬롯이 있기 때문입니다. 이것은 일반적으로 나타나지 않아야하지만 다음과 같이 할 수 있습니다.

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

  • 의 생성을 막으려면 __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

Ubuntu의 Python 3.5에서는 슬롯 액세스가 거의 30 % 빠릅니다.

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

Windows의 Python 2에서는 약 15 % 더 빠릅니다.

사용 이유 __slots__: 메모리 절약

또 다른 목적은 __slots__각 객체 인스턴스가 차지하는 메모리 공간을 줄이는 것입니다.

문서에 대한 본인의 공헌은 다음과 같은 이유를 분명히 나타냅니다 .

사용하여 절약 된 공간 __dict__이 상당 할 수 있습니다.

SQLAlchemy 는 많은 메모리 절약 효과를 제공 __slots__합니다.

이를 확인하기 위해 Ubuntu Linux에서 Python 2.7의 Anaconda 배포판을 사용하면 선언 되지 않은 클래스 인스턴스의 크기 ( guppy.hpy와 힙이 있음) 는 64 바이트입니다. 포함 되지 않습니다 . 게으른 평가를 다시 한 번 감사드립니다. 참조 될 때까지 존재하지는 않지만 분명히 데이터가없는 클래스는 쓸모가 없습니다. 존재로 호출되면 속성은 추가로 최소 280 바이트입니다.sys.getsizeof__slots____dict____dict____dict__

반대로 (데이터 없음) 으로 __slots__선언 된 클래스 인스턴스 ()는 16 바이트에 불과하며 슬롯에 하나의 항목이있는 총 56 바이트, 2가있는 64 개입니다.

64 비트 파이썬, 내가 파이썬 2.7와 3.6 바이트의 메모리 소비 도시 __slots__하고 __dict__딕셔너리 (0, 1, 2 개 속성 제외) 3.6에서 성장 각 점에 대해 (어떤 슬롯이 정의되지 않은)를 :

       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

따라서 Python 3의 작은 dicts에도 불구하고 __slots__메모리를 절약하기 위해 인스턴스를 얼마나 잘 확장 할 수 있는지 알 수 있습니다 __slots__. 이것이 주요한 이유 입니다.

내 노트의 완성을 위해, 파이썬 2에서는 클래스 네임 스페이스에 64 바이트, 파이썬 3에는 72 바이트의 슬롯 당 1 회 비용이 발생합니다. 슬롯은 "멤버"라고하는 속성과 같은 데이터 디스크립터를 사용하기 때문입니다.

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

데모 __slots__:

의 생성을 거부하려면 __dict__서브 클래스를 작성 해야합니다 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__슬롯 객체를 서브 클래 싱하는 동안 생성 을 허용하려면 다음을 추가 '__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__부모로부터, 또는 부모의 컨트롤이있는 경우,이를 추상화에 슬롯, 또는 리팩토링을 비워 줄 :

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__가리키는 이름을 사용할 때와 동일한 종류의 시맨틱을 얻게 되지만 다른 값은 인스턴스의에 배치 __dict__됩니다.

__slots__속성을 즉시 추가 할 수 있기를 피하는 것은 실제로 좋은 이유가 아닙니다 . 필요한 경우 추가 "__dict__"하십시오 __slots__.

해당 기능이 필요한 경우 비슷하게 명시 적으로 추가 __weakref__할 수 있습니다 __slots__.

명명 된 튜플을 서브 클래 싱 할 때 비어있는 튜플로 설정하십시오.

명명 된 튜플 내장은 매우 가벼운 불변 인스턴스 (실제로 튜플의 크기)를 만들지 만 이점을 얻으려면 서브 클래스로 만들면 직접해야합니다.

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__튜플의 하위 유형.

가장 큰주의 사항 : 다중 상속

비어 있지 않은 슬롯이 여러 부모에 대해 동일한 경우에도 함께 사용할 수 없습니다.

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__슬롯 레이아웃이 동일하지 않으면 클래스가없는 다른 클래스에 할당 을 수행 하거나 추가 할 수없는 경우에는 피하십시오. (누가이 일을하는지, 왜 배우는 지에 관심이 있습니다.)
  • long, tuple 또는 str과 같은 가변 길이 내장을 서브 클래스로 만들고 속성을 추가하려는 경우이를 피하십시오.
  • 인스턴스 변수의 클래스 속성을 통해 기본값을 제공해야하는 경우이를 피하십시오.

내가 최근에 많은 기여를 한 __slots__ 문서 의 나머지 부분 (3.7 dev 문서가 최신 버전 임) 에서 추가 경고를 해결할 수 있습니다 .

다른 답변의 비판

현재의 최고 답변은 오래된 정보를 인용하고 손을 많이 들며 몇 가지 중요한 방법으로 마크를 놓칩니다.

" __slots__많은 객체를 인스턴스화 할 때만 사용하지 마십시오 "

나는 인용한다 :

" __slots__동일한 클래스의 객체 (수백, 수천)를 인스턴스화 하려는 경우 사용하고 싶습니다 ."

예를 들어, collections모듈의 추상 기본 클래스 는 인스턴스화되지 않았지만 __slots__선언되어 있습니다.

왜?

사용자가 거부 __dict__하거나 __weakref__만들려면 부모 클래스에서 해당 항목을 사용할 수 없어야합니다.

__slots__ 인터페이스 또는 믹스 인을 만들 때 재사용성에 기여합니다.

많은 파이썬 사용자가 재사용 성을 위해 글을 쓰지 않는 것이 사실이지만, 필요한 경우 불필요한 공간 사용을 거부 할 수있는 옵션을 갖는 것이 중요합니다.

__slots__ 산세를 끊지 않습니다

슬롯이있는 객체를 피클 링하면 오해의 소지가 있음을 알 수 있습니다 TypeError.

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

이것은 실제로 잘못되었습니다. 이 메시지는 가장 오래된 프로토콜 인 기본값입니다. -1인수를 사용 하여 최신 프로토콜을 선택할 수 있습니다 . 파이썬 2.7에서는 이것이 22.3에 도입되었으며, 3.6에서는입니다 4.

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

파이썬 2.7에서 :

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

파이썬 3.6에서

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

그래서 나는 이것이 해결 된 문제이므로 이것을 명심해야합니다.

(2016 년 10 월 2 일까지)의 답변 비평

첫 번째 단락은 반 짧은 설명, 반 예측입니다. 실제로 질문에 대답하는 유일한 부분은 다음과 같습니다.

__slots__객체의 공간을 절약 하는 것이 적절하게 사용 됩니다. 언제라도 객체에 속성을 추가 할 수있는 동적 dict 대신, 생성 후 추가를 허용하지 않는 정적 구조가 있습니다. 이것은 슬롯을 사용하는 모든 객체에 대해 하나의 받아쓰기의 오버 헤드를 절약합니다

후반은 희망적인 생각이며, 표시에서 벗어납니다.

이것은 때때로 유용한 최적화이지만, 파이썬 인터프리터가 동적으로 충분히 객체가 아니라면 실제로 객체에 추가가있을 때만 필요한 경우에는 완전히 불필요합니다.

파이썬은 실제로 이와 비슷한 것을 수행하여 __dict__액세스 할 때만 생성 하지만 데이터가없는 많은 객체를 생성하는 것은 상당히 어리 석습니다.

두 번째 단락은 피해야 할 실제 이유를 지나치게 단순화하고 그리워 __slots__합니다. 아래는 슬롯을 피하는 실제 이유 가 아닙니다 ( 실제 이유로 위의 나머지 답변을 참조하십시오).

그들은 제어 괴물과 정적 타이핑 이유에 의해 남용 될 수있는 방식으로 슬롯이있는 객체의 동작을 변경합니다.

그런 다음 파이썬과 관련이없는 목표를 달성하는 다른 방법에 대해 논의하고 관련이 없습니다. __slots__ .

세 번째 단락은 더 희망적인 생각입니다. 응답자가 사이트를 비판하는 탄약에 대한 글을 쓰지 않고 기여한 것은 대부분 비표준 콘텐츠입니다.

메모리 사용 증거

일반 객체와 슬롯 객체를 만듭니다.

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

이것은 파이썬 2.2의 유형과 클래스 통합 에서 파이썬의 역사와 일치합니다

당신이 서브 클래스 경우 내장 타입, 여분의 공간이 자동으로 수용하는 경우에 추가 __dict__하고 __weakrefs__. ( __dict__사용할 때까지는 초기화되지 않으므로 생성하는 각 인스턴스에 대해 빈 사전이 차지하는 공간에 대해 걱정하지 않아도됩니다.)이 추가 공간이 필요하지 않은 경우 " __slots__ = []" 라는 문구를 추가 할 수 있습니다. 수업.


14
와우, 대답의 한 지옥-감사합니다! 그러나 나는 class Child(BaseA, BaseB): __slots__ = ('a', 'b')empy-slot-parents와 함께 예제를 잡지 못했습니다 . 여기에 for dictproxy를 올리는 대신 생성 된 이유는 무엇 입니까? AttributeErrorc
Skandix

@Skandix는 오타를 주목 해 주셔서 감사합니다 .c는 인스턴스화가 아니 었습니다. 아마도 포스트 히스토리에 저장했을 때 해당 부분을 편집하는 것을 잊었습니다. 내가 옳은 일을 하고 코드를 더 복사 가능하게 만들면 더 빨리 잡혔을 것입니다 ... 다시 한번 감사드립니다!
Aaron Hall

38
이 답변은에 대한 공식적인 파이썬 문서의 일부 여야합니다 __slots__. 진심으로! 감사합니다!
NightElfik

13
@NightElfik 믿거 나 말거나, 나는 __slots__약 1 년 전에 파이썬 문서에 기여했습니다 : github.com/python/cpython/pull/1819/files
Aaron Hall

환상적인 세부 답변. 하나의 질문이 있습니다 : 사용법이 경고 중 하나에 부딪치지 않는 한 슬롯을 기본값으로 사용해야 합니까? 또는 속도 / 메모리에 어려움을 겪을 것이라는 것을 알고 있다면 고려해야 할 슬롯입니까? 다시 말해, 초보자가 초보자에 대해 배우고 처음부터 사용하도록 격려해야합니까?
freethebees

265

인용 제이콥 할렌 :

__slots__객체의 공간을 절약 하는 것이 적절하게 사용 됩니다. 언제든 객체에 속성을 추가 할 수있는 동적 dict 대신, 생성 후 추가를 허용하지 않는 정적 구조가 있습니다. [이러한 사용은 __slots__모든 객체에 대해 하나의 dict의 오버 헤드 를 제거합니다.] 이것은 때때로 유용한 최적화이지만, 파이썬 인터프리터가 충분히 동적 인 경우 실제로 불필요합니다. 목적.

불행히도 슬롯에는 부작용이 있습니다. 그들은 제어 괴물과 정적 타이핑 이유에 의해 남용 될 수있는 방식으로 슬롯이있는 객체의 동작을 변경합니다. 컨트롤 괴물이 메타 클래스를 학대하고 정적 타이핑 이유가 데코레이터를 학대해야하기 때문에 이것은 나쁘다. 파이썬에서는 무언가를하는 명백한 방법이 하나 밖에 없기 때문이다.

CPython이 공간 절약을 처리 할 수있을만큼 똑똑하게 만드는 __slots__것은 중요한 작업이므로 P3k (아직) 변경 목록에없는 이유 일 수 있습니다.


86
나는 "정적 타이핑"/ 장식 포인트에 대해 자세히 설명하고 싶습니다. 부재자에게 인용하는 것은 도움이되지 않습니다. __slots__정적 타이핑과 동일한 문제를 해결하지 못합니다. 예를 들어 C ++에서는 멤버 변수 선언이 제한되지 않고 의도하지 않은 유형 (및 컴파일러 적용)이 해당 변수에 할당됩니다. 의 사용을 용납하지 않고 __slots__대화에 관심이 있습니다. 감사!
hiwaylon

126

__slots__같은 클래스의 많은 객체 (수백, 수천)를 인스턴스화 하려는 경우 에 사용 하려고합니다. __slots__메모리 최적화 도구로만 존재합니다.

__slots__속성 생성을 제한하는 데 사용하지 않는 것이 좋습니다 .

__slots__피클 링 객체 는 기본 (가장 오래된) 피클 프로토콜에서 작동하지 않습니다. 이후 버전을 지정해야합니다.

파이썬의 일부 다른 내부 검사 기능도 악영향을받을 수 있습니다.


10
내 답변에 슬롯으로 된 객체를 산 세척하고 시연의 첫 부분을 설명합니다.
Aaron Hall

2
나는 당신의 요점을 알지만 슬롯은 더 빠른 속성 액세스를 제공합니다 (다른 사람들이 언급했듯이). 이 경우 성능을 얻기 위해 "동일한 클래스의 객체 (수백, 수천)를 인스턴스화"할 필요가 없습니다 . 대신 필요한 것은 동일한 인스턴스의 동일한 (슬롯 된) 속성에 대한 많은 액세스입니다. (내가 틀렸다면 정정 해주세요.)
Rotareti

61

각 파이썬 객체에는 __dict__다른 모든 속성을 포함하는 사전 인 속성이 있습니다. 예를 들어 self.attrpython 을 입력하면 실제로 수행 self.__dict__['attr']됩니다. 사전을 사용하여 속성을 저장한다고 가정하면 속성에 액세스하는 데 약간의 공간과 시간이 필요합니다.

그러나를 사용하면 __slots__해당 클래스에 대해 생성 된 객체에는 __dict__속성 이 없습니다 . 대신, 모든 속성 액세스는 포인터를 통해 직접 수행됩니다.

따라서 본격적인 클래스가 아닌 C 스타일 구조를 원한다면 __slots__객체의 크기를 줄이고 속성 액세스 시간을 줄이는 데 사용할 수 있습니다 . 좋은 예는 x & y 속성을 포함하는 Point 클래스입니다. 당신이 많은 포인트를 가질 예정이라면, 당신은 __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 문서에 언급되어 있습니다 . 슬롯을 사용하여 일반적으로 호출되는 함수의 함수 오버 헤드를 줄이려는 고성능 라이브러리의 경우 훨씬 빠릅니다.

이제 이것은 OP 질문과 직접 ​​관련이 없을 수도 있습니다. 객체 에서 슬롯 구문 을 사용하는 것보다 확장을 빌드하는 것과 관련이 있습니다 . 그러나 슬롯 사용과 그 뒤에있는 추론에 대한 그림을 완성하는 데 도움이됩니다.


7

클래스 인스턴스의 속성에는 인스턴스, 속성 이름 및 속성 값의 3 가지 속성이 있습니다.

에서 일반 속성 액세스 , 인스턴스는 사전의 역할을하고 사전에 키 값을 찾는으로 속성의 이름이 역할을합니다.

인스턴스 (속성)-> 값

에서 __slots__ 액세스 , 속성의 이름은 사전 역할을하며 사전에 키 값을 찾는대로 인스턴스는 역할을합니다.

속성 (인스턴스)-> 값

에서 플라이급 패턴 , 속성의 이름은 사전 역할을하며 값은 인스턴스를 찾는 것을 사전에 핵심 역할을합니다.

속성 (값)-> 인스턴스


이것은 좋은 점유율이며 플라이급을 제안하는 답변 중 하나에 대한 의견에는 잘 맞지 않지만 질문 자체에 대한 완전한 답변은 아닙니다. 특히 (질문의 맥락에서) : 왜 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}

위의 예를 보면 obj1obj2에 고유 한 xy 속성이 있으며 python이 dict각 객체 ( obj1obj2 )에 대한 속성 도 생성 했음을 알 수 있습니다 .

내 클래스 Test 에 수천 개의 이러한 객체가 있는지 가정하십시오 . dict각 객체에 대한 추가 속성 을 만들면 내 코드에 많은 오버 헤드 (메모리, 컴퓨팅 성능 등)가 발생합니다.

솔루션 : __slots__

이제 다음 예제에서 내 클래스 Test__slots__속성을 포함 합니다. 이제 객체에 새 속성을 추가 할 수 없으며 (attribute 제외 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__이전 PEAK 프로젝트의 일부인 ProxyTypes 패키지에서 객체 프록시에 속성을 추가하는 것입니다. 그는 ObjectWrapper프록시에 다른 개체를 당신을 허용하지만 프록시 객체와의 모든 상호 작용 절편. 일반적으로 사용되지는 않지만 Python 3를 지원하지는 않지만 스레드 안전을 사용하여 ioloop를 통해 프록시 객체에 대한 모든 액세스를 반송하는 토네이도 기반 비동기 구현 주위에서 스레드 안전 차단 래퍼를 구현하는 데 사용했습니다. 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__때는 실제로 경량 또는 플라이급 디자인 패턴 을 사용하려고합니다 . 더 이상 순수한 Python 객체를 사용하지 않으려는 경우입니다. 대신 배열, 구조체 또는 numpy 배열 주위에 Python 객체와 같은 래퍼를 원합니다.

class Flyweight(object):

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

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

클래스와 같은 래퍼는 속성이 없으며 기본 데이터에 작용하는 메소드 만 제공합니다. 메소드를 클래스 메소드로 줄일 수 있습니다. 실제로 기본 데이터 배열에서 작동하는 함수로 축소 될 수 있습니다.


17
Flyweight는 무엇과 관련이 __slots__있습니까?
oefe

3
@oefe : 확실히 질문이 없습니다. " 슬롯 이 필요할 것으로 생각 될 때 실제로 사용하고 싶습니다 ... Flyweight 디자인 패턴"에 도움이된다면 내 대답을 인용 할 수 있습니다 . 이것이 Flyweight가 slot 과 관련이있는 것입니다 . 더 구체적인 질문이 있습니까?
S.Lott

21
@oefe : Flyweight이며 __slots__메모리를 절약하는 최적화 기법입니다. __slots__Flyweight 디자인 패턴뿐만 아니라 많은 객체가있을 때 이점을 보여줍니다. 둘 다 같은 문제를 해결합니다.
jfs

7
메모리 소비 및 속도와 관련하여 슬롯 사용과 Flyweight 사용 사이에 비교할 수 있습니까?
kontulai

8
Flyweight가 일부 상황에서는 확실히 유용하지만 믿거 나 말거나 "실 리언 객체를 만들 때 파이썬에서 메모리 사용을 줄일 수있는 방법"에 대한 대답이 "실 질리언 객체에 Python을 사용하지 않는"것은 아닙니다. __slots__Evgeni가 지적한 것처럼 때로는 실제로 답이 될 수 있습니다. 간단한 추론으로 추가 할 수 있습니다 (예 : 먼저 정확성에 집중 한 다음 성능을 추가 할 수 있음).
Patrick Maupin

0

원래 질문은 메모리뿐만 아니라 일반적인 사용 사례에 관한 것이 었습니다. 따라서 많은 양의 객체를 인스턴스화 할 때 더 큰 성능을 얻는다는 점을 언급해야합니다. 큰 문서를 객체로 구문 분석하거나 데이터베이스에서 구문 분석하는 경우와 같습니다.

다음은 슬롯을 사용하고 슬롯을 사용하지 않고 백만 개의 항목으로 오브젝트 트리를 작성하는 비교입니다. 참조로 나무에 일반 dicts를 사용할 때의 성능 (OSX의 Py2.7.10) :

********** 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

테스트 클래스 (식별, 슬롯의 아파트) :

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)

테스트 코드, 상세 모드 :

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.