Midnight Commit
105 subscribers
3 photos
1 file
43 links
Beyond Syntax.
The fundamentals behind great software.
Download Telegram
چطور کلاس‌هامون مثل آبجکت‌های داخلی پایتون رفتار کنن؟ 🤔
وقتی اولین کلاس‌هامون رو توی پایتون می‌نویسیم، معمولاً تمام تمرکزمون روی __init__ و نگهداری داده‌هاست.
ولی پشت صحنه، یه مفهوم خیلی مهم وجود داره که باعث میشه آبجکت‌های ما مثل آبجکت‌های داخلی خود پایتون رفتار کنن:
Python Data Model همون چیزیه که باعث میشه بتونیم از len()، عملگرها، حلقه‌ها، sorted() و کلی قابلیت دیگه روی کلاس‌هامون استفاده کنیم.
یه مثال ساده 👇
فرض کنید یه کلاس برای یک دسته کارت (Deck) نوشتیم:
from collections import namedtuple

Card = namedtuple("Card", ["rank", "suit"])


class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list("JQKA")
suits = "spades diamonds clubs hearts".split()

def __init__(self):
self._cards = [
Card(rank, suit)
for suit in self.suits
for rank in self.ranks
]

def __len__(self):
return len(self._cards)

def __getitem__(self, position):
return self._cards[position]

فقط دو تا Special Method پیادlenکردیم:
__len__
__getitem__
اما همین دو تا کلی قابلیت به کلاس ما اضافه می‌کنن. 😄

چه قابلیت‌هایی رایگان گرفتیم؟ 🎁
len(deck) ← به لطف __len__
دسترسی با اندیس (deck[0]) ← به لطف __getitem__
برش (Slicing) ← به لطف __getitem__
پیمایش با for ← به لطف __getitem__
استفاده از in ← به لطف __getitem__
random.choice() ← به لطف __getitem__
بدون اینکه هیچ‌کدوم از این قابلیت‌ها رو مستقیماً پیاده‌سازی کرده باشیم!

اصلاًleneciiterdهاgetitemتدهایی مثل:
__len__()
__iter__()
__getitem__()
__contains__()
__repr__()
__str__()
__eq__()
__hash__()

قرار نیست مستقیماً توسط ما صدا زده بشن.
مثلاً وlenنویسیم:
len(obj)

پایتون پشت صحنه این رو اجرا می‌کنه:
obj.__len__()

همین داستان برای عملگرها هم صدق می‌کنه.
a + b

در واقع میشه:
a.__add__(b)

یعنی پایتون با استفاده از Data Model می‌فهمه هر آبجکت باید چطور رفتار کنه.
نکته‌ی جالب اینجاست که تقریباً هر قابلیتی که از آبجکت‌های داخلی پایتون انتظار داریم، از طریق همین Special Methodها تعریف شده. یعنی وقتی این قراردادها رو رعایت کنیم، کلاس‌هامون هم به شکل طبیعی با کل اکوسیستم پایتون هماهنگ می‌شن. 🚀

حرف اصلی Python Data Model 💡
هدف این نیست که برای هر قابلیت، متدهای جدیدی طراحی کنیم.
هدف اینه که آبجکت‌هامون از قراردادهای پایتون پیروی کنن تا به‌صورت طبیعی با کل اکوسیستم پایتون هماهنگ باشن.
به همین دلیله که خیلی از قابلیت‌های پایتون، به جای اینکه بر اساس نوع (Type) یک شیء کار کنن، بر اساس رفتار (Behavior) اون تصمیم می‌گیرن.

💡در نهایت هدف Python Data Model فقط اضافه کردن چند قابلیت به کلاس‌ها نیست؛ هدف اینه که کلاس‌هامون شهروندهای درجه‌یک اکوسیستم پایتون باشن. این کار هم تجربه‌ی توسعه برای خودمون رو بهتر می‌کنه، هم استفاده و توسعه‌ی کد رو برای بقیه ساده‌تر و طبیعی‌تر؛ مخصوصاً وقتی در حال ساخت یک کتابخانه یا Framework باشیم. 🚀
#️⃣ #Python #FluentPython


