your programing

__slots__ 사용?

lovepro 2020. 9. 30. 11:11
반응형

__slots__ 사용?


__slots__Python 의 목적은 무엇입니까 ? 특히 사용하고 싶을 때와 사용하지 않을 때와 관련하여?


파이썬에서 목적은 __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

파이썬은 이것이 잘못되었을 때 반대하지 않으며 (아마 그래야 할 것입니다), 문제가 나타나지 않을 수도 있지만, 객체는 그렇지 않으면 더 많은 공간을 차지할 것입니다.

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)

가장 큰주의 사항은 다중 상속에 대한 것입니다. 여러 "빈 슬롯이없는 상위 클래스"는 결합 할 수 없습니다.

이 제한을 수용하려면 모범 사례를 따르십시오. 각각의 구체적인 클래스와 새 구체적인 클래스가 집합 적으로 상속 할 부모의 추상화를 제외한 모든 부모의 추상화를 제거합니다. 추상화에 빈 슬롯을 제공합니다 ( 표준 라이브러리).

예제는 아래의 다중 상속 섹션을 참조하십시오.

요구 사항 :

  • in이라는 속성 __slots__이 실제로 대신 슬롯에 저장되도록 하려면 __dict__클래스가에서 상속해야합니다 object.

  • a의 생성을 방지하기 위해 __dict__, 당신은에서 상속해야 object하고, 상속의 모든 클래스를 선언해야 __slots__하고 그들 중 누구도이 가질 수 없습니다 '__dict__'항목을.

계속 읽고 싶다면 많은 세부 사항이 있습니다.

사용 이유 __slots__: 더 빠른 속성 액세스.

