🧑‍💻PythonDev🧑‍💻
365 subscribers
86 photos
3 videos
15 files
78 links
Python tips and tricks
The Good, Bad and the Ugly

📚توی این کانال فقط قرار هست در مورد core python صحبت کنیم.

👨‍💻این کانال یک بلاگ شخصی هست و پیرامون نظرات و چیزهایی که توی این چند سال کد زدن یاد گرفتم (فقط برای کمک به دوستان تازه‌کار)👨‍💻


@Mtio975
Download Telegram
🧑‍💻PythonDev🧑‍💻
یه سری جزئیات داریم که به شرح زیر می باشند حتی اگه از یه سری عوامل مثلا وابستگی هایی که کنترلی روی آن ها ندارین همچنان شما رو لزوم به پشتیبانی از پایتون 2 می کنه که این مانع از براشتن یه گام برای گنجاندن پشتیبانی از پایتون 3 نمیشه اکثر تغییرات مورد نیاز…
مطمئن بشین که پشتیبانی از نسخه مناسب رو تو فایل setup.py که دارین مشخص کردین
تو فایل setup.py خودتون باید طبقه بندی کننده trove مناسب رو داشته باشین که مشخص میکنه از کدوم نسخه هایی از پایتون پشتیبانی می کنید و اونجایی که پروژه هاتون از python 3 پشتیبانی نمیکنه حداقل باید زبان برنامه نویسی python 2 فقط مشخص شده باشه تو ایده آل ترین حالت ممکن باید هر نسخه اصلی / فرعی پایتون رو که ازش پشتیبانی میشه رو مشخص کنید
مثل زبان برنامه نویسی python 2.7

سعی کنید پوشش تست خوبی داشته باشین
وقتی که کد خودتون رو از قدیمی ترین نسخه پایتون 2 که میخوایین پشتیبانی میکنه باید مطمئن بشین که مجموعه آزمایشی شما پوشش خوبی داره یه قانون سرانگشتی خوب این هستش که اگر میخواهید به اندازه کافی تو مجموعه آزمایشی خودتون اطمینان داشته باشین که هر گونه نقصی از که بعد از بازنویسی ابزار ها ظاهر میشه اشکالات واقعی تو ابزار ها هستن نه تو کدهای که دارین اگر میخواهید شماره ای رو هدف بگیرین سعی کنید بیش از 80 درصد پوشش داشته باشین ( واگر دریافت بهتر از پوشش 90 درصد براتون سخته احساس بدی اصلا نداشته باشین ) اگر از قبل ابزاری برای اندازه گیری پوشش تست ندارید توصیه میکنم که از coverage.py استفاده کنید
از تفاوت های پایتون 2 و3 آگاه باشین وقتی که کد خودتون رو خیلی خوب آزمایش کردین و آماده هستین تا کدهاتون رو به پایتون 3 انتقال بدین برای اینکه متوجه بشین که کدی که دارین چطور تغییر میکنه و میخواهین تو حین کد نویسی به یه سری موارد توجه کنید باید یاد بگیرین که پایتون 3 چه تغییر های رو تو پایتون 2 ایجاد میکنه

خوب تا همین جا که گفتم میمونه بقیه موارد میره برای روزهای دیگه با دقت به مواردی که گفتم توجه کنید
Admin: @Itsec2024
Github:https://github.com/ChiefInformationSecurityOfficer
chanel 1 : https://t.me/pythonlearnme
Chanel 2:
https://t.me/hackerone715
این پکیج توی پایتون بتون کمک میکنه که خیلی سریع دیتاستتون رو بررسی کنید و به اصطلاح فنی  یک تجربه ی  Exploratory Data Analysis هستش..