🔈 MidnightCommit 🌙
چرا همه‌ی Sequenceهای پایتون شبیه هم نیستن؟ 🤔
وقتی از Sequenceها صحبت می‌کنیم، معمولاً ذهنمون میره سمت list، tuple، str یا bytes.
اما پشت صحنه، پایتون اون‌ها رو به دو دسته‌ی اصلی تقسیم می‌کنه.
-📦 ‏Container
-‏📄 Flat Sequences
تفاوت این دو، فقط در API یا Immutable بودنشون نیست؛ بلکه به نحوه‌ی ذخیره شدن داده‌ها در حافظه برمی‌گرده.

📦 Container Sequences
‏Container Sequenceها داده‌ها رو مستقیماً نگهداری نمی‌کنن؛ بلکه Reference به آبجکت‌های دیگه رو ذخیره می‌کنن.
مثل:
- ‏list
- ‏tuple
- ‏collections.deque
هر عنصر داخل این Sequenceها یک آبجکت مستقل در حافظه است که Header مخصوص خودش (شامل نوع، Reference Count و سایر متادیتاهای داخلی) و همچنین فضای مربوط به داده‌ی خودش رو داره.
در واقع، خود list فقط آرایه‌ای از Pointerها رو نگهداری می‌کنه و هر Pointer به یک آبجکت پایتونی اشاره می‌کنه.
به همین دلیل می‌تونیم داخل یک list همزمان int، str، dict یا حتی یک list دیگه داشته باشیم.

📄 Flat Sequences
‏Flat Sequenceها رویکرد متفاوتی دارن.
در این نوع Sequenceها، داده‌ها به‌صورت پشت سر هم (Contiguous) و بدون نگهداری Reference برای هر عنصر ذخیره میشن.
مثل:
- ‏str
- ‏bytes
- ‏bytearray
- ‏array.array
اینجا خبری از Header جداگانه برای هر عنصر نیست؛ چون عناصر آبجکت‌های مستقل پایتونی محسوب نمیشن و فقط مقدار خام (Raw Value) اون‌ها در حافظه ذخیره میشه.
همین موضوع باعث میشه Flat Sequenceها هم حافظه‌ی کمتری مصرف کنن و هم هنگام پیمایش یا پردازش، به دلیل Locality بهتر حافظه، عملکرد بالاتری داشته باشن.

تفاوت اصلی 💡
📦 ‏Container Sequence
عتاصر به صورت Reference ذخیره میشن.
هر عنصر یک آبجکت مستقل با header مخصوص خودشه.
امکان نگهداری انواع مختلف داده وجود داره.
انعطاف پذیری بیشتری دارن.

📄 ‏Flat Sequence
داده ها مستقیما در حافظه ذخیره میشن.
عناصر header جداگانه ندارن.
معمولا همه ی عناصر از یک نوع هستن.
مصرف حافظه کمتر و performance بهتر.

چرا این موضوع مهمه؟
در پروژه‌های معمولی شاید تفاوت محسوسی احساس نکنیم.
اما وقتی با میلیون‌ها داده، پردازش‌های سنگین یا توسعه‌ی کتابخانه‌هایی که Performance در اون‌ها اهمیت داره سروکار داریم، انتخاب بین یک Container Sequence و یک Flat Sequence می‌تونه روی مصرف حافظه، سرعت اجرای برنامه و حتی طراحی API تأثیر قابل‌توجهی بذاره.
گاهی انتخاب درست یک Structure، از بهینه‌سازی‌های پیچیده‌ی بعدی ارزشمندتره.

📚 منبع این مطلب فصل Overview of Built-in Sequences از کتاب Fluent Python هست.

#️⃣ #Python #FluentPython


🌙 CHANNEL | GROUP
تا وقتی پروژه‌های کوچیک می‌نویسیم، معمولاً فقط چند متد معروف مثل append() یا pop() رو استفاده می‌کنیم.
اما list و tuple متدها و قابلیت‌های بیشتری دارن که بعضی از اون‌ها کمتر شناخته شدن، در حالی که می‌تونن کد رو خواناتر و حتی ساده‌تر کنن.
یه Cheat Sheet از متدهای مهم list و tuple آماده کردم که امیدوارم به دردتون بخوره. 🚀

