آیا تا به حال فکر کرده اید که چگونه پایتون باعث می شود اشیاء با اپراتورها مانند کار کنند + یا -؟ یا چگونه می داند چگونه می توانید اشیاء را در هنگام شما نمایش دهید print آنها؟ پاسخ در روشهای جادویی پایتون ، که به عنوان Dunder نیز شناخته می شود نهفته است (Dاوج زیر) روشها.

روشهای جادویی روشهای ویژه ای هستند که به شما امکان می دهند روش رفتار اشیاء خود را در پاسخ به عملیات های مختلف و توابع داخلی تعریف کنید. آنها چیزی هستند که باعث می شود برنامه نویسی شی گرا پایتون بسیار قدرتمند و شهودی باشد.

در این راهنما ، یاد می گیرید که چگونه از روشهای جادویی برای ایجاد کد ظریف تر و قدرتمندتر استفاده کنید. مثالهای عملی را مشاهده خواهید کرد که نشان می دهد چگونه این روش ها در سناریوهای دنیای واقعی کار می کنند.

پیش نیازهای

  • درک اساسی از نحو پایتون و مفاهیم برنامه نویسی شی گرا.

  • آشنایی با کلاس ها ، اشیاء و وراثت.

  • دانش انواع داده های داخلی پایتون (لیست ها ، فرهنگ لغت ها و غیره روی).

  • نصب پایتون 3 کار برای مشارکت فعال با نمونه های اینجا توصیه می شود.

فهرست مطالب

  1. روشهای جادویی چیست؟

  2. نمایندگی شیء

    • str در مقابل مجدداً

    • مثال عملی: کلاس خطای سفارشی

  3. اضافه بار اپراتور

    • اپراتورهای حسابی

    • اپراتورهای مقایسه

    • مثال عملی: کلاس پول

  4. Container روش

    • پروتکل توالی

    • پروتکل نقشه برداری

    • مثال عملی: حافظه پنهان سفارشی

  5. دسترسی ویژگی

    • باکتری وت GetAttribute

    • متصدی وت دلهره

    • مثال عملی: خصوصیات اتصال خودکار

  6. مدیران متن

    • وارد کردن وت خروج

    • مثال عملی: مدیر اتصال پایگاه داده

  7. اشیاء قابل تماس

    • فراخوانی

    • مثال عملی: دکوراتور یادآوری

  8. روشهای جادویی پیشرفته

    • جدید برای ایجاد شیء

    • اسلات برای بهینه سازی حافظه

    • مفقود برای مقادیر فرهنگ لغت پیش فرض

  9. ملاحظات عملکرد

  10. بهترین روشها

  11. پیچیدن

  12. منابع

روشهای جادویی چیست؟

روشهای جادویی در پایتون روشهای ویژه ای هستند که با زیربناهای مضاعف شروع و پایان می یابند (__). وقتی از عملیات یا کارکردهای خاصی استفاده می کنید روی اشیاء شما ، پایتون به طور خودکار این روش ها را فراخوانی می کند.

به عنوان مثال ، هنگام استفاده از + عملگر روی دو شی ، پایتون به دنبال __add__ روش در عمل سمت چپ. اگر آن را پیدا کند ، آن روش را با عمل صحیح به عنوان یک آرگومان می نامد.

در اینجا یک مثال ساده وجود دارد که نشان می دهد چگونه این کار می کند:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2  # This calls p1.__add__(p2)
print(p3.x, p3.y)  # Output: 4 6

بیایید آنچه را که در اینجا اتفاق می افتد تجزیه کنیم:

  1. ما ایجاد می کنیم Point کلاس که نشان دهنده یک نقطه در فضای 2D است

  2. در __init__ روش مختصات x و y را آغاز می کند

  3. در __add__ روش تعریف می کند که وقتی دو امتیاز اضافه می کنیم چه اتفاقی می افتد

  4. وقتی می نویسیم p1 + p2، پایتون به طور خودکار تماس می گیرد p1.__add__(p2)

  5. نتیجه جدید است Point با مختصات (4 ، 6)

این فقط آغاز است. پایتون روشهای جادویی زیادی دارد که به شما امکان می دهد روش رفتار اشیاء خود را در موقعیت های مختلف تنظیم کنید. بیایید برخی از مفیدترین آنها را کشف کنیم.

نمایندگی شیء

هنگامی که با اشیاء در پایتون کار می کنید ، اغلب باید آنها را به رشته ها تبدیل کنید. این اتفاق می افتد وقتی شما print یک شیء یا سعی کنید آن را در تعاملی نمایش دهید consoleبشر پایتون برای این منظور دو روش جادویی ارائه می دهد: __str__ وت __repr__بشر

str vs rep

در __str__ وت __repr__ روشها اهداف مختلفی را ارائه می دهند:

  • __str__: توسط str() عملکرد و توسط print() عملکرد. این باید رشته ای را که برای کاربران نهایی قابل خواندن است ، برگرداند.

  • __repr__: توسط repr() عملکرد و در تعاملی استفاده می شود consoleبشر این باید رشته ای را برگرداند که در حالت ایده آل می تواند برای بازآفرینی شی استفاده شود.

در اینجا مثالی آورده شده است که تفاوت را نشان می دهد:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __str__(self):
        return f"{self.celsius}°C"

    def __repr__(self):
        return f"Temperature({self.celsius})"

temp = Temperature(25)
print(str(temp))      # Output: 25°C
print(repr(temp))     # Output: Temperature(25)

در این مثال:

  • __str__ یک رشته کاربر پسند را نشان می دهد که دما را با یک نماد درجه نشان می دهد

  • __repr__ رشته ای را نشان می دهد که روش ایجاد شی را نشان می دهد ، که برای اشکال زدایی مفید است

وقتی از این اشیاء در زمینه های مختلف استفاده می کنید ، تفاوت مشخص می شود:

  • وقتی تو print دما ، نسخه کاربر پسند را مشاهده می کنید: 25°C

  • وقتی شی را در پایتون بازرسی می کنید console، نسخه مفصل را مشاهده می کنید: Temperature(25)

مثال عملی: کلاس خطای سفارشی