من خوشم اومد، جالبه.. براتون یه گزارش مفصل و خیلی یوزرفرندی میسازه از داده هاتون، داده های Nan رو مشخص میکنه و خیلی چیزای دیگه!
اگر میخواین شروع کنید، توی Vscode یا Jupyter notebook دستورای توی تصویر رو اجرا کنید..
استفاده از پایتون در پلتفرم های یونیکس
پایتون روی اکثر توزیع ها لینوکس از پیش نصب شده است و به صورت بسته در همه توزیع های دیگر در دسترس است ولی یه سری ویژگی های خاصی هستن که ممکن بخواهیم ازشون استفاده کنیم که تو بسته های توزیع ما موجود نیستن به راحتی هم میتونیم از اخرین نسخه پایتون رو از منبع کامپایل کنیم
در صورتی که پایتون از پیش نصب نشده باشه و تو مخزن ها هم وجود نداشته باشه می تونیم به راحتی بسته های رو برای توزیع خودمون بسازیم به لینک های زیر یه نگاه بندازین
همچنین ببینید
https://www.debian.org/doc/manuals/maint-guide/first.en.html
برای کاربران دبیان

https://en.opensuse.org/Portal:Packaging
برای کاربران OpenSuse

https://docs.fedoraproject.org/en-US/package-maintainers/Packaging_Tutorial_GNU_Hello/
برای کاربران فدورا

https://slackbook.org/html/package-management-making-packages.html
برای کاربران Slackware
در FreeBSD و OpenBSD
چطوری در تلگرام یک قطعه کد برنامه نویسی رو بنویسیم؟ 

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



a=5
b=9
print(a+b)
🧑‍💻PythonDev🧑‍💻
چطوری در تلگرام یک قطعه کد برنامه نویسی رو بنویسیم؟ فقط کافیه کدتون رو به روش معمول تایپ کنید ولی اول و آخرش رو سه تا علامتی که در عکس میبینید بزارید خودش به صورت کد نمایش داده میشه. اول کدتون هم اسم زبانش رو بنویسید که مردم بدونند زبانش چیه. برای مثال:…
import os
from pdfminer.high_level import extract_text

def search_keywords_in_pdfs(folder_path, keywords):
    try:
        # Iterate over each file in the specified folder
        for filename in os.listdir(folder_path):
            if filename.endswith(".pdf"):
                file_path = os.path.join(folder_path, filename)
                print(f"\nSearching in file: {file_path}")

                # Extract text from the PDF file
                text = extract_text(file_path)

                # Check if any of the keywords are present in the extracted text
                if any(keyword.lower() in text.lower() for keyword in keywords):
                    print(f"Keyword(s) found in {filename}")

    except Exception as e:
        print(f"Error: {e}")

# Example usage:
pdf_folder_path = "path/to/your/pdf/folder"
search_keywords = ["python", "programming", "example"]

search_keywords_in_pdfs(pdf_folder_path, search_keywords)
✔️ سوال

خروجی چیست؟
1) NameError: name 'e' does not exists
2) 10
3) ZeroDivisionError()

چرا؟ 😁
✔️ سوال: best practice‌ها و ابزارات مورد نیاز برای توسعه یه بسته، لایبرری یا نرم‌افزار پایتونی چیه؟

ابزارهای زیادی واسه این‌کارا وجود داره که بر اساس تجربه و چیزی که از 80 درصد پروژه های معروف و بزرگ دیدم، سعی میکنم بهتریناش رو معرفی کنم:


Dependency Management System

برای مدیریت وابستگی های یک پروژه بزرگ (یا نسبتا بزرگ)، پیشنهاد میکنم از Poetry استفاده کنید، چون ابزار بسیار قدرتمندی هست، کار باهاش بسیار راحته (خیلی شبیه به Cargo در زبان Rust هست)، کامیونیتی بزرگی داره، به راحتی محیط dev و prod رو میتونید مدیریت کنید، مستندات بسیار خوب و روانی داره، در سریع ترین زمان ممکن میتونید پروژه رو در PyPI منتشر کنید و همچنین پروژه⁧های معروفی از جمله Rich (کتابخونه⁧ای که خود pip هم ازش برای رنگی کردن progress barها استفاده میکنه) از اون استفاده میکنن، که همین نشون میده چقدر کاربردی هست.
همچنین build و run هم توسط Poetry انجام میشه

- منابع آموزشی:

1. آشنایی و شروع به کار با Poetry
https://realpython.com/dependency-management-python-poetry/

2. جزیات عمیق درباره هر قابلیت و نحوه استفاده
https://python-poetry.org/docs

3. نحوه انتشار یک package پایتونی در PyPI به کمک Poetry
https://johnfraney.ca/blog/create-publish-python-package-poetry/


