وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

استفاده از __slots__ برای ذخیره داده های شی در پایتون

0 14
زمان لازم برای مطالعه: 5 دقیقه


معرفی

در پایتون، هر نمونه شی با توابع و ویژگی های استاندارد از پیش ساخته شده است. به عنوان مثال، پایتون از یک فرهنگ لغت برای ذخیره ویژگی های نمونه یک شی استفاده می کند. این مزایای بسیاری دارد، مانند اینکه به ما اجازه می دهد تا ویژگی های جدید را در زمان اجرا اضافه کنیم. با این حال، این راحتی هزینه دارد.

دیکشنری‌ها می‌توانند مقدار زیادی از حافظه را مصرف کنند، به‌خصوص اگر نمونه‌های بسیاری از اشیاء با تعداد زیادی ویژگی داشته باشیم. اگر عملکرد و کارایی حافظه کد حیاتی باشد، می توانیم راحتی دیکشنری ها را با __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'}

این را می توان به صورت تصویری نشان داد:

شکل 1: رفتار یک شیء کلاس معمولی

حال، بیایید ببینیم چگونه می توانیم این کلاس را با استفاده از اسلات ها پیاده سازی کنیم:

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__ صفت. در پشت صحنه، به جای ذخیره متغیرهای نمونه در یک فرهنگ لغت، مقادیر با مکان های فهرست مانند شکل زیر نگاشت می شوند:

پیشنهاد می‌کنیم بخوانید:  Remote Branch در Git چیست؟ نحوه بررسی شاخه های راه دور از GitHub

شکل 2: رفتار یک شیء کلاس با

در حالی که وجود ندارد __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__ با استفاده از دیکشنری ها در اندازه و سرعت برتری می دهد. در حالی که تفاوت سرعت به ویژه قابل توجه نیست، تفاوت اندازه قابل توجه است.

پیشنهاد می‌کنیم بخوانید:  Enums در جاوا اسکریپت

Gotchas با استفاده از اسلات

قبل از استفاده از اسلات ها در تمام کلاس های خود، چند نکته وجود دارد که باید به آنها توجه کنید:

  1. فقط می تواند ویژگی های تعریف شده در را ذخیره کند __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__ متغیر.

  1. طبقات فرعی از این پیروی نمی کنند __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

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید