از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
استفاده از __slots__ برای ذخیره داده های شی در پایتون
سرفصلهای مطلب
معرفی
در پایتون، هر نمونه شی با توابع و ویژگی های استاندارد از پیش ساخته شده است. به عنوان مثال، پایتون از یک فرهنگ لغت برای ذخیره ویژگی های نمونه یک شی استفاده می کند. این مزایای بسیاری دارد، مانند اینکه به ما اجازه می دهد تا ویژگی های جدید را در زمان اجرا اضافه کنیم. با این حال، این راحتی هزینه دارد.
دیکشنریها میتوانند مقدار زیادی از حافظه را مصرف کنند، بهخصوص اگر نمونههای بسیاری از اشیاء با تعداد زیادی ویژگی داشته باشیم. اگر عملکرد و کارایی حافظه کد حیاتی باشد، می توانیم راحتی دیکشنری ها را با __slots__
.
در این آموزش به بررسی چه چیزی می پردازیم __slots__
هستند و روش استفاده از آنها در پایتون. ما همچنین در مورد معاوضه های استفاده بحث خواهیم کرد __slots__
و در مقایسه با کلاس های معمولی که ویژگی های نمونه خود را با دیکشنری ها ذخیره می کنند، به عملکرد آنها نگاه کنید.
چه هستند _اسلات_ و چگونه از آنها استفاده کنیم؟
اسلات ها متغیرهای کلاسی هستند که می توان به آنها یک رشته، یک تکرار یا دنباله ای از رشته ها از نام متغیرهای نمونه اختصاص داد. هنگام استفاده از اسلات ها، شما متغیرهای نمونه یک شی را از جلو نام می برید و توانایی اضافه کردن آنها را به صورت پویا از دست می دهید.
یک نمونه شی که از اسلات ها استفاده می کند فرهنگ لغت داخلی ندارد. در نتیجه فضای بیشتری ذخیره می شود و دسترسی به ویژگی ها سریعتر می شود.
بیایید آن را در عمل ببینیم. این کلاس معمولی را در نظر بگیرید:
class CharacterWithoutSlots():
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
without_slots = character_without_slots('Fred Flinstone', 'Bedrock')
print(without_slots.__dict__)
در قطعه بالا:
organization
یک متغیر کلاس استname
وlocation
متغیرهای نمونه هستند (به کلمه کلیدی توجه کنیدself
در برابر آنها)
در حالی که هر نمونه شی از کلاس ایجاد می شود، یک فرهنگ لغت پویا تحت نام ویژگی as تخصیص داده می شود __dict__
که شامل تمام ویژگی های قابل نوشتن یک شی است. خروجی قطعه کد بالا به صورت زیر است:
{'name': 'Fred Flinstone', 'location': 'Bedrock'}
این را می توان به صورت تصویری نشان داد:
حال، بیایید ببینیم چگونه می توانیم این کلاس را با استفاده از اسلات ها پیاده سازی کنیم:
class CharacterWithSlots():
__slots__ = ("name", "location")
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
with_slots = CharacterWithSlots('Fred Flinstone', 'Bedrock')
print(with_slots.__dict__)
در قطعه بالا:
organization
یک متغیر کلاس استname
وlocation
متغیرهای نمونه هستند- کلمه کلیدی
__slots__
یک متغیر کلاس است که لیستی از متغیرهای نمونه را نگه میدارد (name
وlocation
)
با اجرای آن کد این خطا را به ما می دهد:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'character_without_slots' object has no attribute '__dict__'
درست است! نمونه های شی از کلاس های دارای اسلات انجام ندهید داشتن یک __dict__
صفت. در پشت صحنه، به جای ذخیره متغیرهای نمونه در یک فرهنگ لغت، مقادیر با مکان های فهرست مانند شکل زیر نگاشت می شوند:
در حالی که وجود ندارد __dict__
ویژگی، شما همچنان همانطور که معمولا انجام می دهید به ویژگی های شی دسترسی پیدا می کنید:
print(with_slots.name)
print(with_slots.location)
print(with_slots.organization)
اسلات ها صرفاً برای بهبود عملکرد ایجاد شده اند، همانطور که Guido در خود بیان کرده است پست وبلاگ معتبر.
بیایید ببینیم آیا آنها از کلاس های استاندارد بهتر عمل می کنند یا خیر.
کارایی و سرعت شکاف ها
ما قصد داریم اشیاء نمونه سازی شده با اسلات را با اشیاء نمونه سازی شده با دیکشنری ها با دو تست مقایسه کنیم. اولین آزمایش ما روش تخصیص حافظه را بررسی خواهد کرد. تست دوم ما به زمان اجرا آنها می پردازد.
این بنچمارک برای حافظه و زمان اجرا انجام شده است روی پایتون 3.8.5 با استفاده از ماژول ها tracemalloc
برای ردیابی تخصیص حافظه و timeit
برای ارزیابی زمان اجرا
نتایج ممکن است متفاوت باشد روی کامپیوتر شخصی شما:
import tracemalloc
import timeit
class Benchmark:
def __enter__(self):
self.allocated_memory = None
tracemalloc.start()
return self
def __exit__(self, exec_type, exec_value, exec_traceback):
present, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
self.allocated_memory = present
class CharacterWithoutSlots():
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
class CharacterWithSlots():
__slots__ = ("name", "location")
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
def calculate_memory(class_, number_of_times):
with Benchmark() as b:
_ = (class_("Barney", "Bedrock") for x in range(number_of_times))
return b.allocated_memory / (1024 * 1024)
def calculate_runtime(class_, number_of_times):
timer = timeit.Timer("instance.name; instance.location",
setup="instance = class_('Barney', 'Bedrock')",
globals={'class_': class_})
return timer.timeit(number=number_of_times)
if __name__ == "__main__":
number_of_runs = 100000
without_slots_bytes = calculate_memory(
CharacterWithoutSlots, number_of_runs)
print(f"Without slots Memory Usage: {without_slots_bytes} MiB")
with_slots_bytes = calculate_memory(CharacterWithSlots, number_of_runs)
print(f"With slots Memory Usage: {with_slots_bytes} MiB")
without_slots_seconds = calculate_runtime(
CharacterWithoutSlots, number_of_runs)
print(f"Without slots Runtime: {without_slots_seconds} seconds")
with_slots_seconds = calculate_runtime(
CharacterWithSlots, number_of_runs)
print(f"With slots Runtime: {with_slots_seconds} seconds")
در قطعه بالا، calculate_memory()
تابع حافظه تخصیص یافته را تعیین می کند calculate_runtime()
تابع ارزیابی زمان اجرا کلاس با اسلات در مقابل کلاس بدون اسلات را تعیین می کند.
نتایج چیزی در این راستا به نظر می رسد:
Without slots Memory Usage: 15.283058166503906 MiB
With slots Memory Usage: 5.3642578125 MiB
Without slots Runtime: 0.0068232000012358185 seconds
With slots Runtime: 0.006200600000738632 seconds
بدیهی است که استفاده از __slots__
با استفاده از دیکشنری ها در اندازه و سرعت برتری می دهد. در حالی که تفاوت سرعت به ویژه قابل توجه نیست، تفاوت اندازه قابل توجه است.
Gotchas با استفاده از اسلات
قبل از استفاده از اسلات ها در تمام کلاس های خود، چند نکته وجود دارد که باید به آنها توجه کنید:
- فقط می تواند ویژگی های تعریف شده در را ذخیره کند
__slots__
متغیر کلاس به عنوان مثال، در قطعه زیر زمانی که ما سعی می کنیم یک ویژگی را برای نمونه ای تنظیم کنیم که در آن وجود ندارد__slots__
متغیر، یک را دریافت می کنیمAttributeError
:
class character_with_slots():
__slots__ = ("name", "location")
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
with_slots = character_with_slots('Fred Flinstone', 'Bedrock')
with_slots.pet = "dino"
خروجی:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'character_with_slots' object has no attribute 'pet'
با اسلات ها، شما باید تمام ویژگی های موجود در کلاس را بدانید و آنها را در کلاس تعریف کنید __slots__
متغیر.
- طبقات فرعی از این پیروی نمی کنند
__slots__
تکلیف در سوپرکلاس فرض کنید کلاس پایه شما دارای این است__slots__
ویژگی اختصاص داده شده و این به یک زیر کلاس به ارث می رسد، زیر کلاس دارای a خواهد بود__dict__
ویژگی به طور پیش فرض
قطعه زیر را در نظر بگیرید که در آن شیء زیرکلاس بررسی می شود اگر دایرکتوری آن حاوی این باشد __dict__
ویژگی و خروجی معلوم می شود True
:
class CharacterWithSlots():
__slots__ = ("name", "location")
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
class SubCharacterWithSlots(CharacterWithSlots):
def __init__(self, name, location):
self.name = name
self.location = location
sub_object = SubCharacterWithSlots("Barney", "Bedrock")
print('__dict__' in dir(sub_object))
خروجی:
True
با اعلام این می توان از این امر جلوگیری کرد __slots__
متغیر یک بار دیگر برای زیر کلاس برای همه متغیرهای نمونه موجود در زیر کلاس. اگرچه این کار زائد به نظر می رسد، اما این تلاش را می توان با مقدار حافظه ذخیره شده سنجید:
class CharacterWithSlots():
__slots__ = ("name", "location")
organization = "Slate Rock and Gravel Company"
def __init__(self, name, location):
self.name = name
self.location = location
class SubCharacterWithSlots(CharacterWithSlots):
__slots__ = ("name", "location", "age")
def __init__(self, name, location):
self.name = name
self.location = location
self.age = 40
sub_object = SubCharacterWithSlots("Barney", "Bedrock")
print('__dict__' in dir(sub_object))
خروجی:
False
نتیجه
در این مقاله، ما اصول اولیه را در مورد آن یاد گرفتیم __slots__
ویژگی، و اینکه چگونه کلاس های دارای اسلات با کلاس های دارای دیکشنری متفاوت هستند. ما همچنین این دو کلاس را با اسلاتهایی که به طور قابلتوجهی کارآمدتر حافظه هستند، محک زدیم. در نهایت، برخی از اخطارهای شناخته شده را در مورد استفاده از اسلات در کلاس ها مورد بحث قرار دادیم.
در صورت استفاده در مکان های مناسب، __slots__
می تواند عملکرد را افزایش داده و کد را در کارآمدتر کردن حافظه بهینه کند.
(برچسبها به ترجمه)# python
منتشر شده در 1403-01-13 13:02:04