Linting & Type Checking

برای Linting ابزارهای flake8 و pylint از معروف ترین⁧ها هستن، هر یک رو میتونید شدت سخت گیریشون رو تنظیم کنید، به راحتی همراه با Poetry تنظیماتشون در یک فایل قرار میگیره و نیاز به نگه داشتن فایل⁧های متفاوت برای هر کدوم ندارید. به نظرم خود flake8 به تنهایی نیازتون رو برطرف میکنه. اگر از PyCharm استفاده کنید یک Linter به صورت Built-in در اختیارتون میذاره که خوبه و کارتون رو راه میندازه.

اگر عادت دارید کدهای خودتون رو Annotate کنید و زیاد از Type Hint ها استفاده میکنید، استفاده از MyPy به عنوان یک static type checker به شما کمک میکنه ایرادات موجود در کلاس ها، توابع و ... رو پیدا کنید و بتونید typeهای دقیق تری استفاده کنید. همچنین ابزار Pyright که توسط مایکروسافت توسعه داده میشه، در VSCode به صورت integrated با افزونه Pylance کار میکنه و اگر از VSCode استفاده میکنید پیشنهاد میکنم حتما افزونه Pylance رو نصب کنید. PyCharm هم یک Type Checker داره ولی هنوز توانمندی Pylance و MyPy رو نداره.


- منابع آموزشی:

1. داکیومنت Flake8
https://flake8.pycqa.org/en/latest/user/invocation.html

2. داکیومنت MyPy
https://mypy.readthedocs.io/en/stable/getting_started.html


Code Formatting

برای مرتب و organize کردن importها از isort استفاده کنید، هم به صورت CLI در دسترس هست و هم یک API داره که میتونید در کد پایتون استفاده کنید. یک code formatter بسیار معروف که در اکثر پروژه⁧ها استفاده میشه Black هست، زیر نظر PSF (یا Python Software Foundation) و برنامه نویسای با تجربه و کار کشته ای همچون آقای Łukasz Langa (ریلیس منیجر پایتون 3.9) توسعه داده میشه، با دیگر ابزار ها مثل isort سازگار هست، با PEP8 سازگاره و کلی قابلیت مزیت دیگه داره که حقیقتا اینجا جا نمیشن :)

- منابع آموزشی:

1. داکیومنت isort
https://pycqa.github.io/isort/

2. داکیومنت Black
https://black.readthedocs.io/en/stable/getting_started.html

3. یک ویدیو از PyCon 2019 درباره Black
https://youtu.be/esZLCuWs_2Y

* این ابزارها همگی داکیومنت ساده و خوانایی دارن و به نظرم بهترین منبع همین داکیومنت⁧هاشون هست.


Testing

دو کتابخونه Pytest و unittest (در stdlib موجود هست) در تست نویسی پروژه ها استفاده میشن، برای تست کردن پروژه در environmentهای متفاوت از ابزار tox استفاده میشه و باهاش میتونید در نسخه های متفاوتی از پایتون کدتون رو تست کنید.


- منابع آموزشی:

1. یک مقاله جامع درباره تست نویسی، انواع آن و روش⁧های مورد استفاده در پایتون
https://realpython.com/python-testing/

2. ویدیو درباره unittest در پایتون
- آقای ned batchelder:
https://m.youtube.com/watch?v=FxSsnHeWQBY
- آقای Corey Schafer:
https://www.youtube.com/watch?v=6tNS--WetLI


* همچنین اگر از Git در پروژه خودتون استفاده میکنید، پیشنهاد میکنم از pre-commit حتما استفاده کنید و تمام این ابزارهای گفته شده رو automate کنید تا در هربار commit کردن، قبل از اینکه commit انجام بشه، این ابزارها روی کدتون اجرا بشن و اگر اشکالی در کدتون وجود داره پیدا بشه و اون رو برطرف کنید و دوباره commit کنید.

وبسایت pre commit:
https://pre-commit.com/
📚 آموزش پیشرفته Set در پایتون

ست‌ها در پایتون، مجموعه‌هایی بدون ترتیب و با عناصر منحصربه‌فرد هستن که به دلیل سرعت بالا در عملیات‌های مجموعه‌ای و حذف داده‌های تکراری، یک ابزار کارآمد برای تحلیل داده‌ها محسوب می‌شن.