#️⃣ #Python


🌙 CHANNEL | GROUP
چرا بعضی object ها در Python hashable هستن و بعضی‌ها نه؟ 🤔
اگر تا حالا با خطایی شبیه این مواجه شده باشین:
TypeError: unhashable type: 'list'

احتمالاً براتون سؤال پیش اومده که اصلاً hashable بودن یعنی چی؟
و مهم‌تر از اون، چرا پایتون برای بعضی object ها این محدودیت رو گذاشته؟

‏Hash چیه؟ 🧠

‏Hash یک عدد صحیحه که از روی وضعیت یک object تولید میشه.
توی پایتون می‌تونیم با تابع hash() ببینیمش:
hash("hello")
hash((1, 2, 3))

نکته مهم اینجاست که Hash قرار نیست محتوای object رو ذخیره کنه یا بتونیم از روش داده اصلی رو بازسازی کنیم.
هدف Hash اینه که یک شناسه سریع و قابل اعتماد از وضعیت فعلی یک object تولید کنه.

‏Hashable بودن یعنی چی؟ 📦

یک object زمانی Hashable محسوب می‌شه که:
1. دارای مقدار Hash باشه.
2. مقدار Hash اون در طول عمر object تغییر نکنه.
3. رفتارش با متد __eq__ سازگار باشه.

در واقع اگر دو object برابر باشن:
a == b

باید Hash یکسانی هم داشته باشن:
hash(a) == hash(b)


چرا همه object ها Hashable نیستند؟
اینجا مفهوم Immutable بودن اهمیت پیدا می‌کنه.
فرض کنید یک List قابل Hash شدن بود:
items = [1, 2, 3]

اگر Hash این object محاسبه بشه و بعد محتویات List تغییر کنه:
items.append(4)

دیگر Hash قبلی نماینده وضعیت فعلی object نخواهد بود.
به همین دلیل اکثر object های Mutable مثل:
- ‏list
- ‏dict
- ‏set
به صورت پیش‌فرض Hashable نیستن.
در مقابل Objectهای Immutable مثل:
- ‏int
- ‏str
- ‏bytes
- ‏tuple
معمولاً Hashable هستن.

چطور یک آبجکت Hashable بسازیم؟ 🛠

برای ساخت یک آبجکت Hashable باید دو متد رو تعریف کنیم:
__eq__
__hash__
مثلاً:
class User:
def __init__(self, username):
self.username = username

def __eq__(self, other):
return self.username == other.username

def __hash__(self):
return hash(self.username)

توی این مثال Hash بر اساس username تولید می‌شه.
>>> denver = User("denver")
>>> hash(denver)
6843638685520863834
>>>


نکته مهم اینه که فیلدی که برای Hash استفاده می‌کنیم نباید بعداً تغییر کنه؛ در غیر این صورت رفتار object غیرقابل پیش‌بینی می‌شه.

چرا Hashable بودن مهمه ؟ 💡

بخش بزرگی از ساختارهای داده و مکانیزم‌های پرکاربرد پایتون بر پایه Hash ساخته شدن.
از جمله:
- ‏dict
- ‏set
- ‏frozenset
- بسیاری از سیستم‌های Cache و Memoization

وقتی یه hashable, object باشه، پایتون می‌تونه اون رو به سرعت پیدا، مقایسه و ذخیره کنه.
به همین دلیل Hashable بودن فقط یک ویژگی کوچیک توی Python Data Model نیست؛ بلکه یکی از مفاهیم بنیادی‌ای هست که بخش بزرگی از Performance ساختارهای داده پایتون بر پایه اون شکل گرفته.

#️⃣ #Python #Programming


🌙 CHANNEL | GROUP
چرا در Python می‌تونیم Functionها رو مثل داده جابه‌جا کنیم؟ 🤔
بعضی از قابلیت‌های پایتون اونقدر طبیعی به نظر می‌رسن که اصلاً بهشون فکر نمی‌کنیم.
مثلاً اینکه یک Function رو به Function دیگه پاس بدیم، داخل یک متغیر ذخیره کنیم یا حتی از یک Function برگردونیم.
اما پشت این سادگی، یکی از مهم‌ترین ویژگی‌های طراحی Python قرار گرفته.

