تا این قسمت تمام کدهایی که نوشته بودیم در یک فایل قرار داشتند. از طرف دیگر با توابع در پایتون آشنا شدیم و توابع به ما اجازه می دادند تا از تکرار بی رویه کدها جلوگیری کنیم. در مرحله بعدی در رابطه با کلاس ها (برنامه نویسی شیء گرا) صحبت کردیم و بعد از آن به سراغ برنامه نویسی تابع گرا رفتیم. تمام این پارادایم ها برای منظم کردن کدهای ما است و شما می توانید بر اساس سلایق و دلایل خود از آن ها استفاده کنید اما هنوز مشکل دیگری وجود دارد.
همانطور که گفتم تمام کدهای ما درون یک فایل نوشته شده اند اما چنین کاری در دنیای واقعی ممکن نیست چرا که تعداد خطوط کد بسیار بسیار زیاد است (صدها هزار خط کد) و اگر قرار باشد همه چیز را درون یک فایل قرار بدهیم، خواندن و ویرایش آن کدها عملا غیرممکن می شود. به همین دلیل برنامه نویسان modules یا ماژول ها را ساختند. ماژول ها فایل هایی هستند که قطعه هایی از کدهای مورد نظر ما را در خود دارند بنابراین نگهداری از آن ها بسیار ساده تر می شود.
من تا این قسمت تمام کدهای خود را درون فایلی به نام test.py نوشته بودم اما در این جلسه فایل جدیدی را در کنار آن ساخته و نامش را utility.py می گذاریم. نام گذاری ماژول ها (فایل های کد ما) بر اساس قرارداد snake_case است بنابراین در صورت وجود چند کلمه، از علامت _ برای جداسازی آن ها استفاده می کنیم. در ابتدای کار شاید برایتان سخت باشد که مشخص کنید چه کدهایی درون چه فایل هایی نوشته شوند اما به مرور زمان با آن آشنا می شوید. برای درک بهتر باید به ماژول ها به عنوان قسمت های منطقی و منفصل نگاه کنید. به طور مثال اگر قسمتی از وب سایت ما مسئول ساخت حساب کاربری برای کاربران است می توانیم آن قسمت را جدا کرده و در فایل مخصوص خودش قرار بدهیم.
کلمه utility به معنای ابزار است و معمولا کدهای کمکی را در چنین فایلی قرار می دهند. منظور من از کد کمکی، کدهایی است که استفاده عمومی و پرتکرار دارند و مخصوص به قسمت خاصی از برنامه نیستند. به طور مثال بیایید چند تابع کمکی را در فایل utility.py خودمان بنویسیم:
def multiply(num1, num2): return num1*num2 def divide(num1, num2): return num1 / num2
این دو تابع فقط برای مثال و آشنایی با ماژول ها نوشته شده اند. در دنیای واقعی هیچ وقت چنین توابعی را نمی نویسیم چرا که دو اپراتور * و / کارشان را انجام می دهند! اما فعلا برای مثال آن ها را قبول کنید. حالا فایل utility.py یک ماژول در برنامه ما است و برای اینکه از آن در فایل test.py استفاده کنیم باید به فایل test.py رفته و به شکل زیر عمل کنیم:
import utility print(utility)
برای اینکه بتوانیم از محتویات utility.py درون فایل test.py استفاده نماییم باید دستور import (به معنی وارد کردن) را نوشته و سپس نام فایل مورد نظر را ذکر کنیم. توجه داشته باشید که ما پسوند فایل (py.) را ذکر نمی کنیم چرا که در محیط کدنویسی پایتون هستیم و پیش فرض ما این است که فایل های وارد شده همگی پایتون خواهند بود. با اجرای کد بالا نتیجه زیر را دریافت می کنید:
<module 'utility' from '/mnt/Development/Roxo Academy/Python/utility.py'>
با این حساب مطمئن می شویم که ماژول ما (utility.py) در این فایل وارد شده است. نکته جالب اینجاست که با انجام این کار و اجرای کد بالا پوشه جدیدی به نام __pycache__ برایتان ساخته می شود که درون خود فایلی به نام utility.cpython-38.pyc دارد. زمانی که فایلی را که دارای دستور import می باشد اجرا نمایید، چنین فایل pyc ای برایتان ساخته می شود. آیا می دانید این فایل چیست؟ این فایل همان فایل ماژول utility.py است که کامپایل شده است! از این به بعد زمانی که کدهای فایل test.py را اجرا کرده و به دستور import برای utility.py برخورد می کنیم، نیازی به بازخوانی فایل utility.py نیست بلکه محتویات کامپایل شده آن درون فایل پیدا خواهیم کرد.
از این به بعد به تمامی کدهای درون utility.py دسترسی خواهیم داشت بنابراین:
import utility print(utility.multiply(2, 3))
همانطور که می بینید من می توانیم تابع multiply از فایل utility را صدا بزنم. با اجرای این کد عدد ۶ را دریافت می کنیم که حاصل ضرب ۲ و ۳ در هم است. همچنین شما هیچ محدودیتی برای تعداد فایل های وارد شده ندارید و می توانید از هر تعداد دستور import ای که بخواهید استفاده کنید.
حالا که با ماژول ها آشنا شده ایم باید به سراغ پکیج ها برویم. فرض کنید که نیاز ما بیشتر از یک فایل ساده است؛ به طور مثال برای مایکروسافت کار می کنیم و قرار است قابلیت خرید را به وب سایت آن ها اضافه کنیم. طبیعتا «خرید» مفهوم بزرگی است و خودش شامل قسمت های زیادی می شود بنابراین باید پوشه جدیدی به نام shopping (یا هر نام دلخواه دیگری) را ایجاد کرده و درون آن فایلی به نام shopping_cart.py (به معنی سبد خرید) را می سازیم. من وارد این فایل شده و کدهای زیر را در آن می نویسم:
def buy(item): cart = [] cart.append(item) return cart
این کد یک تابع ساده است که یک آیتم (در این مثال، یک محصول) را گرفته و آن را به لیستی اضافه می کند. حالا می توانیم به فایل test.py رفته و این بار این فایل را نیز import کنیم:
import utility import shopping.shopping_cart print(utility.multiply(2, 3)) print(shopping.shopping_cart)
پکیج ها مجموعه ای از ماژول ها هستند بنابراین در پوشه جداگانه ای قرار می گیرند. در مثال ما و در پوشه shopping فقط یک ماژول وجود دارد (shopping_cart) اما این فقط یک مثال است. معمولا پکیج ها دارای ده ها یا صد ها ماژول هستند. همچنین از آنجایی که ماژول shopping_cart دقیقا در کنار test.py نبوده و در پوشه ای به نام shopping قرار دارد نمی توانیم مستقیما آن را import کنیم، بلکه باید مانند روش بالا نام پوشه را نیز ذکر کنیم. با اجرای کد بالا نتیجه زیر را دریافت می کنیم:
6 <module 'shopping.shopping_cart' from '/mnt/Development/Roxo Academy/Python/shopping/shopping_cart.py'>
بنابراین همه چیز به درستی کار می کند. مثال:
import utility import shopping.shopping_cart print(utility.multiply(2, 3)) print(shopping.shopping_cart.buy('ram'))
با اجرای این کد نتیجه زیر را دریافت می کنیم:
6 ['ram']
همه چیز با موفقیت اجرا شده است و به تابع buy نیز دسترسی داریم. البته کاری که ما در این قسمت انجام داده ایم صحیح نیست اما می خواستم برای شروع، کلیت کار را یاد بگیرید. در پکیج های واقعی یک پوشه داریم که همان پکیج ما است و سپس درون آن فایلی به نام init__.py___ را خواهیم داشت. مفسر پایتون هنگام استفاده از یک پکیج حتما به دنبال این فایل می گردد بنابراین باید آن را بسازیم اما می توانید آن را خالی بگذارید (فقط مطمئن شوید که در پوشه پکیج وجود دارد). در واقع تفاوت بین پوشه و پکیج در همین است که پکیج ها فایل init را دارند.
ما تا این قسمت از روش ساده import برای وارد کردن یک ماژول در فایل test.py استفاده کرده ایم اما این روش ساده همیشه کارساز نیست. در حال حاضر برای صدا زدن متد buy نام پکیج را نیز ذکر کرده ایم:
print(shopping.shopping_cart.buy('ram'))
در حالی که ممکن است ما یک پکیج را داخل پکیج دیگری داشته باشیم. در چنین حالتی باید نام آن پکیج دیگر را نیز ذکر کنیم:
print(shopping.some_other_package.shopping_cart.buy('ram'))
همانطو که می بینید برای صدا زدن یک متد ساده مانند buy مجبور به ذکر نام چندین پکیج شده ایم که کد را بسیار شلوغ و طولانی کرده است. برای حل این مشکل چند راه مختلف وجود دارد. راه اول این است که مستقیما تابع buy را از ماژول آن وارد کنیم تا اصلا نیازی به ذکر نام پکیج نباشد:
import utility from shopping.shopping_cart import buy print(utility.multiply(2, 3)) print(buy('ram'))
در این روش از کلیدواژه from استفاده می کنیم تا نام پکیج را ذکر کنیم، سپس دستور import را آورده و هر چیزی را که بخواهیم از آن پکیج وارد این فایل می کنیم. با این کار می توانیم مستقیما تابع buy را بدون ذکر نام پکیج صدا بزنیم. اگر بخواهیم این کار را برای دو تابع موجود در utility نیز انجام بدهیم به شکل زیر عمل می کنیم:
from utility import multiply, divide from shopping.shopping_cart import buy print(multiply(2, 3)) print(buy('ram'))
من هر دو تابع multiply و divide را می خواستم بنابراین با یک ویرگول آن ها را از هم جدا کرده و هر دو را وارد این فایل کرده ام. در صورتی که بخواهید کل ماژول را وارد کنید، باید از روش ساده ای استفاده کنید که تا این لحظه استفاده می کردیم یا اینکه از روش زیر استفاده کنید:
from utility import multiply, divide from shopping import shopping_cart print(multiply(2, 3)) print(shopping_cart.buy('ram'))
شما می توانید از هر روشی مانند روش بالا برای وارد کردن کدهای خودتان استفاده کنید. راه دوم این است که به پکیج وارد شده نام خاصی بدهیم. به مثال زیر توجه کنید:
import utility import shopping.shopping_cart as shop print(utility.multiply(2, 3)) print(shop.buy('ram'))
استفاده از کلمه as به ما اجازه می دهد که یک نام دلخواه (هر نامی) را برای پکیج وارد شده انتخاب کنیم. از این به بعد به جای آن که نام کامل پکیج خود را ذکر کنیم کافی است این نام کوتاه را بنویسیم بنابراین shop.buy به راحتی کار می کند. دقت کنید که در این روش یک نام کوتاه تر را به عنوان نام پکیج انتخاب می کنیم. البته باید در نظر داشته باشید که این روش تنها برای راحت تر کردن کار شما نیست بلکه معمولا برای رفع name collision یا تصادم نام ها استفاده می شود. فرض کنید در ماژول utility.py خود متدی به نام max را داشته باشیم:
def multiply(num1, num2): return num1*num2 def divide(num1, num2): return num1 / num2 def max(): return 'some_data'
این تابع فقط رشته some_data را برمی گرداند. حالا به ماژول اصلی test.py می رویم و متد max را نیز وارد آن می کنیم:
from utility import multiply, divide, max from shopping import shopping_cart print(multiply(2, 3)) print(shopping_cart.buy('ram')) print(max([1, 2, 3]))
از طرفی می دانیم که تابع max از توابع پیش فرض پایتون است که بزرگترین مقدار در یک لیست را به ما می دهد. اگر ما فراموش کرده باشیم که متدی به نام max را وارد این فایل کرده ایم و بخواهیم از max (تابع پیش فرض پایتون) استفاده کنیم به این خطا برخورد می کنیم:
6 ['ram'] Traceback (most recent call last): File "/mnt/Development/Roxo Academy/Python/training/test.py", line 6, in <module> print(max([1, 2, 3])) TypeError: max() takes 0 positional arguments but 1 was given
این خطا می گوید max هیچ آرگومانی نمی گیرد اما شما یک لیست را به آن پاس داده اید. این خطا به این دلیل ایجاد شده است که با وارد کردن max نوشته شده توسط خودمان، باعث حذف تابع max پیش فرض در زبان پایتون از این فایل شده ایم. به این مسئله (یکی بودن نام توابع ما و پایتون) name collision یا تصادم نام ها می گویند. راه حل استفاده از alias (به معنی لقب) یا همان استفاده از as می باشد:
from utility import multiply, divide, max as my_max from shopping import shopping_cart print(multiply(2, 3)) print(shopping_cart.buy('ram')) print(max([1, 2, 3])) print(my_max())
کد بالا می گوید max نوشته شده توسط خودمان را با نام my_max وارد این فایل کن. با اجرای این کد نتیجه زیر را دریافت می کنیم:
6 ['ram'] 3 some_data
عدد ۶ حاصل ضرب ۲ و ۳ است بنابراین multiply به خوبی کار می کند، ram محصول متد buy است بنابراین آن هم بدون مشکل کار می کند، عدد ۳ بزرگترین عدد از لیست پاس داده شده به max (تابع پیش فرض پایتون) است بنابراین به اشتباه باعث حذف تابع max نشده ایم و نهایتا my_max نیز رشته some_data را چاپ کرده است. به همین سادگی باعث جلوگیری تصادم نام شده ایم.
البته به طور کلی ماژول ها کدهای بسیار زیادی دارند بنابراین پیشنهاد نمی شود که تک تک متدهایشان را (مانند کاری که ما برای utility انجام داده ایم) وارد فایل خود کنید، بلکه تمام این کدها صرفا جهت یادگیری دستور و نحوه نوشتن آن ها است. نکته آخر اینجاست که اگر بخواهیم همه چیز را از یک ماژول یا پکیج وارد کنیم می توانیم دستور ستاره را به جای نام توابع و کدها قرار بدهیم:
from utility import * from shopping import shopping_cart print(multiply(2, 3)) print(shopping_cart.buy('ram')) print(max())
با انجام این کار و پاس دادن علامت ستاره می توانید مستقیما از تمام توابع موجود در یک ماژول استفاده کنید. البته با این کار دوباره با مشکل name collision روبرو می شویم به همین دلیل بهتر است همیشه نام توابع تعریف شده در ماژول خود را به درستی و با دقت انتخاب کنید تا دچار این مشکلات نشوید.
یکی از مباحثی که برای افراد تازه کار سخت و گنگ به نظر می رسد، آشنایی با name و main است. برای آشنایی با این بحث ابتدا به فایل shopping_cart.py رفته و در ابتدای آن __name__ را چاپ کنید:
print(__name__) def buy(item): cart = [] cart.append(item) return cart
سپس این کار را با فایل utility.py نیز انجام می دهیم:
print(__name__) def multiply(num1, num2): return num1*num2 def divide(num1, num2): return num1 / num2 def max(): return 'some_data'
حالا به فایل test.py رفته و کدهای آن را به شکل زیر مرتب می کنیم تا خطای name collision نداشته باشیم:
import utility as util from shopping import shopping_cart print(util.multiply(2, 3)) print(shopping_cart.buy('ram'))
با اجرای کدهای فایل test.py نتیجه زیر را می گیریم:
utility shopping.shopping_cart 6 ['ram']
دستورات print ما باعث چاپ مقادیر utility و shopping.shopping_cart شده اند. آیا متوجه مفهوم __name__ شدید؟ زمانی که می خواهیم برنامه پایتونی را اجرا کنیم، همیشه باید از یک فایل خاص شروع کنیم (نمی توانیم ده فایل را همزمان اجرا کنیم). در این پروژه فایل اصلی که اجرا می شود test.py است. زمانی که این فایل را اجرا می کنیم مفسر پایتون خط به خط کدهای درون آن را می خواند و ابتدا به دستور import ماژول utility می رسد و الی آخر. __name__ نامی است که مفسر پایتون به ماژول های مختلف می دهد و معمولا از نام همان فایل استفاده می کند.
اما __main__ چطور؟ بیایید __name__ را در فایل اصلی test.py نیز چاپ کنیم:
import utility as util from shopping import shopping_cart print(util.multiply(2, 3)) print(shopping_cart.buy('ram')) print(__name__)
با اجرای این کد نتیجه زیر را می گیرید:
utility shopping.shopping_cart 6 ['ram'] __main__
بله! فایل اصلی ما همیشه __main__ نام دارد؛ یعنی هر فایلی را که شما اجرا کنید، __main__ خواهد بود. در طول برنامه نویسی خود احتمالا صدها بار به کدی شبیه به کد زیر برخورد کنید:
if __name__ == "__main__": print("THIS IS MAIN")
من یک دستور print ساده را قرار داده ام اما در برنامه های واقعی از این کد برای پیدا کردن فایل اصلی استفاده می شود و به جای print کدهای دیگری را می نویسند. اگر یادتان باشد زمانی که ()type را روی نمونه ای از یک کلاس صدا می زدیم چیزی شبیه به همین main دریافت می کردیم:
<class '__main__.student'>
زمانی که چنین چیزی را می بینی یعنی کلاسی که از آن نمونه سازی شده است در فایل main (فایلی که اجرا شده است - فایل اصلی برنامه) قرار دارد. برنامه نویسان با استفاده از این موضوع می توانند جنبه های مختلف برنامه خود را مدیریت کنند. ما نیز در این دوره آموزشی چندین پروژه را با هم می نویسیم و آنجا خواهید دید که چطور می توانیم از __name__ در پروژه هایمان استفاده کنیم. در قسمت بعدی به سراغ ماژول های پیش فرض پایتون می رویم، یعنی ماژول هایی که توسط تیم پایتون نوشته شده و درون زبان پایتون قرار داده شده اند. چرا به آن ها ماژول می گوییم؟ به دلیل اینکه این کدها درون قسمت اصلی پایتون نوشته نشده اند و اگر بخواهیم از آن ها استفاده کنیم باید آن ها را مانند یک ماژول وارد کنیم. چنین مثالی را قبلا در نوشتن decorator خود دیده بودیم (تابع time).
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.