بیایید یک کلاس خطای سفارشی ایجاد کنیم که اطلاعات اشکال زدایی بهتری را ارائه دهد. این مثال نشان می دهد که چگونه می توانید استفاده کنید __str__ وت __repr__ تا پیام های خطای خود را مفیدتر کنید:

class ValidationError(Exception):
    def __init__(self, field, message, value=None):
        self.field = field
        self.message = message
        self.value = value
        super().__init__(self.message)

    def __str__(self):
        if self.value is not None:
            return f"Error in field '{self.field}': {self.message} (got: {repr(self.value)})"
        return f"Error in field '{self.field}': {self.message}"

    def __repr__(self):
        if self.value is not None:
            return f"ValidationError(field='{self.field}', message="{self.message}", value={repr(self.value)})"
        return f"ValidationError(field='{self.field}', message="{self.message}")"

# Usage
try:
    age = -5
    if age < 0:
        raise ValidationError("age", "Age must be positive", age)
except ValidationError as e:
    print(e)  # Output: Error in field 'age': Age must be positive (got: -5)

این کلاس خطای سفارشی مزایای مختلفی را ارائه می دهد:

  1. این شامل نام فیلد است که در آن خطا رخ داده است

  2. این مقدار واقعی را که باعث خطا شده است نشان می دهد

  3. این هم پیام های خطای کاربر پسند و هم مفصل را ارائه می دهد

  4. با درج تمام اطلاعات مربوطه ، اشکال زدایی را آسانتر می کند

اضافه بار اپراتور

اضافه بار اپراتور یکی از قدرتمندترین ویژگی های روشهای جادویی پایتون است. این به شما امکان می دهد تا هنگام استفاده از اپراتورها ، اشیاء خود را چگونه رفتار کنید +با -با *وت ==بشر این باعث می شود کد شما بصری و خواندنی تر شود.

اپراتورهای حسابی

پایتون روشهای جادویی را برای کلیه عملیات حسابی اساسی فراهم می کند. در اینجا یک جدول نشان داده شده است که کدام روش با کدام اپراتور مطابقت دارد:

عملگر روش جادویی شرح
+ __add__ افزودن
- __sub__ تفریق
* __mul__ ضرب
/ __truediv__ بخش
// __floordiv__ تقسیم کف
% __mod__ مدولو
** __pow__ مفهوم

اپراتورهای مقایسه

به همین ترتیب ، می توانید روش مقایسه اشیاء خود را با استفاده از این روشهای جادویی تعریف کنید:

عملگر روش جادویی شرح
== __eq__ برابر
!= __ne__ برابر نیست
< __lt__ کمتر از
> __gt__ بزرگتر از
<= __le__ کمتر از یا برابر با
>= __ge__ بزرگتر از یا برابر با

مثال عملی: کلاس پول

بیایید یک ایجاد کنیم Money کلاس که عملیات ارزی را به درستی انجام می دهد. این مثال روش اجرای چندین اپراتور و رسیدگی به موارد لبه را نشان می دهد:

from functools import total_ordering
from decimal import Decimal

@total_ordering  # Implements all comparison methods based روی __eq__ and __lt__
class Money:
    def __init__(self, amount, currency="USD"):
        self.amount = Decimal(str(amount))
        self.currency = currency

    def __add__(self, other):
        if not isinstance(other, Money):
            return NotImplemented
        if self.currency != other.currency:
            raise ValueError(f"Cannot add different currencies: {self.currency} and {other.currency}")
        return Money(self.amount + other.amount, self.currency)

    def __sub__(self, other):
        if not isinstance(other, Money):
            return NotImplemented
        if self.currency != other.currency:
            raise ValueError(f"Cannot subtract different currencies: {self.currency} and {other.currency}")
        return Money(self.amount - other.amount, self.currency)

    def __mul__(self, other):
        if isinstance(other, (int, float, Decimal)):
            return Money(self.amount * Decimal(str(other)), self.currency)
        return NotImplemented

    def __truediv__(self, other):
        if isinstance(other, (int, float, Decimal)):
            return Money(self.amount / Decimal(str(other)), self.currency)
        return NotImplemented

    def __eq__(self, other):
        if not isinstance(other, Money):
            return NotImplemented
        return self.currency == other.currency and self.amount == other.amount

    def __lt__(self, other):
        if not isinstance(other, Money):
            return NotImplemented
        if self.currency != other.currency:
            raise ValueError(f"Cannot compare different currencies: {self.currency} and {other.currency}")
        return self.amount < other.amount

    def __str__(self):
        return f"{self.currency} {self.amount:.2f}"

    def __repr__(self):
        return f"Money({repr(float(self.amount))}, {repr(self.currency)})"

بیایید ویژگی های اصلی این را تجزیه کنیم Money کلاس:

  1. دست زدن به: ما استفاده می کنیم Decimal به جای float برای جلوگیری از مشکلات دقیق شناور با محاسبات پول.

  2. ایمنی ارز: کلاس برای جلوگیری از خطا از عملکرد بین ارزهای مختلف جلوگیری می کند.

  3. نوع بررسی: هر روش بررسی می کند که آیا عمل دیگر از نوع صحیح استفاده شده است isinstance()بشر

  4. نشانگر: وقتی یک عمل معنی ندارد ، ما برمی گردیم NotImplemented برای اجازه دادن به پایتون عمل معکوس را امتحان کنید.

  5. total_ordering: این دکوراتور به طور خودکار تمام روشهای مقایسه را مبتنی بر پیاده سازی می کند روی __eq__ وت __lt__بشر

در اینجا روش استفاده از Money کلاس:

# Basic arithmetic
wallet = Money(100, "USD")
expense = Money(20, "USD")
remaining = wallet - expense
print(remaining)  # Output: USD 80.00

# Working with different currencies
salary = Money(5000, "USD")
bonus = Money(1000, "USD")
total = salary + bonus
print(total)  # Output: USD 6000.00

# Division by scalar
weekly_pay = salary / 4
print(weekly_pay)  # Output: USD 1250.00

# Comparisons
print(Money(100, "USD") > Money(50, "USD"))  # Output: True
print(Money(100, "USD") == Money(100, "USD"))  # Output: True

# Error handling
try:
    Money(100, "USD") + Money(100, "EUR")