🔵 یکی از ویژگی‌های پیچیده‌ در setها، توانایی انجام عملیات‌های ریاضی مجموعه‌ای‌، مثل اجتماع، تقاطع، تفاضل و تفاضل متقارن هست.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)  # اجتماع
print(a & b)  # تقاطع
print(a - b)  # تفاضل
print(a ^ b)  # تفاضل متقارن
🔜 {1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
🔵 ست‌ها در پایتون از Comprehensions پشتیبانی می‌کنن، که با استفاده از شرط‌ها و حلقه‌ها، امکان ساخت setهای پیچیده‌تر فراهم می‌شه.
squared_primes = {x**2 for x in range(10) if is_prime(x)}
print(squared_primes)
🔜 {4, 9, 25, 49}
🔵 متد‌های پیشرفته setها مثل isdisjoint, issubset, و issuperset اجازه می‌دن روابط بین دو set رو بررسی کنیم.
c = {1, 2}
d = {1, 2, 3, 4}
print(c.isdisjoint(b))  # بررسی عدم اشتراک
print(c.issubset(d))    # بررسی زیرمجموعه بودن
print(d.issuperset(c))  # بررسی سوپرمجموعه بودن
🔜 False
True
True
🔵 برای افزودن یا حذف عناصر از ست‌ها، متدهای add, update, و discard کاربرد دارن که امکان مدیریت انعطاف‌پذیر عناصر رو می‌دن.
e = {1, 2, 3}
e.add(4)            # افزودن عنصر
e.update([5, 6, 7]) # افزودن چندین عنصر
e.discard(3)        # حذف عنصر
print(e)
🔜 {1, 2, 4, 5, 6, 7}
🔵 پایتون همچنین اجازه می‌ده ست‌های frozen ایجاد کنیم، که setهایی غیرقابل تغییر هستن و می‌توانن به عنوان کلید در دیکشنری‌ها استفاده شن.
f = frozenset([1, 2, 3])
print(f)
🔜 frozenset({1, 2, 3})
Channel name was changed to «🧑‍💻PythonDev🧑‍💻»
🧑‍💻PythonDev🧑‍💻
✔️ سوال خروجی چیست؟ 1) NameError: name 'e' does not exists 2) 10 3) ZeroDivisionError() چرا؟ 😁
جواب:
تو حالت های دیگه مثلا زمانی که از context manager استفاده میکنیم دستور as درواقع همون کار assignment رو انجام میده. مثلا:
e = 10
with open("text.txt") as e:
pass
print(e)
اینجا دیگه e اشاره میکنه به فایل باز شده.

ولی اگه این اتفاق توی exception ها هم بیفته مشکل ساز میشه. آبجکت exception داخل خودش یه traceback آبجکتی داره که حاوی اطلاعاتی از exception ما هست. مثلا خود این traceback آبجکت یه رفرنس داره به frame ای که توش exception رخ داده و اون frame هم رفرنس داره به تمام متغیر های لوکال اون scope از جمله؟ e !
e -> traceback -> frame -> e
پس یک circular reference ایجاد میشه:


e = 10
try:
raise ZeroDivisionError("hiiiii")
except ZeroDivisionError as e:
print(e is e.__traceback__.tb_frame.f_locals["e"])

در نتیجه پایتون میاد بعد از اینکه بلاک except تموم شد اون اسمی که اشاره میکرد به آبجکت exception یعنی e رو پاک میکنه از namespace.

تو این مثال پایین ما میخوایم از قصد یک رفرنس ایجاد کنیم به e تا زنده بمونه، انگار که همچین مکانیزمی نداشته پایتون. ببینید چه اتفاقی میفته: (فرض کنید obj یک آبجکت خیلی بزرگ هست)
class A:
def __del__(self) -> None:
print("object is deleted")

def fn():
obj = A()
exc = None
try:
raise ZeroDivisionError()
except ZeroDivisionError as e:
exc = e