‏Higher-Order Function یعنی چی؟ 🧠
یک Function زمانی Higher-Order محسوب میشه که حداقل یکی از این دو ویژگی رو داشته باشه:
- یک Function دیگه رو به عنوان آرگومان دریافت کنه.
- یک Function دیگه رو به عنوان خروجی برگردونه.
برای مثال:
sorted(users, key=len)

در اینجا len خودش یک فانکشن هست که به عنوان آرگومان به sorted پاس داده شده.
بنابراین sorted یک Higher-Order Function محسوب میشه.

چرا این امکان توی Python وجود داره؟ ⚙️
دلیلش اینه که توی پایتون، فانکشن ها First-Class Object هستن.
یعنی Functionها تقریباً مثل هر Object دیگه‌ای رفتار می‌کنن:
می‌تونن داخل متغیر ذخیره بشن.
می‌تونن داخل Collectionها قرار بگیرن.
می‌تونن به Functionهای دیگه پاس داده بشن.
می‌تونن از Functionها برگردونده بشن.
مثلاً:
def greet():
print("Hello")

handler = greet

اینجا handler و greet به یک Function اشاره می‌کنن.

این موضوع چه فایده‌ای داره؟ 💡
در نگاه اول ممکنه صرفاً یک قابلیت جالب به نظر برسه.
اما بخش بزرگی از قابلیت‌های مدرن Python بر پایه‌ی همین ایده ساخته شدن:
- ‏Decoratorها
- ‏Callbackها
- ‏Strategy Pattern
- ‏Event Handling
- ‏Functional Programming Tools
در واقع وقتی Functionها هم مثل داده قابل جابه‌جایی باشن، می‌تونیم رفتار برنامه رو به شکل بسیار انعطاف‌پذیرتری طراحی کنیم.

‏Decoratorها از کجا میان؟ 🎭
یکی از معروف‌ترین کاربردهای Higher-Order Functionها، Decoratorها هستن.
‏Decoratorها در اصل Functionهایی هستن که یک Function دیگه رو دریافت می‌کنن و نسخه‌ی جدیدی از اون رو برمی‌گردونن.
به همین دلیل فهم Decoratorها بدون درک Higher-Order Functionها معمولاً باعث میشه فقط نحوه‌ی استفاده از اون‌ها رو یاد بگیریم، نه دلیل کار کردنشون رو.

جمع‌بندی ✍️
‏Higher-Order Function ها نتیجه مسقتیم این هستن که فانشکن ها توی پایتون، First-Class Object محسوب میشن.
و همین ویژگی پایه‌ی بسیاری از مفاهیم مهم زبان، از جمله Decoratorها، محسوب میشه.
گاهی پشت یک قابلیت ظاهراً ساده، یکی از بنیادی‌ترین ایده‌های طراحی زبان قرار گرفته.

📚 منبع این مطلب فصل Higher-Order Functions از کتاب Fluent Python هست.

#️⃣ #Python #Programming #FluentPython


🌙 CHANNEL | GROUP
چرا نباید از Mutableها به عنوان Default در Python استفاده کنیم؟ 🤔
توی Python، مقدارهای default آرگومان‌های تابع فقط یک‌بار و در زمان تعریف تابع ساخته میشن، نه هر بار که تابع اجرا بشه. این یعنی اگر از یک object mutable مثل list یا dict به عنوان مقدار پیش‌فرض استفاده کنیم، اون object بین تمام callهای تابع shared باقی می‌مونه.

این موضوع در ظاهر شاید مشکلی ایجاد نکنه، اما وقتی تابع چند بار صدا زده میشه، همون state قبلی حفظ میشه و روی نتیجه‌ی callهای بعدی تأثیر می‌ذاره. در واقع تابع به جای اینکه مستقل و بدون وابستگی به گذشته باشه، وارد یک وضعیت stateful ناخواسته میشه.