except ValueError as e:
    print(e)  # Output: Cannot add different currencies: USD and EUR

این Money کلاس چندین مفهوم مهم را نشان می دهد:

  1. روش رسیدگی به انواع مختلف عمل

  2. روش اجرای صحیح خطای مناسب

  3. روش استفاده از @total_ordering تزیین کننده

  4. روش حفظ دقت در محاسبات مالی

  5. روش ارائه هر دو روش رشته و نمایش

Container روش

Container روشها به شما امکان می دهد اشیاء خود را مانند ظروف داخلی مانند لیست ، فرهنگ لغت یا مجموعه رفتار کنید. این امر به ویژه در صورت نیاز به رفتار سفارشی برای ذخیره و بازیابی داده ها مفید است.

پروتکل توالی

برای اینکه شیء خود مانند یک دنباله رفتار کند (مانند لیست یا tuple) ، باید این روش ها را پیاده سازی کنید:

روش شرح استفاده از مثال
__len__ طول طول container len(obj)
__getitem__ اجازه فهرست بندی با obj[key] obj[0]
__setitem__ به تکلیف اجازه می دهد obj[key] = value obj[0] = 42
__delitem__ اجازه حذف با del obj[key] del obj[0]
__iter__ یک تکرار کننده را برای container for item in obj:
__contains__ پیاده سازی in عملگر 42 in obj

پروتکل نقشه برداری

برای رفتار شبیه به فرهنگ لغت ، شما می خواهید این روش ها را پیاده سازی کنید:

روش شرح استفاده از مثال
__getitem__ مقدار را با کلید دریافت کنید obj["key"]
__setitem__ مقدار را بر اساس کلید تنظیم کنید obj["key"] = value
__delitem__ جفت ارزش کلید را حذف کنید del obj["key"]
__len__ تعداد جفت های ارزش کلید را دریافت کنید len(obj)
__iter__ بیش از کلیدها for key in obj:
__contains__ بررسی کنید که آیا کلید وجود دارد "key" in obj

مثال عملی: حافظه پنهان سفارشی

بیایید یک حافظه پنهان مبتنی بر زمان را اجرا کنیم که به طور خودکار ورودی های قدیمی را منقضی می کند. این مثال روش ایجاد یک عرف را نشان می دهد container این مانند یک فرهنگ لغت اما با عملکرد اضافی رفتار می کند:

import time
from collections import OrderedDict

class ExpiringCache:
    def __init__(self, max_age_seconds=60):
        self.max_age = max_age_seconds
        self._cache = OrderedDict()  # {key: (value, timestamp)}

    def __getitem__(self, key):
        if key not in self._cache:
            raise KeyError(key)

        value, timestamp = self._cache[key]
        if time.time() - timestamp > self.max_age:
            del self._cache[key]
            raise KeyError(f"Key '{key}' has expired")

        return value

    def __setitem__(self, key, value):
        self._cache[key] = (value, time.time())
        self._cache.move_to_end(key)  # Move to end to maintain insertion order

    def __delitem__(self, key):
        del self._cache[key]

    def __len__(self):
        self._clean_expired()  # Clean expired items before reporting length
        return len(self._cache)

    def __iter__(self):
        self._clean_expired()  # Clean expired items before iteration
        for key in self._cache:
            yield key

    def __contains__(self, key):
        if key not in self._cache:
            return False

        _, timestamp = self._cache[key]
        if time.time() - timestamp > self.max_age:
            del self._cache[key]
            return False

        return True

    def _clean_expired(self):
        """Remove all expired entries from the cache."""
        now = time.time()
        expired_keys = [
            key for key, (_, timestamp) in self._cache.items()
            if now - timestamp > self.max_age
        ]
        for key in expired_keys:
            del self._cache[key]

بیایید روش عملکرد این حافظه پنهان را تجزیه کنیم:

  1. انباره: حافظه نهان از آن استفاده می کند OrderedDict برای ذخیره جفت های ارزش کلیدی به همراه زمان بندی.

  2. انقضاء: هر مقدار به عنوان یک تاپل ذخیره می شود (value, timestamp)بشر هنگام دسترسی به یک مقدار ، بررسی می کنیم که آیا منقضی شده است یا خیر.

  3. Container روش: کلاس تمام روشهای لازم را برای رفتار مانند فرهنگ لغت پیاده سازی می کند:

    • __getitem__: مقادیر را بازیابی می کند و انقضا را بررسی می کند

    • __setitem__: مقادیر را با Timestamp فعلی ذخیره می کند

    • __delitem__: ورودی ها را حذف می کند

    • __len__: تعداد ورودی های غیر گسترده را برمی گرداند

    • __iter__: تکرار بیش از کلیدهای غیر گسترده

    • __contains__: در صورت وجود یک کلید ، بررسی می کند

پیشنهاد می‌کنیم بخوانید:  اتریوم چگونه کار می کند؟

در اینجا روش استفاده از حافظه نهان آورده شده است:

# Create a cache with 2-second expiration
cache = ExpiringCache(max_age_seconds=2)

# Store some values
cache["name"] = "Vivek"
cache["age"] = 30

# Access values
print("name" in cache)  # Output: True
print(cache["name"])    # Output: Vivek
print(len(cache))       # Output: 2

# Wait for expiration
print("Waiting for expiration...")
time.sleep(3)

# Check expired values
print("name" in cache)  # Output: False
try:
    print(cache["name"])
except KeyError as e:
    print(f"KeyError: {e}")  # Output: KeyError: 'name'

print(len(cache))  # Output: 0

این اجرای حافظه نهان مزایای مختلفی را ارائه می دهد:

  1. انقضا خودکار ورودی های قدیمی

  2. رابط مانند فرهنگ لغت برای استفاده آسان

  3. راندمان حافظه با از بین بردن ورودی های منقضی شده

  4. عملیات ایمن موضوع (با فرض دسترسی تک رشته ای)

  5. ترتیب درج ورودی ها را حفظ می کند

دسترسی ویژگی

روشهای دسترسی به ویژگی ها به شما امکان می دهد روش کنترل ، تنظیم و حذف ویژگی ها را کنترل کنید. این امر به ویژه برای اجرای خصوصیات ، اعتبار سنجی و ورود به سیستم مفید است.