fn()
print("Object is still alive!")
input()
فانکشن تموم شده، obj فقط داخل فانکشن ساخته شده بوده باید رفرنسش صفر میشد و از بین میرفت ولی نرفت...
‏گروه هکری که قبلا به اطلاعات تپ‌سی دسترسی پیدا کرده بود، مدعی هک اسنپ‌فود شد و برخی اطلاعات (شامل اطلاعات ۱‌۰۰۰ کاربر و...) را به عنوان دیتای نمونه منتشر کرد.
✔️ کلاس zip

ببینید zip یک کلاس هست که میاد به عنوان پارامتر های ورودی خودش، iterable (یا iterable هایی) رو به صورت postional argument میگیره و یک zip object به ما برمیگردونه!

شاید بگید iterable چیه؟ iterable میشه چیزی که شما توی پایتون بتونی مثلا روش for بزنی و به اصطلاح iterate کنی! مثل چی؟ مثل list - str - tuple و ...

خوب حالا این zip چطوری کار میکنه 🤔

این کلاس میاد و از iterable هایی که بهش داده شده، به تعداد اون Iterable ها (فرض کنیم n تا) tuple (هایی) به سایز n برامون yield میکنه! حالا اعضای اون tuple که سایزش n هست (یعنی n تا عضو داره) چی هستن؟ zip میاد و اولین عضو رو از اولین iterable و دومین عضو رو از دومین iterable و همینطوری تا آخر میگیره! این تا کی ادامه داره؟ تا زمانی که اعضای کوتاه ترین iterable تمام بشه!

نکته مهم ❗️

کلاس zip هم مثل filter و map و ... یک lazy iterator هست، یعنی چی؟ یعنی جواب رو درجا برای ما محاسبه نمیکنه و هر بار که روش next زده بشه، به ما یک چیزی yield میکنه.

مثال 🧪
names = [
"Ali",
"Mohsen",
"Hassan",
]

last_names = [
"Hassanzadeh",
"Mohammadi",
]

z = zip(names, last_names)

الان هر بار که روی z ما next بزنیم به ما یک tuple به سایز 2 میده (چرا 2 ؟ چون 2 تا iterable بهش دادیم)

>>> next(z)
('Ali', 'Hassanzadeh')

به همین صورت میتونید به لیست، ست و ... این zip object رو تبدیل کنید:

>>> list(z)
[('Ali', 'Hassanzadeh'), ('Mohsen', 'Mohammadi')]

چرا هر tuple دوتا عضو بیشتر نداره؟ چون همونطور که بالا گفتم تعداد به اندازه کوتاه ترین iterable هست که در اینجا last_names کوتاه تر هست و فقط 2 عضو داره!

پس signature کلی کلاس zip به شکل زیر هست (از پایتون 3.10 پارامتر strict بهش اضافه شده که توضیحش میدم)

zip(*iterables, strict=False) —> zip object

خوب ی نکته میمونه، اینکه از پایتون 3.10 به این کلاس یک پارامتری اضافه شده به اسم strict ! کارش چیه؟ مقدارش به صورت پیشفرض False هست، ولی زمانی که شما اون رو True کنید، چک میکنه که همه iterable هایی که بهش دادید آیا طول یکسان دارند یا نه و اگر این شرایط برقرار نبود یک خطای ValueError به شما میده! مثال:

names = [
"Ali",
"Mohsen",
"Hassan",
]

last_names = [
"Hassanzadeh",
"Mohammadi",
]
print(list(zip(names, last_names, strict=True)))

خروجی:

ValueError: zip() argument 2 is shorter than argument 1


#builtins
✔️ کلاس map

کلاس map یکی دیگه از تایپ‌های built-in پایتون است.
این تایپ برای اولین پارامتر یک Callable دریافت میکنه (مثلا یک تابع یا یک کلاس که داندر ____call____ رو پیاده‌سازی میکنه) و پارامتر بعدیش تعداد متغیری iterable هست که بعد از callable بهش پاس می‌دیم. signature عه تابع مپ به این شکل هست:

map(callable, *iterables) —> map object

اما map چه کاری انجام میده؟ 🤔
اول از همه تایپ‌ مپ درست مانند range و فیلتر lazy iterator هست، یعنی فوری نتایج رو محاسبه نمیکنه،
کاری که map میکنه اینه که میاد دونه دونه از اون iterable (یا iterableهایی) که بهش داده شده، به اون callable پاس میده و نتیجه ای که اون callable بهش داده (در واقع مقداری که از callable ما return شده) رو هر بار که ما next می‌زنیم به ما برمی‌گردونه!

