~6 دقیقه مطالعه • بروزرسانی ۲۴ اسفند ۱۴۰۴
سیگنالها در Django چیست؟
Django یک «signal dispatcher» داخلی دارد که به بخشهای مختلف برنامه اجازه میدهد بدون وابستگی مستقیم، از وقوع یک رویداد مطلع شوند. سیگنالها زمانی مفید هستند که چندین بخش از کد نیاز داشته باشند از یک اتفاق مشترک باخبر شوند.
مثلاً یک اپلیکیشن ثالث میتواند از تغییر تنظیمات Django مطلع شود:
from django.apps import AppConfig
from django.core.signals import setting_changed
def my_callback(sender, **kwargs):
print("Setting changed!")
class MyAppConfig(AppConfig):
def ready(self):
setting_changed.connect(my_callback)
هشدار: سیگنالها ظاهر «loosely coupled» دارند، اما استفادهٔ زیاد از آنها میتواند کد را پیچیده و سختدیباگ کند. هرجا ممکن باشد، بهتر است کد را مستقیم فراخوانی کنید.
گوش دادن به سیگنالها
برای دریافت یک سیگنال باید یک receiver ثبت کنید.
این کار با Signal.connect() انجام میشود.
پارامترهای connect()
- receiver: تابعی که هنگام ارسال سیگنال فراخوانی میشود
- sender: فقط سیگنالهایی که از یک sender خاص ارسال شدهاند را دریافت میکند
- weak: اگر False نباشد، ممکن است تابع جمعآوری زباله شود
- dispatch_uid: جلوگیری از ثبت تکراری سیگنال
تعریف یک Receiver
def my_callback(sender, **kwargs):
print("Request finished!")
همهٔ receiverها باید sender و **kwargs را بپذیرند. حتی اگر سیگنال فعلاً آرگومانی ارسال نکند، ممکن است در آینده تغییر کند.
Receiverهای async
async def my_callback(sender, **kwargs):
await asyncio.sleep(5)
print("Request finished!")
Django بهطور خودکار async یا sync بودن receiver را مدیریت میکند.
اتصال Receiver به سیگنال
روش ۱: اتصال دستی
from django.core.signals import request_finished
request_finished.connect(my_callback)
روش ۲: استفاده از decorator
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
این روش خواناتر و استانداردتر است.
کد سیگنالها باید کجا قرار بگیرد؟
بهترین محل برای قرار دادن سیگنالها:
- در یک فایل signals.py داخل اپلیکیشن
- اتصال سیگنالها در متد ready() کلاس AppConfig
مثال:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
def ready(self):
from . import signals
این کار باعث میشود decoratorها بهطور ضمنی سیگنالها را ثبت کنند.
اتصال به سیگنالهای یک Sender خاص
گاهی فقط میخواهید سیگنالهایی را دریافت کنید که از یک مدل خاص ارسال شدهاند.
مثلاً سیگنال pre_save قبل از ذخیرهٔ هر مدل ارسال میشود، اما شما فقط مدل MyModel را میخواهید:
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
در این حالت فقط ذخیرهٔ MyModel باعث اجرای handler میشود.
نکات مهم دربارهٔ سیگنالها
- سیگنالها میتوانند کد را پیچیده کنند—با احتیاط استفاده کنید
- receiverها باید همیشه **kwargs را بپذیرند
- برای جلوگیری از ثبت تکراری، از dispatch_uid استفاده کنید
- در تستها ممکن است ready() چندبار اجرا شود
جمعبندی
سیگنالها در Django ابزاری قدرتمند برای اطلاعرسانی رویدادها بین بخشهای مختلف برنامه هستند. با تعریف receiverها، استفاده از decoratorها، مدیریت senderهای خاص و قرار دادن کد در محل مناسب، میتوانید سیستمهای انعطافپذیر و قابلگسترش بسازید. با این حال، همیشه باید مراقب باشید که استفادهٔ بیشازحد از سیگنالها باعث پیچیدگی غیرضروری نشود.
جلوگیری از ثبت تکراری سیگنالها
وقتی dispatch_uid مشخص نشده باشد، Django هویت receiver را بر اساس object identity تشخیص میدهد. برای توابع سطح ماژول، متدهای استاتیک و متدهای کلاس، این هویت ثابت است؛ بنابراین اتصال دوبارهٔ همان receiver هیچ تغییری ایجاد نمیکند:
def my_handler(sender, **kwargs):
...
my_signal.connect(my_handler) # اجرای دوبارهٔ این خط بیاثر است.
اما متدهای وابسته به نمونه (Bound Methods) متفاوتاند
متدهای وابسته به نمونه (self) هویت متفاوتی برای هر instance دارند. بنابراین هر بار که یک instance جدید ساخته شود، متد آن یک receiver جدید محسوب میشود:
def connect_signals():
backend = Backend()
my_signal.connect(backend.my_handler)
connect_signals() # هر بار یک receiver جدید ثبت میشود.
برای جلوگیری از این مشکل، از dispatch_uid استفاده کنید:
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
تعریف سیگنالهای سفارشی
میتوانید سیگنالهای اختصاصی خود را با django.dispatch.Signal تعریف کنید:
import django.dispatch
pizza_done = django.dispatch.Signal()
نکتهٔ مهم: اگر sender و receiver هر دو داخل پروژهٔ شما هستند، معمولاً بهتر است بهجای سیگنال از فراخوانی مستقیم تابع استفاده کنید.
ارسال سیگنالها
Django چهار روش برای ارسال سیگنال دارد:
- send() — همزمان، بدون مدیریت خطا
- send_robust() — همزمان، با مدیریت خطا
- asend() — غیرهمزمان (async)، نیازمند await
- asend_robust() — غیرهمزمان، با مدیریت خطا
مثال ارسال سیگنال سفارشی
class PizzaStore:
def send_pizza(self, toppings, size):
pizza_done.send(
sender=self.__class__,
toppings=toppings,
size=size
)
تمام متدهای ارسال سیگنال یک لیست از زوجها برمیگردانند:
[(receiver, response), ...]
تفاوت send و send_robust
- send() — اگر receiver خطا بدهد، ارسال متوقف میشود
- send_robust() — خطاها را میگیرد و به ارسال ادامه میدهد
خطاهای send_robust در ویژگی __traceback__ ذخیره میشوند.
ارسال سیگنال بهصورت async
asend() و asend_robust() coroutine هستند و باید await شوند:
async def asend_pizza(self, toppings, size):
await pizza_done.asend(
sender=self.__class__,
toppings=toppings,
size=size
)
نحوهٔ مدیریت sync و async توسط Django
- receiverهای async با
asyncio.gather()بهصورت همزمان اجرا میشوند - receiverهای sync هنگام asend با
sync_to_async()فراخوانی میشوند - receiverهای async هنگام send با
async_to_sync()فراخوانی میشوند
Django برای کاهش هزینهٔ تبدیل sync/async، ابتدا receiverها را گروهبندی میکند. به همین دلیل ممکن است ترتیب اجرای receiverها دقیقاً مطابق ترتیب ثبتشان نباشد.
قطع اتصال سیگنالها
برای حذف یک receiver از سیگنال:
Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)
نتیجهٔ فراخوانی:
- True — یک receiver حذف شد
- False — receiver پیدا نشد
- None — اگر sender یک reference تنبل باشد
اگر از dispatch_uid استفاده کرده باشید، نیازی به ارسال تابع receiver نیست.
جمعبندی
سیگنالها در Django ابزار قدرتمندی برای ارتباط رویدادمحور بین بخشهای مختلف برنامه هستند. با استفاده از dispatch_uid میتوانید از ثبت تکراری جلوگیری کنید. با متدهای send()، send_robust()، asend() و asend_robust() میتوانید سیگنالها را در حالتهای sync و async ارسال کنید. در نهایت، با disconnect() کنترل کامل حذف receiverها را در اختیار دارید.
نوشته و پژوهش شده توسط دکتر شاهین صیامی