파이썬의 창시자 귀도 반 로섬 (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(heapy) 및 sys.getsizeof, __slots__선언 되지 않은 클래스 인스턴스의 크기 는 64 바이트입니다. 즉 않습니다 하지 을 포함한다 __dict__. 게으른 평가를 위해 파이썬에게 다시 감사드립니다. __dict__참조되기 전까지는 분명히 존재하지 않지만 데이터가없는 클래스는 일반적으로 쓸모가 없습니다. 존재로 호출 될 때 __dict__속성은 추가로 최소 280 바이트입니다.

반면 __slots__에 be ()(데이터 없음) 선언 된 클래스 인스턴스 는 16 바이트에 불과하며 슬롯에 한 항목이있는 총 56 바이트, 두 항목이있는 64 바이트입니다.

64 비트 Python의 경우, dict가 3.6에서 증가하는 각 지점 (0, 1 및 2 속성 제외)에 대해 __slots____dict__(정의 된 슬롯 없음)에 대한 Python 2.7 및 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의 작은 딕셔너리에도 불구하고 __slots__인스턴스가 메모리를 절약하기 위해 얼마나 잘 확장 되는지 알 수 있으며 이것이 사용하려는 주요 이유 __slots__입니다.

내 노트의 완성도를 위해, 슬롯은 "멤버"라는 속성과 같은 데이터 설명자를 사용하기 때문에 Python 2에서는 64 바이트, Python 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__.

명명 된 튜플을 서브 클래 싱 할 때 빈 튜플로 설정합니다.

namedtuple 내장은 매우 가벼운 불변 인스턴스를 만들지 만 (본질적으로 튜플의 크기) 이점을 얻으려면 하위 클래스를 만드는 경우 직접 수행해야합니다.

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__'동적 할당을 얻기 위해, 위의 섹션 참조) a의 생성을__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__ 인터페이스 또는 믹스 인을 만들 때 재사용에 기여합니다.

많은 Python 사용자가 재사용 성을 위해 작성하지 않는 것은 사실이지만, 재사용을 위해 작성하는 경우 불필요한 공간 사용을 거부하는 옵션을 갖는 것이 중요합니다.

__slots__ 산세를 깨지 않는다

슬롯 형 객체를 피클 링 할 때 오해의 소지가 있다고 불평 할 수 있습니다 TypeError.

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

이것은 실제로 잘못된 것입니다. 이 메시지는 기본값 인 가장 오래된 프로토콜에서 제공됩니다. -1인수를 사용 하여 최신 프로토콜을 선택할 수 있습니다 . Python 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>

그래서 나는 이것이 해결 된 문제이기 때문에 이것을 명심할 것입니다.

(2016 년 10 월 2 일까지) 수용된 답변에 대한 비판

첫 번째 문단은 반은 짧은 설명이고 반은 예측입니다. 여기에 실제로 질문에 답하는 유일한 부분이 있습니다.

의 적절한 사용은 __slots__개체의 공간을 절약 하는 것입니다 . 언제든지 객체에 속성을 추가 할 수있는 동적 사전을 갖는 대신 생성 후 추가를 허용하지 않는 정적 구조가 있습니다. 이것은 슬롯을 사용하는 모든 객체에 대해 하나의 dict의 오버 헤드를 절약합니다.

후반부는 희망찬 생각이며 획기적인 것입니다.

이것은 때때로 유용한 최적화이지만, 파이썬 인터프리터가 충분히 동적이어서 객체에 실제로 추가가있을 때만 dict 만 필요하면 완전히 불필요 할 것입니다.

파이썬은 실제로 이와 비슷한 일을합니다. __dict__접근 할 때만 생성 하지만 데이터없이 많은 객체를 생성하는 것은 상당히 우스꽝 스럽습니다.

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

그들은 슬롯이있는 객체의 동작을 제어 광과 정적 타이핑 애호가가 남용 할 수있는 방식으로 변경합니다.

그런 다음 .NET과 관련된 어떤 것도 논의하지 않고 Python으로 이러한 비뚤어진 목표를 달성하는 다른 방법에 대해 논의 __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
...

이는 Python 2.2의 유형 및 클래스 통합 에서 나온 Python의 역사와 일치합니다.

기본 제공 유형을 서브 클래 싱하면 인스턴스에 추가 공간이 자동으로 추가되어 __dict____weakrefs__. ( __dict__사용할 때까지 초기화되지 않으므로 생성하는 각 인스턴스에 대해 빈 사전이 차지하는 공간에 대해 걱정할 필요가 없습니다.)이 추가 공간이 필요하지 않은 경우 " __slots__ = []" 구문을 다음에 추가 할 수 있습니다. 수업.


Jacob Hallen 인용 :

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

불행히도 슬롯에는 부작용이 있습니다. 그들은 슬롯이있는 객체의 동작을 제어 광과 정적 타이핑 애호가가 남용 할 수있는 방식으로 변경합니다. 이것은 나쁘다. 왜냐하면 제어 광은 메타 클래스를 남용해야하고 정적 타이핑 싫은 사람은 데코레이터를 남용해야하기 때문이다. 파이썬에서는 무언가를하는 명백한 방법이 하나만 있어야하기 때문이다.

CPython을 공간 절약을 처리 할 수있을만큼 스마트하게 만드는 __slots__것은 중요한 작업이며, 이것이 아마도 P3k의 변경 사항 목록에없는 이유 일 것입니다 (아직).


__slots__동일한 클래스의 많은 (수백, 수천) 개체를 인스턴스화 하려는 경우 사용하고 싶을 것입니다. __slots__메모리 최적화 도구로만 존재합니다.

__slots__속성 생성을 제한하는 데 사용하는 것은 매우 권장하지 않으며 일반적으로 Python의 다른 내부 검사 기능과 함께 pickle을 깨기 때문에 피하고 싶습니다.


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

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

따라서 완전한 클래스가 아닌 C 스타일 구조를 원한다면 __slots__객체의 크기를 압축하고 속성 액세스 시간을 줄이는 데 사용할 수 있습니다 . 좋은 예는 x & y 속성을 포함하는 Point 클래스입니다. 많은 점수를 얻으 __slots__려면 메모리를 절약하기 위해 사용해 볼 수 있습니다 .


다른 답변 외에도 다음을 사용하는 예가 있습니다 __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 문서에 언급되어 있습니다 . 슬롯을 사용하여 일반적으로 호출되는 함수에 대한 함수 오버 헤드를 줄이려는 고성능 라이브러리의 경우 훨씬 빠릅니다.

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


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

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

인스턴스 (속성)-> 값

에서 __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}

위의 예를 살펴보면 obj1obj2 에는 고유 한 xy 속성이 있으며 파이썬은 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'

다소 모호한 또 다른 용도는 __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

기본적으로 __slots__.

필요하다고 생각되는 시간 동안 __slots__실제로 Lightweight 또는 Flyweight 디자인 패턴 을 사용하려고합니다 . 더 이상 순수하게 Python 객체를 사용하고 싶지 않은 경우입니다. 대신 배열, 구조체 또는 numpy 배열 주위에 Python 객체와 같은 래퍼가 필요합니다.

class Flyweight(object):

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

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

클래스와 유사한 래퍼에는 속성이 없으며 기본 데이터에 대해 작동하는 메서드 만 제공합니다. 메서드는 클래스 메서드로 축소 할 수 있습니다. 실제로 기본 데이터 배열에서 작동하는 함수로 축소 될 수 있습니다.


원래 질문은 메모리뿐만 아니라 일반적인 사용 사례에 관한 것이 었습니다. 따라서 여기서 언급해야 할 점은 대량의 개체를 인스턴스화 할 때 더 나은 성능을 얻을 수 있다는 것입니다. 예를 들어 큰 문서를 개체 또는 데이터베이스로 구문 분석 할 때 흥미 롭습니다.

다음은 슬롯을 사용하고 슬롯을 사용하지 않고 백만 개의 항목이있는 오브젝트 트리를 만드는 비교입니다. 참고로 트리 (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

테스트 클래스 (ident, appart from slots) :

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

참고 URL : https://stackoverflow.com/questions/472000/usage-of-slots

반응형