چند مثال کاربردی 🧪

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

lst = [2, 4, 6, 8]
print(list(map(lambda x: x ** 2, lst)))

Output: [4, 16, 36, 64]

تبدیل تمام استرینگ‌های یک لیست به شکل Uppercase خودشان:

lst = ["John", "David", "Mike"]
print(list(map(str.upper, lst)))

Output: ['JOHN', 'DAVID', 'MIKE']

نکته مهم ❗️

اگر به map چند تا iterable میدید، باید حواستون باشه که اون callable باید به تعداد iterableها پارامتر داشته باشه! مثال:


x = list(map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]))
print(x)

Output: [5, 7, 9]

در مثال بالا دو تا iterable به مپ پاس دادیم پس باید تابعی که به map می‌دیم دوتا آرگومان بگیره. حالا map میاد پارامتر اول (یعنی x) رو از اولین iterable و دومی رو (یعنی y) از دومین iterable میگیره! نکته قابل توجه دیگه اینکه، این فرایند توی تابع (یعنی جمع x و y) به تعداد اعضای کوتاه ترین iterable انجام میشه! یعنی اگر یکی از iterable ها 2 عضو داشت و بقیه 5 عضو، فقط روی 2 عضو اول هر iterable مپ کار انجام میده! مثال:

x = list(map(lambda x, y: x + y, [2, 3], [4, 5, 6, 4]))
print(x)

Output: [6, 8]

#builtins
هیچوقت نباید اسم ماژولی که داریم داخلش کد میزنیم رو اسم یکی از ماژول/پکیج های خود پایتون یا ماژول/پکیج هایی که نصب کردیمشون تا استفاده کنیم قرار بدیم...

مثال: من قرار هست با تاریخ کمی کار کنم و خب ماژول datetime رو که پایتون توی استاندارد لایبرری گذاشته رو import میکنم. ولی اسم فایل یا ماژولی که دارم کد میزنم داخلش رو میذارم datetime.py !
# datetime.py
import datetime
t = datetime.date(2020, 10, 10)
print(t)
اروری دریافت میکنم :
AttributeError: partially initialized module 'datetime' has no attribute 'date' (most likely due to a circular import)

این ارور تا حدی خوبه یه چیزایی داره بهمون راجع partially initialized شدن و همچنین circular import میگه...

سناریوی بد موقعی هست که اسم فایلی که دارم توش کد میزنم datetime نیست ولی کنار فایلم توی "همون پوشه" یه فایل به اسم datetime.py به اشتباه درست کردم و خالی هست... حالا چی :
# main.py
import datetime
t = datetime.date(2020, 10, 10)
print(t)
متن ارور :
AttributeError: module 'datetime' has no attribute 'date'

این جا دیگه نه تنها کمک نمیکنه بلکه باعث میشه اول فکر کنیم که خب آها حتما این ماژول قبلا attribute عه date رو داشته الان حذف کردن برم ببینم معادلش چیه! یا اگه مثلا datetime ماژولی بوده باشه که نصب کرده بودیم، حتما ما ورژن دیگه ای ازشو نصب کردیم... ما که عین کدی که مثلا توی ویدیو آموزشی هست رو داریم میزنیم چرا میگه date رو نداره ؟؟

ماجرا از این قرار هست که وقتی دارید یه ماژولی رو import میکنید ، پایتون یه مسیر های مشخصی رو برای پیدا کردن اون ماژول طی میکنه که میتونید با پرینت کردن sys.path ببینید :
import sys
for path in sys.path:
print(path)

توی ویندوز حدودا(ممکنه بسته به نسخه پایتون و platform ترتیبش فرق کنه یا کم و زیاد باشه ما کاری به این قضیه نداریم) به این شکل هست: (اون فولدر test فولدری هست که ماژولی که داره ران میشه داخلش هست)
C:\Users\usr\Desktop\test
C:\Users\usr\...\Python39\python39.zip
C:\Users\usr\...\Python39\DLLs
C:\Users\usr\...\Python39\lib
C:\Users\usr\...\Python39
C:\Users\usr\...\Python39\lib\site-packages