getAttr و getAttribute

پایتون دو روش برای کنترل دسترسی به ویژگی ها فراهم می کند:

  1. __getattr__: فقط هنگامی که یک جستجوی ویژگی از بین می رود ، نامیده می شود (یعنی وقتی ویژگی وجود ندارد)

  2. __getattribute__: برای دسترسی به هر ویژگی ، حتی برای ویژگی هایی که وجود دارد ، خواسته شده است

تفاوت اصلی این است که __getattribute__ در حالی که برای دسترسی به تمام ویژگی ها فراخوانی می شود __getattr__ فقط زمانی خوانده می شود که این ویژگی از طریق روش عادی پیدا نشود.

در اینجا یک مثال ساده وجود دارد که تفاوت را نشان می دهد:

class AttributeDemo:
    def __init__(self):
        self.name = "Vivek"

    def __getattr__(self, name):
        print(f"__getattr__ called for {name}")
        return f"Default value for {name}"

    def __getattribute__(self, name):
        print(f"__getattribute__ called for {name}")
        return super().__getattribute__(name)

demo = AttributeDemo()
print(demo.name)      # Output: __getattribute__ called for name
                      #        Vivek
print(demo.age)       # Output: __getattribute__ called for age
                      #        __getattr__ called for age
                      #        Default value for age

SetATTR و Delattr

به همین ترتیب ، می توانید روش تنظیم و حذف ویژگی ها را کنترل کنید:

  1. __setattr__: هنگامی که یک ویژگی تنظیم شده است

  2. __delattr__: هنگامی که یک ویژگی حذف می شود

این روش ها به شما امکان می دهد اعتبار ، ورود به سیستم یا رفتار سفارشی را هنگام اصلاح ویژگی ها اجرا کنید.

مثال عملی: خصوصیات اتصال خودکار

بیایید یک کلاس ایجاد کنیم که به طور خودکار تمام تغییرات خاصیت را ثبت کند. این برای اشکال زدایی ، حسابرسی یا ردیابی تغییرات وضعیت شیء مفید است:

import logging

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

class LoggedObject:
    def __init__(self, **kwargs):
        self._data = {}
        # Initialize attributes without triggering __setattr__
        for key, value in kwargs.items():
            self._data[key] = value

    def __getattr__(self, name):
        if name in self._data:
            logging.debug(f"Accessing attribute {name}: {self._data[name]}")
            return self._data[name]
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

    def __setattr__(self, name, value):
        if name == "_data":
            # Allow setting the _data attribute directly
            super().__setattr__(name, value)
        else:
            old_value = self._data.get(name, "<undefined>")
            self._data[name] = value
            logging.info(f"Changed {name}: {old_value} -> {value}")

    def __delattr__(self, name):
        if name in self._data:
            old_value = self._data[name]
            del self._data[name]
            logging.info(f"Deleted {name} (was: {old_value})")
        else:
            raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

بیایید روش عملکرد این کلاس را تجزیه کنیم:

  1. انباره: کلاس از یک خصوصی استفاده می کند _data فرهنگ لغت برای ذخیره مقادیر ویژگی.

  2. دسترسی ویژگی:

    • __getattr__: مقادیر را از _data و پیام های اشکال زدایی را ثبت کنید

    • __setattr__: مقادیر را در _data و تغییرات را تغییر می دهد

    • __delattr__: مقادیر را از بین می برد _data و حذف گزارش ها

  3. دست زدن به: _data خود ویژگی برای جلوگیری از بازگشت بی نهایت به طور متفاوتی اداره می شود.

در اینجا روش استفاده از کلاس آورده شده است:

# Create a logged object with initial values
user = LoggedObject(name="Vivek", email="hello@wewake.dev")

# Modify attributes
user.name = "Vivek"  # Logs: Changed name: Vivek -> Vivek
user.age = 30         # Logs: Changed age: <undefined> -> 30

# Access attributes
print(user.name)      # Output: Vivek

# Delete attributes
del user.email        # Logs: Deleted email (was: hello@wewake.dev)

# Try to access deleted attribute
try:
    print(user.email)
except AttributeError as e:
    print(f"AttributeError: {e}")  # Output: AttributeError: 'LoggedObject' object has no attribute 'email'

این اجرای مزایای مختلفی را ارائه می دهد:

  1. ورود خودکار همه تغییرات ویژگی

  2. ورود به سطح اشکال برای دسترسی به ویژگی

  3. پیام های خطا را برای ویژگی های گمشده پاک کنید

  4. ردیابی آسان تغییرات حالت شی

  5. برای اشکال زدایی و حسابرسی مفید است

مدیران متن

مدیران زمینه یک ویژگی قدرتمند در پایتون هستند که به شما در مدیریت صحیح منابع کمک می کند. آنها اطمینان می دهند که منابع به درستی به دست آمده و آزاد شده اند ، حتی اگر خطایی رخ دهد. در with بیانیه متداول ترین روش برای استفاده از مدیران زمینه است.

وارد و خروج شوید

برای ایجاد یک مدیر زمینه ، شما باید دو روش جادویی را پیاده سازی کنید:

  1. __enter__: هنگام ورود به with بلوک این باید منبع را برای مدیریت برگرداند.

  2. __exit__: هنگام خروج از with بلوک ، حتی اگر یک استثنا رخ دهد. باید پاکسازی را کنترل کند.

در __exit__ روش سه استدلال دریافت می کند:

  • exc_type: نوع استثنا (در صورت وجود)

  • exc_val: نمونه استثنا (در صورت وجود)

  • exc_tb: Traceback (در صورت وجود)

مثال عملی: مدیر اتصال پایگاه داده

بیایید یک مدیر زمینه برای اتصالات پایگاه داده ایجاد کنیم. این مثال روش مدیریت صحیح منابع پایگاه داده و رسیدگی به معاملات را نشان می دهد:

import sqlite3
import logging

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