ریشه‌ی این رفتار به این برمی‌گرده که پایتون، default argumentها رو در زمان تعریف تابع evaluate می‌کنه، نه در زمان اجرا. برای همین object مربوط به default، ثابت می‌مونه و فقط mutate میشه، نه اینکه دوباره ساخته بشه.

به همین دلیل استفاده از mutableها به عنوان default می‌تونه باعث bugهای خیلی subtle و سخت‌دیباگ بشه. راه استاندارد اینه که به جای اون‌ها از None استفاده کنیم و داخل تابع object جدید بسازیم تا هر بار یک state کاملاً مستقل داشته باشیم.

#️⃣ #Python #Programming


🌙 CHANNEL | GROUP
Python Memory Management 🧠
وقتی توی Python یه Object می‌سازیم، مثلاً یه List یا یه Instance از یک Class، این Object باید جایی توی Memory قرار بگیره. Python خودش مسئول مدیریت این Memory هست و برخلاف زبان‌هایی مثل C، معمولاً لازم نیست ما دستی مشخص کنیم چه زمانی Memory Allocate یا Free بشه.
اما اینکه Python خودش این کارها رو انجام میده به این معنی نیست که Memory Management ساده هست یا یسری مفاهیم رو از داده. پشت این رفتار، مفاهیمی مثل Reference Counting، Garbage Collection، Memory Allocator و Heap وجود دارن که با هم مشخص می‌کنن Objectها کجا قرار بگیرن، چه زمانی دیگه مورد استفاده نیستن و چه زمانی Memory مربوط به اون‌ها آزاد بشه.

Python Objectها کجا قرار می‌گیرن؟ 📦
توی CPython، آبجکت که توی برنامه می‌سازیم معمولاً روی Heap قرار می‌گیرن. وقتی مثلاً می‌نویسیم users = []، پایتون برای ساختن List یک Object ایجاد می‌کنه و Memory مورد نیازش رو از Allocator خودش دریافت می‌کنه.
این Memory با Stack اشتباه گرفته نشه. Variableای که اسم users رو نگه داشته، در واقع یک Reference به Objectهست و خود List یک Object مستقل روی Heap محسوب میشه. به همین دلیل چند Variable می‌تونن همزمان به یک Object اشاره کنن.

‏Reference Counting 🔢
یکی از مهم‌ترین بخش‌های Memory Management تو پایتون، Reference Counting هست. هر Object یک شمارنده داره که تعداد Referenceهایی که به اون Object اشاره می‌کنن رو دنبال می‌کنه. وقتی یه Reference جدید به Object اضافه میشه، این Count افزایش پیدا می‌کنه و وقتی Reference از بین میره، Count کاهش پیدا می‌کنه.
وقتی Reference Count یه Object به صفر برسه، یعنی دیگه هیچ چیزی از طریق Reference به اون Object دسترسی نداره و CPython می‌تونه Memory مربوط به اون Object رو آزاد کنه.

پس Garbage Collector برای چیه؟ ♻️
اگه Reference Counting داریم، شاید این سؤال پیش بیاد که پس Garbage Collector دیگه چه کاری انجام میده؟
مشکل زمانی ایجاد میشه که Objectها به صورت Circular به هم Reference داشته باشن. مثلاً فرض کنین آبجکت A به B اشاره کنه و B هم به A. حالا ممکنه هیچ Reference خارجی به این دو Object وجود نداشته باشه، اما Reference Count هر دو همچنان بیشتر از صفر باقی بمونه.
در نتیجه Reference Counting به تنهایی نمی‌تونه بفهمه این Objectها دیگه قابل استفاده نیستن. Garbage Collector مخصوصاً برای پیدا کردن و جمع کردن همین Reference Cycleها وارد عمل میشه.