پایتون این مسیر هارو "به ترتیب" از بالا تا پایین چک میکنه تا ماژولی که اسمشو جلوی import statement زدیم رو پیدا کنه و "به محض" اینکه بهش برسه همون رو برای ما import میکنه.

به مسیر ها نگاه کنید: اولین مسیر همیشه مسیری هست که script شما داخلش هست. (اگه PYTHONPATH رو set کرده باشید به عنوان مسیر دوم بعد از current directory اضافه میشه به sys.path. ) یه مسیر دیگه اون folder عه site-packages هست که میبینید. این دقیقا همونجایی هست که پکیج هایی که نصب میکنید داخلش میرن. باقی مسیر ها هم برای ماژول های دیگه ی پایتون هستند.

حالا میتونیم دلیلشو متوجه بشیم چرا پایتون توی هر دو مثالی که اول پست زدیم اول پکیج مارو پیدا کرد... site-package و فولدر های دیگه ای که ماژول های خود پایتون داخلش هستن "بعد" از اولین مسیری که داخل لیست دیدیم سرچ میشن پس در نتیجه اینجا datetime داره اشاره میکنه به همین ماژولی که داریم توش کد میزنیم(مثال اول) یا اون ماژول datetime ای که کنار فایمون ساختیم (مثال دوم)... اونو پایتون پیدا میکنه و میاره و خب طبیعتا ما چیزی به عنوان "date" تعریف نکرده بودیم !!

اگه کمی کلی تر بخوایم بگیم در واقع "هرجا" از این مسیرها یه فایل datetime.py قرار بدیم که نسبت به اونی که پایتون توی استاندارد لایبرری قرار داده جلوتر باشه این مشکل رو داریم...

سوال: همه ی ماژول ها به همین صورت هستن ؟ نه ! اون هایی که داخل مفسر compile شدن شامل موارد بالا نمیشن یعنی اگه اسم فایلتون رو این اسامی بذارین باعث نمیشه پایتون اونارو پیدا کنه اول. اینجوری ببینینشون :
import sys
for i in sys.builtin_module_names:
print(i)
هیچوقت مثلا gc.py شما زود تر پیدا نمیشه.
درود.
میخواستم درباره ی آبجکت معروف و شناخته شده ی generator حرف بزنیم ولی با نگاه کمی متفاوت‌تر تا به این برسیم که دقیقا چطور کار میکنه و چطور پیداش شد. نیاز هست که کمی حرف های پیش نیاز بزنیم صبور باشید.

قبل از هر چیزی درباره ی خود فانکشن حرف بزنیم؛ ولی نه تو پایتون بلکه تو C:
وقتی یه فانکشنی کال میشه، توی call stack یک frame جدید میاد که برای اون فانکشن هست. این frame شامل تمام متغیر های لوکال و پارامتر های اون فانکشنه. وقتی فانکشن تموم میشه چه اتفاقی میفته؟ اون frame از stack پاپ میشه (یا دقیق ترش stack pointer کم میشه)
و نکته اینجاس که هرچی که توی اون frame هست دیگه قابل دسترس نیست و اگر استفادشون کنیم، undefined behavior هست. چرا؟ چون توی "مموری استک" این frame قرار داده شده بود و اون فضا الان آزاد شده و قابل استفاده هست برای بقیه (توی پرانتز، در C که مدیریت حافظه نداره، باید آبجکت هایی که توی heap میسازیم رو خودمون مدیریت کنیم نه استک):

int *returnArray() {
    int arr[3] = {11, 22, 33};
    printf("%p\n", arr);
    printf("%d\n", arr[1]);
    return &arr;
}
int main(void) {
    int *arr;
    arr = returnArray();
    printf("%p\n", arr);
    printf("%d\n", arr[1]); // ???
}