class DatabaseConnection:
    def __init__(self, db_path):
        self.db_path = db_path
        self.connection = None
        self.cursor = None

    def __enter__(self):
        logging.info(f"Connecting to database: {self.db_path}")
        self.connection = sqlite3.connect(self.db_path)
        self.cursor = self.connection.cursor()
        return self.cursor

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            logging.error(f"An error occurred: {exc_val}")
            self.connection.rollback()
            logging.info("Transaction rolled back")
        else:
            self.connection.commit()
            logging.info("Transaction committed")

        if self.cursor:
            self.cursor.close()
        if self.connection:
            self.connection.close()

        logging.info("Database connection closed")

        # Return False to propagate exceptions, True to suppress them
        return False

بیایید روش عملکرد این مدیر متن را تجزیه کنیم:

  1. شروع:

    • کلاس یک مسیر پایگاه داده را طی می کند

    • اتصال و مکان نما را به عنوان هیچ یک از آنها آغاز می کند

  2. روش وارد کردن:

    • اتصال پایگاه داده ایجاد می کند

    • یک مکان نما ایجاد می کند

    • مکان نما را برای استفاده در with محاصره کردن

  3. روش خروج:

    • مدیریت معامله (تعهد/بازگشت)

    • مکان نما و اتصال را می بندد

    • همه عملیات را ثبت کنید

    • به استثنائات دروغ می گوید

در اینجا روش استفاده از مدیر زمینه آورده شده است:

# Create a test database in memory
try:
    # Successful transaction
    with DatabaseConnection(":memory:") as cursor:
        # Create a table
        cursor.execute("""
            CREATE TABLE users (
                id INTEGER PRIMARY KEY,
                name TEXT,
                email TEXT
            )
        """)

        # Insert data
        cursor.execute(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            ("Vivek", "hello@wewake.dev")
        )

        # Query data
        cursor.execute("SELECT * FROM users")
        print(cursor.fetchall())  # Output: [(1, 'Vivek', 'hello@wewake.dev')]

    # Demonstrate transaction rollback روی error
    with DatabaseConnection(":memory:") as cursor:
        cursor.execute("""
            CREATE TABLE users (
                id INTEGER PRIMARY KEY,
                name TEXT,
                email TEXT
            )
        """)
        cursor.execute(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            ("Wewake", "contact@wewake.dev")
        )
        # This will cause an error - table 'nonexistent' doesn't exist
        cursor.execute("SELECT * FROM nonexistent")
except sqlite3.OperationalError as e:
    print(f"Caught exception: {e}")

این مدیر زمینه مزایای مختلفی را ارائه می دهد:

  1. منابع به طور خودکار مدیریت می شوند (مثال: اتصالات همیشه بسته هستند).

  2. با ایمنی معامله ، تغییرات متعهد شده یا به درستی انجام می شود.

  3. استثنائات با لطف گرفتار و رسیدگی می شوند

  4. همه عملیات برای اشکال زدایی وارد شده اند

  5. در with بیانیه کد را روشن و مختصر می کند

اشیاء قابل تماس

در __call__ روش جادویی به شما امکان می دهد نمونه هایی از کلاس خود مانند توابع رفتار کنید. این برای ایجاد اشیاء که حالت بین تماس ها یا اجرای رفتار عملکردی مانند با ویژگی های اضافی را حفظ می کنند ، مفید است.

فراخوانی

در __call__ وقتی سعی می کنید نمونه ای از کلاس خود را فراخوانی کنید ، به عنوان یک عملکرد ، روش فراخوانی می شود. در اینجا یک مثال ساده وجود دارد:

class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return x * self.factor

# Create instances that behave like functions
double = Multiplier(2)
triple = Multiplier(3)

print(double(5))  # Output: 10
print(triple(5))  # Output: 15

این مثال نشان می دهد که چگونه __call__ به شما امکان می دهد در حالی که مانند توابع قابل تماس هستند ، اشیاء ایجاد کنید که حالت (عامل) را حفظ کنند.

مثال عملی: دکوراتور یادآوری

بیایید با استفاده از یک دکوراتور Memoization پیاده سازی کنیم __call__بشر این دکوراتور برای جلوگیری از محاسبات اضافی ، نتایج عملکرد حافظه پنهان را خواهد داشت:

import time
import functools

class Memoize:
    def __init__(self, func):
        self.func = func
        self.cache = {}
        # Preserve function metadata (name, docstring, etc.)
        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        # Create a key from the arguments
        # For simplicity, we assume all arguments are hashable
        key = str(args) + str(sorted(kwargs.items()))

        if key not in self.cache:
            self.cache[key] = self.func(*args, **kwargs)

        return self.cache[key]

# Usage
@Memoize
def fibonacci(n):
    """Calculate the nth Fibonacci number recursively."""
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Measure execution time
def time_execution(func, *args, **kwargs):
    start = time.time()
    result = func(*args, **kwargs)
    end = time.time()
    print(f"{func.__name__}({args}, {kwargs}) took {end - start:.6f} seconds")
    return result

# Without memoization, this would be extremely slow
print("Calculating fibonacci(35)...")
result = time_execution(fibonacci, 35)
print(f"Result: {result}")

# Second call is instant due to memoization
print("\nCalculating fibonacci(35) again...")
result = time_execution(fibonacci, 35)
print(f"Result: {result}")

بیایید روش عملکرد این دکوراتور Memoization را تجزیه کنیم:

  1. شروع:

    • به عنوان یک آرگومان عملکردی می گیرد

    • یک فرهنگ لغت حافظه نهان برای ذخیره نتایج ایجاد می کند

    • ابرداده عملکرد را با استفاده از آن حفظ می کند functools.update_wrapper

  2. روش فراخوانی:

    • یک کلید منحصر به فرد از آرگومان های عملکرد ایجاد می کند

    • اگر نتیجه در حافظه پنهان باشد ، بررسی می کند

    • اگر اینطور نیست ، نتیجه را محاسبه می کند و آن را ذخیره می کند

    • نتیجه ذخیره شده را برمی گرداند

  3. استفاده:

    • به عنوان دکوراتور برای هر عملکردی اعمال می شود

    • به طور خودکار نتایج تماس های مکرر را ذخیره می کند

    • عملکرد ابرداده و رفتار را حفظ می کند

