توی یه مقاله به اسم __slots__ برخوردم. برام سوال پیش اومد که چی هست و چه استفاده ایی داره. جمع بندی چیز هایی که خوندم رو اینجا قرار میدم.
چی هست؟ بنا به تعریف نام متغیر هایی رو که توسط instance استفاده میشه رو به صورت رشته یا دنباله ای از رشته ها براش تعریف می کنیم. بدین صورت فضای مورد نیاز برای تعریف متغیر ها ذخیره میشه و از ایجاد خودکار __dict__ و __weakref__ جلوگیری میکنه.
خب حالا __dict__ و __weakref__ چیه؟ همه چیز در پایتون ابجکته. کلاس, فانکشن و … . __dict__ یه دیکشنری که ویژگی های ابجکت رو در خودش ذخیره میکنه. __weakref__ یه ویژگی که امکان ارتباط ضعیف رو به ابجکت میده. ارتباط ضعیف به چه دردی میخوره؟ وقتی تنها ارتباط های باقی مونده به یه ابجکت weakref باشه garbage callector ابجکت رو از بین میبره و فضای اون رو به چیز های دیگه اختصاص میده.
حالا که چی؟ ویژگی __slots__ این امکان رو به شما میده که ویژگی هایی که ابجکت شما نیاز داره رو صراحتا بهش معرفی کنید. این کار باعث سرعت بیشتر در دسترسی به ويژگی ها و اشغال فضای کمتر میشه.
نکته استفاده از __slots__ اینه که برای ممانعت از ایجاد __dict__ باید در ارث بری همه کلاس ها پیشین __slots__ رو تعریف کرده باشن و هیچ یک از اون ها __dict__ رو تعریف نکرده باشه. نکته دیگه اینه که وقتی یه ویژگی در کلاس پدر تعریف شده دیگه نیازی نیست که در کلاس فرزند هم باز تعریف بشه. البته تعریف شدنش منجر به خطا نمیشه تنها موردی که داره اینه که فضای بیشتری اشغال میشه.
class Base: __slots__ = 'foo', 'bar' class Right(Base): __slots__ = 'baz', class Wrong(Base): __slots__ = 'foo', 'bar', 'baz' >>> from sys import getsizeof >>> getsizeof(Right()), getsizeof(Wrong()) (64, 80)
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
نزدیک به ۳۰ درصد سرعت بهتر در اجرا با استفاده از __slots__ داره.
توی این قسمت بهتره این پست از sqlalchemy رو مطالعه کنید.
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'
اگه نیاز به استفاده از __dict__ داشتید. با در نظر گرفتن اینکه شما مقداری از سرعت بیشتر و مصرف بهینه حافظه رو از دست میدین ولی میتونید به صورت پویا ویژگی به ابجکت اضافه و استفاده کنید.
class SlottedWithDict(Child): __slots__ = ('__dict__', 'b') swd = SlottedWithDict() swd.a = 'a' swd.b = 'b' swd.c = 'c' >>> swd.__dict__ {'c': 'c'}
اگه ارث بری بدون تعریف __slots__ باشه.
class NoSlots(Child): pass ns = NoSlots() ns.a = 'a' ns.b = 'b' >>> ns.__dict__ {'b': 'b'}
بیان کردن چند نکته خالی از لطف نیست. اگر از ابجکتی ارث بری بکنید که خودش __slots__ رو تعریف نکرده ویژگی هایی که در __slots__ تعریف بشن به صورت slot شده قابل دسترس خواهند بود اما سایر ویژگی ها در __dict__ قرار میگیرن. همچنین اگه لازم داشتید میشه __weakref__ رو هم به __slots__ اضافه کنید.
در آخر باید گفت بزرگترین ایراد استفاده از __slots__ اینه که پشتیبانی از ارث بری چندگانه رو به همراه نداره.
class Foo(object): __slots__ = 'foo', 'bar' class Bar(object): __slots__ = 'foo', 'bar' >>> 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
بخش زیادی از این پست با استفاده از مرجع نوشته شده است.