مشکل ایمپورت های حلقوی در پایتون
در این مقاله، در رابطه با مشکل circualr imports در پایتون و روش حل آن، صحبت خواهد شد.
محتویات این صفحه:
# وابستگی حلقوی چیست؟
circular dependency زمانی اتفاق میفتد که دو یا چند ماژول به یکدیگر وابسته باشند. این یعنی یک هر ماژول، ماژول دیگری را در خود تعریف کرده است.
به عنوان مثال:
functionA():
functionB()
functionB():
functionA()
کد بالا یک مثال کامل از وابستگی حلقوی است. functionA در بدنه خود functionB را صدا میزند، پس به آن وابسته است و functionB نیز در بدنه خود functionA را صدا میزند، پس به آن وابسته است. این نوع از وابستگی حلقوی یک مشکل بزرگ دارد که در بخش بعدی آن را توضیح خواهیم داد. فعلا عکس زیر را ببینید تا درک بهتر از آن پیدا کنید:
# مشکل وابستگی حلقوی چیست؟
وابستگی های حلقوی می توانند مشکلات زیادی در کد شما ایجاد کنند. به عنوان مثال، ممکن است وابستگی محکم بین ماژول ها ایجاد کند و در نتیجه قابلیت استفاده مجدد کد را کاهش دهد. این واقعیت همچنین حفظ کد را در دراز مدت دشوارتر می کند.
علاوه بر این، وابستگیهای حلقوی میتوانند منشأ خرابیهای بالقوه مانند infinite recursions، استفاده بیش از حد از حافظه و cascade effects باشند. اگر مراقب نباشید و یک وابستگی حلقوی در کد خود ایجاد کنید، اشکال زدایی بسیاری از مشکلات احتمالی که ایجاد می کند می تواند بسیار دشوار باشد.
ویدیو مرتبط: حل مشکل circular imports در فلسک
# ایمپورت حلقوی چیست؟
circular imports نوعی از circular dependency است که در جملات import پایتون اتفاق میفتد. برای مثال بیایید کد زیر را در نظر بگیریم:
# module1
import module2
def function1():
module2.function2()
def function3():
print('Goodbye, World!')
# module2
import module1
def function2():
print('Hello, World!')
module1.function3()
# __init__.py
import module1
module1.function1()
وقتی پایتون یک ماژول را import می کند، رجیستری ماژول را بررسی می کند تا ببیند آیا ماژول قبلا import شده است یا خیر. اگر ماژول قبلا ثبت شده بود، پایتون آن را از حافظه کش میخواند. رجیستری ماژول، جدولی از ماژول هایی است که با نام ماژول index شده اند. برای دسترسی به این جدول میتوانید از sys.modules استفاده کنید.
اگر ثبت نشده بود، پایتون ماژول را پیدا می کند، در صورت لزوم آن را آماده می کند و در namespace ماژول جدید اجرا می کند.
در مثال ما، وقتی پایتون به import module2 میرسد، آن را بارگذاری و اجرا میکند. با این حال، module2 همچنین module1 را فراخوانی می کند، که به نوبه خود function3 را تعریف می کند.
مشکل زمانی رخ می دهد که function2() سعی می کند function3() از module1 را فراخوانی کند. از آنجایی که module1 ابتدا بارگیری شد و به نوبه خود قبل از اینکه به function3() برسد، module2 بارگذاری شد، این تابع هنوز تعریف نشده است و هنگام فراخوانی خطا می دهد:
$ python __init__.py
Hello, World!
Traceback (most recent call last):
File "__init__.py", line 3, in <module>
module1.function1()
File "/Users/scott/projects/sandbox/python/circular-dep-test/module1/__init__.py", line 5, in function1
module2.function2()
File "/Users/scott/projects/sandbox/python/circular-dep-test/module2/__init__.py", line 6, in function2
module1.function3()
AttributeError: 'module' object has no attribute 'function3'
مقاله پیشنهادی: 14 روش برای افزایش سرعت پایتون
# چطور مشکل وابستگی حلقوی را حل کنیم؟
به طور کلی، مشکل ایمپورت حلقوی نتیجه طراحی بد برنامه توسط توسعه دهنده است. اگر کمی عمیقتر کدتان را بررسی کنید، متوجه خواهید شده که شاید آن وابستگی نیاز نیست، یا میتوانید وابستگی را به ماژول دیگری منتقل کنید که مشکل ایمپورت حلقوی را ایجاد نکند.
یک راه حل ساده این است که گاهی اوقات هر دو ماژول را می توان در یک ماژول واحد و بزرگتر ترکیب کرد. کد به دست آمده از مثال ما در بالا چیزی شبیه به این خواهد بود:
# module 1 & 2
def function1():
function2()
def function2():
print('Hello, World!')
function3()
def function3():
print('Goodbye, World!')
function1()
با این حال، ماژول ادغام شده ممکن است عملکردهای نامرتبط داشته باشد (tight coupling) و اگر دو ماژول قبلاً کد زیادی در خود داشته باشند، می تواند بسیار بزرگ شود.
بنابراین اگر این روش برای شما کار نمی کند، راه حل دیگری می تواند این باشد که ایمپورتهای module2 را به تعویق بیندازید تا آن را فقط در صورت نیاز import کنید. این را می توان با قرار دادن import module2
در تعریف function1() انجام داد:
# module 1
def function1():
import module2
module2.function2()
def function3():
print('Goodbye, World!')
در این صورت پایتون قادر خواهد بود تمام توابع را در module1 بارگذاری کند و تنها در صورت نیاز ماژول 2 را بارگذاری کند.
این رویکرد با سینتکس پایتون در تضاد نیست، همانطور که مستندات پایتون میگوید: "الزامی نیست که همه دستورات import را در ابتدای یک ماژول قرار دهیم".
مستندات پایتون همچنین میگوید که توصیه میشود از import X، به جای عبارات دیگر، مانند from import module *
، یا from module import a,b,c
استفاده کنید.
# نتیجه گیری
Circular imports یک مورد خاص از circular references است. به طور کلی، آنها را می توان با طراحی کد بهتر، حل کرد. با این حال، گاهی اوقات، طرح به دست آمده میتواند حاوی مقدار زیادی کد باشد یا عملکردهای نامرتبط را با هم ترکیب کند.