مزایای این اجرای شامل موارد زیر است:

  1. عملکرد بهتر ، زیرا از محاسبات اضافی جلوگیری می کند

  2. بهتر ، شفافیت ، زیرا بدون تغییر عملکرد اصلی کار می کند

  3. انعطاف پذیر است و با هر عملکرد قابل استفاده است

  4. این حافظه کارآمد است و برای استفاده مجدد نتایج ذخیره می شود

  5. این مستندات عملکرد را حفظ می کند

روشهای جادویی پیشرفته

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

جدید برای ایجاد شیء

در __new__ روش قبلاً خوانده می شود __init__ و مسئول ایجاد و بازگرداندن نمونه جدیدی از کلاس است. این برای اجرای الگوهای مانند مجرد یا اشیاء تغییر ناپذیر مفید است.

در اینجا نمونه ای از الگوی تک آهنگ استفاده شده است __new__:

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, name=None):
        # This will be called every time Singleton() is called
        if name is not None:
            self.name = name

# Usage
s1 = Singleton("Vivek")
s2 = Singleton("Wewake")
print(s1 is s2)  # Output: True
print(s1.name)   # Output: Wewake (the second initialization overwrote the first)

بیایید روش عملکرد این تک آهنگ را تجزیه کنیم:

  1. متغیر کلاس: _instance نمونه ای از کلاس را ذخیره می کند

  2. جدید روش:

    • اگر نمونه ای وجود داشته باشد بررسی می کند

    • اگر این کار را ایجاد کند یکی ایجاد می کند

    • اگر این کار را انجام دهد ، نمونه موجود را برمی گرداند

  3. شروع کردن روش:

    • هر بار که از سازنده استفاده می شود فراخوانی می شود

    • ویژگی های نمونه را به روز می کند

پیشنهاد می‌کنیم بخوانید:  روش مدیریت خطاهای کلیدی در پایتون – با مثال کد

اسلات برای بهینه سازی حافظه

در __slots__ متغیر کلاس محدود می کند که به عنوان نمونه می تواند ، صرفه جویی در حافظه. این امر به ویژه هنگامی مفید است که شما نمونه های زیادی از یک کلاس با یک مجموعه ثابت از ویژگی ها داشته باشید.

در اینجا مقایسه کلاسهای منظم و شکاف وجود دارد:

import sys

class RegularPerson:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

class SlottedPerson:
    __slots__ = ['name', 'age', 'email']

    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

# Compare memory usage
regular_people = [RegularPerson("Vivek" + str(i), 30, "hello@wewake.dev") for i in range(1000)]
slotted_people = [SlottedPerson("Vivek" + str(i), 30, "hello@wewake.dev") for i in range(1000)]

print(f"Regular person size: {sys.getsizeof(regular_people[0])} bytes")  # Output: Regular person size: 48 bytes
print(f"Slotted person size: {sys.getsizeof(slotted_people[0])} bytes")  # Output: Slotted person size: 56 bytes
print(f"Memory saved per instance: {sys.getsizeof(regular_people[0]) - sys.getsizeof(slotted_people[0])} bytes")  # Output: Memory saved per instance: -8 bytes
print(f"Total memory saved for 1000 instances: {(sys.getsizeof(regular_people[0]) - sys.getsizeof(slotted_people[0])) * 1000 / 1024:.2f} KB")  # Output: Total memory saved for 1000 instances: -7.81 KB

اجرای این کد نتیجه جالبی را به وجود می آورد:

Regular person size: 48 bytes
Slotted person size: 56 bytes
Memory saved per instance: -8 bytes
Total memory saved for 1000 instances: -7.81 KB

با کمال تعجب ، در این مثال ساده ، نمونه شکاف در واقع 8 بایت بزرگتر از نمونه معمولی است! به نظر می رسد این با توصیه های مشترک در مورد آن مغایرت دارد __slots__ ذخیره حافظه.

پس چه می گذرد روی اینجا؟ صرفه جویی در حافظه واقعی از __slots__ بیا از:

  1. از بین بردن فرهنگ لغت: اشیاء معمولی پایتون ویژگی های خود را در یک فرهنگ لغت ذخیره می کنند (__dict__) ، که دارای سربار است. در sys.getsizeof() عملکرد اندازه این فرهنگ لغت را به خود اختصاص نمی دهد.

  2. ذخیره سازی ویژگی ها: برای اشیاء کوچک با ویژگی های اندک ، سربار توصیف کننده های شکاف می تواند از پس انداز فرهنگ لغت فراتر رود.

  3. مقیاس پذیری: سود واقعی وقتی ظاهر می شود:

    • شما موارد زیادی دارید (هزاران یا میلیون ها نفر)

    • اشیاء شما ویژگی های زیادی دارند

    • شما به صورت پویا ویژگی ها را اضافه می کنید

بیایید یک مقایسه کامل تر را ببینیم:

# A more accurate memory measurement
import sys

def get_size(obj):
    """Get a better estimate of the object's size in bytes."""
    size = sys.getsizeof(obj)
    if hasattr(obj, '__dict__'):
        size += sys.getsizeof(obj.__dict__)
        # Add the size of the dict contents
        size += sum(sys.getsizeof(v) for v in obj.__dict__.values())
    return size

class RegularPerson:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

class SlottedPerson:
    __slots__ = ['name', 'age', 'email']

    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

regular = RegularPerson("Vivek", 30, "hello@wewake.dev")
slotted = SlottedPerson("Vivek", 30, "hello@wewake.dev")

print(f"Complete Regular person size: {get_size(regular)} bytes")  # Output: Complete Regular person size: 610 bytes
print(f"Complete Slotted person size: {get_size(slotted)} bytes")  # Output: Complete Slotted person size: 56 bytes

با این اندازه گیری دقیق تر ، خواهید دید که اشیاء شکاف دار معمولاً از حافظه کل کمتری استفاده می کنند ، به خصوص که ویژگی های بیشتری را اضافه می کنید.