با اینکه آدرسش رو return کردیم ولی باز هم نمیتونیم به آیتم های لیست دسترسی داشته باشیم.
حالا اینارو گفتم که موضوع مهمی رو بگم. اونم اینه که تو پایتون هم همین call stack و اینا هست ولی اون frame object توی heap ساخته میشه. این یعنی اگر بخوایم میتونیم اون رو ذخیره داشته باشیم و همیشه بمونه! مثلا مانع از نابود شدن خودش و آبجکت های درونش بشیم. تو مثال زیر global f  رو اگه از کامنت در بیارید obj از بین نمیره چون frame رو ذخیره کردیم:
from gc import collect
from sys import _getframe

class A:
    def __del__(self):
        print("del called")

def fn():
    # global f
    f = _getframe(0)
    obj = A()

fn()
collect()
input()

خب حالا که اینو گفتیم بریم سراغ خود آبجکت فانکشن تو پایتون. وقتی فانکشن کال میشه یه frame object ساخته میشه. این frame object داخلش آبجکت های زیادی هست (مستقیم یا غیرمستقیم) از جمله رفرنس داره به متغیر های داخل اون namespace و رفرنسی داره به code object که یک unit ئه executable هست. داخل این code object ما bytecode ها رو داریم که همون instruction ها هستن.

درواقع instruction ها هستن که اجرا میشن و این state ذخیره میشه. تو کد زیر lasti یعنی last instruction. (توی cpu هم اتفاق مشابهی میفته. اینجا pvm میخواد بدونه چی رو اجرا کرده و حالا نوبت چیه):

from sys import _getframe
def fn():
    print(_getframe(0).f_lasti)
    a = 10
    print(_getframe(0).f_lasti)

fn()


خب حالا بخش جالب ماجرا اینجاست. ما به عنوان طراحان فرضی زبان پایتون، میدونیم که frame ما میتونه خارج از موقع کال شدن هم زنده بمونه + از طرفی به state هم که دسترسی داریم. ( اینکه الان متغیر های local چیا هستن، اینکه الان تا instruction چندم اجرا شده و غیره)

فقط یه مشکلی هست، فانکشن های ما وقتی کال میشن از اولین instruction تا آخرینش رو اجرا میکنن و تموم میشن و همه ی آبجکت های داخل اون frame از بین میرن (اگر رفرنس دیگه ای نداشته باشن جای دیگه).

الان همه چیز محیا هست برای اینکه یه ساختار یا keyword جدیدی بیاریم تو زبان که هرجایی از execution فانکشن خواستیم بتونیم pause کنیم و اون رو با هر state ای که داره به حال خودش رها کنیم.
بیایم yield رو معرفی کنیم! هروقت yield اومد، کافیه اجرا رو متوقف کنیم و مثل فانکشن ها (که بعد از تموم شدنشون، frame شون از stack frame جدا میشن)  frame این generator ها رو هم جدا کنیم.

بعدا اگه خواستیم generator رو ادامه بدیم و روش next بزنیم (مستقیم خودمون یا غیر مستقیم توسط پایتون) تنها کاری که باید بکنیم اینه که frameش رو برداریم و بچسبونیم به stack frame ممون و از اون state ای که بودیم ادامه بدیم.
def gen():
    a = 1
    yield
    b = 1
    yield

g = gen()
next(g)
print(g.gi_frame.f_lasti, g.gi_frame.f_locals)
next(g)
print(g.gi_frame.f_lasti, g.gi_frame.f_locals)


این call stack با linked list پیاده سازی شده و frame ها نود های اون هستن. با f_back به frame قبلی اشاره میکنن به راحتی وصل میشن و جدا میشن.

جنریتور ها با وجود سرعت خوبی که دارن، برای سرعت بیشتر ساخته نشدن بلکه برای استفاده بهینه‌تر از مموری ساخته شدن. داشتن همچین آبجکتی (به اضافه ساختار هایی مثل yield from) میتونه زمینه خیلی چیز ها رو فراهم کنه. از جمله فریموورک هایی مثل asyncio :)
پارو، aur helper ای که ما پیشفرض توی پارچ داریمش و یک aur helper نوشته شده در راست، یک ابزار شخص ثالثی براش هست به نام paruz که میاد و با fzf همچین چیزی میسازه.


اینطوری خیلی راحت تر میتونید دنبال بسته هاتون با paru بگردید.
نصبش:
paru -S paruz


به زودی paruz رو توی پارچ به همراه چندتا بسته دیگه به صورت پیشفرض قرار خواهم داد.