Python Memory Allocator ⚙️
Python هم برای گرفتن و آزاد کردن Memory مستقیماً برای تک‌تک Objectها با سیستم‌عامل درگیر نمیشه. ‏CPython یک Memory Allocator داره که مدیریت Memory مورد نیاز Objectهای Python رو بهینه‌تر انجام میده. برای Objectهای کوچیک، CPython از مکانیزم‌هایی مثل pymalloc استفاده می‌کنه تا به جای اینکه برای هر Allocation مستقیماً سراغ سیستم‌عامل بره، Memory رو تو بلوک‌های بزرگ‌تر دریافت و خودش مدیریت کنه.
این کار باعث میشه Allocation و Deallocation تعداد زیادی Object کوچیک سریع‌ تر انجام بشه و هزینه‌ی تعامل مستقیم با سیستم‌عامل کمتر بشه.

چرا Memory همیشه به سیستم‌عامل برنمیگرده؟ 🤔
یکی از چیزهایی که ممکنه موقع بررسی Memory یک برنامه‌ی Python گیج‌کننده باشه اینه که وقتی یک Object رو حذف می‌کنیم، ممکنه مقدار Memory مصرفی Process بلافاصله به همون اندازه کاهش پیدا نکنه.
دلیلش اینه که آزاد شدن یک Object لزوماً به معنی پس دادن فوری اون Memory به سیستم‌عامل نیست. Python ممکنه Memory آزاد شده رو داخل Allocator خودش نگه داره تا بعداً برای Objectهای جدید ازش استفاده کنه.
پس ممکنه Object از بین رفته باشه، اما Process همچنان مقدار زیادی Memory از سیستم‌عامل گرفته باشه. این دو موضوع با هم تناقضی ندارن.

🧩Part01


#️⃣ #programming #backend #python


🌙 CHANNEL | GROUP
حالا del دقیقاً چی کار می‌کنه؟ 🗑
del مستقیماً به معنی «این Object رو از Memory پاک کن» نیست. وقتی می‌نویسیم:
del users

در واقع Reference با اسم users رو حذف می‌کنیم. اگه Reference دیگه‌ای به Object وجود داشته باشه، Object همچنان باقی می‌مونه.
اگه این آخرین Reference باشه، در CPython معمولاً Reference Count به صفر می‌رسه و Object قابل Deallocation میشه. اما همون‌طور که گفتیم، Memory آزادشده ممکنه همچنان توسط Python Allocator نگه داشته بشه و فوراً به سیستم‌عامل برنگرده.

‏Memory Leak توی
Python هم داریم؟ ⚠️
‏پایتون Garbage Collector داره، اما این به معنی غیرممکن بودن Memory Leak نیست. اگه برنامه به Objectهایی Reference نگه داره که دیگه واقعاً بهشون نیاز نداره، Garbage Collector نمی‌تونه اون‌ها رو حذف کنه، چون از دید Python هنوز قابل دسترس هستن.
مثلاً یک Cache بدون محدودیت، یک Global Collection که دائماً بزرگ‌تر میشه یا نگه داشتن Reference به Objectهای قدیمی می‌تونه باعث بشه Memory مصرفی برنامه به مرور افزایش پیدا کنه.
پس Garbage Collector فقط Objectهایی رو جمع می‌کنه که واقعاً دیگه قابل دسترسی نیستن. اگه خود برنامه همچنان Reference رو نگه داشته باشه، GC نمی‌تونه تشخیص بده که ما دیگه به اون Object احتیاج نداریم.

‏جمع‌بندی ✍️
‏Memory Management تو Python ترکیبی از چند بخش مختلفه. Objectها روی Python Heap قرار می‌گیرن، Reference Counting بیشتر Objectهای بدون Reference رو مدیریت می‌کنه و Garbage Collector برای پیدا کردن Reference Cycleها وارد عمل میشه.
از طرف دیگه، CPython با Memory Allocator خودش Allocationهای کوچک رو مدیریت می‌کنه و Memory آزادشده رو لزوماً بلافاصله به سیستم‌عامل برنمی‌گردونه.
به همین دلیل وقتی درباره‌ی Memory حرف می‌زنیم، فقط اینکه «Python خودش Garbage Collection داره» تصویر کاملی بهمون نمیده. باید بدونیم Referenceها، Objectها، Allocator و Garbage Collector چطور کنار هم کار می‌کنن.

🧩Part02


#️⃣ #programming #backend #python


🌙 CHANNEL | GROUP