نکات کلیدی در مورد __slots__:

  1. مزایای حافظه واقعی: صرفه جویی در حافظه اولیه ناشی از حذف نمونه است __dict__

  2. محدودیت های پویا: شما نمی توانید ویژگی های دلخواه را به اشیاء شکاف دار اضافه کنید

  3. ملاحظات وراثت: با استفاده از __slots__ با وراثت نیاز به برنامه ریزی دقیق دارد

  4. موارد استفاده: بهترین برای کلاسها با بسیاری از موارد و ویژگی های ثابت

  5. جایزه عملکرد: همچنین می تواند در برخی موارد دسترسی به ویژگی های سریعتر را فراهم کند

برای مقادیر فرهنگ لغت پیش فرض وجود ندارد

در __missing__ وقتی یک کلید پیدا نشود ، روش توسط زیر کلاسهای فرهنگ لغت خوانده می شود. این برای اجرای فرهنگ لغت با مقادیر پیش فرض یا ایجاد کلید خودکار مفید است.

در اینجا نمونه ای از فرهنگ لغت است که به طور خودکار لیست های خالی را برای کلیدهای گمشده ایجاد می کند:

class AutoKeyDict(dict):
    def __missing__(self, key):
        self[key] = []
        return self[key]

# Usage
groups = AutoKeyDict()
groups["team1"].append("Vivek")
groups["team1"].append("Wewake")
groups["team2"].append("Vibha")

print(groups)  # Output: {'team1': ['Vivek', 'Wewake'], 'team2': ['Vibha']}

این اجرای مزایای مختلفی را ارائه می دهد:

  1. نیازی به بررسی وجود یک کلید وجود ندارد ، که راحت تر است.

  2. اولیه سازی خودکار مقادیر پیش فرض را در صورت لزوم ایجاد می کند.

  3. دیگ بخار را برای اولیه سازی فرهنگ لغت کاهش می دهد.

  4. این انعطاف پذیر تر است و می تواند هر منطق ارزش پیش فرض را پیاده سازی کند.

  5. فقط در صورت لزوم مقادیر ایجاد می کند و باعث می شود حافظه بیشتر باشد.

ملاحظات عملکرد

در حالی که روش های جادویی قدرتمند هستند ، اگر شما از آنها با دقت استفاده نکنید ، می توانند بر عملکرد تأثیر بگذارند. بیایید برخی از ملاحظات عملکرد مشترک و روش اندازه گیری آنها را بررسی کنیم.

تأثیر روشهای جادویی روی عمل

روشهای مختلف جادویی پیامدهای عملکرد متفاوتی دارند:

روشهای دسترسی به ویژگی:

  • __getattr__با __getattribute__با __setattr__وت __delattr__ غالباً نامیده می شوند

  • عملیات پیچیده در این روشها می تواند کد شما را به میزان قابل توجهی کند کند

Container روش:

  • __getitem__با __setitem__وت __len__ اغلب در حلقه ها خوانده می شوند

  • اجرای ناکارآمد می تواند شما را ایجاد کند container بسیار کندتر از انواع داخلی

اضافه بار اپراتور:

  • اپراتورهای حسابی و مقایسه به طور مکرر استفاده می شوند

  • پیاده سازی های پیچیده می توانند عملیات ساده را به طور غیر منتظره ای آهسته کنند

بیایید تأثیر عملکرد را اندازه گیری کنیم __getattr__ در مقابل دسترسی مستقیم به ویژگی:

import time

class DirectAccess:
    def __init__(self):
        self.value = 42

class GetAttrAccess:
    def __init__(self):
        self._value = 42

    def __getattr__(self, name):
        if name == "value":
            return self._value
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

# Measure performance
direct = DirectAccess()
getattr_obj = GetAttrAccess()

def benchmark(obj, iterations=1000000):
    start = time.time()
    for _ in range(iterations):
        x = obj.value
    end = time.time()
    return end - start

direct_time = benchmark(direct)
getattr_time = benchmark(getattr_obj)

print(f"Direct access: {direct_time:.6f} seconds")
print(f"__getattr__ access: {getattr_time:.6f} seconds")
print(f"__getattr__ is {getattr_time / direct_time:.2f}x slower")

اجرای این معیار تفاوتهای قابل توجهی را نشان می دهد:

Direct access: 0.027714 seconds
__getattr__ access: 0.060646 seconds
__getattr__ is 2.19x slower

همانطور که مشاهده می کنید ، استفاده __getattr__ بیش از دو برابر دسترسی به ویژگی مستقیم است. این ممکن است برای ویژگی های گاه به گاه قابل دسترسی نباشد ، اما می تواند در کد مهم عملکردی که به ویژگی های حلقه های تنگ دسترسی پیدا می کند ، قابل توجه باشد.

استراتژی های بهینه سازی

خوشبختانه روشهای مختلفی وجود دارد که می توانید روشهای جادویی را بهینه کنید.

  1. برای بهره وری حافظه از شکافهایی استفاده کنید: این باعث کاهش مصرف حافظه می شود و سرعت دسترسی به ویژگی را بهبود می بخشد. برای کلاس هایی با موارد بسیاری بهتر است.

  2. مقادیر محاسبه شده حافظه پنهان: می توانید نتایج عملیات گران قیمت را ذخیره کرده و حافظه پنهان را فقط در صورت لزوم به روز کنید. استفاده کردن @property برای ویژگی های محاسبه شده

  3. تماس های روش را به حداقل برسانید: اطمینان حاصل کنید که از تماس های روش جادویی غیر ضروری خودداری می کنید و در صورت امکان از دسترسی مستقیم به ویژگی استفاده می کنید. استفاده را در نظر بگیرید __slots__ برای ویژگی های مکرر.

بهترین روشها

هنگام استفاده از روشهای جادویی ، این بهترین روشها را دنبال کنید تا اطمینان حاصل شود که کد شما قابل حفظ ، کارآمد و قابل اعتماد است.

1. سازگار باشید

هنگام اجرای روشهای جادویی مرتبط ، سازگاری در رفتار را حفظ کنید:

from functools import total_ordering

@total_ordering
class ConsistentNumber:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if not isinstance(other, ConsistentNumber):
            return NotImplemented
        return self.value == other.value

    def __lt__(self, other):
        if not isinstance(other, ConsistentNumber):
            return NotImplemented
        return self.value < other.value

2. بازگرداندن یادداشت

وقتی یک عمل منطقی نیست ، برگردید NotImplemented برای اجازه دادن به پایتون عمل معکوس را امتحان کنید:

class Money:
    def __add__(self, other):
        if not isinstance(other, Money):
            return NotImplemented
        # ... rest of the implementation

3. آن را ساده نگه دارید

روشهای جادویی باید ساده و قابل پیش بینی باشند. از منطق پیچیده ای که می تواند منجر به رفتار غیر منتظره شود ، خودداری کنید:

# Good: Simple and predictable
class SimpleContainer:
    def __init__(self):
        self.items = []

    def __getitem__(self, index):
        return self.items[index]

# Bad: Complex and potentially confusing
class ComplexContainer:
    def __init__(self):
        self.items = []
        self.access_count = 0

    def __getitem__(self, index):
        self.access_count += 1
        if self.access_count > 100:
            raise RuntimeError("Too many accesses")
        return self.items[index]

4. رفتار اسناد

به وضوح مستند می کنید که چگونه روشهای جادویی شما رفتار می کنند ، به خصوص اگر از انتظارات استاندارد منحرف شوند:

class CustomDict(dict):
    def __missing__(self, key):
        """
        Called when a key is not found in the dictionary.
        Creates a new list for the key and returns it.
        This allows for automatic list creation when accessing
        non-existent keys.
        """
        self[key] = []
        return self[key]

5. عملکرد را در نظر بگیرید

از پیامدهای عملکرد آگاه باشید ، به خصوص برای روشهای مکرر:

class OptimizedContainer:
    __slots__ = ['items']  # Use __slots__ for better performance

    def __init__(self):
        self.items = []

    def __getitem__(self, index):
        return self.items[index]  # Direct access is faster

6. موارد لبه دسته

همیشه موارد لبه را در نظر بگیرید و آنها را به طور مناسب اداره کنید:

class SafeContainer:
    def __getitem__(self, key):
        if not isinstance(key, (int, slice)):
            raise TypeError("Index must be integer or slice")
        if key < 0:
            raise ValueError("Index cannot be negative")
        # ... rest of the implementation

پیچیدن

روشهای جادویی پایتون روشی قدرتمند برای ایجاد رفتار کلاسهای شما مانند انواع داخلی فراهم می کند و باعث می شود کد بصری تر و بیانی تری داشته باشند. در طول این راهنما ، ما بررسی کرده ایم که چگونه این روش ها کار می کنند و چگونه می توان از آنها به طور مؤثر استفاده کرد.

غذای اصلی

  1. نمایندگی شیء:

    • استفاده کردن __str__ برای خروجی کاربر پسند

    • استفاده کردن __repr__ برای اشکال زدایی و توسعه

  2. اضافه بار اپراتور:

    • اپراتورهای حساب و مقایسه را پیاده سازی کنید

    • بازگشت NotImplemented برای عملیات پشتیبانی نشده

    • استفاده کردن @total_ordering برای مقایسه مداوم

  3. Container رفتار:

    • پروتکل های توالی و نقشه برداری را پیاده سازی کنید

    • عملکرد را برای عملیات متداول در نظر بگیرید

    • رسیدگی به موارد لبه مناسب

  4. مدیریت منابع:

    • از مدیران زمینه برای رسیدگی به منابع مناسب استفاده کنید

    • اجرا __enter__ وت __exit__ برای پاکسازی

    • استثنائات را در __exit__

  5. بهینه سازی عملکرد:

    • استفاده کردن __slots__ برای راندمان حافظه

    • مقادیر محاسبه شده در صورت لزوم

    • تماس های روش را در کد متداول به حداقل برسانید

چه موقع از روشهای جادویی استفاده کنید

روشهای جادویی در صورت نیاز بیشتر مفید هستند:

  1. ساختارهای داده سفارشی ایجاد کنید

  2. انواع خاص دامنه را پیاده سازی کنید

  3. منابع را به درستی مدیریت کنید

  4. رفتار ویژه ای به کلاسهای خود اضافه کنید

  5. کد خود را بیشتر فیثونی کنید

چه موقع باید از روشهای جادویی جلوگیری کنیم

از روشهای جادویی جلوگیری کنید وقتی:

  1. دسترسی ساده به ویژگی کافی است

  2. رفتار گیج کننده یا غیر منتظره خواهد بود

  3. عملکرد بسیار مهم است و روش های جادویی سربار را اضافه می کنند

  4. اجرای بیش از حد پیچیده خواهد بود

به یاد داشته باشید که با قدرت عالی مسئولیت بزرگی به همراه دارد. با توجه به پیامدهای عملکرد آنها و اصل کمترین تعجب از روشهای جادویی استفاده کنید. در صورت استفاده مناسب ، روشهای جادویی می توانند خوانایی و بیان کد شما را به میزان قابل توجهی افزایش دهند.

منابع و خواندن بیشتر

پیتون رسمی Documentation

  1. مدل داده پایتون – رسمی Documentation – راهنمای جامع برای مدل داده های پایتون و روش های جادویی.

  2. functools.total_ordering – Documentation برای دکوراتور Total_ordering که به طور خودکار روش های مقایسه از دست رفته را پر می کند.

  3. نام روشهای ویژه پایتون – مرجع رسمی برای شناسه های روش ویژه در پایتون.

  4. مجموعه کلاسهای پایه چکیده – در مورد کلاسهای پایه انتزاعی برای ظروف که رابط هایی را که شما تعریف می کنند ، بیاموزید container کلاس ها می توانند پیاده سازی کنند.

منابع جامعه

  1. راهنمای روشهای جادویی پایتون – Rafe Kettler – نمونه های عملی روش های جادویی و موارد استفاده مشترک.

خواندن بیشتر

اگر از این مقاله لذت بردید ، ممکن است این مقالات مربوط به پایتون را پیدا کنید روی وبلاگ شخصی من مفید است:

  1. آزمایش های عملی برای بهینه سازی پرس و جو Django ORM – یاد بگیرید که چگونه می توانید نمایش داده های Django ORM خود را با نمونه ها و آزمایشات عملی بهینه کنید.

  2. هزینه بالای UWSGI همزمان – پیامدهای عملکرد پردازش همزمان در UWSGI و چگونگی تأثیر آن بر برنامه های وب پایتون شما را درک کنید.