<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Vahid</title>
        <link>https://virgool.io/feed/@vahid_fathi</link>
        <description>یه وحید از نوع برنامه نویسش :)</description>
        <language>fa</language>
        <pubDate>2026-06-16 16:02:26</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/80966/avatar/EQB86P.jpeg?height=120&amp;width=120</url>
            <title>Vahid</title>
            <link>https://virgool.io/@vahid_fathi</link>
        </image>

                    <item>
                <title>آشنایی عمیق تر با Timescale DB</title>
                <link>https://virgool.io/@vahid_fathi/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%B9%D9%85%DB%8C%D9%82-%D8%AA%D8%B1-%D8%A8%D8%A7-timescale-db-r6p90aomdl1d</link>
                <description>مقدمهاگر با داده‌های بر اساس زمان سر و کار داشته باشید، می‌دونید که پایگاه‌داده‌های RDBMS موقع جستجو زیاد عملکرد خوبی ندارن و می‌تونن کُند باشند(مخصوصا توی حجم دیتای بالا). برای حل این مشکل، پایگاه‌داده‌های بر اساس زمان(time series database) به وجود اومدند. این پایگاه‌داده‌ها نحوه ذخیره‌سازیشون متفاوته و به جای اینکه دیتا رو به صورت سطری ذخیره کنند، به صورت ستونی ذخیره می‌کنند. اگر هم بخوایم چند تا نمونه از این پایگاه‌داده‌ها بگیم، می‌تونیم به click house، InfluxDB، QuestDB و timescale db اشاره کنیم. از بین این موارد که ما به timescale db علاقه مندیم. دلیلش هم اینه که، این پایگاه داده اومده از postgres استفاده کرده! اینکه چطوری یک پایگاه داده RDBMS می‌تونه همچین چیزی رو پشتیبانی کنه سوالی بود که منتج به این مطلب شد.https://thenewstack.io/time-series-data-care/TimescaleDBشاید اگر کسی اسم TimescaleDB رو بشنوه با خودش فکر کنه که داره اسم یک پایگاه داده رو می‌شنوه(منظور یک چیزی مثل mysql یا mongodb)، ولی در واقعیت TimescaleDB یک افزونه(extension) برای postgres هستش و روی این پایگاه داده نصب میشه همین! اتفاقا TimescaleDB خیلی با این موضوع هم حال می‌کنه و میگه که من از تجربه ۳۰ سال توسعه پستگرس استفاده می‌کنم پس به من اعتماد کنید(آیا واقعا میشه بهش اعتماد کرد؟ این موضوع یک بحث دیگه‌ست). اما خب پستگرس چطوری تونسته این زیرساخت رو برای TimescaleDB فراهم کنه؟ آیا بقیه RDBMS ها نمی‌تونن؟ اینکه آیا بقیه RDBMS ها می‌تونن یا نه؟ خودم اطلاع دقیقی ازش ندارم(یک سرچ ریزی که زدم برای معروف‌ها چیزی ندیدم. اگر کسی می‌دونه خوشحال میشم دانشش رو با ما هم توی کامنت‌ها به اشتراک بزاره). منطقی هم هستش که مورد به مورد بررسی بشه ولی در لحظه زیاد برای ما مهم نیست چون می‌خوایم درمورد TimescaleDB حرف بزنیم که اونم داره از پستگرس استفاده می‌کنه. پس بریم ببینیم که پستگرس برای این موضوع چه کرده.Hypertablesهایپرتیبل یک نوع از جدول‌هایی که پستگرس تو خودش داره و به خاطر بعضی ویژگی‌هایی که دارن کار رو برای مدیریت داده‌های زمانی رو آسون می‌کنن. تازه هر کاری که با یک جدول عادی توی پستگرس انجام میدیم رو توی هایپرتیبل‌ها هم می‌تونیم انجام بدیم. خب فهمیدیم کلمه کلیدی ماجرا hypertable هستش ولی هنوز متوجه نشدیم که این هایپرتیبل ها چی دارن که کار کردن با داده‌های زمانی رو برای ما آسون می‌کنه؟ به طور خاص یک ویژگی اساسیه که این موضوع رو راحت می‌کنه و اون چیزی نیست جر Time partitioning. البته یک ویژگی دیگه هم داره به اسم Space partitioning ولی خب نکته اساسی ماجرا همون تقسیم بندی براساس زمانه.اگر بخوایم بیشتر بریم تو عمق ماجرا، اینطوریه که وقتی ما یک جدول از نوع hypertable می‌سازیم، در واقع این جدول تفاوتش با جدول عادی در اینکه حتما یک فیلد بر اساس زمان داره. اصلا ما امکان ساخت هایپرتیبل‌هارو به صورت مستقیم نداریم یعنی همچین چیزی امکان پذیر نیست و خطا می‌گیریم:CREATE HYPERTABLE table_name(
   column1 datatype,
   column2 TIMESTAMPTZ,
   ...
   PRIMARY KEY( one or more columns )
);برای ساخت hypertable ما باید اول یک جدول عادی بسازیم:CREATE TABLE table_name(
   column1 datatype,
   column2 TIMESTAMPTZ,
   .....
   columnN datatype,
   PRIMARY KEY( one or more columns )
);بعد بیایم این جدول رو به hypertable تبدیلش کنیم. نکته اساسی هم اینه که جدولی که می‌خوایم این اتفاق براش بیوفته، حتما یک فیلد بر اساس زمان داشته باشه(توی مثال بالا column2). این تبدیل هم به این شکل انجام میشه:SELECT create_hypertable(&#039;table_name&#039;, &#039;time_column_name&#039;);این تبدیل حتی برای جدول‌هایی که از قبل دیتا دارن هم امکان پذیره(باید موقع تبدیل متغیر migrate_data رو بفرستیم). میزان اطلاعات جدول هم هر چی بیشتر باشه، مدت زمان این تبدیل بیشتر میشه.ساختار داخلی hypertableپس فهمیدیم که hypertable‌ها بر اساس زمان(time) و فضا(space) کار می‌کنه. این نکته وقتی جالب‌تر میشه که بدونیم:موقعی که ما یک hypertable می‌سازیم، به صورت خودکار بر اساس زمان تقسیم‌بندی میشه(به صورت دلخواه میشه براساس space هم این کار رو کرد). در واقع هر hypertable یک از سری جدول‌های کوچک‌تر به اسم chunk تشکیل شده. این بخش‌های کوچیک یک بازه زمانی رو دربرمی‌گیرن که فقط دیتا‌های اون بازه زمانی رو تو خودشون دارن. اگر هم hypertable براساس فضا(space) تقسیم‌بندی شده باشه، هر chunk یک بخشی از مقادیر اون فضا رو خواهد داشت. شکل پایین رو یک نگاه بندازین:https://docs.timescale.com/timescaledb/latest/how-to-guides/hypertables/about-hypertables/سوال بعدی که ممکنه پیش بیاد اینه که ایا سایز این بخش‌ها(chunkها) قابل تنظیم هستش یا نه؟ که جواب کوتاه بله ست. اما تعیین سایز این بخش‌ها نقش مهمی رو توی سرعت کوئری‌های ما داره. طبیعتا اینکه بخش‌های ما یک هفته ای باشه یا یک روزه خیلی با هم فرق داره. برای این کار متغیر chunk_time_interval به ما کمک می‌کنه. طبق مستندات TimescaleDB مقدار توصیه شده برای این متغیر عبارت است از: ۲۵٪ از حافظه اصلی برای یک بخش(chunk). یعنی ۲۵ درصد از حافظه اصلی توی بشه یک بخش(chunk) دیتا یا به عبارت دیگه یک بخش دیتا ۲۵٪ حافظه اصلی رو پر کنه. مثلا اگر به طور تقریبی روزانه ۲ گیگ دیتا رو خواهید داشت و حافظه اصلی‌تون هم ۶۴ گیگه، هر بخش(chunk) رو می‌تونیم ۱ هفته بزاریم یا اگر به صورت روزانه ۱۰ گیگ دیتا داشته باشیم و همون ۶۴ گیگ، سایز بخش‌هامون ۱ روز باشه، کوئری‌هامون بهتر میشه.ایندکس‌ها هم توی سایز بخش‌ها تاثیر دارن. مخصوصا ایندکس‌های سنگین مثل PostGIS. برای مطالعه بیشتر اینجا رو مطالعه کنید.برای مطالعه بیشتر در مورد سایز بخش‌ها اینجا و اینجا می‌تونه کمک کننده باشه.درمورد space partitioning چون اختیاریه و مطلب هم طولانی میشه چیزی نمیگم ولی از اینجا می‌تونید درموردشون مطالعه داشته باشید. توصیه ام اینه حتما یک نگاه حتی سرسری هم که شده بندازین.Bucket &amp; Originاین دو مفهوم قالب(bucket) و مبدا(origin) خودشون رو موقع واکشی(fetch) اطلاعات نشون میدن. به عنوان مثال وقتی که می‌خوایم اطلاعات چند ماه اخیر رو در قالب هفتگی داشته باشیم. TimescaleDB این ویژگی رو داره که بازه زمانی رو بهش بدی و بگی اطلاعات در قالب‌های روزانه، هفتگی و ماهانه و سالانه بهم برگردون. اما این موضوع رو چرا مطرح کردم؟ به خاطر تفاوت تقویم شمسی و میلادی. در تقویم میلادی شروع هفته دوشنبه‌ست(منظور اکثر کشورهاست) یعنی وقتی به TimescaleDB میگیم اطلاعات در قالب هفتگی بهمون بده قالب‌ها(bucket) از روز دوشنبه شروع میشن یا این موضوع در قالب ماهانه هم وجود داره و ماه‌های میلادی مدنظر قرار می‌گیرن. برای حل این مشکل TimescaleDB با پارامتر مبدا(origin) کار می‌کنه. به صورت پیشفرص مقادیر این پارامتر برای قالب‌های قرن و سال و ماه از اول ماه ژانویه(January) سال ۲۰۰۰ و برای بقیه قالب‌ها از ۳ ژانویه(January) سال ۲۰۰۰ تنظیم شده. پس ما برای تغییر و گرفتن اطلاعات در قالب درست باید این پارامتر origin رو عوض کنیم.نکته آخر هم اینکه TimescaleDB این امکان رو هم میده که برای قالب‌ها در بازه‌های زمانی که ممکنه دیتا نداشته باشیم، از gapfill استفاده کنیم و با صفر برگردونه، ولی فکر کنم این پارامتر origin برای gapfill هنوز پیاده‌سازی نشده.بخش پایانیدرمورد TimescaleDB نکات زیاده، مثلا ما اصلا درمورد فشرده‌سازی دیتا اصلا هیچ حرفی نزدیم یا انتقال اطلاعات، یا replication یا distribute hypertable‌ها و خیلی چیزهای دیگه(طبیعتا منم بلد نیستم تا در موردشون حرف بزنم D:).ولی مستندات و بلاگ خوبی دارن که توضیحات رو اونجا دادن.منابعhttps://docs.timescale.com/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Thu, 18 May 2023 13:02:25 +0330</pubDate>
            </item>
                    <item>
                <title>انواع index در پستگرس</title>
                <link>https://virgool.io/@vahid_fathi/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-index-%D8%AF%D8%B1-%D9%BE%D8%B3%D8%AA%DA%AF%D8%B1%D8%B3-xe9yqdmd330u</link>
                <description>مقدمهکلمه پایگاه داده در این مطلب به پایگاه داده‌های رابطه(RDBMS) اشاره دارد.استفاده از index باعث نشانه‌گذاری و پیدا کردن سریع‌تر رکوردها پایگاه‌داده ما خواهد شد. به صورت پیشفرض وقتی یک ستونی دارای index نباشد، به هنگام جستجوی بر اساس اون ستون، پایگاه‌داده شروع به بررسی تک‌تک مقادیر اون ستون در سطرهای جدول می‌کنه، که طبیعتا با افزایش حجم داده‌های جدول این جستجو روز به روز کندتر میشه. برای همین منظور توسعه دهندگان پایگاه‌داده‌ها با معرفی index گذاری سعی در رفع این مشکل کردند. از اونجایی که postgresql هم یکی از پایگاه‌داده‌های محبوبه و استفاده زیادی دارد، چه بهتر که با انواع مختلف ایندکس(index) گذاری تو این پایگاه داده بیشتر آشنا بشیم.انواع indexهابه طور کلی postgres دارای ۶ نوع ایندکس‌گذاری می‌باشد.B-TreeGINGiSTBrinHashSPGiSTBloom(in contrib)که ما با سه‌تای آخری کاری نداریم!!B-Tree Indexاین ایندکس‌گذاری، معروف‌ترین نوع ایندکس‌گذاری می‌باشد که اکثر پایگاه‌داده‌ها نیز از آن پشتیبانی می‌کنند. اما رعایت بعضی موارد باعث استفاده بهتر از این ایندکس‌گذاری ‌می‌شود. احتمالا با خودتون میگین که رعایت چی؟ ایندکسه دیگه، میزاری تموم میشه میره. آیا چیزی به اسم partial index رو شنیدین؟ این موراد چیزایی که باعث میشه ما ایندکس‌گذاری بهتری داشته باشیم.نحوه ساخت B-Tree در postgres:در واقع B-Tree یک درخت متوازن هستش که تعداد بچه های یک والد می‌تونه بیشتر از ۲ باشه(درخت دودویی نیست) و این باعث میشه که عمق درخت کمتر باشه. درضمن فاصله همه‌ی برگ‌ها از ریشه هم به یک اندازه‌ست(بازهم دوباره یعنی مثل درخت دودویی(binary) نیست). اما هر گره این درخت شامل چه چیزهاییه:توی پستگرس هرچیزی توی یک چیزی به اسم page ذخیره میشه و اندازه این page هم ۸ کیلوبایته. به عنوان مثال هر کدوم از گره‌های درخت هم توی یک page ذخیره میشه. بسته به موارد مختلف توی page چیزهای مختلفی ذخیره میشه که این موارد برای درخت B-Tree شامل سه تا مورد میشه:۱− شماره بلاک(برای pointer)۲− بیشترین مقدار اون گره(high key)۳− مقادیر(items)مورد یک که هیچی یک عدده و محل رو نشون میده.مورد دوم، هر گره بیشترین مقداری که توی خودش(زیردرخت خودش) داره رو اینجا ذخیره می‌کنه تا بر اساس این مقدار بررسی بشه که چیزی که دنبالیشم توی اینجا هست یا بریم سراغ بعدی؟ گره آخر هم این مقدار رو نداره.مورد سوم شامل یک لیست از مقادیر هستش که هر مقدار دوتا عنصر توی خودش داره: یکی value که مقدار چیزیه که ایندکسش کردیم و دومی اشاره‌گر(pointer) که اگر والد باشه به بچه اشاره می‌کنه(در واقع به page اون بچه) و اگر برگ باشه به محل واقعی اون سطر اشاره می‌کنه.https://www.wikiwand.com/en/B-treeNormal Indexتوی این مورد ایندکس روی همه مقادیر اون ستون در همه سطرها اعمال میشه. بخوایم هم تعریف کنیم این طوری عمل می‌کنیم:CREATE INDEX [index_name] ON [table_name](column_name_list)
ex:
یک ایندکس روی یک ستون
CREATE INDEX user_phone_idx ON users(phone);
 یک ایندکس برای چند ستون باشه
CREATE INDEX user_full_name_idx ON users(first_name, last_name);توی جنگو(django) به این شکل عمل می‌کنیم:class Meta:
    indexes = [
       models.Index(fields=[&#039;phone&#039;], name=&#039;user_phone_idx&#039;)
    ]
یا اگر بخوایم روی چند تا ستون داشته باشیم:
class Meta:
    indexes = [
        models.Index(fields=[&#039;first_name&#039;, &#039;last_name&#039;], name=&#039;user_full_name_idx&#039;)
    ]دو تا نکته درباره ایندکس روی چند فیلد:۱) اولین ستون رو میشه مثل ایندکس گذاری روی تک ستون فرض کرد، چون موقع ایندکس‌گذاری چند ستونه، اولین ستون مرتب‌شده(ordered) هستش و این یعنی مثل ایندکس روی ستون میشه باهاش رفتار کرد.توی مثال بالا، الان ستون first_name انگار خودش به تنهایی هم ایندکس‌گذاری شده. پس توی ایندکس‌گذاری چند ستونه، حواستون باشه که چه ستونی رو اول انتخاب می‌کنید، و ستونی که بیشترین سرچ رو دارین رو اول بذارین. به این کار استفاده مجدد(reuse) هم میگن. در واقع اگر یک ایندکس چند ستونه داریم و یک ایندکس تنها برای ستون اولِ اون چندتایی، به خاطر ایندکس چندتایی، می‌تونیم اون ایندکس تنهایی رو برداریم.۲) حواستون باشه که موقع کوئری زدن هم تا جایی که ممکنه، بر اساس ستون اول فیلتر رو انجام بدیم، نتیجه بهتر(سریع‌تر) می‌گیریم.Partial indexاین نوع ایندکس یک شرط هم داریم. یعنی میگیم توی فلان ستون اون سطرهایی که فلان شرط رو دارن رو ایندکس کن(توی ایندکس عادی همه سطرها بودن اینجا براساس شرط). با یک مثال موضوع رو بهتر متوجه میشیم:CREATE INDEX [index_name] ON [table_name](columns_name_list) where [condition];
Ex:
CREATE INDEX active_users_partial_idx ON users(is_active) where is_active = 1; بخوایم توی جنگو این کار رو انجام بدیم هم داریم:class Meta:
     indexes = [
        models.Index(
             fields=[&#039;is_active&#039;], name=&#039;active_users_partial_idx&#039;, condition=Q(is_active=True)
        )
     ]ایندکس بخشی(partial index) زمانی به درد می‌خوره که اکثر کوئری‌ها روی یک سری سطر با یک سری شرایط خاص انجام میشه، به عنوان مثال:اگر برای مدل users یک فیلد برای مشخص کردن کاربران فعال و غیرفعال داشته باشیم، معمولا موقع جستجو، نتیجه بر اساس کاربران فعال فیلتر میشه، پس ایندکس‌گذاری روی این کاربران بهتره. شاید بپرسین که چرا روی همه کاربران ایندکس نزاریم و آورده این ایندکس نسبی(partial index) چیه؟ جواب اینکه هر چی تعداد رکوردها بیشتر بشه، حجم ایندکس هم بزرگتر میشه و این باعث میشه پیدا کردن و رسیدن به رکورد بیشتر طول بکشه ولی ایندکس نسبی(partial index) چون بر اساس اون شرایط خاص انجام میشه حجم کوچک‌تری داره و در نتیجه سریع‌تر جواب میده.https://twitter.com/overflow_meme/status/1255903415366029314Unique indexبا این ایندکس دیگه مقدار تکراری برای اون فیلد نخواهیم داشت و همه مقادیر اون ستون با اون یکی‌ها متفاوته. مثلا چیزی مثل کدملی یا ایمیل.CREATE UNIQUE INDEX [index_name] ON [table_name](column_name_list)
ex: یک ایندکس روی
CREATE UNIQUE INDEX user_email_unique_idx ON users(email);توی جنگو برای ساختن این نوع ایندکس موقع تعریف اون فیلد عبارت unique=True رو پاس میدیم.phone = models.charfield(max_lenght=11, unique=True)Partial unique indexاین نوع ایندکس باعث میشه که بر اساس یک شرطی تکراری بودن مقادیر بررسی بشه. مثلا میگیم ایمیل unique باشه ولی فقط برای سطرهایی که بعد از سال ۱۴۰۰ ساخته شدند. این مورد برای soft delete هم جوابه. از اونجایی هم ایندکس کوچک‌تری داریم، در نتیجه موقع insert سرعت بیشتری خواهیم داشت. چون موارد کمتری برای تکراری بودن مقدار، بررسی می‌شوند.برای ساخت این ایندکس داریم:CREATE UNIQUE INDEX [index_name] ON [table_name](column_name_list) WHERE [condition]
Ex:
CREATE UNIQUE INDEX user_email_unique_partial_idx ON users(email) where created_at &gt; &#039;2021-03-20&#039;; Django:class Meta:
      indexes = [
         models.UniqueConstraint(
              fields=[&#039;email&#039;],
              name=&#039;user_email_unique_partial_idx&#039;,
              condition=Q(created_at__gt=&#039;2021-03-20&#039;)
         )
     ]https://imgflip.com/i/keqwp GIN Index(General Inverted iNdex)این ایندکس برای آرایه‌ها، json و tsvector(از tsvector برای full text سرچ استفاده میشه) مورد استفاده قرار می‌گیرد.این ایندکس برای چیزهایی مثل، آیا این آرایه شامل این عنصر هستش یا نه یا این آرایه بخشی از اون آرایه هستش یا نه و ... کاربرد دارد(توی پستگرس این عملیات با عملگرهای @&gt; , &amp;&amp;, @@@ انجام می‌شوند). برای همین هم برای full text خوبه، چون دائما از پایگاه داده می‌پرسیم، آیا این متن توی اون متن هست؟ این کلمه چطور؟ و چیزایی مثل این.برای ساختن این ایندکس داریم:CREATE INDEX [index_name] ON [table_name] USING GIN (column)
CREATE INDEX article_fulltext_gin_idx ON article USING GIN(content);Django:from django.contrib.postgres.indexes import GinIndex
class Article(models.Model):
     ...
    class Meta:
           indexes = [GinIndex(fields=[&#039;content&#039;])]نحوه کار این ایندکس:به عنوال مثال اگر یک ستون که شامل آرایه‌ست رو ایندکس گذاری کنیم، Gin این طوری عمل می‌کنه که هر عنصر آرایه رو میاد جدا می‌کنه و یک entry براش در نظر می‌گیره و البته مقدار این entry یکتا(unique) هستش. یعنی اگر یک عنصر از یک آرایه توی آرایه دیگه هم وجود داشته(مثلا مقدار ۴ توی دو تا آرایه سطر یا همون آرایه متفاوت باشه)، میاد و از entry قبلی استفاده می‌کنه. این entryها هم به یک سری برگ(leaf) اشاره می‌کنند که اونا هم یک سری اشاره‌گر(pointer) به سطرها هستند. از اونجایی هم که برگ یک page هستش و ممکنه سایز یک page برای همه اشاره‌گر ها کوچک باشه(یعنی یک عنصر تو سطرهای مختلف خیلی تکرار شده باشه)، در این صورت از یک چیزی به اسم post tree استفاده میشه(یعنی اون برگ به post tree اشاره می‌کنه).نمای کلی این حرفا میشه، تصویر پایین:http://www.louisemeta.com/blog/indexes-gin/به طور خلاصه Gin:۱− برای آرایه‌ها، json و tsvector مفیده۲− یک درخت متعادله(balanced)۳− به جای ایندکس کردن کل مقدار ستون(کل آرایه)، تک تک مقادیر رو ایندکس می‌کنه.۴− هر مقدار این درخت یکتاست(unique)GiST Index(GeneralIzed Search Tree)نکته‌ای که در رابطه با GiST وجود داره، اینکه که GiST یک ایندکس نیست و یک فریم‌ورک به حساب میاد!! احتمالا الان براتون سوال پیش اومده که این جمله یعنی چی(حق هم دارین)؟ ما می‌تونیم ایندکس خودمون برای data typeهای مختلف بنویسیم(اگر دوست و حوصله داشته باشیم)! یک لیست از توابع هستش که باید پیاده سازی بشن و تامام، ما یک ایندکس داریم. البته باید توجه داشته باشیم که به نکات این ایندکس باید توجه داشته باشیم وگرنه یک ایندکس بد رو پیاده سازی کنیم که نه تنها سرعت رو برای ما به ارمغان نیورده، بلکه بدترش کرده!!بزارین با یک مثال موضوع رو شفاف‌تر کنیم. ما یک رکورد که اطلاعات مربوط به که یک دایره رو برای ما فراهم می‌کنه و یک رکورد هم داریم که یک مستطیل رو به ما میده(اینکه ما یک همچنین چیزی داریم خودش عجیب‌تر به نظر میاد)!!! طبیعتا ما نمی‌تونیم این دوتا رو با هم مقایسه کنیم(نهایتش می‌تونیم سوال کنیم، این دوتا نوعش‌شون با هم یکیه یا نه)، اما می‌تونیم سوالاتی مثل اینکه آیا این دایره توی این مستطیله یا نه رو بپرسیم یا مثلا آيا دایره نزدیک مستطیله یا نه؟ یا اگر بخواهیم یک مثال بهتری داشته باشیم می‌تونیم به سراغ موارد جغرافیایی بریم. چون این جنس سوال‌ها توی موارد جغرافیایی زیاد پرسیده میشه(آیا این لوکیشن توی فلان منطقه‌ست؟ فلان منطقه توی کدوم شهره؟ چه مناطق دیگه‌ای نزدیکه این منطقه داریم؟). به طور کلی این ایندکس برای چیزهای overlap طور خوبه(جغرافیایی، آرایه‌ها و range و ...).نکته آخر درمورد GiST اینکه ما از اون برای full text search هم می‌تونیم استفاده کنیم، ولی اینکه کی بریم سراغ GIN و کی بریم سراغ GiST، سوالیه که تو این پست بهش جواب نمیدیم، شاید یک پست دیگه یا شاید هیچ وقت(به نظرم خودتون برین سراغش و مطالعه کنید بهتر باشه)!!!نحوه ایجاد این ایندکس:CREATE INDEX [index_name] ON [table_name] USING GIST (column);
CREATE INDEX article_aaa_gist_idx ON article USING GIST(aaa);Django:from django.contrib.postgres.indexes import GistIndex
class Article(models.Model):
      ...
     class Meta:
            indexes = [GistIndex(fields=[&#039;aaa&#039;])]الان احتمالا دارین میگین که چی شد؟ ما گفته بودیم یک فریم‌ورک و ایناست و توابعی رو پیاده‌سازی کنیم و ... پس اونا کو؟ جواب این سوال اینکه خود پستگرس یک پیاه‌سازی‌هایی برای data typeهای موجود انجام داده که ما می‌تونیم از اونا استفاده کنیم.https://alibaba-cloud.medium.com/difficult-fuzzy-search-principles-of-unique-gin-gist-sp-gist-and-rum-indexes-of-postgresql-2967c43631beBRIN Index(Block Rang INdex)همین اول کار بهتون میگم که این ایندکس اصلا تشکیل درخت نمیده!! اینکه محل فیزیکی ذخیره یک رکورد با یک رکورد بعدی با هم تفاوت داشته باشن، یک موضوع کاملا عادیه. اما اگر ما یک تیبل داشته باشیم که رایت زیاد داره و به ندرت هم اپدیت میشه این جور مواقع محل فیزیکی سطرهای جدول، توی دیسک هم کنار هم قرار داره و اینجاست که BRIN می‌تونه کمک کننده باشه. چرا؟ چون یک ایندکس با حجم بسیار کوچک درست می‌کنه و گشتن ایندکس با حجم کوچک خیلی سریعه. در واقع توی B-Tree ما به اجرای هر سطر یک page(منظورم page پستگرس هستش که قبلا تو همین پست بهش اشاره کردیم) داریم و طبیعتا هرچی سایز جدولمون بزرگتر بشه این درخت سایزش بیشتر میشه و در نهایت سرعت پیدا کردن می‌تونه کندتر بشه. اما BRIN میاد و به صورت یک بازه‌ای از بلاک‌ها کار می‌کنه. به صورت دقیق‌تر بخوایم بگیم چیزی که ذخیره می‌کنه اینجوریه که میگه این بازه از مقادیر توی این بلاک‌هاست، برو فلان و اونجا دنبالش بگرد(محل دقیق رو به ما نمیگه)! خوبی این مورد توی جدول‌های خیلی بزرگ به خوبی عمل میکنه چون ایندکس کوچک داریم. اما باید حواسمون باشه که چیزی که داریم ایندکس می‌کنیم بازه مقادیر خیلی عجیب غریبی نداشته باشه که یک حجم خوبی از رکوردهارو شامل بشه و به فنا بریم.نحوه تعریفCREATE INDEX [index_name] ON [table_name] USING BRIN (column);
CREATE INDEX article_created_at_idx ON article USING GIST(created_at);Django:from django.contrib.postgres.indexes import BrinIndex
class Article(models.Model):
      ...
     class Meta:
            indexes = [BrinIndex(fields=[&#039;created_at&#039;])]مثال خوبی که می‌تونیم از استفاده  این ایندکس بزنیم، به created_at توی جدول برمیگرده. معمولا وقتی یک سطر توی جدول ساخته میشه یک فیلد به اسم created_at داره که نشون میده کی این سطر ساخته شده و ما می‌تونیم بر اساس این مورد یک بازه خیلی مناسب داشته باشیم. مثال بد این موضوع هم می‌تونه استفاده از فیلد تاریخ تولد باشد چون که ترتیب ایجاد سطرها و محل فیزیکی اون‌ها با تاریخ تولد هیچ هم‌خوانی نداره(کاربرهایی که توی یک بازه به دنیا اومدن یهو به سایت یا برنامه ما نمیان). پس حواسمون باشه چی رو داریم ایندکس می‌کنیم.نکته۱: کلا این ایندکس چیزهای افزایشی خوبه(مثلا اگر کلید اصلی جدولمون افزایشیه و خیلی هم بزرگه).نکته ۲: اگر ما سطرهای جدول رو پاک کنیم و عملیات vacuum رو انجام بدیم دیگه این ایندکس زیاد خوب عمل نمی‌کنه.https://bajratech.github.io/2016/09/16/Postgres-BRIN-Index/SP GiST(Space Partitioning GiST)این مورد هم مثل GiST در واقع یک فریم‌ورک هستش و طبیعتا یک سری تفاوت‌هایی با GiST دارد. اینکه چطوری کار می‌کنه و ... متاسفانه در لحظه که دارم این مقاله رو می‌نویسم هنوز نرسیدم برم بررسی کنم. شاید تو آینده این بخش آپدیت کنم. یک عکس از بلاگ alibaba-cloud پیدا کردم که از همون استفاده می‌کنم، لینک مطلب زیر عکس هستش، نمیدونم چقدر خوب توضیح داده.https://alibaba-cloud.medium.com/difficult-fuzzy-search-principles-of-unique-gin-gist-sp-gist-and-rum-indexes-of-postgresql-2967c43631beنحوه تعریف:CREATE INDEX [index_name] ON [table_name] USING SPGIST (column);
CREATE INDEX article_aaa_gist_idx ON article USING SPGIST(aaa);Django:from django.contrib.postgres.indexes import SpGistIndex
class Article(models.Model):
      ...
     class Meta:
            indexes = [SpGistIndex(fields=[&#039;aaa&#039;])]Hash Indexاز روی اسمش میشه یک چیزهایی رو حدس زد، به هش(درهم‌سازی) و hash table ربط داره. نکته مهم این ایندکس اینه که تا نسخه ۱۰ پستگرس زیاد چیز خوبی نبود و از نسخه ۱۰ به بعد stable شد.هر مقدار به یک هش کد ۳۲ بیتی تبدیل میشه و این موضوع خودش رو توی مواردی که مقدار خیلی بزرگ هستش به خوبی نشون میده(b-tree کل مقدار رو توی خودش ذخیره می‌کنه و بنابراین برای مواردی که سایز بزرگی دارن مناسب نیست. درمورد TOAST توی پستگرس یک سرچی بزنید). شکل کلی این ایندکس اینطوریه:https://leopard.in.ua/2015/04/13/postgresql-indexes#.Yg5TSoxBxH4نکته درمورد هش ایندکس اینکه اگر تصادم(collision) داشته باشیم، توی یک خونه از باکت داریمشون. توی عکس بالا John Smith و Sandra Dee این وضعیت رو دارند.نحوه تعریف:CREATE INDEX [index_name] ON [table_name] USING HASH (column);
CREATE INDEX article_aaa_gist_idx ON article USING HASH(aaa);Django:from django.contrib.postgres.indexes import HashIndex
class Article(models.Model):
      ...
     class Meta:
            indexes = [HashIndex(fields=[&#039;name&#039;])]Bloom Indexبه صورت رسمی نداریمش با extension میشه نصب کرد! شبیه هش هستش ولی با کمی متفاوت. برای ایندکس کردن چند ستونی مناسبه و برای این مورد سرعت خوبی هم داره. این جوریه که همه مقادیر اون ستون‌هارو میگیره و هش می‌کنه. بر خلافه B-Tree که ستون اول توی چند ستونی مهمه اینجا اینطوری نیست.برای اینکه یک دید کلی هم داشته باشیم به این شکل تعریف میشه:CREATE INDEX [index_name] ON [table_name] USING BLOOM (columns) WITH (length= , col1=, col2= , col3=);
CREATE INDEX a_simple_idx_ ON sample_table USING BLOOM (country, city, region, zipcode) WITH (length=80 , col1=7, col2=7 , col3=7, col4=7);اینکه این عدد چطوری انتخاب میشه یک فرمول داره به این شکل m = −nlog2p / ln 2. اینکه چطوری اعداد به دست میاد رو از این لینک و برای توضیحات بیشتر این لینک رو بخونید!همین! لبخند بزنین لطفا :)منابع:https://www.youtube.com/watch?v=Xv0NFozBIbM&amp;ab_channel=PostgresOpenhttps://www.youtube.com/watch?v=ncwqtsjlSBE&amp;ab_channel=DjangoConUS</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Wed, 09 Mar 2022 09:09:37 +0330</pubDate>
            </item>
                    <item>
                <title>داکر، multi stage و کاهش حجم</title>
                <link>https://virgool.io/@vahid_fathi/%D8%AF%D8%A7%DA%A9%D8%B1-multi-stage-%D9%88-%DA%A9%D8%A7%D9%87%D8%B4-%D8%AD%D8%AC%D9%85-bzdd3aojk6ih</link>
                <description>مقدمهکاهش حجم image داکر یکی از چیزهاییه که همیشه مدنظر بوده و هست. به عنوان مثال وجود فایل‌هایی که بهشون نیازی نداریم، زیادتر شدن تعداد لایه‌ها و ... همه از مواردیه هستند که می‌تونند عامل این موضوع باشند. اینکه چه کارهایی بکنیم تا image با حجم بهینه‌تر داشته باشیم، چیزیه که در ادامه باهم به اون‌ می‌پردازیم.https://blog.jasonmeridth.com/posts/docker-daemon-error-when-running-docker-compose/multi stage buildاصولا الان براتون سواله که اصلا multi stage building یعنی چی؟ به طور خیلی خلاصه یعنی اینکه یک Dockerfile داشته باشیم که چندتا image پایه(from statement) داشته باشه! سوالی بعدی که براتون پیش اومده اینکه خب کاربرد این چندتا From داشتن چیه؟ گاها ما موقع ساخت یک image یک سری فایل داریم که تو یک مرحله‌ای مورد استفاده قرار می‌گیرند و بعدا به کارمون نمیان! به عنوان مثال تو برنامه‌هایی که با js نوشته شدند(مثلا angular) پس از گرفتن build prod نیازی به وابستگی‌ها نداریم و می‌تونیم اون‌ها رو حذف کنیم. اینجاست که multi stage معنی پیدا می‌کنه و ازش استفاده می‌کنیم. تو From اول (image پایه اول) وابستگی‌ها رو نصب می‌کنیم و تست‌ها رو اجرا می‌کنیم و build prod رو انجام می‌دیم  و بعد از گرفتن خروجی، میایم و این فایل‌های تولید شده رو تو From بعدی(image پایه دوم) استفاده می‌کنیم. اگر براتون سواله که آورده این ماجرا چیه؟ کاهش حجم، وابستگی‌های مدنظر که حجم خوبی هم دارند، تو image نهایی خودمون نداریم و در نتیجه ما گل‌های شاد و خندانیم(به مناسبت ماه مهر D:)!!احتمالا هنوز براتون سوالاتی وجود داره، بزارین با یک مثال عملی موضوع رو یکم شفاف‌تر کنیم.FROM node:latest as my_builder_stage
COPY  package.json /app/
COPY my_node_proj /app/
WORKDIR /app
RUN npm install
RUN npm run build --prod --outpath=./prod_build  #  این دستور الکیه و همینجوری نوشتم!

# Second stage 
FROM nginx:latest
COPY --from=my_builder_stage /app/prod_build /var/www/my_front
COPY my_custom.conf /etc/nginx/conf.d/default.confزیباست؟یک توضیح خیلی مختصر داشته باشیم برای کسایی که شاید براشون سوال شده بالا چیکار کردیم. ما اومدیم اول فایل‌های برنامه خودمون رو(با جاوااسکریپت نوشته شده) کپی کردیم و وابستگی‌های اون رو نصب کردیم(دستور npm install) و بعد از نصب وابستگی‌ها اومدیم و build پروداکشن اون رو گرفتیم. تو برنامه‌های انگولاری موقعی که build پروداکشن می‌گیریم فقظ چیزهایی می‌خواد رو برمیداره و اونارو میزاره توی یک فایل(البته اون دستور گرفتن build بالا الکلیه و دستورات انگولار با ng هستند). خب حالا دیگه به وابستگی‌ها نیازی نداریم، چون هر چیزی ازشون میخواستیم رو گرفتیم و می‌تونیم پاکشون کنیم. ولی نکته اینه حتی اگر پاکشون کنیم هم بازم توی لایه‌های قبلی image ها هستند و حجمشون تو image ما تاثیر می‌زارند. پس میایم از FROM دوم استفاده می‌کنیم و میگیم که برو از فلان stage این فایل‌ها رو بردار و بیار تو این image جدید که می‌خوایم بسازیم و این باعث میشه دیگه اون وابستگی‌ها نیان و حجم کاهش پیدا کنه و بریم آهنگ ملی‌پوشان پیروز باشید رو پلی کنیم(وقتی دارم این مطلب رو می‌نویسم کشتی تبش داغه. امان از آدم جو گیر!). استفاده از imageهای پایه بهینهیک اشتباهی خیلی رایجی که موقع ساخت image وجود داره، عدم انتخاب image پایه مناسب هستش. به عنوان مثال اگر توجه کرده باشید توی مثال بخش قبل برای image دوم ما از nginx استفاده کردیم و سراغ node نرفتیم! یا سراغ ubuntu نرفتم و یا سراغ alpine(alpine واقعا سم بدیه!) چرا این انتخاب image پایه مهمه؟ ببینید ما می‌تونیم یک image پایه خیلی خام مثل alpine رو انتخاب کنیم و بیایم خودمون تک تک وابستگی‌ها را نصب کنیم. ایده هم اینه که فقط چیزهایی که نیاز داریم رو نصب می‌کنیم و این باعث میشه حجم imageمون کم بشه. در حالی که این جوری نیست و علاوه بر اینکه این وابستگی‌ها بعضا چیزهایی نصب می‌کنند که زیاد بهشون نیازی نداریم، گاها به دلیل خیلی خام بودن این imageها، یک سری پکیج رو تو image نداریم که این باعث میشه برنامه‌مون تو پروداکشن بره تو دیوار(در حالی که یک موقع توسعه حالش خیلی هم خوب بوده)، نگم براتون از دیباگ کردن این موضوع که، واقعا اذیت کننده میشه. محصول به فنا رفته و تو پروداکشنی و در سریع‌ترین زمان ممکن باید پیدا کنی چرا و به چه علت! مثلا وقتی داری با پایتون ۳٫۹ کار می‌کنی خب برو از python3.9 استفاده کن دیگه، این وسط alpine چی میگه!! افراد مختلف هم اومدن imageهای مختلف برای موارد مختلف ارائه دادن که بد نیست اون‌ها رو هم یک نگاهی بندازین. در کل یکم وقت گذاشتن روی انتخاب image پایه می‌تونه تو آینده و سردردهای احتمالی ارور‌ها کم کنه.https://subscription.packtpub.com/book/virtualization_and_cloud/9781787280243/1/ch01lvl1sec9/understanding-dockerبررسی و حذف فایل‌های داخلی imageایده بدی نیست که هر چند وقت یکبار بیایم و فایل‌های داخل image نهایی ساخته شده رو بررسی کنیم. به عنوان مثال تجربه خودم بخوام بگم، وقتی داشتم یکی از imageهای خودمون رو بررسی می‌کردم دیدم یک کتابخونه‌ای داریم که حجم خوبی رو به خودش اختصاص داده و اگر‌ ما بیایم و یک نسخه دیگه از اون کتابخونه رو استفاده کنیم، از لحاظ حجمی خیلی بهینه‌تر میشه و مشکلی هم موقع اجرای برنامه نخواهیم داشت. من برای این کار از ابزار dive استفاده می‌کنم که کار راه‌اندازه و سنگین هم نیست. لینک ریپو گیت‌هابشنمونه خروجی diveخوبی dive اینه می‌تونیم توی CI هم ازش استفاده کنیم و ببینیم که image نهایی ساخته شده چقدر بهینه‌ست.فلگ squash موقع ساخت یک image می‌تونیم یک فلگ به نام squash رو به داکر پاس بدیم تا عمل  squash رو برای ما انجام بده. البته این فلگ هنوز به صورت experimental هستش و برای اینکه بتونیم ازش استفاده کنیم باید مقدار experimental را برای سرویس داکر فعال کرده باشیم. برای این کار می‌تونیم به این شکل عمل کنیم:sudo vim /etc/docker/daemon.jsonحالا مقدار عبارت زیر را در داخل فایل قرار می‌دیم:{
   &amp;quotexperimental&amp;quot: true
} و سرویس داکر رو restart می‌کنیم:sudo service restart dockerاما این فلگ چیکار می‌کنه؟ این فلگ میاد هیستوری لایه‌ها رو از بین میبره و  تمام لایه‌ها رو بررسی می‌کنه و هیستوری‌های مرتبط با یک فایل رو از بین می‌بره. یعنی چی؟ اگر مطلب مرتبط با overlay رو خونده باشید، یک فایل تو لایه‌های مختلف می‌تونه تغییر کنه و این تغییرات رو هم تو هر لایه خواهیم داشت، این فلگ میاد همه این تغییرات رو میریزه دور و فقط آخرین وضعیت اون فایل رو نگه میداره(موارد مرتبط با upperdir و merged). اما یک نکته هم داره اونم اینکه دیگه نمیاد فایل‌هایی که بی استفاده و پاک شده رو هم پیدا کنه و بریزه دور! فقط فایل‌هایی که تغییر داشتن رو بررسی می‌کنه، برای همین ممکنه یک سری فایل داشته باشیم که تو لایه‌های پایین ساخته شده باشند و استفاده‌ای نداشته باشند ولی این فلگ کاری باهاشون نداره. برای مطالعه بیشتر مستندات خود داکر رو مطالعه کنید.نمونه استفاده از این فلگ:docker build --squash -t my_image:1.0.0 .https://blog.formpl.us/everyone-gets-a-container-b8f755b404e7دستور exportاین دستور مختص حامل(container) هستش و میاد از وضعیت فعلی حامل خروجی میگیره. این خروجی میاد تمام فایل‌ها رو بررسی میکنه و فایل‌هایی رو مورد نیازش نیست رو پاک میکنه. نمونه این دستور:docker export my_container &gt; my_tar_image.tar.gz حالا باید این image به شکل tar.gz هستش و باید تو سرویس داکر بارگذاری کنیم، برای این کار:docker import my_tar_image.tar.gzمشکل دستور export:دستور export یک مشکلی داره و اونم اینه که میاد و metadata مرتبط با اون image رو پاک می‌کنه. به عنوان مثال CMD و ENTRYPOINT پاک می‌شن(یادم نیست که env variable ها هم پاک می‌شوند یا نه). به عنوان مثال میشه از حامل خودمون export بگیریم و بهش یک تگی بدیم و توی یک dockerfile دیگه بیایم و اون image رو استفاده کنیم.همین! لبخند بزنین لطفا :)منابع:مستندات داکر</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Tue, 02 Nov 2021 11:32:02 +0330</pubDate>
            </item>
                    <item>
                <title>شبکه در docker</title>
                <link>https://virgool.io/@vahid_fathi/%D8%B4%D8%A8%DA%A9%D9%87-%D8%AF%D8%B1-docker-aoca9zz9imnf</link>
                <description>مقدمهدر ادامه سری مطالب داکر قرار بود که شبکه‌ی داکر رو هم مورد بررسی قرار بدیم. فهمیدن نحوه کار شبکه‌ی داکر به ما کمک می‌کنه تا بدونیم که حامل‌ها(container) چطور با هم ارتباط می‌گیرند. تو این مطلب سعی می‌کنیم این موضوع رو بررسی و نحوه کار اون رو بفهمیم.اگر مطالب قبلی رو نخوندین، بد نیست یک نگاهی به اون‌ها هم بندازید(البته پیش نیاز این مطلب نیستند). https://vrgl.ir/n3zr0  https://vrgl.ir/srZqV به طور کلی داکر ۵ نوع(یا به تفسیر دیگه ۶ نوع) شبکه رو می‌تونه داشته باشد، که عبارت‌اند از:bridge(پیشفرض)hostoverlaymacvlannonePlugin(اون به عبارتی که گفتم این مورده)اینکه هر کدوم از این موارد کی استفاده ‌می‌شوند و چه کاربردی دارند، موضوعیه که تو ادامه با هم بررسی می‌کنیم.Bridgeاگر با تکنولوژی‌های مجازی‌ساز(vmware, kvm, ...) کار کرده باشید، باید بهتون بگم شبکه داکر تو حالت bridge با مجازی‌سازها متفاوته! ما تو مجازی‌سازها وقتی از bridge استفاده می‌کنیم، یک IP در رنج همون IP ماشین میزبان(Host) بهمون داده میشه(شاید یک تکنولوژی وجود داشته باشه که این‌طوری نباشه، اولا اینکه موارد معروف رو مدنظرم بود و دوما اینکه سواد من در این حده :) ). به عنوان مثال اگر ماشین میزبان ما تو رنج 172.19.0.0/16 باشه، ماشین مجازی(vm) ما هم تو همون رنج یک ‌IP رو می‌گیره و تو شبکه هم قابل دسترسیه، در واقع مستقیم از interface شبکه‌مون استفاده می‌کنه. اما داکر این طوری نیست! داکر میاد یک شبکه با رنجی که خودش مشخص می‌کنه، می‌سازه و حامل‌های مارو تو اون رنج IPدهی می‌کنه. دلیل این تفاوت هم تو اینه که داکر، برای ساخت bridge از software device استفاده می‌کنه. این مورد باعث ایزوله سازی این شبکه میشه. برای اینکه لیست شبکه‌های داکر رو ببینیم کافیه دستور زیر رو اجرا کنیم:docker network lsکه خروجی اون یک همچین چیزی میشه:خروجی دستور بالاداکر این سه مورد به صورت پیشفرض خودش می‌سازه.حالا اگر بخوایم دامنه IPهای خودمون رو بدونیم کافیه دستور زیر رو اجرا کنیم:docker network inspect &lt;ID_OF_NETWORD___OR___NAME_OF_NETWORK&gt;توی خروجی این دستور تو بخش IPAM و داخل اون بخش Config می‌تونیم دامنه رو ببینیم:بخشی از خروجی دستور بالااما bridge کی استفاده میشه؟ طبق گفته خود داکر اکثر مواقعی چندتا حامل standalone داریم و این‌ حامل‌ها با هم دیگه ارتباط دارند از bridge استفاده می‌کنیم. مثلا docker-compse یک مثال بسیار خوب برای این مورده. میایم یک شبکه برای اون چندتا حاملی که می‌خوایم با docker-compose بیاریم بالا می‌سازیم(حتی می‌تونیم شبکه جدا نسازیم) و این حامل‌ها می‌تونند با اسم با هم‌دیگه در ارتباط باشند.نکته خوب ماجرا اینکه ما می‌تونیم شبکه bridge خودمون رو بسازیم(حتی با دامنه مدنظر خودمون). چرا این خوبه؟ فرض کنید که چند دسته حامل داریم(هر کدوم متعلق به یک سرویس) و نمی‌خوایم این دسته‌ها بتونند با هم دیگه ارتباط داشته باشند. داکر به حامل‌هایی که تو یک شبکه نباشند&amp; اجازه ارتباط با همدیگه رو نمیده.برای اینکه موضوع شفاف‌تر بشه. بیاین یک شبکه bridge رو بسازیم:docker network create &lt;NAME_OF_NETWORK&gt;نکته: از اونجایی که دایور پیشفرض داکر bridge هستش، دیگه نگفتیم که bridge می‌خوایم. دامنه IP رو هم می‌تونستیم مشخص کنیم و ... . برای اینکه بدونیم دستور create چه گزینه‌هایی داره از این دستور استفاده می‌کنیم:docker network create --helpساخت یک شبکه bridgeحالا اگر بخوایم که یک حامل توی این شبکه باشه کافیه:docker create --network vahid -p 8080:80 --name my-container nginx:latestاگر هم یک حامل در حال اجرا داریم می‌تونیم به این شکل عمل کنیم:docker network connect vahid my_containerقطع ارتباط(disconnect) هم داریم:docker network disconnect vahid my_containerتفاوت شبکه ایجاد شده توسط کاربرگفتیم که به صورت پیشفرض داکر یک شبکه bridge رو اول کار میسازه که این شبکه با شبکه bridge که ما بعدا می‌سازیم، تفاوت‌هایی داره. این تفاوت‌ها عبارت‌اند از:امکان وصل شدن و یا قطع ارتباط در لحظه(on the fly) به شبکه غیرپیشفرض     همون طور که مثالش رو بالا داشتیم، خیلی راحت می‌تونیم به یک شبکه که خودمون ساختیم، وصل و یا از اون قطع بشیم. در حالی که در شبکه پیشفرض برای خارج شدن، نیازه تا حامل stop بشه.به اشتراک گذاشتن env variablesشبکه پیشفرض امکان به اشتراک گذاری این مقادیر بین دوتا حامل رو به ما میده(استفاده از فلگ --link)، در حالی که برای شبکه‌هایی که خودمون ساختیم این طوری نیست. هرچند که میشه برای این موضوع روش‌های بهتری نسبت به --link رو استفاده کرد.امکان تعریف دامنهدامنه IP که به ما اختصاص داده میشه توی شبکه پیشفرض دست ما نیست و خود داکر مشخص می‌کنه در حالی که می‌تونیم یک شبکه بسازیم و دامنه IP مدنظر خودمون رو بهش اختصاص بدیم. البته این مورد رو هم میشه با فایل daemon.json حل کرد. برای این کار:$ sudo vim /etc/docker/daemon.jsonبعد محتویات زیر رو میزاریم(طبیعتا اگر تنظیمات دیگه‌ای هم داشته باشیم، این موارد میان بغل اونا):{
  &amp;quotbip&amp;quot: &amp;quot192.168.1.5/24&amp;quot,
  &amp;quotfixed-cidr&amp;quot: &amp;quot192.168.1.5/25&amp;quot,
  &amp;quotfixed-cidr-v6&amp;quot: &amp;quot2001:db8::/64&amp;quot,
  &amp;quotmtu&amp;quot: 1500,
  &amp;quotdefault-gateway&amp;quot: &amp;quot10.20.1.1&amp;quot,
  &amp;quotdefault-gateway-v6&amp;quot: &amp;quot2001:db8:abcd::89&amp;quot,
  &amp;quotdns&amp;quot: [&amp;quot10.20.1.2&amp;quot,&amp;quot10.20.1.3&amp;quot]
}در نهایت سرویس داکر رو ریستارت کنید.یک نکته دیگه هم اینکه داکر میگه شبکه غیرپیشفرض(شبکه‌ای که کاربر می‌سازه) باعث ایزوله سازی بهتری میشه.چطور؟ اگر برای یک حامل شبکه رو مشخص نکنیم، از شبکه پیشفرض استفاده خواهد کرد. این موضوع می‌تونه باعث بشه، حامل‌هایی که نمی‌خوایم همدیگه رو ببینند، این امکان رو داشته باشند. پس روش مهندسی‌تر اینکه شبکه خودمون رو بسازیم و حامل رو موقع ساخت با اون شبکه بیاریم بالا تا دسترسی ناخواسته بهش وجود نداشته باشه.https://memegenerator.net/img/instances/81395542/users-cant-submit-jira-tickets-if-the-network-is-down.jpgHostتو این مورد خیلی ساده ایزوله‌سازی‌ای وجود نداره و حامل مستقیم از به طور مستقیم از شبکه ماشین میزبان استفاده می‌کنه. در واقع حامل از خودش IP نداره و فلگ --publish یا -p هم درنظر گرفته نمیشه. مثلا یک حامل nginx داشته باشیم که شبکه ش از نوع Host باشه تنها کافیه آدرس اون ماشین رو در شبکه بزنیم تا درخواست برسه به دست حامل ما.این نوع شبکه در ماشین‌های میزبان با سیستم‌عامل مک و ویندوز کار نمی‌کند.شبکه Host برای مواقعی بخوایم یک طیف وسیعی از درگاه‌ها(port) رو داشته باشه ایده‌ی خوبیه(از لحاظ پرفورمنسی).Overlayاین نوع شبکه چیز باحالیه! ما می‌تونیم به کمک overlay چندتا docker daemon رو بهم دیگه وصل کنیم. این شبکه باعث میشه بتونیم دو حامل داشته باشیم که توی دوتا docker daemon جدا با همدیگه ارتباط داشته باشند یا سرویس‌های swarm بتونند با هم حرف بزنند یا یک حامل با swarm حرف بزنه. حتی این ارتباط میتونه به کمک رمزنگاری امن هم بشه.با یک مثال موضوع رو ادامه بدیم. یک شبکه از نوع overlay می‌سازیم:docker network create -d overlay vahidالبته فلگ‌های --encrypted و --attachable هم زیاد استفاده می‌شوند. یکی برای فعال‌سازی رمز نگاری ارتباطات و یکی هم برای امکان ارتباط یک حامل با سرویس swarm.اگر بخوایم یک عکس کلی از این شبکه داشته باشیم، این رو خواهیم داشت:https://medium.com/@tukai.anirban/container-networking-overlay-networks-b712d6ddfb67اینکه این شبکه چطوری کار میکنه، بحث مسیریابی(routing) چطوریه و کلا جنبه‌های مختلف اون، علاوه بر اینکه برای توضیحش سواد فنی خوبی می‌خواد(که من ندارمش)، باعث طولانی‌تر شدن مطلب میشه(مطلب طولانی مخاطب رو پس می‌زنه). ولی به واقع ارزش خوندن و فهمیدنش رو داره، مخصوصا اگر با swarm کار می‌کنید. طبیعتا گوگل بهترین گزینه ممکن هستش ولی منم دوتا لینک میزارم.لینک ۱(مستندات خود داکر درباره شبکه overlay) و لینک ۲. اگر کسی لینک خوبی سراغ داشت لطفا به اشتراک بزاره. :)macvlanتو این مورد، ما به حامل‌های خودمون یک MAC Address رو اختصاص می‌دیم که باعث میشه حامل توی شبکه به عنوان یک دستگاه شناخته بشه. سرویس داکر هم عملیات مسیریابی(routing) رو بر اساس مک آدرس انجام میده. به گفته خود داکر این نوع شبکه، گاها می‌تونه به عنوان بهترین انتخاب برای نرم‌افزارهای قدیمی که انتظار دارند، مستقیم به شبکه فیزیکی(physical network) وصل شوند، باشد.نکته اینکه برای این شبکه، تجهیزات شبکه ما باید توانایی تخصیص چندین مک آدرس رو داشته باشد(اصطلاحا بهش promiscuous mode میگن).نکته دوم هم اینکه این مورد می‌تونه باعث مشکلاتی(VLAN spread, ip exhaustion) توی شبکه‌هایی بشه که تعداد زیادی مک آدرس یکتا(unique) تو خودشون دارند.باز هم به خاطر اینکه مطلب طولانی نشه توضیح بیشتری نمیدم و شما رو به این لینک(از مستندات خود داکر) ارجاع میدم. توصیه می‌کنم یک نگاهی بندازین چیزای جالبی دارد.noneبحث شبکه رو برای حامل‌مون کلا غیرفعال می‌کنه(تامام). برای اینکه این مورد رو فعال کنیم این طوری می‌تونیم عمل کنیم:docker run --network none --name my-container nginx:latestPluginمی‌تونیم یک افزونه(plugin) third-party رو نصب و استفاده کنیم. برای دیدن نمونه هم می‌تونین که اینجا(داکر هاب) رو یک نگاه بندازید. شاید برای جاهایی که شبکه‌شون رو به صورت خاصی پیاده‌سازی کردند، استفاده بشه.جمع‌بندیاگر بخواهیم به صورت خیلی خلاصه این موارد رو جمع‌بندی کنیم، خود مستندات داکر خیلی خوب گفته این موارد رو، پس این شما و این خلاصه چیزهای گفته شده:User-defined bridge networks are best when you need multiple containers to communicate on the same Docker host.Host networks are best when the network stack should not be isolated from the Docker host, but you want other aspects of the container to be isolated.Overlay networks are best when you need containers running on different Docker hosts to communicate, or when multiple applications work together using swarm services.Macvlan networks are best when you are migrating from a VM setup or need your containers to look like physical hosts on your network, each with a unique MAC address.Third-party network plugins allow you to integrate Docker with specialized network stacks.میدونم که خیلی سریع و سطحی گفته شد، سوای بحث اینکه مطلب خیلی طولانی میشد(همین مطلب ۸ دقیقه اینا شد!)، توضیح در همین حد هم، کار خیلی‌ها(اون‌هایی که کار زیرساختی انجام نمیدن) رو راه میندازه!همین! لبخند بزنین لطفا :)منابع:https://docs.docker.com/network/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Tue, 24 Aug 2021 10:03:57 +0430</pubDate>
            </item>
                    <item>
                <title>داکر و آشنایی با overlay2</title>
                <link>https://virgool.io/@vahid_fathi/%D8%AF%D8%A7%DA%A9%D8%B1-%D9%88-%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-overlay2-ks4dmcqsiisf</link>
                <description>مقدمهتو پست قبلیِ مرتبط با داکر، در رابطه با نحوه‌ ایزوله‌سازی حرف زدیم، که تو یکی از کامنت‌های اون پست حسین سلیمانی(به نظر من حسین یکی از آدم‌های باسواد جامعه IT کشورمونه) گفتش که اگر به فایل سیستم‌ها تو داکر هم پرداخته بشه خوبه. پیشنهاد خوبی بود و استقبال کردم ازش، نتیجه اون هم شد این چیزی که می‌بینید( بحث بعدی این زنجیره میشه، شبکه تو داکر که اگر عمری باشه به امید خدا درباره اونم حرف می‌زنیم). https://vrgl.ir/n3zr0 داکر برای مدیریت فایل‌ها از چندتا driver می‌تونه استفاده کنه:overlay2aufsfuse-overlaydevicemapperbtrfs, zfs, vfs ما آخر پست(عکس پایین) - ساخته شده توسط خودم :)&quot; /&gt;ما اول پست(عکس بالا)  -&gt; ما آخر پست(عکس پایین) - ساخته شده توسط خودم :)درایور پیشفرضدر بین driverهای گفته شده، تیم داکر overlay2 رو به عنوان درایور ترجیحی خودش انتخاب و داکر به صورت پیشفرض از اون استفاده می‌کنه(مگر اینکه اون سیستم‌عامل از overlay2 پشتیبانی نکنه و درایورهای دیگه استفاده بشه. به عنوان مثال اوبونتو ۱۴٫۰۴ با کرنل ۳٫۱۳ و داکر نسخه ۱۸٫۰۶ از aufs استفاده می‌کنه).برای اینکه بفهمیم داکر ما از چه درایوری به صورت پیشفرض استفاده می‌کنه از دستور زیر کمک می‌گیریم:docker info | grep Storageکه خروجی اون چیزی مثل عکس پایین می‌شه:خروجی دستور بالاهمونطور که از عنوان پست مشخصه، ما تو این پست قراره تنها درمورد overlay2 حرف بزنیم، دو دلیل اصلی این رویکرد: ۱− بقیه رو بلد نیستم!!! ۲− چون درایور ترجیحی داکر هستش و ما معمولا این مورد رو تغییر نمیدیم(مثل اون ماجرای metaclassهای پایتون، به نظرم اگر کسی نیاز داشته باشه خودش متوجه میشه و میره پی ماجرا).تغییر درایوراینکه overlay2 درایور پیشفرضه، به این معنی نیست که نمیشه اون رو عوض کرد. برای عوض کردن اون باید فایل daemon.json(این فایل موقع راه اندازی سرویس docker استفاده می‌شه) رو دستکاری یا سرویس docker را با فلگ --storage-driver مدنظرمون اجرا کنیم. به عنوان مثال فرض کنیم که می‌خوایم از aufs برای درایور خودمون انتخاب کنیم:sudo vim /etc/docker/daemon.jsonاین فایل موقع راه اندازی سرویس docker استفاده می‌شه. فرض کنیم که این فایل خالی بوده و قبلا تنظیمات دیگه‌ای نداشتیم که در این صورت محتویات زیر را داخلش می‌نویسیم:{
    &amp;quotstorage-driver&amp;quot: &amp;quotaufs&amp;quot
}در انتها هم سرویس داکر رو دوباره راه‌اندازی می‌کنیم.systemctl restart docker.service
یا 
service docker restartشاید بگین که چطور میشه که یکی بیاد درایور رو عوض کنه؟ ما تو دنیای نرم‌افزار همیشه بر اساس نیازمون موارد مختلف رو انتخاب می‌کنیم. مثلا ممکنه تحت شرایطی ویندوز سرور برامون بهتر از لینوکس باشه(نیاین بگین لینوکس سیستم‌عامل نیست، اصل موضوع رو بچسبین)، به همین دلیل این مورد هم استثنا نیست.به عنوان مثال گفته میشه که تو PaaS استفاده از aufs بهتر overlay2 هستش چون اشتراک‌گذاری بهتری تو لایه‌ها دارد(نمیدونم این حرف در عمل چطوره! اما دوستان فندق یا همچین سرویس‌هایی بهتر می‌تونند جواب بدهند).نحوه کار overlay2قبل از که به خود overlay2 برسیم بزارین یک قدم برگردیم عقب‌ و ببینیم که اصلا کار storage driverها چیه و چرا وجود دارند؟ یک حامل(container) از یک image به وجود میاد که این image هم از یک سری لایه‌های فقط خواندنی(readonly)  تشکیل شده، در واقع حامل میاد یک لایه نوشتنی(writable) روی آخرین لایه فقط خواندنی اون image می‌سازد. یک image هم معمولا از یک image پایه ساخته شده و اومدیم تغییرات خودمون رو روش اعمال ‌کردیم، ممکنه که خود این image پایه هم که از یک image دیگه استفاده کرده باشه و تغییرات خودش رو داده باشه(سوال: تا حالا به این فکر کردین که imageهای خیلی پایه مثل alpine، چطوری ساخته می‌شوند؟). در واقع هی لایه روی لایه گذاشته میشه، این لایه‌ها هم یا فایلی کم یا اضافه و یا تغییر می‌دهند. حالا storage driverها وظیفه مدیریت این فایل‌ها بر عهده دارند. چیزهایی مثل، آیا فلان فایل رو داریم؟ اگر آره تو کدوم لایه؟ فلان فایل تغییر کرد؟ تغییرات چی بود؟ این تغییرات رو چطوری ذخیره کنم(فقط diff یا کل فایل)؟ تفاوت storage driverها هم تو نحوه رفتار اون‌هاست، از نحوه پیدا کردن و واکشی فایل تا ذخیره اون‌ها. استراتژی copy on writeبه صورت مختصر این استراتژی میگه اگر فایل تغییر کرد یک کپی ازش بگیر. با یک مثال موضوع رو شفاف‌تر می‌کنیم، فرض کنیم که یک فایل تنظیمات داریم که این فایل در  لایه‌ی پایینی قرار داره(فقط هم قصد خوندنش رو داریم)، وقتی که میخوایم اون فایل رو بخونیم، داکر دست ما رو می‌گیره، می‌بره می‌رسونه به فایل اصلی و محتوای اون رو مستقیم از خود فایل بهمون می‌ده. تا اینجا همه چیز عادیه اما تصمیم‌مون عوض میشه و  می‌خوایم که تغییراتی رو تو این فایل تنظیمات داشته باشیم، اینجاست که داکر میاد، یک کپی از اون فایل می‌گیره و تو لایه فعلی قرارش میده و تغییرات رو اعمال می‌کنه(در واقع یک فایل جدید داریم). از این به بعد هم هر وقت بخوایم به اون فایل دسترسی داشته باشیم(حتی فقط برای خواندن) به این فایل جدید دسترسی پیدا می‌کنیم. به خاطر همین موضوع بازدهی و سرعت یک حامل می‌تونه در مواردی که تعداد فایلی که تغییر می‌کنند زیاد باشه، می‌تونه تحت تاثیر قرار بگیرد. نکته بعدی اینه که تا جایی که میدونم overlay2 در حد فایل کار می‌کنه نه بلاک(داکر تو مستندات گفته الان که دارم مینویسم یادم نیست). یعنی اگر یک بخش کوچک از یک فایل بسیار بزرگ تغییر کنه، کل اون فایل کپی میشه که این هم باید برای بازدهی و سرعت مدنظر قرار بدیم(به این کپی کردن از فایل از لایه پایین‌تر و آوردنش تو لایه بالاتر copy up گفته میشه).شاید سوال بشه که چرا این استراتژی انتخاب شده و خوبی اون چیه؟ جواب کوتاه میشه: امکان استفاده همزمان چندین حامل یا image از یک فایل. یعنی در واقع فایل به اشتراک گذاشته میشه و این هم باعث میشه که حجم حامل کم بشه. چرا حجم حامل کم میشه؟ چون فایل کپی نمیشه و به لایه فعلی(نوشتنی) نمیاد. حتی جالب‌تر اینکه این فایل بین تمام حامل‌هایی که به این لایه دسترسی دارند به اشتراک گذاشته میشه. یعنی چی؟‌ گفتیم که یک image از چندین لایه تشکیل شده(اگر flat نکرده باشیم) خب؟ حالا ما مثلا سه تا image مختلف می‌سازیم که تو یک لایه با هم اشتراک دارند و اون لایه هم یک فایل رو تو خودش دارد. حالا اگر ما بیایم برای هر کدوم از این imageها، حامل‌های مختلفی بسازیم مادامی که این حامل‌ها اون فایل رو تغییر ندادند، همگی از اون فایل موجود در اون لایه مشترک استفاده می‌کنند و این زیبایی و مهندسی کاره( یک لحظه به خودم دیدم که چقدر دارم با این موضوع حال می‌کنم! در این حد geek :) ).حجم حامل(این پاراگراف صرفا جهت اطلاعات بیشتره!) حامل دوتا حجم دارد، یکی واقعی و یکی مجازی که به ترتیب به اسم‌های size و virtual size شناخته می‌شوند. size یعنی میزان حجمی که حامل تو دیسک اشغال کرده، از اون ور virtual size هم میشه میزان فضایی که لایه‌های image اشغال کرده به همراه size. یعنی virtual size همیشه بزرگتر مساوی size خواهد بود. این اشتراک‌گذاری که تو مرحله قبل گفتیم هم میشه باعث میشه که این virtual size کم بشه.نکته درمورد size: چیزهایی مثل فایل log، تنظیمات اون حامل، volumnها و چیزهایی از این دست شامل size نمیشه. در واقع size فقط نشان‌دهنده اندازه لایه نوشتنی(witable layer) هستش.لایه‌ها و overlay2به طور کلی موقع بحث تو دامنه overlay2 با دو موضوع upper dir و lower dir زیاد سروکار داریم، که lowerdir همون لایه پایینی و جایی که فایل‌های اصلی قبلی قرار گرفتند و upperdir هم میشه همین لایه‌ای که تو اون قرار داریم و هر تغییری تو فایل‌های قبلی تو این لایه ذخیره میشه. در ادامه با شکل این موارد رو مفصل‌تر توضیح می‌دیم.https://blogs.cisco.com/developer/373-containerimages-03توی تصویر بالا ما یک لایه پایین داریم که سه تا فایل دارد(lowerdir) و البته لایه فعلی که می‌خوایم تغییراتی رو تو فایل‌ها داشته باشیم(upperdir). File 1 دچار تغییر محتوا و به خاطر همین یک کپی ازش گرفته میشه(توجه کنید که از این به بعد هر وقت میخوایم File 1 رو بخونیم، به فایلی که تو upperdir قرار دارد دسترسی خواهیم داشت). File 2 حذف شده و توی upperdir به عنوان حذف شده نشانه‌گذاری میشه(این فایل تو لایه پایینی هنوز وجود دارد ولی چون گفتیم حذف کن، میاد تو upperdir اون رو به عنوان حذف شده علامت میزنه). File 3 که هیچی تغییری نداشته و تو upperdir هم نداریمش. در نهایت هم File 4 که تازه ساخته شده و فقط تو upperdir داریمش.توی تصویر بالا احتمالا merged براتون سواله و با خودتون میگید که اون چیزیه. همون طور که از اسمش مشخصه merged چیزیه که از ترکیب این دو لایه بدست میاد(چه جواب فاخری)! در واقع فایل‌ها تو خودش دارد. به عبارت دیگه upperdir تغییرات رو به ما میگه و merged فایل‌ها رو دارد. اما برای اینکه موضوع ملموس‌تر بشه با یک مثال میریم جلو! برای شروع فرض کنید که همچین پوشه‌بندی رو داریم:مرحله ۱ - ساختار اولیهحالا میایم به lowerdir سه تا فایل اضافه می‌کنیم:مرحله ۲ - اضافه کردن سه تا فایلبعد میایم دوتا دستور زیر رو اجرا می‌کنیم:# mount -t overlay overlay \
-o lowerdir=/root/virgool_overlay_example/lowerdir \
-o upperdir=/root/virgool_overlay_example/client_1/upperdir \
-o workdir=/root/virgool_overlay_example/client_1/workdir \
/root/virgool_overlay_example/merged/client_1
-------------------------------------------------------------
# mount -t overlay overlay \
-o lowerdir=/root/virgool_overlay_example/lowerdir \ -o upperdir=/root/virgool_overlay_example/client_2/upperdir \ -o workdir=/root/virgool_overlay_example/client_2/workdir \ /root/virgool_overlay_example/merged/client_2که خروجی این دوتا دستور میشه:خروجی اجرای دستورحالا تصمیم می‌گیریم که تغییراتی رو تو client_1 داشته باشیم:# cd merged/client_1 
# echo &amp;quotextra info to file_1&amp;quot &gt;&gt; file_1.txt
# rm file_2.txt
# echo &amp;quotcreating new file 4&amp;quot &gt; file_4.txtیعنی اگر دستور ls رو بگیریم نتیجه این طوری میشه(حواستون باشه بالا cd کرده بودیم به merged/client_1):# ls
file_1.txt  file_3.txt  file_4.txtحالا بیاین tree بگیریم:جالب بود نه؟ :)نکته: چون تغییراتی تو client_2 نداشتیم پس برای اون اتفاقی نیافتاده و همچنان رفرنس فایل‌ها به lowerdir هستش. این موضوع برای فایل file_3.txt تو client_1 هم صادقه.همه این چیزها تو داکر هم اتفاق میوفته یعنی این lowerdir و upperdir رو برای حامل‌هامون داریم. برای اینکه مطمئن بشیم، کافیه از یکی از حامل‌هامون inspect بگیریم و به بخش GraphDriver نگاه کنیم که همچین چیزی خواهد بود:کل این مثال کپی شده از یکی از منابع این پست هستش. خواستین اون رو نگاه کنید.اگر این موضوع رو دوست دارید و علاقه‌مندید، یک نگاه به دایرکتوری پایین بندازین، چیزهای جالبی رو می‌بینید: /var/lib/docker/overlay2/اگر دایرکتوری بالا رو یک نگاه بندازید، می‌بینید که آخرین عضو این لیست یک دایرکتوریه به اسم l ( همون L کوچک انگلیسی) که لایه‌های imageهامون تو این دایرکتوری قرار دارند. نکته اینه که دایرکتوری‌های داخل l، خودشون چیزی ندارند و فقط لینک شدند(دلیل این لینک بودن، محدودیت دستور mount تو پیچ سایز آرگومان ورودی هستش)، یعنی به این شکل دایرکتوری‌ها به این شکل هستند:چگونه محدودیت را دور بزنیم! :)به خاطر اینکه مطلب طولانی نشه، ریز نمیشم ولی حتما یک نگاهی به دایرکتوری /var/lib/docker/overlay2 بیندازید، چیزهای باحالی پیدا می‌کنید. مثلا یکی از اون‌ چیزها دایرکتوری link هستش(این link با اون لینک پاراگراف قبلی فرق داره! اینجا اسم یک دایرکتوری هستش ولی اونجا منظور بهم وصل شدن بود) که تو پایین‌ترین لایه وجود دارد و کارش اینکه که شناسه کوتاه شده(shortened identifier) رو مشخص کنه(همون اسمی که تو دایرکتوری l هستش).همین! امیدوارم که مفید بوده باشه(به شخصِ از مهندسی این موضوع لذت بردم). شاد باشید و لبخند بزنین لطفا :)منابع:https://docs.docker.com/storage/storagedriver/overlayfs-driver/https://blogs.cisco.com/developer/373-containerimages-03https://docs.docker.com/storage/storagedriver/select-storage-driver/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Tue, 13 Jul 2021 10:05:42 +0430</pubDate>
            </item>
                    <item>
                <title>درک عمیق‌تری از ssh</title>
                <link>https://virgool.io/@vahid_fathi/%D8%AF%D8%B1%DA%A9-%D8%B9%D9%85%DB%8C%D9%82-%D8%AA%D8%B1%DB%8C-%D8%A7%D8%B2-ssh-yukwn4peoig5</link>
                <description>اگر با سرور سروکله زده باشین(یا دارید می‌زنید) می‌دونید امورات زندگی به کمک ssh می‌گذره(یادش بخیر چند سال قبل برای کاری یک سرور بهم دادن و چون cli(خط فرمان) بلد نبودم، روی سرور دسکتاپ نصب کردم و با rdp بهش وصل میشدم!!!!! خدایا توبه، جوون بودم خامی کردم! D:). اما نحوه شکل گیری این ارتباط از اون دست مطالبیه که دونستن‌ش باعث درک بهتر ماجرا میشه. مثلا تو ansible اگر اجرای playbook رو با vvvv انجام بدیم، مرحله به مرحله این ماجرا رو نشون میده.نکته ۱: بعضی از جمله‌ها با م ssh شروع میشه اونم دلیلش اینه که تو ویرگول اگر اولین حرف انگلیسی باشه، align پاراگراف به left to right تبدیل میشه(یا حداقل من بلد نیستم چطوری درستش کنم)، به خاطر همین حرف م اول پاراگراف گذاشته میشه تا alignment راست به چپ حفظ بشه.نکته ۲: مقدمه به توضیح مفاهیم ابتدایی می‌پردازه، پس اگر با این مفاهیم آشنا هستید می‌تونید از مطالعه این بخش صرف نظر کنید.مقدمهم ssh مخفف secure shell هستش و برای ایجاد یک ارتباط امن ایجاد شد، دلیلش هم اینه که استفاده از telnet باعث میشه که رمزنگاری نداشته باشیم و این مقادیر به صورت متن واضح(clear text) انتقال پیدا می‌کنه، که نتیجه این ماجرا هم مشکلات امنیتی خواهد بود. بهتر بخوایم بگیم ssh یک پروتکل برای ارتباط امن بین دو تا کامپیوتر استفاده میشه. مخفف کلمه secure shell که تو فارسی گاها به پوسته امن هم ترجمه شده. ssh روی tcp و پورت ۲۲(البته این پورت پیشفرضه و میشه عوضش کرد) به کانکشن‌ها گوش میده و برای کانکشن‌های ورودی یک سری مراحل طی میشه تا اون‌ها امنیت لازم را داشته باشند. برای برقراری ارتباط امن از رمزنگاری استفاده می‌کنه. بحث رمزنگاری هم به کمک سه تکنیک انجام میشه:کلید مقارن(Symmetrical encryption)          (هر دو طرف با یک کلید یکسان یک چیزی رو رمزنگاری یا رمزگشایی می‌کنند به عنوان مثال الگوریتم AES)کلید نامتقارن(Asymmetrical encryption)          (چیزهایی مثل RSA و استفاده از کلید عمومی و کلید خصوصی)درهم‌سازی(Hashing)          (مثل یک تابع یک طرفه که یک چیزی رو ورودی می‌گیره و یک چیز درهمی رو به عنوان            خروجی تحویل میده. این تابع برای یک ورودی ثابت یک خروجی ثابت خواهد داشت)https://sudonull.com/post/15682-SSLH-Hide-SSH-HTTPS-OpenVPN-Telegram-behind-a-single-port-443روند مذاکرهبه طور کلی خیلی خلاصه بخوام بگم روند کار اینطوریه(بخش پایین برای اینکه اگر خواستین برای یکی توضیح کلی بدین که روند کار چطوریه، یک ایده‌ای داشته باشید D:):− ماشین اولی به ماشین دومی میرسه ماسکشو میده پایین(متاسفانه بعضیا تو این کرونا پروتکل‌های بهداشتی رو رعایت نمی‌کنند) و میگه: پیس پیس، سلام علیرضا حسینی تویی(مرحله ایجاد ارتباط − این مراحل به صورت فنی پایین‌تر توضیح داده میشه)؟+ ماشین دومی برمیگرده میگه: سلام، بله بفرمائید یک کارت شناسایی نشون میده(داداش مگه پلیسه بهش کارت شناسایی نشون میدی؟ - مرحله نمایش کلید عمومی سرور).− ماشین اولی: حله داداش(مرحله تایید کلید) و اروم در گوشش میگه، اقا من یه کاری باهات داشتم ولی اینجا نمیشه گفت(در حین گفتن این حرف یک نگاهی هم به اطراف میندازه - شروع فرایند دیفی-هلمن)!+ ماشین دومی: بهش یه چشمک میزنه که یعنی حله( انگار ۱۸۹۴ئه و اینام عضو انجمن زیرزمینی - مرحله توافق بر سر کلید اولیه) − ماشین اولی: دستشو دراز میکنه که دست بده، در حالی که یک چیزی تو دستشه(به نظرتون دستش تمیزه؟مرحله تبادل کلید)!!+ماشین دومی: دستش رو دراز می‌کنه اون چیز رو می‌گیره و خودشم همزمان یک چیزی میده به اون ور(بیاین مثبت فکر کنیم بچه‌ها).−بعد ماشین اول میگه: dsfdsf#$#%$GVSD#$RWR@#F بله متاسفانه ما نامحرم بودیم و دیگه نمی‌تونیم بفهمیم چی گفتن بهم(البته که ما همچنان دیدمون مثبته - استفاده از کلید اشتراکی جلسه و امن شدن ارتباط)!× این روند اینجا تموم نمیشه و بعد مرحله بالا مرحله احراز هویت شروع میشه.http://rabexc.org/posts/using-ssh-agentاحتمالا دارید میگید این چرت و پرتا چیه؟ فنی بگو! واقعیت اینه حق با شماست و خودمم نمیدونم در لحظه چه فعل و انفعالی تو مغزم گذشت که این بخش بالا رو نوشتم(شاید به خاطر اینکه ۱۱ شبه و من هنوز شام نخوردم)!! ولی بریم سراغ روند فنی ماجرا.− ماشین اول یک ارتباط tcp با ماشین هدف ایجاد می‌کنه.− ماشین دوم لیست پروتکل‌هایی که پشتیبانی می‌کنه رو بهش میده، به همراه کلید عمومی خودش.− ماشین اول یکی از پروتکل‌ها رو انتخاب می‌کنه. اون کلید عمومی هم برای اینکه بررسی کنه ببینه که آیا ماشینی که باهاش ارتباط گرفته شده، همونی هست که میخوایم یا نه؟      در واقع اونجا که تو ترمینال بهمون یک چیز عجیب غریب بهمون نشون میده تا تایید کنیم این همون کلید عمومی ماشین هدفه. بعد از اینکه تاییدش کردیم، این کلید عمومی توی ماشین خودمون توی فایل known_hosts در ~/.ssh ذخیره میشه. این ذخیره کردن کلید عمومی(به همراه یکی سری اطلاعات دیگه) باعث میشه تا تو ارتباط‌های بعدی روند تایید کلید عمومی اون به صورت خودکار انجام بگیره و نیازی به تایید دوباره ما نباشه. نمونه‌ای از چیزی که ذخیره میشه رو تو تصویر پایین نشون داده شده:نمونه اطلاعاتی که توی فایل known_hosts ذخیره شده− بعد از این دو تا ماشین با کمک الگوریتم دیفی-هلمن بر روی یک کلید که بهش session key توافق می‌کنند. در واقع این کلید برای بخش رمزنگاری متقارن و کلید اون استفاده خواهد شد. انتقال اطلاعات بین این دو ماشین به کمک این کلید رمز خواهد شد. شاید سوالی پیش بیاد که چرا از همون کلید عمومی و خصوصی برای  انتقال اطلاعات استفاده نشد باید عرض کنم که رمزنگاری نامتقارن هزینه بیشتری رو برای رمزنگاری/گشایی داره. به عنوان مثال نسبت به رمزنگاری متقارن کندتر هستند.    بزارید برای درک بهتر یک توضیح خلاصه‌ای هم از الگوریتم دیفی-هلمن داشته باشیم، این توضیح رو هم به کمک رنگ‌ها انجام میدیم تا ملموس‌تر باشه. اول کار دو طرف روی یک رنگ با هم توافق می‌کنند(مثلا روی رنگ زرد). این انتخاب عمومیه و اگر کسی اون ارتباط رو شنود کنه از این رنگ آگاه هستش. بعد هر طرف برای خودش یک رنگ رو در نظر می‌گیره و به هیچ کس نمیگه(مثلا من قرمز رو انتخاب میکنم و طرف مقابل هم سبز رو). چون این رنگ رو به کسی نگفتم پس پیش من محرمانه مونده و شنود هم تاثیری تو ماجرا نداره. حالا این رنگ زردی که اول کار توافق کرده بودیم رو با رنگی که خودم انتخاب کردم(قرمز) قاطی می‌کنم و میدم به طرف مقابل(طرف مقابل من هیچ ایده‌ای نداره من چه رنگی انتخاب کردم). اون هم همین کار رو می‌کنه، یعنی رنگ زرد رو با سبز که خودش انتخاب کرده قاطی می‌کنه و میده به من(منم هیچ ایده‌ای از انتخاب اون ندارم). حالا بخش جالب ماجرا اتفاق میوفته. من رنگ خودم یعنی قرمز رو به ترکیب طرف مقابل(یعنی ترکیب زرد و سبز) اضافه می‌کنم و طرف مقابل من هم سبز رو به ترکیب من(یعنی زرد و قرمز) اضافه می‌کنه و بووم هر دو می‌رسیم به یک ترکیب رنگ ثابت(کلید)!! [نکته اینکه تو ریاضی ترتیب ضرب کردن اعداد تاثیری تو نتیجه نهایی نداره]https://skerritt.blog/diffie-hellman-merkle/خب بالاخره ارتباط ما امن شد و حالا تازه می‌رسیم به اینکه ببینیم که آیا طرفی که میخواد به ماشین هدف دسترسی داشته باشه، آدم درستی هست یا نه(یعنی رمز عبور یا هر چیزی که داده درسته یا نه)؟احراز هویتبرای احراز هویت چند روش وجود دارد که راحت‌ترین روش، استفاده از رمز عبوره، یعنی کاربر میگه می‌خوام به عنوان فلان کاربر وصل شم اینم رمزش. ماشین هدف هم خیلی عادی میره چک میکنه ببینه که اطلاعاتی که داده شده درسته یا نه؟ این ارسال اطلاعات(رمزعبور اینا) هم توی همون ارتباط امن ایجاد شده انجام میشه.اما روش پیشنهاد شده استفاده از احراز هویت با استفاده از جفت کلیدهای ssh هستش(تو این روش باید کلید عمومی ماشین اول تو ماشین هدف کپی شده باشه یا یک جفت کلیدی باشه که به هر نحوی کلید عمومی دست ماشین هدف و کلید خصوصی دست ماشین اول باشه)! روند کار چطوریه؟− ماشین اول یک شناسه برای ماشینی که میخواد بهش وصل بشه رو میفرسته و میگه این شناسه کلید منه.− ماشین هدف میره سراغ کلیدهایی که تو خودش ذخیره داره و دنبال اون کلیدی که ماشین اول گفته میگرده.− بعد از اینکه کلید رو پیدا کرد(اگر پیدا نکرد میگه به سلامت ما شمارو نمی‌شناسیم) یک عدد تصادفی ایجاد می‌کنه و اون با کلید عمومی ماشین اول رمز می‌کنه و برای ماشین می‌فرسته(و میگه اگر راست میگی این عبارت رو رمزگشایی کن ببینم).− ماشین اول پیام دریافتی رو با کلید خصوصی رمزگشایی می‌کنه و عدد تصادفی که ماشین هدف ایجاد کرده بود را با کلید جلسه(session key) که تو مرحله امن کردن ارتباط ایجاد شده بود ترکیب می‌کنه و از MD5 استفاده می‌کنه تا درهم‌ش رو پیدا کنه(hashing) و به ماشین هدف ارسال می‌کنه.− ماشین هدف عدد تصادفی که ایجاد کرده بود رو با کلید جلسه ترکیب و MD5 می‌گیره اگر با این مقدار با مقداری که ماشین اول فرستاده یکی باشه میگه بله درسته شما احراز هویت شدی و یک shell بهش میده میگه بیا کاراتو بکن. https://spectralops.io/blog/guide-to-ssh-keys-in-gitlab/همین! لبخند بزنین لطفا :)منابع:https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Thu, 20 May 2021 12:30:06 +0430</pubDate>
            </item>
                    <item>
                <title>در جدول session جنگو چه اطلاعاتی ذخیره می‌شود؟</title>
                <link>https://virgool.io/@vahid_fathi/%D8%AF%D8%B1-%D8%AC%D8%AF%D9%88%D9%84-session-%D8%AC%D9%86%DA%AF%D9%88-%DA%86%D9%87-%D8%A7%D8%B7%D9%84%D8%A7%D8%B9%D8%A7%D8%AA%DB%8C-%D8%B0%D8%AE%DB%8C%D8%B1%D9%87-%D9%85%DB%8C-%D8%B4%D9%88%D8%AF-gc1q0tfi9ply</link>
                <description>اگر شما هم مثل من زیاد با django سروکله میزنید، با فهمیدن روند کار بخش‌های مختلف اون، درک بهتری از رفتار اون خواهیم داشت، که اون هم باعث میشه در مواقع مختلف(از خطایابی یا توسعه ویژگی جدید) ‌بتونیم تصمیمات بهتری رو بگیریم. یکی از بخش‌ها مدیریت sessionهاست که به صورت پیشفرض هم تو پروژه‌های جنگویی وجود داره و جدولش تو پایگاه داده ساخته میشه. تو این پست می‌خوایم بفهمیم که داخل این جدول چه چیزهایی ذخیره میشه و جنگو ازش چطوری استفاده می‌کنه.جنگو تو نسخه ۳ خودش تو این بخش یک سری تغییراتی داشته، ولی از اونجایی که هنوز بخش خوبی از پروژه‌های جنگویی با نسخه ۲ اون هستش، به خاطر همین به هر دو نسخه پرداخته میشه(البته من نسخه ۲٫۲ و ۳٫۲ جنگو رو بررسی کردم و از بقیه نسخه‌ها خبر ندارم).ساختار جدولبرای شروع با پایگاه داده و یک جدول طبیعتا آشنایی با ساختار اون بهترین گزینه روی میزه(یادش بخیر یه زمانی هم با &quot;همه گزینه‌ها روی میزه&quot; تهدید می‌شدیم. البته خوبیش این بود فقط تهدید بود D:). جدول session سه تا ستون به اسم‌های session_key, session_data, expire_date رو شامل میشه. نوع داده‌های این سه تا ستون رو می‌تونید تو عکس پایین ببنید(اون NN یعنی Not Null):ساختار جدول sessionو البته نمونه‌ای از داده‌ای که تو اون ذخیره شده:نمونه داده‌ یک سطر جدول sessionخب از اونجایی که session_key و expire_date مشخصه برای چی هستند، توضیحی درموردشون نمیدیم و میریم سراغ ستون session_data تا ببنیم که چه اطلاعاتی رو تو خودش ذخیره کرده.تفاوت نسخه ۲ با نسخه ۳ از داده‌‌ای که تو این ستون ذخیره میشه. در واقع اطلاعاتی که ذخیره میشه یکیه ولی نحوه پیش‌پردازش این اطلاعات متفاوته که باعث تولید داده متفاوت برای هر نسخه میشه.جنگو ۳اول از همه بریم ببینیم که تو این ستون چی ذخیره شده:اطلاعات ذخیره شده در ستون session_dataخب مشخصه که چیز قابل فهمی برای ما نیست. اما از اون جایی که ما خیلی کنجکاویم، به این راحتی‌ها بیخیال ماجرا نمیشیم و میریم که یک نگاهی به کد منبع(source code) جنگو بندازیم. سوال اولی که مطرح میشه اینکه که خب برادر من جنگو دو خط کد نیست که جای کد جنگو رو نیگا کنیم؟ جواب سوال هم اینکه خب اصولا وقتی داریم درمورد session حرف میزنیم باید ماژول session رو نگاه کنیم دیگه! برای همین میریم تو مسیر زیر و یک نگاهی به کلاس SessionStore می‌ندازیم:from django.contrib.sessions.backends.db import SessionStoreو با همین صحنه‌ ای مواجه می‌شیم:بله theme من دارک نیست!تو این کلاس اون بخشی که تو عکس highlight شده، جایی که میره داده رو از پایگاه داده می‌خونه و به زبون آدمیزاد تبدیلش می‌کنه(البته ممکنه تو ادامه دوباره دستکاری بشه). گرفتن رکورد مرتبط از پایگاه داده که هیچی مشخصه(خط ۴۳)، پس بریم سراغ تابع decode و پیاده سازی اون رو ببینیم(خط ۴۴). این تابع توی کلاس پدر این کلاس(یعنی کلاس SessionBase) پیاده‌سازی شده و به این شکله:def decode(self, session_data):
    try:
        return signing.loads(session_data, salt=self.key_salt, serializer=self.serializer)
    # RemovedInDjango40Warning: when the deprecation ends, handle here
    # exceptions similar to what _legacy_decode() does now.
    except signing.BadSignature:
         try:
             # Return an empty session if data is not in the pre-Django 3.1
             # format.
             return self._legacy_decode(session_data)
        except Exception:
             logger = logging.getLogger(&#039;django.security.SuspiciousSession&#039;)
             logger.warning(&#039;Session data corrupted&#039;)
             return {}
   except Exception:
         return self._legacy_decode(session_data)ما به خط سوم رو بهش علاقه‌مندیم یعنی این خط:signing.loads(session_data, salt=self.key_salt, serializer=self.serializer)اگر هم براتون سواله که signing چیه؟ خدمتتون عرض کنم که یه ماژول جنگو که از مسیر زیر می‌تونید بهش بهش دسترسی داشته باشید:from django.core import signingو تابع loads اون هم به این شکله:def loads(s, key=None, salt=&#039;django.core.signing&#039;, serializer=JSONSerializer, max_age=None):
    &amp;quot&amp;quot&amp;quot
    Reverse of dumps(), raise BadSignature if signature fails.
    The serializer is expected to accept a bytestring.
   &amp;quot&amp;quot&amp;quot
   return TimestampSigner(key, salt=salt).unsign_object(s, serializer=serializer, max_age=max_age)خب اینم که داره unsign_object رو صدا می‌زنه(دنبال کردن این  فراخوانی‌های تو در تو، در مهندسی معکوس کد یه چیز کاملا عادیه)! این تابع هم به این شکله:def unsign_object(self, signed_obj, serializer=JSONSerializer, **kwargs):
   # Signer.unsign() returns str but base64 and zlib compression operate
   # on bytes.
   base64d = self.unsign(signed_obj, **kwargs).encode()
   decompress = base64d[:1] == b&#039;.&#039;
   if decompress:
      # It&#039;s compressed; uncompress it first.
      base64d = base64d[1:]
   data = b64_decode(base64d)
   if decompress:
      data = zlib.decompress(data)
   return serializer().loads(data)و بله تبریک میگم بالاخره رسیدیم به اونجایی که باید می‌رسیدیم.خب مشخصا این تابع داره کار اصلی رو انجام میده و متن ورودی را از اون حالت غیر قابل فهم به یک حالت دیگه تبدیل می‌کنه به خاطر همین این تابع رو یکم با دقت نگاه کنید چون در ادامه هی بهش برمی‌گردیم. اول کار میاد اون مقدار رو از حالت sign خارج میکنه که این کار به این صورته:def unsign(self, signed_value):
   if self.sep not in signed_value:
      raise BadSignature(&#039;No &amp;quot%s&amp;quot found in value&#039; % self.sep)
   value, sig = signed_value.rsplit(self.sep, 1)
   if (
      constant_time_compare(sig, self.signature(value)) or (
         self.legacy_algorithm and
         constant_time_compare(sig, self._legacy_signature(value))
       )
     ):
       return value
   raise BadSignature(&#039;Signature &amp;quot%s&amp;quot does not match&#039; % sig)این تابع کار خاصی نمیکنه متن رو می‌گیره اون رو rsplite میکنه با دو نقطه(منظور &#x60;:&#x60; ) از اولین دو نقطه در سمت راست(یا آخرین دو نقطه از سمت چپ) جدا میکنه تا آخر رشته و اون بخش جدا شده میشه بخشیه که برای تایید امضا بهش نیاز داریم و بعد از تایید امضا هم بهش نیازی نیست. پس متن ورودی الان ازش آخرین دو نقطه به بعد جدا شد و برگشت به تایع قبلی مون(روند تایید امضا رو دیگه نرفتیم ولی اونم میشد بریم ببینیم چطوریاست).بعد از تایید صحت‌سنجی نوبت به بررسی این میرسه که آیا متن فشرده سازی شده یا نه؟ این تشخیص این مورد هم جنگو خیلی ساده اومده بررسی میکنه اگر اولین کاراکتر این متن برابر .(همون دات یا نقطه خودمون) بود یعنی بله عملیات فشرده سازی روی اون انجام شده. از اینجا می‌فهمیم که جنگو موقع ذخیره کردن اگر فشرده سازی رو انجام داده باشه یک dot به رشته اضافه می‌کنه(اگر خواستین تابع sign_object رو بغل همین تابع unsign_object نگاه کنید).در ادامه اول dot از متن حذف میشه(خط ۸) و با کمک کتابخونه base64 که برای خود پایتون هستش عملیات decode انجام میشه(خط ۹). حالا اگر متن فشرده‌سازی شده باشه، برای برگردوندن به حالت عادی به تابع از تابع decompress تو کتابخونه zlib استفاده می‌شه(خط ۱۱) و بله تبریک می‌گم داده ‌شما آماده‌ست.و در نهایت چیزی که برمیگرده همچین چیزیه:1a19a60d28166014f835175fbb7b0c2be11a1905: {&amp;quot_auth_user_id&amp;quot:&amp;quot1&amp;quot,&amp;quot_auth_user_backend&amp;quot:&amp;quotdjango.contrib.auth.backends.ModelBackend&amp;quot, &amp;quot_auth_user_hash&amp;quot:&amp;quot0eee586903fb6b3a54d73d8b2e4e8f08d7b0e0b0&amp;quot}جنگو ۲مثل جنگو ۳ اول کار بریم ببینیم که داخل ستون session_data چی ذخیره شده(عکس پایین).داده‌ای که تو session_data ذخیره شده.اینجا هم چیزی که ذخیره شده، قابل فهم نیست اما از شکل و شمایلش به نظر base64 میاد(البته واقعا هم base64 هستش). پس بیایم با همدیگه اون رو decode کنیم و ببینیم که به چی می‌رسیم(یکی از این دیکودرهای آنلاین میشه تست کرد):1a19a60d28166014f835175fbb7b0c2be11a1905:
{&amp;quot_auth_user_id&amp;quot:&amp;quot1&amp;quot,&amp;quot_auth_user_backend&amp;quot:&amp;quotdjango.contrib.auth.backends.ModelBackend&amp;quot,
&amp;quot_auth_user_hash&amp;quot:&amp;quot0eee586903fb6b3a54d73d8b2e4e8f08d7b0e0b0&amp;quot}حتی خودمم انتظار نداشتم به این زودی به خروجی برسیم!! D:اگر هم بخوایم که تابع دیکود را در جنگو۲ ببینیم به همچین چیزی می‌رسیم:def decode(self, session_data):
    encoded_data = base64.b64decode(session_data.encode(&#039;ascii&#039;))
    try:
       # could produce ValueError if there is no &#039;:&#039;
       hash, serialized = encoded_data.split(b&#039;:&#039;, 1)
       expected_hash = self._hash(serialized)
       if not constant_time_compare(hash.decode(), expected_hash):
          raise SuspiciousSession(&amp;quotSession data corrupted&amp;quot)
       else:
          return self.serializer().loads(serialized)
    except Exception as e:
       # ValueError, SuspiciousOperation, unpickling exceptions. If any of
       # these happen, just return an empty dictionary (an empty session).
       if isinstance(e, SuspiciousOperation):
          logger = logging.getLogger(&#039;django.security.%s&#039; % e.__class__.__name__)
          logger.warning(str(e))
          return {}بعد از اینکه این اطلاعات بدست اومد، به واکشی اطلاعات کاربر پرداخته میشه و کاربر به شی request اضافه میشه که در نتیجه اون ما تو جاهای مختلف می‌تونیم با request.user کاربر رو داشته باشیم، بدون اینکه واکشی دوباره اطلاعات نیاز باشه.همین! امیدوارم مفید بوده باشه.لبخند بزنین لطفا :)منابع:بخش جنگو ۳، کد منبع جنگوبخش جنگو ۲ هم از این پست الهام گرفته شده(بعضی از ویژگی‌های postgres رو معرفی میکنه):https://www.arctype.com/blog/decoding-django-sessions-in-postgresql/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Tue, 27 Apr 2021 18:45:10 +0430</pubDate>
            </item>
                    <item>
                <title>پایتون و coroutineش!</title>
                <link>https://virgool.io/@vahid_fathi/%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-%D9%88-coroutine%D8%B4-d4ve9jvzhobp</link>
                <description>تو دنیای امروز از همزمانی(concurrency) به صورت بسیار گسترده‌ای استفاده میشه. شناخت جنبه‌های مختلف این موضوع هم به ما کمک می‌کنه تا موقع تصمیم‌گیری بهتر عمل کنیم. coroutine یکی از مواردیه که میشه از اون در همزمانی استفاده کرد، به خاطر همین تو این پست سعی می‌کنیم که ابتدا با مفهوم coroutine بیشتر آشنا بشیم و بعد از اون بریم سراغ پایتون و ببینیم که پایتون چطوری این موضوع رو مدیریت کرده و چطور باید ازش استفاده کنیم؟ امیدوارم که مفید باشد.برای اینکه این پست برای همه قابل فهم باشه توی بخش اول به توضیح بعضی مفاهیم می‌پردازیم و بعد از اون به سراغ پیاده‌سازی اون تو پایتون می‌ریم. پس اگر با این مفاهیم آشنا هستید، می‌تونید مستقیم سراغ بخش پایتونی بروید.ویرگول داره میگه که برای خوندن این پست ۱۹ دقیقه زمان لازمه ولی واقعیت اینه که چون میزان کد خوبی داره پس این زمان درست نیست. حدسم اینه کمتر طول بکشه! ولی در هر صورت پست طولانیه‌ پس سفت بشینین که رفتیم. D:https://www.slideshare.net/sauravmodak/a-beginners-guide-to-asynchronous-javascript-using-memesمفاهیممفهوم Event loopاگر بخوایم خیلی خیلی ساده توضیح‌ش بدیم اینطوریه که یک حلقه همیشه در حال اجرا رو فرض کنید، حالا داخل این حلقه به اتفاق‌های(Event) رخ داده واکنش نشون میدیم و اون‌هارو به دست مدیریت کننده‌های اتفاق می‌سپاریم(event dispatching). سوالی که الان براتون پیش اومده اینکه، منظور از اتفاق چیه؟ جوابم میشه خیلی چیزا(جواب جامع و شاملی بود D:)!! از کلیک کردن روی یک دکمه یا وارد کردن یک مقدار از طرف کاربر بگیر تا تموم شدن خواندن یک فایل. بزارین با یک مثال موضوع رو شفاف‌تر کنیم. یک وب سرور همیشه در حال اجراست که یک درخواست بهش می‌رسه(یک اتفاق رخ داد) درخواست رو می‌گیره و میده به کسی که باید اون رو مدیریت کنه. منتظر اتفاق بعدی می‌مونه. پاسخ به این درخواست حاضر میشه(دوباره یک اتفاق رخ داد) پاسخ رو میگیره و به کسی که باید جواب رو به کاربر برسونه میده(البته در این بین، وب سرورها مفاهیمی مثل صف و polling این جور چیزهارو هم دارند ولی خب هدف اینجا درک موضوع event loop هاست نه موشکافی نحوه کار وب سرورها. به نظرم یک سرچ درباره نحوه کار وب سرور داشته باشین واقعا مفیده).اگر هم بخوایم برای event loop یک شبه کدی بنویسیم یک همچنین چیزی میشه:while 1:
     wait for something to happen
     react to whatever happenedمفهوم coroutineتعریف coroutine:یک نوع تابع که امکان متوقف کردن(pause) اجرای خود قبل از تمام شدن بدنه‌ی تابع را دارد(یعنی قبل از رسیدن به انتهای تابع) و همچنین می‌تواند روند اجرا را به coroutine دیگری هدایت کند. به خاطر همین به courotineها cooperative multitasking هم گفته میشه.بزارین یکم تعریف رو بهترش کنیم، ما وقتی یک تابع می‌نویسیم یعنی از خط اول شروع به اجرا کن تا آخرش و بعد از تابع بیا بیرون، قبول؟ حالا coroutine یک تابع هستش که وقتی در بخشی از بدنه خودش وقتی cpu باید منتظر بمونه اون رو آزاد می‌کنه یا به عبارت دیگه خودش رو متوقف می‌کنه(منظور از منتظر موندن cpu هم موارد مرتبط با I/O هستش. درمورد مسئله‌های I/O-bound و CPU-bound سرچ کنید). این برگشتن وسط تابع شاید شمارو یاد generatorها تو پایتون بندازه!اگر با مسئله‌های I/O-bound و multithreading آشنا باشید می‌تونیم بگیم که، رفتار coroutine شبیه رفتار یک thread که تو multithreading هستش(برای فهم بهتر این مورد رو گفتم در عمل این دوتا خیلی با هم تفاوت دارند)!! با این تفاوت که، coroutine مورد اعتمادمونه و موقعی که cpu رو در اختیار دارد، کاری به کارش نداریم و در عوض اون هم موقعی که به cpu نیازی ندارد، آزادش می‌کنه. احتمالا الان دارین میگین که این حرفا یعنی چی؟ اعتماد چیه؟ مگه به بقیه اعتماد نداریم؟ و ... که البته حق هم دارید. بزارید با شباهت ماجرا شروع کنیم، تو پایتون multithreading اینطوریه که cpu هی از یک thread گرفته میشه و داده میشه به thread بعدی درسته(بحث GIL)؟ بشیه این اتفاق تو coroutine هم داریم یعنی cpu از یک coroutine داده میشه به coroutine دیگه. اما برسیم به تفاوت.ببینید thread یک مشکلی داره و اونم اینکه وقتی cpu رو می‌گیره، ولش نمی‌کنه و اون رو تا زمانی که کارش تموم بشه در اختیار خودش نگه می‌داره، حتی اگر بین کارش یک زمانی‌هایی از cpu استفاده نکنه و منتظر یک سری موارد خارجی باشه، خیلی شیک میگه cpu فقط برای منه! عین یک بچه لجباز که همه چیز رو برای خودش می‌خواد! اما coroutine این طوری نیست و مثل یک بچه خوب وقتی کاراش رو انجام داد و دیگه نیازی به cpu نداشت، میگه بغلی بگیر، چیو بگیرم؟ cpu رو(حالا از این به بعد هر وقت بچه لجباز دیدین یاد thread و بچه خوب دیدین یاد coroutine نیوفتین و زندگی عادی رو داشته باشین D:)!توجه کنید دو تا پاراگراف بالا coroutine و thread رو از لحاظ رفتاری با هم بررسی شدند. از لحاظ ساختار با هم خیلی متفاوت‌اند. به عنوان مثال ما تو thread یک چیزی به اسم call stack مجزا برای هر thread داریم در حالی که تو coroutine اصلا این طوری نیست(همیشه این یادتون باشه که coroutine یک فقط تابع‌ست).thread and context switchمشکل انحصار طلبی thread توسط سیستم‌عامل حل شده، یعنی سیستم‌عامل هر چند وقت یک بار میاد وسط و میگه بچه جون این cpu رو بده ببینیم، کار داریم باهاش(اصلا هم براش مهم نیست که کجای کار بوده و با بی‌رحمی تمام این کار رو انجام میده)! به این عمل گرفتن cpu و عوض کردن کار در حال اجرا context switch گفته میشه. طبیعتا این context switch هم هزینه‌هایی دارد(جلوتر میگیم).https://i.redd.it/9tu18n684z331.jpgاما شاید براتون سوال باشه که چرا context switch بَده؟ فرض کنید cpu در اختیار یک thread و داره کارش رو انجام میده و اتفاقا خیلی هم با شدت داره کار می‌کنه و زمان بی‌استفاده‌ای هم این وسط نداره. اما از اونجایی که سیستم‌عامل بهش اعتماد نداره، دقیقا وسط کارش میاد و cpu رو ازش می‌گیره و بعد تازه باید تصمیم بگیره ببینه نفر بعدی کیه و کی باید cpu رو در اختیار بگیره(حتی ممکنه پس از بررسی، cpu دوباره به همون نخ قبلی داده بشه). این context switch و گرفتن و دادن cpu یک سری هزینه‌هایی داره، به عنوان مثال باید یک داده‌هایی رو در جاهای مختلف مثل ثَبات‌ها(registery) و... بارگذاری(load) کنه و این موارد باعث افزایش زمان اجرا خواهند شد. طبیعتا هرچی تعداد این context switch بیشتر باشه این هزینه‌ها هم بیشتر میشه و این یعنی زمان اجرای یک برنامه بیشتر خواهد شد. دقیقا هم به خاطر همین موضوع context switch هستش که تو کارهای از جنس cpu bound(توجه کنید cpu-bound نه i/o-bound)، چند نخی(multi threading) زمان بیشتری رو نسبت به حالت یک نخی(single threading) خواهد داشت. چون cpu هی داره از این thread گرفته میشه و داده میشه به اون یکی و این تعویض کردن‌ها زمان اجرا رو بالا می‌بره در حالی که تو single thread این عملیات تعویض رو نداریم و کار پیش میره. از اون طرف کارهای I/O bound به دلیل اینکه cpu اکثرا منتظر ورودی هستش و بیکار نشسته، هزینه این context switch به چشم نمیاد هیچ، حتی باعث استفاده بهتر از cpu هم میشه. چون thread فعلی cpu رو بیکار نگه داشته و داره هدر میره، پس گرفتن cpu و دادن اون به thread که به cpu احتیاج داره به صرفه‌ست.یک نکته‌ای که باید بهش توجه کرد اینکه coroutineهارو در عین حال که به عنوان یک بچه‌ی خوب میشناسیمشون، ولی اگر خوب تربیت نشده باشند، می‌تونند بچه خیلی بدی باشند! چطوری؟ به خاطر برنامه‌نویسی بد cpu رو آزاد نشه! این انحصار می‌تونه به شدت روی برنامه ما تاثیر بدی بزاره.https://img.devrant.com/devrant/rant/r_1541402_ZzPjJ.jpgاین بخش حسابی طولانی شد! بُدو بریم سراغ پایتون و ببنیم که این چیزهایی که گفتیم تو پایتون چطوریه؟بخش پایتونیبی هیچ حرف و حدیثی بیاین یک دونه coroutine بنویسیم:&gt;&gt;&gt; async def my_method():
...      print(&amp;quotHi there, I&#039;m a coroutine!&amp;quot)
&gt;&gt;&gt; my_method()
&lt;coroutine object my_method at 0x7f3d46e94a40&gt;بله به همین سادگی! البته ما اجراش نکردیم(در واقع این فراخوانی منجر به اجرا نشد)!! ولی جلوتر نحوه اجرای اون رو هم خواهیم دید. در ضمن واقعیت اینکه ما تو coroutineها await هم داریم و همین await میگه که آقا جان cpu رو آزاد و خدمت شما. بزارین یک مثال ازش ببینیم:&gt;&gt;&gt; import asyncio
&gt;&gt;&gt;
&gt;&gt;&gt; async def my_method():
...      print(&amp;quotHi there, I&#039;m a real coroutine!&amp;quot)
...      await asyncio.sleep(5)
...      print(&amp;quotwith await in it.&amp;quot)
&gt;&gt;&gt; my_method()
&lt;coroutine object my_method at 0x7f3d46e94a40&gt;نکته مهم:‌ قبل از اینکه ادامه بدیم بزارین یک نکته خیلی مهم رو خدمتتون عرض کنم(این مورد اکثرا اشتباه گرفته میشه) و اونم اینکه coroutine لزوما به معنای همزمانی(concurrency) نیست! بله تو همزمانی استفاده زیادی می‌تونه داشته باشه ولی نوشتن یک coroutine به معنای همزمانی نیست. جلوتر مثال‌هایی از این مورد رو می‌بینیم.کتابخونه asyncioاین کتابخونه(asyncio) تو نسخه ۳٫۴(شایدم ۳٫۳ دقیق یادم نیست!!) رسما به cpython اضافه شد ولی یک سری مشکلات(خوانایی کم) باعث شد که تو نسخه‌های بعدی پایتون بروزرسانی‌هایی داشته باشه. به خاطر همین ممکنه تو نت یک سری کدهارو پیدا کنید که کار نکنه. پس حواستون به این موضوع باشه.توصیه‌ای که دارم اینکه از نسخه ۳٫۸ به بالا استفاده کنید چون بعضی از چیزها تو ۳٫۸ از رده خارج شده و قراره تو نسخه های ۳٫۱۰ یا ۳٫۱۱ کلا از پایتون حذف شوند. مطالب این پست برای نسخه 3.7 و به بالا پایتون می‌باشد.طبق مستندات asyncio یک کتابخونه برای نوشتن کدهای asynchronous با استفاده از سینتکس async/await هستش. از این کتابخونه در مواردی مثل فریم‌ورک‌های asynchronous استفاده میشه تا بتونیم چیزهایی مثل web-servers, database connection libraries, distributed task queues داشته باشیم.بزارین با یک مثال ساده شروع کنیم:# Python 3.7+
import asyncio

async def main():
    print(&#039;Hello ...&#039;)
    await asyncio.sleep(1)
    print(&#039;... World!&#039;)

asyncio.run(main())بله برای اجرای یک coroutine می‌تونیم از asyncio.run کمک بگیریم.توجه کنید که asyncio.run همیشه یک event loop ایجاد میکنه و بعد از اتمام کار نیز اون رو می‌بنده. درضمن به صورت همزمان هم نمی‌تونیم از دوتا asyncio.run در یک thread استفاده کنیم و خطا می‌گیریم. به مثال زیر توجه کنید:import asyncio

async def test():
      await asyncio.sleep(2)
      print(&amp;quottest method&amp;quot)

async def main():
    asyncio.run(test())
    print(&amp;quotmain method&amp;quot)

asyncio.run(main())وقتی این کد را اجرا کنیم به همچین اروری برمی‌خوریم:RuntimeError: asyncio.run() cannot be called from a running event loopبرای همین گفته میشه از این run برای entry point اصلی برنامه‌های async استفاده بشه و ایده‌آل هم اینه فقط یک بار فراخوانی بشه.خب پس برای فراخوانی coroutineهای مختلف چیکار کنیم؟ به طور کلی برای اجرای یک coroutine سه روش وجود دارد:asyncio.runawaitasyncio.create_taskتوضیحات و نحوه استفاده asyncio.run رو بالاتر دیدیم اما برسیم به مورد دوم و ببینیم که تو عمل چطوره؟ تکه کد زیر مدنظر داشته باشید(به زمان‌ها هم توجه کنید):import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print(&amp;quot******************************&amp;quot)
    print(f&amp;quotstarted at {time.strftime(&#039;%X&#039;)}&amp;quot)
    await say_after(5, &#039;hello&#039;)
    await say_after(2, &#039;world&#039;)
    print(f&amp;quotfinished at {time.strftime(&#039;%X&#039;)}&amp;quot)
    print(&amp;quot******************************&amp;quot)

asyncio.run(main())

نتایج 
******************************
started at 17:10:50
hello
world
finished at 17:10:57
******************************اگر حواستون به زمان‌ها باشه میبینین که کد کاملا به صورت ترتیبی اجرا شده و اصلا بحث همزمانی(concurrency) رو نداریم. دلیلش هم اینکه اگر coroutine با await اجرا بشه، منتظر تموم شدن اجرای اون coroutine می‌مونه و بعد میره خط بعد. به خاطر همین هم به جای ۵ ثانیه ۷ ثانیه طول کشید و به جای اینکه اول world چاپ بشه، hello رو اول می‌بینیم. پس می‌بینیم که coroutineها لزوما همیشه به صورت همزمان(concurrency) نیستند.https://media.makeameme.org/created/this-is-confusing-ifq340.jpgبریم سراغ مثال حالت سوم و ببینیم اون چطوریاست:import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(say_after(5, &#039;hello&#039;))
    task2 = asyncio.create_task(say_after(2, &#039;world&#039;))
    print(f&amp;quotstarted at {time.strftime(&#039;%X&#039;)}&amp;quot)
    # Wait until both tasks are completed (should take around 2 seconds.)
    await task1
    await task2
    print(f&amp;quotfinished at {time.strftime(&#039;%X&#039;)}&amp;quot)
نتایج
****************************** 
started at 17:20:30
world
hello
finished at 17:20:35
******************************خب الان اون نتیجه ای که می‌خواستیم رو گرفتیم. دلیلش هم اینکه در حالت سوم coroutine تحت چیزی به اسم Task اجرا میشه(coroutine با wrapper تحت عنوان task اجرا میشه). Task برای اجرای coroutineها به صورت زمانبندی شده هستند. در واقع وقتی create_task رو فراخوانی می‌کنیم داریم میگیم که این coroutine رو زمان‌بندی کن تا در سریع زمان ممکن اجرا بشه. در واقع task یک چیزی شبیه future هستش(جلوتر میگیم). Task برای اجرای حتما نیاز داره تا یک event loop وجود داشته باشه. یعنی تحت شرایط زیر به خطا می‌خوریم:&gt;&gt;&gt; import asyncio
&gt;&gt;&gt; async def test(a):
...     await asyncio.sleep(2)
...     print(&amp;quottest&amp;quot)
&gt;&gt;&gt; async def main():
...      await test(1)
...      print(&amp;quotmain&amp;quot)
&gt;&gt;&gt; asyncio.create_task(main())
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 1, in &lt;module&gt;
  File &amp;quot/usr/lib/python3.8/asyncio/tasks.py&amp;quot, line 381, in create_task
    loop = events.get_running_loop()
RuntimeError: no running event loopراستی task یک مورد داره و اونم اینکه thread-safe نیست. task یکی از مواردیه که تو پایتون ۳٫۷ اضافه شد و قبل اون از asyncio.ensure_future استفاده می‌شد.بزارین از خوبی‌های task چند مورد رو تیتروار اشاره کنیم(از اونجایی که پست طولانی میشه توضیح داده نمیشه):امکان لغو یک taskامکان بررسی وضعیت یک task که آیا تموم شده یا لغو شده.امکان دریافت نتیجه یا خطای حاصلحذف یا اضافه کردن یک callback برای بعد از اتمام taskو...اشیا awaitableاگر ما بتونیم عبارت await رو برای یک شی استفاده کنیم در واقع اون شی یک awaitable هستش. توی پایتون ۳ نوع اصلی awaitable داریم:coroutineTaskFuturesکه دو مورد اول رو بالاتر توضیح دادیم پس بریم سراغ Future و ببنیم چیه؟میشه گفت که Future یک چیزی شبیه promise تو javascript هستش(احتمالا الان دارید میگین که خب promise چیه؟ D:). یا به عبارت دیگه یک شی‌ایه که نتیجه اجرای یک عملیات async رو بالاخره ارائه خواهد داد، که این نتیجه می‌تونه خطا هم باشه حتی! یا مثلا می‌تونید مثل یک صندوق پست در نظر بگیرید که اول کار وقتی نصب میشه خالیه ولی در آینده پر میشه و بسته پستی خواهد داشت.از اونجایی که من تا حالا هیچ تجربه‌ای تو استفاده از Futures نداشتم و فقط خوندم درموردش، به خاطر همین برای این بخش به مستندات خود پایتون رجوع می‌کنم:A Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation. Not thread-safe.When a Future object is awaited it means that the coroutine will wait until the Future is resolved in some other place.Future objects in asyncio are needed to allow callback-based code to be used with async/await.Normally there is no need to create Future objects at the application level code.طبق گفته خود پایتون یک مثال خوب استفاده از Future این میتونه باشه:import asyncio
import concurrent.futures

def blocking_io():
    # File operations (such as logging) can block the
    # event loop: run them in a thread pool.
    with open(&#039;/dev/urandom&#039;, &#039;rb&#039;) as f:
        return f.read(100)

def cpu_bound():
    # CPU-bound operations will block the event loop:
    # in general it is preferable to run them in a
    # process pool.
    return sum(i * i for i in range(10 ** 7))

async def main():
    loop = asyncio.get_running_loop()
    ## Options:
    # 1. Run in the default loop&#039;s executor:
    result = await loop.run_in_executor(None, blocking_io)
    print(&#039;default thread pool&#039;, result)
    # 2. Run in a custom thread pool:
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, blocking_io)
        print(&#039;custom thread pool&#039;, result)
    # 3. Run in a custom process pool:
    with concurrent.futures.ProcessPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, cpu_bound)
        print(&#039;custom process pool&#039;, result)

asyncio.run(main())برای اینکه مطلب طولانی‌تر نشه کد رو توضیح نمیدم ولی اگر کد بالا رو کسی خواست تو کامنت‌ها بگه تا توضیح بدم.خب بریم سراغ gather و wait یک چند تا مثال ازشون ببینیم و مطلب رو کم کم جمعش کنیم.gatherما از gather برای اجرای چندین awaitable به صورت همزمان(concurrent) در ترتیبی که داده شده، استفاده می‌کنیم. حالا اگر نوع این awaitable برابر با coroutine باشه به صورت خودکار به Task تبدیل میشه.احتمالا یکم براتون گنگ و نامفهومه پس بریم سراغ مثال عملی:import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f&amp;quotTask {name}: Compute factorial({i})...&amp;quot)
        await asyncio.sleep(1)
        f *= i
    print(f&amp;quotTask {name}: factorial({number}) = {f}&amp;quot)

async def main():
    # Schedule three calls *concurrently*:
    await asyncio.gather(
        factorial(&amp;quotA&amp;quot, 2),
        factorial(&amp;quotB&amp;quot, 3),
        factorial(&amp;quotC&amp;quot, 4),
    )

asyncio.run(main())

# Expected output:
#     Task A: Compute factorial(2)...
#     Task B: Compute factorial(3)...
#     Task C: Compute factorial(4)...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3)...
#     Task C: Compute factorial(3)...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4)...
#     Task C: factorial(4) = 24استفاده از gather چندتا نکته داره ولی به نظرم بهتره اول با wait آشنا بشیم و بعد بریم سراغ اون‌ها(برای اینکه با گفتن تفاوتشون درک اون‌ها مملوس‌تر باشه).waitعملکردش شبیه به gather هستش. به عنوان ورودی یک iterable از awaitableهارو می‌گیره و اون‌هارو به صورت همزمان اجرا می‌کنه. شاید با خودتون بگید که خب چرا دوتا ویژگی شبیه هم‌ وجود داره؟ اینطوری نیست که gather و wait دقیقا عین هم باشند، نه طبیعتا تفاوت‌هایی که دارند و هر کدوم استفاده خودش رو، اما قبل از اینکه به این تفاوت‌ها برسیم بزارین یک مثال از wait و نحوه استفاده اون ببینیم:# this line is for keep align left to right.
همون تکه کد مثال قبل رو مدنظر داشته باشید.
async def main():
 await asyncio.wait( [
    factorial(&amp;quotA&amp;quot, 2), 
    factorial(&amp;quotB&amp;quot, 3),  
    factorial(&amp;quotC&amp;quot, 4),
 ])  
asyncio.run(main()) https://pics.me.me/when-you-want-to-write-async-code-in-javascript-async-34986517.pngتفاوت‌های gather و waitبرای wait یک دونه iterable از awaitable هارو پاس میدیم ولی gather نه، تک تک awaitable هارو پاس داده می‌شوند. ما تو gather نتیجه اجرا رو خواهیم داشت ولی تو wait یک set به این شکل done, pending خواهیم داشت. یعنی:#gather
async def main():
   results = await asyncio.gather(aw1(&amp;quotA&amp;quot, 2), aw2(&amp;quotB&amp;quot, 3),  aw3(&amp;quotC&amp;quot, 4))
#gather
async def main():
    done, pending = await asyncio.wait([aw1(&amp;quotA&amp;quot, 2), aw2(&amp;quotB&amp;quot, 3),  aw3(&amp;quotC&amp;quot, 4)])یکی از مهم‌ترین تفاوت این دوتا تو پارامتریه که wait می‌گیره. پارامتر return_when مشخص می‌کنه که wait نتیجه برگشتی رو کی بهمون بده. کلا هم سه تا حالت داره: FIRST_COMPLETE, FIRST_EXCEPTION, ALL_COMPLETE که به صورت پیشفرض با حالت ALL_COMPLETE کار می‌کنه. تو FIRST_COMPLETE اولین awaitable که تموم شد نتیجه برمیگرده تو FIRST_EXCEPTION اولین حطایی که رخ بده و ALL_COMPLETE هم منتظر میمونه تموم شه چه خطا داشته باشیم چه نداشته باشیم. در حالی که توی gather اگر خطایی پیش نیاد، منتظر اجرای همه awaitableها می‌مونیم.توی gather اگر خطایی رخ بده بقیه awaitablها اجرا نمی‌شوند ولی توی wait نه. البته برای gather هم میتونیم پارامتر return_exceptions رو برابر True بگیرم تا خطاها‌رو هم به عنوان خروجی در نظر بگیره و ادامه روند رو داشته باشیم.یعنی در حالت عادی اینطوری میشه:import asyncio

async def my_method(i):
    await asyncio.sleep(i)
    if i % 2 == 0:
       print(i, &amp;quotok&amp;quot)
    else:
        print(i, &amp;quotcrashed!&amp;quot)
        raise ValueError
async def main():
   coros = [my_method(i) for i in range(10)]
   await asyncio.gather(*coros)
asyncio.run(main())که نتیجه خروجی این میشه:0 ok
1 crashed
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 1, in &lt;module&gt;
  File &amp;quot/usr/lib/python3.8/asyncio/runners.py&amp;quot, line 43, in run
    return loop.run_until_complete(main)
  File &amp;quot/usr/lib/python3.8/asyncio/base_events.py&amp;quot, line 616, in run_until_complete
    return future.result()
  File &amp;quot&lt;stdin&gt;&amp;quot, line 3, in main
  File &amp;quot&lt;stdin&gt;&amp;quot, line 7, in my_method
ValueErrorحالا اگه ما بیایم به gather پارامتر return_exceptions رو با مقدار True پاس بدیم، همه اجرا می‌شوند و خروجی به شکل زیر میشه:async def main():
    coros = [my_method(i) for i in range(10)]
    await asyncio.gather(*coros, return_exceptions=True)

0 ok
1 crashed
2 ok
3 crashed
4 ok
5 crashed
6 ok
7 crashed
8 ok
9 crashedاگر با wait فراخوانی رو انجام می‌دادیم همچین خروجی‌ای داشتیم:async def main():
   coros = [my_method(i) for i in range(10)]
   await asyncio.wait(coros)

0 ok
1 crashed
2 ok
3 crashed
4 ok
5 crashed
6 ok
7 crashed
8 ok
9 crashed
Task exception was never retrieved
future: &lt;Task finished name=&#039;Task-67&#039; coro=&lt;my_method() done, defined at &lt;stdin&gt;:1&gt; exception=ValueError()&gt;
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 7, in my_method
ValueError
Task exception was never retrieved
future: &lt;Task finished name=&#039;Task-66&#039; coro=&lt;my_method() done, defined at &lt;stdin&gt;:1&gt; exception=ValueError()&gt;
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 7, in my_method
ValueError
Task exception was never retrieved
future: &lt;Task finished name=&#039;Task-68&#039; coro=&lt;my_method() done, defined at &lt;stdin&gt;:1&gt; exception=ValueError()&gt;
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 7, in my_method
ValueError
Task exception was never retrieved
future: &lt;Task finished name=&#039;Task-72&#039; coro=&lt;my_method() done, defined at &lt;stdin&gt;:1&gt; exception=ValueError()&gt;
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 7, in my_method
ValueError
Task exception was never retrieved
future: &lt;Task finished name=&#039;Task-69&#039; coro=&lt;my_method() done, defined at &lt;stdin&gt;:1&gt; exception=ValueError()&gt;
Traceback (most recent call last):
  File &amp;quot&lt;stdin&gt;&amp;quot, line 7, in my_method
ValueErrorتوی wait نتیجه همه‌ی awaitableها به عنوان done در نظر گرفته میشه حتی اگر exception رخ داده باشه و برای اینکه بتونیم خطای رخ داده رو بفهمیم یک همچین حرکتی می‌زنیم:async def main():
    coros = [my_method(i) for i in range(10)]
    done, pending = await asyncio.wait(coros)
    for task in done:
        try:
             await task
        except Exception as err:
            print(&amp;quotooh crap we have exception!&amp;quot,  repr(err))توی cancel کردن هم با هم تفاوت دارند ولی چون پست خیلی طولانی شده به این لینک اکتفا می‌کنم!دیگه چون مطلب خیلی خیلی طولانی شده، بعضی چیزها درباره coroutine رو تیتروار مرور می‌کنیم و رد میشیم:− می‌تونیم به کمک shield از کنسل شدن یک Task جلوگیری کنیم.res = await shield(something())− برای تنظیم محدودیت مدت زمان اجرا از timeout استفاده می‌کنیم.async def eternity():
    # Sleep for one hour
    await asyncio.sleep(3600)
    print(&#039;yay!&#039;)

async def main():
    # Wait for at most 1 second
    try:
        await asyncio.wait_for(eternity(), timeout=1.0)
    except asyncio.TimeoutError:
        print(&#039;timeout!&#039;)

asyncio.run(main())

# Expected output:
#     timeout!− با استفاده از to_thread می‌تونیم تابع خودمون رو توی یک thread جدید اجرا کنیم.− به کمک run_coroutine_threadsafe میتونیم یک coroutine رو داخل event loop مدنظر خودمون اجرا کنیم.− برای گرفتن Task در حال اجرا از current_task استفاده می‌کنیم.− با all_tasks هم همه‌ی Taskهایی که هنوز تموم نشدند رو بدست میاریم.به نظرم با coroutine و کتابخونه asyncio(البته بخش coroutine) تا حد خوبی آشنا شدیم. میدونم که میشد بعضی جاهارو بیشتر بسط داد ولی خب هدف بیشتر آشنا شدن با این موراد بود نه جزيیات خیلی زیاد و اگر کسی نیاز داشت یا کنجکاو بود قطعا می‌تونه با جستجو بیشتر به اهدافش برسه. :)https://medium.com/hackernoon/has-the-python-gil-been-slain-9440d28fa93dبا یک مثال که ارسال چند درخواست http به صورت همزمان هستش، مطلب رو تموم می‌کنیم:import asyncio

from aiohttp import request

async def fetch(url):
    async with request(&amp;quotGET&amp;quot, url) as r:
        return await r.text(&amp;quotutf-8&amp;quot)

URLS = [&amp;quothttps://python.org&amp;quot, &amp;quothttps://duckduckgo.com&amp;quot]
async def main():
  coros = [fetch(url) for url in URLS]
  result = await asyncio.gather(*coros)
  # or you can use wait --&gt; done, pending = await asyncio.wait(coros)
  for result in results:
     print(f&amp;quot{result[:20]}&amp;quot)

if __name__ == &amp;quot__main__&amp;quot:
    asyncio.run(main())همین! شاد و خندون باشید و لبخند لطفا :)منابع:https://docs.python.org/3/library/asyncio-task.htmlhttps://youtu.be/GSiZkP7cI80   (ویدیو فاخریه ببینید)</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Wed, 14 Apr 2021 21:33:08 +0430</pubDate>
            </item>
                    <item>
                <title>داکر چگونه ایزوله‌سازی را فراهم می‌کند؟</title>
                <link>https://virgool.io/@vahid_fathi/%D8%AF%D8%A7%DA%A9%D8%B1-%DA%86%DA%AF%D9%88%D9%86%D9%87-%D8%A7%DB%8C%D8%B2%D9%88%D9%84%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%B1%D8%A7-%D9%81%D8%B1%D8%A7%D9%87%D9%85-%D9%85%DB%8C-%DA%A9%D9%86%D8%AF-b8rvl0sh9m5z</link>
                <description>اولین باری که با مفهوم container آشنا شدم، اولین چیزی که گفتم این بود که چی شده؟ چطوری شد این طوری شد؟ یعنی چی بدون vm محیط‌ ایزوله(isolate) داریم؟ داکر کیه( من با lxc بعدا آشنا شدم و هنوزم پشت صحنه چیکار میکنه رو نمیدونم)؟ این کار رو چطوری انجام میده؟ چطوری ممکنه؟ اصلا ممکنه یا ایستگاه‌مون کردی؟https://pbs.twimg.com/media/Bl1hKGuIMAATV2I.pngجواب کوتاه این سوال(سوال مطرح شده تو عنوان مطلب) استفاده از cgroup و namespaceهاست، که البته به داکر هم ربطی ندارد و از ویژگی‌های هسته لینوکس‌اند، که داکر ازشون برای ایزوله‌سازی استفاده می‌کند. اما اینکه این دو ویژگی چی هستند رو در ادامه با هم بررسی می‌کنیم.ویژگی Namespaceیکی از نیازمندی‌های ایزوله‌سازی، جدا بودن پردازش‌های(process) مرتبط با اون از دیگر پردازش‌هاست به گونه‌ای که انگار این پردازش‌ها در یک دنیای خودشون هستند و دیگر پردازش‌ها هم براشون قابل رویت(visibility) نباشد. این عمل توسط Namespace انجام ‌می‌شود. در واقع Namespace امکان حداسازی چندتا بخش رو برای ما فراهم می‌کند. چیزهایی مثل رابط‌های(interface) شبکه، نصب(mount) فایل‌ سیستم‌ها، pidها و حتی user idها، که داکر با استفاده از این قابلیت‌ها جداسازی موارد مختلف رو انجام ‌میده.خب اگر این ویژگی جزء هسته لینوکس به حساب میاد، پس قاعدتا ما هم باید بتونیم ازش استفاده کنیم. به عنوان مثال می‌تونیم به کمک دستور زیر یک namepace از نوع pid بسازیم و داخلش bash رو اجرا کنیم.$ sudo unshare --fork --pid --mount-proc  bashنتیجه خروجی:root@test:~# ps aux
USER   PID | %CPU | %MEM |  VSZ  |  RSS   |  TTY   | STAT | START| TIME | COMMAND
root    1  | 0.0  | 0.0   | 12932 |  4216 | pts/0 |   S   | 19:54  | 0:00  | bash
root    8  | 0.0  | 0.0   | 14588 | 3520  | pts/0 |  R+  | 19:54  | 0:00  | ps auxجالب بود نه؟حواستون باشه که pid اصلی این bash تو namespace عمومی یک مقدار دیگه‌ست.برای وارد شدن به namespace‌های مختلف هم می‌تونیم از دستور nsenter استفاده می‌کنیم. به نظرتون آیا docker exec معادل همین دستوره؟ نه نیست!! چرا؟ اینجا رو بخونید.https://wvi.cz/diyC/img/ns-pid.pngویژگی Cgroupعبارت cgroup مخفف Control Group هستش و برای کنترل منابع، طراحی و استفاده می‌شود. منظور از منابع هم چیزهایی مثل CPU, RAM, Network, IO هستش. کارش هم اینه که تعیین کند یک پردازش(یا یک گروه از پردازش‌ها) چه میزان از منبع(یا منابع) رو می‌تونه دسترسی داشته باشد. اگر هم بخوایم که یک cgroup ایجاد کنیم، خواهیم داشت:# mkdir /sys/fs/cgroup/memory/vahid/خب الان ما یک محدودیت برای حافظه داریم با نام vahid که داخلش یک سری فایل دارد که هر کدوم استفاده خاص خودش رو دارد. یک فایل به اسم memory.limit_in_bytes وحود دارد که میزان محدودیت استفاده از حافظه رو به بایت مشخص می‌کند و می‌تونیم مقدار اون رو با مقدار دلخواه خودمون عوضش کنیم.مرحله بعد اینکه این محدودیت رو به پردازش مدنظرمون اعمال کنیم. بعد از اعمال اون اگر این پردازش ما از اون حد تعیین شده مقدار حافظه بیشتری رو درخواست کنه با خطای memory allocation مواجه خواهد شد.(عکس پایین رو روش کلیک کنید و تو حالت بزرگ تر ببنین اگر باز مشخص نبود از این لینک کمک بگیرید.)https://wizardzines.com/zines/containers/samples/cgroups.jpgبه عنوان مثال می‌تونیم مقدار محدودیت رو ۱۰ مگ بزاریم و اون رو روی یک bash اعمال کنیم. بعدش بیایم و پایتون رو با این bash اجرا کنیم، اینجاست که یک خطا دریافت می‌کنیم.$ sudo echo  10000000 &gt;  /sys/fs/cgroup/memory/vahid/memory.limit_in_bytes
$ echo $$ &gt; /sys/fs/cgroup/memory/vahid/cgroup.proc
$ pythonتو خط اول حد حافظه رو مشخص کردیم. خط دوم pid مربوط به bash فعلی رو گرفتیم و اون رو به این cgroup ایجاد شده اضافه کردیم(در واقع از cgroup فعلی جدا کردیم و بردیم داخل vahid). یعنی از این به بعد این پردازش با محدودیت‌های تعریف شده برای vahid اجرا بشه. اخر سر هم اومدیم داخل این bash با محدودیت حافظه پایتون رو اجرا کردیم که با خطا مواجه میشیم و از bash میاد بیرون.به همین راحتی! :)ویژگی seccomp-bpfما ایزوله‌سازی با دو ویژگی قبلی انجام دادیم، اما چی می‌شد اگر می‌خواستیم، که system callهارو هم محدود کنیم؟ اینجاست که باید از seccomp-bpf باید استفاده کنیم که مشخص می‌کنه هر پردازش چه system callهایی رو می‌تونه انجام بده.همین! لبخند بزنین لطفا :)منابع:https://jvns.ca/blog/2016/10/10/what-even-is-a-container/https://www.silicon.co.uk/software/open-source/linux-kernel-cgroups-namespaces-containers-186240</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Sun, 17 Jan 2021 19:21:33 +0330</pubDate>
            </item>
                    <item>
                <title>مفهوم virtual subclass در پایتون</title>
                <link>https://virgool.io/@vahid_fathi/%D9%85%D9%81%D9%87%D9%88%D9%85-virtual-subclass-%D8%AF%D8%B1-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-awuzxpqjetz3</link>
                <description>مفهومی که تو این پست درموردش حرف می‌زنیم، اینطوریه که یک کلاس به عنوان فرزند(child) یک کلاس دیگه به حساب میاد، بدون اینکه از کلاس والد(parent) به طور صریح ارث‌بری کرده باشه! چطوری؟ بریم ببینیم.https://thewebtier.com/wp-content/uploads/2019/06/Important-Python-Tips-and-Tricks-for-Programmers.jpg 
آشنایی با مفهوم subclassتوی پایتون وقتی می‌خوایم ببنیم که یک کلاس از یک کلاس دیگه ارث‌بری داشته یا نه از متدی به اسم issubclass استفاده می‌کنیم، یعنی به این شکل:class Parent:
   pass

class Child(Parent):
   pass

print(issubclass(Child, Parent))
# Trueخب تا اینجای کار رفتار طبیعیه و چیز عجیب غریب نداریم.اما ما می‌تونیم به کمک metaclass و بدون ارث‌بری مستقیم همین جواب رو بگیریم! کد زیر نمونه‌ای از این ماجراست:class Base(type):
    def __subclasscheck__(cls, subclass):
           return True

class Parent(metaclass=Base)
    pass

class Child:
   pass

print(issubclass(Child, Parent))
# Trueاگر با metaclass آشنا نیستید، می‌تونید از این مطلب کمک بگیرید: https://vrgl.ir/y13PG یک مثال کاربردی‌ترش رو می‌تونید اینجا ببنید.اما این کار به روش دیگه‌ای هم این کار ممکن می‌باشد، و تو این روش از __subclasshook__ استفاده میشه، به این قطعه کد توجه کنید:from abc import ABC

class Parent(ABC):
   def do_something(self):
       pass

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Parent:
            for B in C.__mro__:
                 if hasattr(B, &#039;do_something&#039;)
                    return True
            return NotImplemented
 اگر دارین میگین که چه نامگذاری مزخرفی بله میدونم :)
البته کد بالا رو میشه این طوری هم نوشت:
       if cls is Parent:
            if any(&amp;quotdo_something&amp;quot in B.__dict__ for B in C.__mro__):
                return True
            return NotImplemented

class Child:
     def do_something(self):
         pass
Parent.register(Child)
print(issubclass(Child, Parent))
# Trueهمین!به یاد عزیزان پروازهای PS752 و IR655و به امید روزهای خوب.منابع:https://docs.python.org/3/library/abc.htmlhttps://realpython.com/python-interface/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Fri, 08 Jan 2021 18:49:53 +0330</pubDate>
            </item>
                    <item>
                <title>موشکافی ماژول GC پایتون و بهبود مصرف حافظه!</title>
                <link>https://virgool.io/@vahid_fathi/%D9%85%D9%88%D8%B4%DA%A9%D8%A7%D9%81%DB%8C-%D9%85%D8%A7%DA%98%D9%88%D9%84-GC-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-%D9%88-%D8%A8%D9%87%D8%A8%D9%88%D8%AF-%D9%85%D8%B5%D8%B1%D9%81-%D8%AD%D8%A7%D9%81%D8%B8%D9%87!-dsqltks0jeew</link>
                <description>تو مطلب قبلی garbage collectorهارو به صورت کلی بررسی کردیم، مورادی مثل مزایا و معیایبشون، الگوریتم‌هایی موجود و ... . پس اگر مطلب قبلی را نخوندین حتما اول اون رو مطالعه کنید و بعد بیاین سراغ این مطلب چون گاها به چیزهایی اشاره میشه که اونجا گفته شدند. https://vrgl.ir/1y8Wd این مطلب به garbage collector پایتون اختصاص داره و اینکه این ماژول از پایتون چطوری کار می‌کند. اما قبل از اون دوتا نکته مهم رو خدمتتون عرض کنم:نکته ۱: این مطلب از دو بخش تشکیل شده که بخش اول به نحوه پیاده‌سازی GC تو پایتون و جزئیات اون می‌پردازیم. اینکه اون زیر چه خبره، پایتون چطوری GC رو استفاده می کنه و کلا درباره جزئیات این ماژول حرف می‌زنیم. توی بخش دوم هم به نحوه‌ی نوشتن برنامه‌های بهتر از جهت حافظه می‌پردازیم. پس اگر به نکات مربوط به بخش اول علاقه‌ای ندارین، می‌تونین مستقیم سراغ بخش دوم مطلب بروید. واقعیت اینه که اگر توسعه دهنده خود پایتون نیستید(منظورم خود زبان پایتونه)، بخش اول و دونستن جزئیات در این حد، تقریبا هیچ کاربردی براتون نداره(D:). من کنجکاویم گُل کرد(یا به قولی کِرم درونم) و تا این حد پیش رفتم، از طرف هم گفتم شاید یک نفر یک روزی مثل من به این کنجکاوی گرفتار بشه. :)نکته ۲: مطلب نوشته شده بر اساس مستندات توسعه دهندگان پایتون و تغییرات اون تا تاریخ ۷ سپتامبر ۲۰۲۰ هستش(تا به امروز که ۲۲ دسامبر ۲۰۲۰ هم تغییری نداشته). پس اگر این مطلب رو تو تاریخ دیگه‌ای مطالعه می‌کنید حتما مستندات رو هم مروری داشته باشید.بخش اول: ماژول GC پایتون و جزئیاتشپایتون برای الگوریتم GC خودش از reference count استفاده می‌کنه که این الگوریتم از شمارش تعداد رفرنس‌ها کمک گرفته می‌گیره(که بهش refcount میگن). اگر کنجکاور هستید که بدونید توی پایتون چطوری میشه تعداد refcount هر عنصر رو بدست آورد، می‌تونید از کد زیر کمک بگیرید:&gt;&gt;&gt; import sys
&gt;&gt;&gt; x = object()
&gt;&gt;&gt; sys.getrefcount(x)
2
&gt;&gt;&gt; y = x
&gt;&gt;&gt; sys.getrefcount(x)
3
&gt;&gt;&gt; del y
&gt;&gt;&gt; sys.getrefcount(x)
2احتمالا براتون سواله که چرا وقتی یک متغیر رو تعریف می‌کنیم مقدار getrefcount برابر ۲ میشه و یک نیست؟ دلیلش اینکه که یک رفرنس هم به خود تابع getrefcount پاس داده میشه.ویرایش بعد از انتشار پست: یک سوال خیلی جالبی رو فرهاد تو بخش نظرات پرسیده، به نظرم از دستش ندین. اگر هم براش جواب یا نظری دارید، خوشحال میشم که اشتراک دانش کنید و من رو از این بی‌سوادی نجات بدین :)اما همون که طور که می‌دونیم(از مطلب قبلی) این الگوریتم مشکل cycle رو داره(تشکیل شدن دور در رفرنس‌ها). این مشکل چطوری پیش میاد؟ کد زیر رو در نظر بگیرید:my_list = list()
my_list.append(my_list)اما این مشکل، یک مشکل اساسی‌ایه که هر زبان برنامه‌نویسی‌ای استفاده کننده این الگوریتم باید اون رو رفعش کنه. پایتون هم از قاعده مستثنی نیست، اگر به طور خلاصه بخوایم بگیم پایتون از بررسی دوره‌ای برای حل این مشکل کمک گرفته، اما اینکه این روش چطوری کار می‌کنه رو در ادامه با هم دیگه بررسی می‌کنیم. اول از همه با ساختار یک شی در حافظه آشنا می‌شویم.طرح معماری و ساختار شیپایتون برای ذخیره یک شی در حافظه ساختاری داره که اون ساختار به این شکله:object ---&gt; +--+--+--+--+--+--+--+--+--+--+--+ \
            |              ob_refcnt            ||
            +--+--+--+--+--+--+--+--+--+--+--+  | PyObject_HEAD
            |              *ob_type             ||
            +--+--+--+--+--+--+--+--+--+--+--+ /
            |              ...                |در پایتون به ساختار بالا یک بخش دیگه هم اضافه میشه تا اینکه از Garbage Collector هم به شکل موثرتری بتونیم استفاده کنیم، در نتیجه ساختار به این صورت تغییر پیدا می‌کنه:            +--+--+--+--+---+--+--+--+--+--+--+--+ \
           |              *_gc_next                ||
            +--+--+--+--+---+--+--+---+---+---+--+|| PyGC_Head
           |              *_gc_prev                ||
object ---&gt; +--+--+--+--+---+--+--+--+--+--+--+--+/
           |             ob_refcnt                |\
            +--+--+--+--+---+--+--+--+--+--+--+--+|| PyObject_HEAD
           |              *ob_type                ||
            +--+--+--+--+---+--+--+--+--+--+--+--+/
           |               ...             |اینکه چرا از لینک لیست دو طرفه استفاده شده، دلایل مختلفی داره که یکی از اون‌ها بحث اشیا و نسل‌ها(Generation) هستش، که در مطلب قبلی بهش اشاره کردیم(اشیا نسل جوان و نسل پیر). اگر دوست دارین که یکم درمورد این ساختار عمیق‌تر بشین می تونین از این لینک کمک بگیرین. خب با ساختار حافظه آشنا شدیم، اما هنوز نمی‌دونیم که مشکل مربوط به cycle چطوری حل شده؟!درمورد مشکل cycle گاها گفته میشه که، cycle در شرایط خیلی خاصی پیش میاد و ممکنه اصلا بار حافظه‌ای زیادی رو نداشته باشه و زبان‌های برنامه‌نویسی هم اگر مشکل حادی پیش نیاد می‌تونه بیخیالش باشه. ولی واقعیت امر اینه که این حرف حداقل تو پایتون درست نیست و این ماجرا رو تو پایتون زیاد داریم! به عنوان نمونه:ا، exceptionها شامل traceback می‌شوند که اون هم شامل یک لیست از فریم‌ها میشه که اونا خودشون exception دارند!نمونه‌های کلاس‌ها به کلاس اشاره دارند که کلاس ها هم به ماژول اشاره دارند، از اون طرف هم ماژول به تمام موارد داخلی خودش رفرنس داره!! حتی یک ماژول گاها به ماژول‌های دیگه هم رفرنس داره که در یک پروسه‌ای امکان به وجود اومدن دور از اون طریق هم وجود داره(اگر در اون‌ها به موردی از این ماژول ارتباط داشته باشیم).استفاده از data structureهایی مثل گراف که شامل دور هستند.خب پس فهمیدیم که مشکل cycle توی پایتون به وجود میاد. بریم سراغ راه‌حل و یه نگاه مختصری هم به اون داشته باشیم.https://pics.ballmemes.com/garbage-collection-gt-reference-counting-cmv-69836224.png پایتون برای شناسایی cycle از دوتا linked list دو طرفه استفاده می‌کنه: یکی شامل تمام اشیایی که باید اسکن بشوند(object to scan)، که جنس این اشیا از نوع container objects هستش(اشیایی ممکنه یک یا چند تا به یک یا چندتا شی رفرنس داشته باشند. اخر کار چند نمونه از این نوع رو مثال میزنیم). یکی هم شامل اشیایی به صورت موقت غیرقابل دسترس درنظر گرفته شده‌اند(unreachable یا بهتر بگیم tentatively unreachable).روش کار اینطوریه که پایتون اول کار، اشیای مدنظر(container objects) رو تو linked list که برای اسکن هستش، قرار میده. همون‌طور که قبلا گفته بودیم، بعدش الگوریتم پیدا کردنِ دور میاد و از مقدار refcount هر عنصر یک دونه کم می‌کنه. توی مرحله بعد، موقع پیمایش میاد به refcountها نگاه می‌کنه، اگر یک شی با refcount صفر پیدا کنه اشیایی که به اون ارتباط دارند رو کاندیدای حذف کردن می‌کنه و به linked list که برای این کار درنظرگرفته انتقال میده(یعنی به tentatively unreachable).خب حالا پایتون بررسی می‌کنه ببینه که آیا اشیایی که کاندیدای حذف هستند قابل دسترسی‌اند یا نه؟ اگر قابل دسترسی بودند(یعنی میشد با پیمایش بهشون رسید) اون‌ها رو به لیست عادی برمی‌گردونه  در غیر این صورت پاک‌شون می‌کنه. این روند کلی ماجرا بود. برای درک بهتر موضوع، یک مثال تصویری هم داشته باشیم.فرض کنید که همچنین شرایطی داریم:&gt;&gt;&gt; import gc     # از این ماژول بگیرین help یا dir خواستین یه 

&gt;&gt;&gt; class Link:
...    def __init__(self, next_link=None):
...        self.next_link = next_link

&gt;&gt;&gt; link_3 = Link()
&gt;&gt;&gt; link_2 = Link(link_3)
&gt;&gt;&gt; link_1 = Link(link_2)
&gt;&gt;&gt; link_3.next_link = link_1
&gt;&gt;&gt; A = link_1
&gt;&gt;&gt; del link_1, link_2, link_3

&gt;&gt;&gt; link_4 = Link()
&gt;&gt;&gt; link_4.next_link = link_4
&gt;&gt;&gt; del link_4

# Collect the unreachable Link object (and its .__dict__ dict).
&gt;&gt;&gt; gc.collect()
2خب حالا روند کار به ترتیب برابره با:مرحله اول همه اشیا که قراره اسکن بشوند.مرحله دوم از تعداد رفرنس‌های هر عنصر یک دونه کم می‌کنیمعناصری که با شی‌ای با رفرنس صفر در ارتباط هستند رو کاندید حذف در نظر می‌گیریمبررسی می‌کنیم ببنیم که از این کاندیداهای حذفی چیزی قابل دسترس هست یا نه؟اشیایی که قابل دسترس نیستند حذف می‌کنیم امیدوارم که روش حل cycle خوب توضیح داده باشم. :)اینکه پایتون چرا این عنصرهارو بین دو linked list جابه‌جا می‌کنه هم دلیل داره، برای اینکه جلوگیری از طولانی شدن مطلب بهش نمی‌پردازیم ولی اگر دوست دارین، می‌تونین دلیلش رو اینجا بخونینش.نسل‌ها در پایتونما توی پایتون ۳ تا نسل داریم. اسمشون نسل۰، نسل۱ و نسل۲ هستش. ترتیب این نسل‌ها به این صورت است که نسل ۰ جوان‌ترین نسل و نسل ۲ هم به عنوان پیرترین نسل به حساب می‌آیند. نسل ۱ رو هم نسل میان‌سال بگیم.نحوه استفاده پایتون از نسل‌هاابتدای امر هر شی‌ای که ایجاد میشه به نسل۰ منتقل میشه. بعد از اجرا شدن GC روی نسل ۰، اون اشیایی که زنده موندن به نسل ۱ منتقل می‌شوند(نسل ۰ بیشترین تعداد دفعات اجرای GC رو داره). همین ماجرا برای نسل ۱ هم اتفاق میوفته، یعنی GC روی نسل ۱ اجرا میشه و اشیایی که پاک نشدن به نسل ۲ منتقل می‌شوند. نکته مهم ماجرا اینکه که اشیا داخل نسل ۲ تا آخر اجرای برنامه زنده می‌مونند(البته واقعیت اینه ممکنه روی این نسل هم GC هم اجرا بشه)!یک نکته جالبی که توی بحث نسل‌ها داریم به زمان اجرا شدن GC روی اون‌ها مربوط میشه. پایتون برای تشخیص زمان اجرای GC روی نسل‌ها از حد(threshold) استفاده می‌کنه. به این صورت که وقتی تعداد اشیا اون نسل به یک حدی رسید، GC روی اون نسل اجرا می‌شوند. جالبی ماجرا اینجاست ما می‌تونیم این مقدار حد رو تغییر بدیم!خب اول ببنیم که مقدار این حد چطوری میشه به دست آورد:&gt;&gt;&gt; import gc
&gt;&gt;&gt; gc.get_threshold()
(700, 10, 10)
اون ۷۰۰ برای نسل صفر هستش
چون انتظار داریم توی نسل‌های پیرتر تعداد شی کمتری داشته باشیم
در نتیجه حد اون‌ها هم کمهاگر هم بخوایم تعداد شی‌های موجود در یک نسل رو بدونیم:&gt;&gt;&gt; gc.get_objects(generation=0)
تعداد اشیا نسل صفر میگه و
طبیعتا به جای اون صفر می تونیم از ۱ و ۲ هم استفاده کنیماما گاها ممکنه دلمون بخواد(یا نیاز داشته باشیم) که gc رو خودمون فراخوانی کنی تا شروع به اجرا بکنه که برای اون هم خواهیم داشت:&gt;&gt;&gt; gc.collect()
برای کل نسل‌ها
&gt;&gt;&gt; gc.collect(generation=0)
برای نسل ۰
طبیعتا برای نسل‌های ۱ و ۲ هم داریمشاید بگین که کی آخه gc رو خودش فراخوانی می‌کنه؟ باید بگم که یکی از دوستان تعریف می‌کرد داشته با کتابخونه pandas کار می‌کرده و رَم‌ش کم بوده به خاطر همین میومده و gc رو فراخوانی می‌کرده(کاری نداریم که این کار درست بوده یا نه! هدف اینه که تحت شرایطی ممکنه نیاز به فراخوانی داشته باشیم).خب برسیم به اینکه چطوری حد نسل‌ها رو عوض کنیم. خیلی راحت:&gt;&gt;&gt; gc.set_threshold(100)
&gt;&gt;&gt; gc.get_threshold()
(100, 10, 10)
&gt;&gt;&gt; gc.set_threshold(200, 30)
&gt;&gt;&gt; gc.get_threshold()
(200, 30, 10)
&gt;&gt;&gt; gc.set_threshold(200, 40, 12)
&gt;&gt;&gt; gc.get_threshold()
(200, 40, 12)اجرای GC روی نسل ۲(پیرترین نسل)اگر متن‌های مختلف رو بخونین احتمالا گفته میشه که پایتون اشیا نسل ۲ رو تا اخر اجرای برنامه زنده نگه می‌داره. واقعیتی وجود داره و اون هم اینه که این حرف درست نیست و تحت شرایطی GC روی این نسل هم اجرا میشه. شرط اجرای GC برای این نسل برابر است با long_lived_pending / long_lived_totalو اگر این درصد از ۲۵ بالاتر بزنه GC شروع به اجرا می‌کنه. منبع: خودم D:دسته بندی نوع‌های اشیابالاتر گفته بودیم که پایتون برای فقط container objects رو باهاشون کار داره و نوع simple رو کاری بهش نداره. ببنیم که این دو دسته چی هستند؟نوع اتمیک(atomic) یا ساده(simple)نوع غیر اتمیک(شامل container )از انواع اتمیک می‌تونیم به int, string و tupleها و dictهایی که فقط شامل immutable هستند اشاره کنیم.از نوع غیر اتمیک میشه به list, class instance و دیکشنری‌هایی که mutable دارند، اشاره کرد.تکه کد زیر می‌تونه موضوع رو براتون شفاف‌تر کنه:&gt;&gt;&gt; gc.is_tracked(0)
False
&gt;&gt;&gt; gc.is_tracked(&amp;quota&amp;quot)
False
&gt;&gt;&gt; gc.is_tracked({})
False
&gt;&gt;&gt; gc.is_tracked({&amp;quota&amp;quot: 1})
False
&gt;&gt;&gt; gc.is_tracked([])
True
&gt;&gt;&gt; gc.is_tracked({&amp;quota&amp;quot: []})
Trueبه نظرم کافیه و تا حد خیلی خوبی با GC پایتون آشنا شدیم. در واقع ما الان اکثر چیزهایی که یک توسعه‌دهنده پایتون درمورد GC اون میدونه ما هم می‌دونیم(طبیعتا اون یکم بیشتر چون ما تمام بخش‌هارو پوشش ندادیم).اما برسیم به یک سری نکات که می‌تونه به ما تو نوشتن برنامه‌های بهتر از لحاظ حافظه کمک کنه.بخش دوم: برنامه پایتونی بهتری بنویسیم!https://memegenerator.net/img/instances/62225259/damn-why-cant-i-run-faster.jpgدر این بخش سعی میشه نکاتی رو بگیم که باعث می‌شوند که برنامه از لحاط مصرف حافظه عملکرد بهتری داشته باشه. به نظرم توضیح خاصی نیاز نیست و بریم سراغ نکات.اولین نکته اینکه پایتون لزوما هر حافظه‌ای که آزاد می‌کنه رو به سیستم‌عامل برنمی‌گردونه! در واقع پایتون یک memory allocator کوچک داره که از خونه‌هایی که آزاد و به سیستم‌عامل برگردونده نشدند استفاده می‌کنه تا اون‌ها رو به اشیایی که تو آینده قراره ساخته بشوند اختصاص بده. این خودش می‌تونه باعث مشکلاتی بشه اون هم به این صورت که اگر هی تعداد این خونه‌های آزاد نشده بیشتر بشود، مصرف حافظه بالا میره.جمع نکردن رشته‌هااز اونجایی که رشته‌ها از نوع immutable هستند در نتیجه هر بار که ما + رو استفاده می‌کنیم، پایتون یک آدرس حافظه رو گرفته و قبلی رو بیخیال میشه.  راه‌کارش هم اینه که به جای + از f-string اینا استفاده کنید.این خوب نیست:part_one = &amp;quothello&amp;quot
part_two = &amp;quotpython&amp;quot
part_three = &amp;quotworld&amp;quot
my_message = part_one + &amp;quot &amp;quot + part_two + &amp;quot &amp;quot + part_threeاین خوبه(توجه کنید کد بالا زشت هم هست حتی):my_message = f&#039;{part_one} {part_two} {part_three}&#039;استفاده از joinاگر یک لیستی داریم که می‌خوایم جمعشون کنیم از join استفاده کنیم.این خوب نیست:lines = [&amp;quotline1\n&amp;quot, &amp;quotline2\n&amp;quot, &amp;quotline3\n&amp;quot]
content  = &amp;quot&amp;quot
for line in lines:
    content += lineاین خوبه:lines = [&amp;quotline1&amp;quot, &amp;quotline2&amp;quot, &amp;quotline3&amp;quot]
&#039;\n&#039;.join(lines)استفاده از generatorها:این عزیزان به ما کمک می‌کنند که در هر بار فقط یک عنصر رو از یک شی iterable برگردونیم(به جای اینکه کل عناصر رو برگردونیم). طبیعتا میزان حافظه‌ای که یک عنصر می‌گیره خیلی کمتر از کل عناصر هستش.for item in items:
    yield itemاز توابع و کتابخانه‌های خود پایتون استفاده کنید.به طور معمول توابع و کتابخانه‌های خود پایتون استفاده بهتر و بهینه‌تری از حافظه دارند.کد بد:my_words = list()
for word in words:
     my_words.append(word.strip())کد خوب:my_words = map(str.strip, words)دقیق مطمئن نیستم ولی فک کنم list comprehensiveها هم موثر هستند.از itertools استفاده کنید.به جای استفاده از کد زیر:results = list()
for item in [True, False]:
    for i in range(1, 5):
        do_some_stuff(item, i)این رو میتونیم بهترش کنیم:from itertools import product, chain
list(chain.from_iterable(do_some_stuff(item, i) for i, item in product ([True, False], range(1,5))))استفاده از __slot__اگر کلاسی قراره تعداد زیادی نمونه داشته باشه، برای بهینه کردن مصرف حافظه بهتره که از __slot__ استفاده کنیم. دلیل چرایی اون رو هم می‌تونین از این مطلب بخونین. https://vrgl.ir/UAJEI اگر عمری بود و یادم نرفت احتمالا احتمالا به مرور زمان این لیست رو تکمیل‌ترش کنم(یه چیزهایی یاد می‌گیرم).همین! لبخند بزنین لطفا :)منابع:https://devguide.python.org/garbage_collector/https://towardsdatascience.com/memory-management-in-python-6bea0c8aecc9</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Fri, 01 Jan 2021 18:28:04 +0330</pubDate>
            </item>
                    <item>
                <title>پشت صحنه garbage collector در زبان‌های برنامه‌نویسی</title>
                <link>https://virgool.io/@vahid_fathi/%D9%BE%D8%B4%D8%AA-%D8%B5%D8%AD%D9%86%D9%87-garbage-collector-%D8%AF%D8%B1-%D8%B2%D8%A8%D8%A7%D9%86%D9%87%D8%A7%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87%D9%86%D9%88%DB%8C%D8%B3%DB%8C-hgw6bp7gcn45</link>
                <description>داشتم یک مطلبی می‌خوندم که دیدم درمورد garbage collector اطلاعات زیاد دقیقی ندارم، به خاطر همین گفتم برم ببینم پشت صحنه چه خبراست و زبان‌های برنامه‌نویسی چطوری دارند، این موضوع را مدیریت می‌کنند. چیزهای خوبی را یادگرفتم و امیدوارم بتونم بعضی از این موارد را منتقل کنم.نکته: تلاش می‌کنم که متن را به صورت محاوره ننویسم ولی احتمالا حواسم نباشه و جملاتی از متن به صورت محاوره باشه. پیشاپیش پوزش بابت این ناهمگونی. :)مقدمهاین مطلب برای آشنایی با garbage collectorهاست. اینکه چطوری کار می‌کنند؟ چه الگوریتم‌ها وجود دارد؟ چه رویکردهایی مورد استفاده‌ست و کلا چیزهایی از این قبیل. اما از اونجایی که برای درک بهتر موضوع باید با برخی موارد آشنا باشیم، به خاطر همین یک مقدمه خیلی مختصری را داریم. بعد به سراغ اصل موضوع یعنی garbage collectorها می‌رویم و  عمیق‌تر بررسی‌شون می‌کنیم.سعی می‌کنم طی چند روز آینده یک پست جداگانه درمورد garbage collector تو پایتون هم داشته باشیم.اجرای برنامه‌هابرنامه‌ها به هنگام اجرا در حافظه باگذاری(load) می‌شوند. قبل‌ترها اینطوری بود یک تکه از حافظه جدا و به برنامه اختصاص داده می‌شد تا در اون برنامه بارگذاری بشود و تمام! برنامه‌ها هم هر جوری دوست داشتند رفتار می‌کردند(ملق می‌زدند، از این‌ور به اون‌ور، اون‌ور به این‌ور و خلاصه شلم شوربایی بود)! اما این کار باعث یک سری مشکلات می‌شد. مشکلاتی مثل مشکلات امنیتی! چطوری؟ چون همه به همه‌ی خانه‌های حافظه دسترسی داشتند و هیچ کنترل و نظارتی نبود، در نتیجه امکان تغییر محتوای خانه‌ها وجود داشت! یک برنامه مخرب خیلی راحت می‌تونست به خراب‌کاری در سیستم بپردازد. این مشکلات باعث تغییر این رویکرد و پدیدار شدن، بخش‌بندی و جداسازی شد. بخش‌هایی مثل بخش کد، بخش دیتا و ... که کنترل هم می‌شدند. به دلیل اینکه مطلب طولانی نشه این بخش‌ها را توضیح نمی‌دهیم(اگر علاقه‌مند هستید این لینک می‌تواند کمک کننده باشد). به طور خلاصه عکس پایین این بخش‌ها را نشان می‌دهد.https://en.wikipedia.org/wiki/Data_segment با توجه به موضوع این مطلب(garbage collector)، در بین این بخش‌ها ما به heap علاقه‌مندیم. دلیل آن هم این است که به طور کلی garbage collectorها در بخش Heap اجرا می‌شوند. توصیه می‌کنم اگر با heap و stack آشنا نیستید، قبل یا بعد از این مطلب حتما یک مطالعه‌ای درباره آن‌ها داشته ‌‌‌‌‌‌‌باشید(حتی به صورت خیلی خلاصه وار. منم آخر سر یک لینک میزارم)، چون هم درک بهتر و عمیق‌تری از برنامه‌نویسی بدست می‌آورید و هم برخی از رفتار‌های زبان‌های برنامه‌نویسی براتون مملوس‌تر می‌شود.مدیریت این بخش heap همیشه از چالش‌هایی بوده که طراحان زبان‌های برنامه‌نویسی باهاش مواجه بودند و هستند. پایه‌ای‌ترین سوالی که همیشه مطرحه اینه که آيا این کار باید توسط خود برنامه‌نویس صورت بگیرد؟ یا خودِ زبانِ برنامه‌نویسی باید قابلیتی را برای آن ارائه کند؟ به عبارت دیگر اصلا یک زبان برنامه‌نویسی باید garbage collector داشته باشد یا نه؟ این مطلب برای این جواب دادن به این سوال نیست! چون این سوال یک جواب مشخص و به اصطلاح صفر و یکی ندارد که بتوان با قاطعیت برای همه یک نسخه واحد پیچید و تامام! اما شناخت جنبه‌هایی از این موضوع، به ما کمک می‌کند تا دید بهتری داشته باشیم.در رابطه با سوال پاراگراف قبل(اینکه مدیریت heap چگونه باشد)،‌ دو تا رویکرد وجود دارد که مثل همیشه هر کدوم از این رویکردها مزایا و معایبی را دارند. در ادامه مطلب یک اشاره مختصر به این مزایا و معایب خواهیم داشت و سپس به اصل موضوع می‌پردازیم. توجه کنید که ما درمورد allocation بحثی نداریم و چالش اصلی، آزاد کردن و ازبین‌بردن اشیا بی‌مصرف حافظه می‌باشد.مدیریت حافظه توسط برنامه‌نویس:از معروف‌ترین زبان‌های برنامه‌نویسی که کنترل حافظه را به خود برنامه‌نویس‌ها سپرده‌اند، می‌توان به c و ++c اشاره کرد. برنامه‌نویس‌های این زبان‌ها باید دائما حواسشون به حافظه و اینکه کِی حافظه را اختصاص و کِی حافظه‌ای را آزاد کنند، باشد.مزایا:سرعت بیشتر: شی بی‌مصرف در لحظه و بدون ایجاد وفقه‌ی خاصی ازبین رفته و فضای اختصاص داده شده به حافظه برمی‌گردد.سادگی: هیچ‌گونه پیچیدگی‌ای ندارد و پیاده‌سازی آن راحت می‌باشد(در واقع پیاده‌سازی‌ای ندارد).رفتار قابل پیش‌بینی: با توجه به اینکه همه چیز به عهده خود برنامه‌نویس می‌باشد در نتیجه رفتار غیرقابل پیش‌بینی رخ نمی‌دهد. به عنوان مثال یک مرتبه روند اجرای برنامه متوقف و garbage collector شروع به اجرا بکند.معایب:معروف‌ترین مورد: memory leaks. البته این مورد ممکن است با garbage collector هم اتفاق بیوفتد، اما در حالتی که حافظه توسط برنامه‌نویس کنترل می‌شود، معمولا بیشتر اتفاق می‌افتد.افزایش زمان توسعه(سردرد شدید): برنامه‌نویس باید حواسش به اشیا مختلف باشد. کِی حافظه را آزاد کنه و کِی نه و این سردرد به حساب میاد. نتیجه‌ی این بررسی دائم نتیجه‌ی کندتر شدن سرعت توسعه می‌باشد(توجه گردد که به هنگام بحث ما باید همه برنامه‌نویس‌ها را مدنظر داشته باشیم نه فقط برنامه‌نویس‌های senior‌).استفاده از حافظه خالی شده: بنا به دلایل مختلف ممکن است یک حافظه خالی شده، توسط خود برنامه‌نویس یا یک برنامه‌نویس دیگر دوباره مورد استفاده قرار می‌گیرد.خالی کردن حافظه خالی: برنامه‌نویس یک خانه از حافظه را که قبلا خالی شده، دوباره خالی می‌کند!شاید با خودتون بگین که چطوری ممکن است که این اتفاق‌ها بیافتد؟ یعنی چی از یک حافظه که خالی شده دوباره استفاده کنیم و یا دوبار خالی شود و... . باید خدمتون عرض کنم که وقتی چند نفر روی یک موضوع کار می‌کنند، از این اتفاق‌ها پیش میاد.https://preview.redd.it/wi0v46k4jhq21.jpg?auto=webp&amp;s=2fb6bd400a7845f61af85c6db635628eb74aa432 مدیریت حافظه توسط زبان برنامه‌نویسی:تقریبا اکثر زبان‌های برنامه‌نویسی سطح بالا این قابلیت را دارند، زبان‌هایی مثل جاوا، پایتون، سی‌شارپ و... . از آنجایی که این فرآیند توسط خود زبان برنامه‌نویسی مدیریت می‌شود، پس باید یک الگوریتم آن را انجام دهد. اینکه این الگوریتم چطوری باشد و کی شروع به کار کند و ... بحث‌هایی هستند که ما بهشون علاقه‌مند هستیم. اما این خودکارسازی(Automation) مزایا و معایبی را به همراه دارد:مزایا:افزایش سرعت توسعه و برنامه‌نویسی راحت: برنامه‌نویس به حافظه و اینکه حافظه کی خالی یا پر می‌شود، توجهی نمی‌کند. طبیعتا تو پروژه‌های حساس و یا چالشی که حجم حافظه کمه، ممکن است برنامه‌نویس مجبور باشد به این مورد اهمیت بدهد ولی به طور کلی در اکثر موارد مهم نمی‌باشد.کمتر شدن مشکلات امنیتی ناشی از مدیریت حافظه: در طی سالیان با کشف باگ‌ها و افزایش تجربه طراحان زبان‌های برنامه‌نویسی این روند رفته رفته بهتر شده است.رفع بعضی از معایب روش مدیریت دستی.معایب:مصرف منابع: برای اجرای دستورات خود garbage collector به cpu نیاز داریم. از سوی دیگر برای نگهداری اطلاعات موردنیاز GC(گاها به جای Garbage Colletor از GC استفاده خواهد شد) به حافظه نیاز داریم که به طبع این‌ها منابع را مصرف و هزینه دارند.کاهش performance: اول از همه بگم که این‌طوری نیست که یهو performance را خیلی کم کند، نه، اما طبیعتا باعث کاهش هرچند خیلی خیلی جزئی‌ای خواهد شد. طبیعتا در اکثر برنامه‌های موجود در جهان این میزان کاهش اصلا، اصلا، اصلا مطرح و مهم نیستند، چون کار performanceی خاصی انجام نمی‌دهند. اما  این کاهش چرا رخ می‌دهد؟ GC نیاز به بررسی حافظه دارد، که این بررسی به زمان نیاز دارد و این زمان یک زمان مفید نمی‌باشد. توجه گردد که در مورد قبل ما درباره استفاده از cpu و حافظه حرف زدیم، اما اینجا درباره خود زمان لازم برای بررسی حرف می‌زنیم(البته طبق ادعای یک مقاله، درمواردی ممکن است، در مجموع نه تنها باعث کاهش performance نگردد بلکه باعث افزایش آن نیز ‌شود).غیرقابل پیش‌بینی بودن: اینکه garbage collector کی و کجای برنامه شروع به کار می‌کند، برای ما مشخص نیست و این ممکن است در شرایط خیلی خیلی خاصی مشکلاتی را به وجود بیارد. به عنوان مثال این اتفاق شاید شاید در یک سفینه فضایی یا راکتور هسته‌ای باعث مشکلاتی گردد!https://memegenerator.net/img/instances/55176653/the-garbage-collector-will-handle-it.jpgGarbage Collectionبرسیم به اصل موضوع و اینکه اصلا چند نوع garbage collector داریم؟ چه الگوریتم‌هایی برای پیدا کردن اشیا بی‌مصرف از حافظه پیشنهاد شده‌اند؟ و مواردی از این دست.اما قبل از همه بیایید درباره‌ی موارد پایه‌ای که در رابطه با garbage collectorها مطرح هستش حرف بزنیم. سه نکته اساسی را باید به هنگام بحث از garbage collectorها در نظر داشته باشیم. این سه عبارت‌اند از:حافظه: میزان حافظه heap که برای برنامه اختصاص داده می‌شود.توان عملیاتی(Throughput): مقدار زمان اجرای کد برنامه نسبت به مدت زمانی که garbage collector اجرا می‌گردد. به عنوان مثال اگر بگیم Throughput برابر %99 می‌باشد یعنی در ۹۹ درصد مواقع کد اصلی(برنامه) اجرا شده و ۱ درصد هم برای garbage collector صرف شده است. طبیعتا هرچقدر این عدد به ۱۰۰ نزدیک‌تر باشد، به معنی بهتر بودن garbage collector خواهد بود.تاخیر: مدت زمانی که اجرای برنامه اصلی به خاطر garbage collector با تاخیر مواجه شده است.انواع garbage collectorبرای دسته‌بندی garbage collectorها دیدگاه ما در بررسی تعیین‌کننده خواهد بود. دوتا دیدگاه را بیان می‌کنیم، اما ممکن است دیدگاه‌های دیگری هم باشند(سواد من در حد این دوتاست).− بر اساس نوع اجراIncremental:در این روش garbage collector همراه با برنامه اصلی اجرا می‌شود.stop-the-worldبرخلاف روش قبل در این روش، اجرای برنامه اصلی متوقف و garbage collector به پیدا کردن اشیا بی‌مصرف می‌پردازد.− براساس مدیریت fragmentationاول با fragmentation و مشکلی که این موضوع به همراه دارد آشنا بشیم:به تکه تکه شدن حافظه fragmentation گفته می‌شود که به دو صورت internal و external پدیدار می‌شود. در حالت internal میزان حافظه‌ای که یک شی اختیار می‌کند، بزرگتر از میزان نیازش می‌باشد. یعنی ما شی‌ای داریم که به اندازه x از حافظه نیاز دارد، اما به اندازه y(که بزرگتر از x هستش) حافظه اختیار می‌کند که در این حالت بخش‌هایی از حافظه بی‌استفاده خواهد بود. در حالت external مشکل وجود تکه‌های خالی کوچک حافظه بین خانه‌های حافظه که در حال استفاده هستند را داریم. این خانه‌ها با اینکه خالی هستند اما به دلیل کوچک بودنشون نمی‌توان شی جدیدی را در آن‌ها جای داد و در نتیجه بی‌استفاده می‌مانند. اگر تعداد این خونه‌ها زیاد گردد، میزان پِرتی حافظه زیاد خواهد بود.Compactingبعضی از garbage collectorها همزمان با پاک کردن اشیا بی‌مصرف، خونه‌های مصرف شده را جابه‌جا و در یک‌جا تجمیع می‌کنند. تا خونه‌های کوچک حافظه از بین بروند.non-compactingدر این مورد garbage collectorها پس از پاک کردن کردن اشیا، کاری با بقیه اشیا موجود ندارد.https://ijavayou.files.wordpress.com/2019/02/2ugnhk.jpgاستراتژی‌های تخصیص حافظهاول مطلب گفتیم که بحثی در رابطه با Allocation نداریم ولی از اونجایی که تخصیص حافظه بحث جالبی هستش حیفم اومد که اصلا درموردش حرف نزنیم به خاطر همین خیلی گذرا به این موضوع اشاره می‌کنیم.طبق معمول برای این کار روش‌ها و الگوریتم‌های مختلفی وجود دارد. چند مورد را به صورت خیلی تیتروار مطرح می‌کنیم:Best fit در این روش حافظه بررسی و کوچک‌ترین بلاک حافظه تخصیص داده می‌شود.First fitاولین بلاک حافظه که می‌تواند شی را در خودش جای دهد را تخصیص داده می‌شود.Worst fitبزرگترین بلاک حافظه را انتخاب می‌کند!(می‌پرسید چرا؟ به دردی هم می‌خورد؟ بله ممکن است تحت شرایطی مفید باشد).Next Fitاین الگوریتم شبیه به first fit هستش ولی با این تفاوت که شروع به جستجو را از آخرین بلاک حافظه‌ای که تخصیص پیدا کرده بود، ادامه می‌یابد.یک الگوریتم دیگه هم به اسم ‌Buddy memory allocation که اونم جالبه، یک نگاه بهش بندازید. لینک ۱ و لینک ۲.احتمالا بر اساس توضیحاتی که برای این الگوریتم‌ها داده شد، با خودتون میگین که طبیعتا best fit همیشه بهترین عملکرد را دارد ولی واقعیت اینه که نه این طوری نیست!! چگونه است که این گونه است؟ این گونه:مثال ۱: فرض کنیم که ما دو بلاک حافظه به اندازه‌های ۱۵۰تایی و ۳۵۰تایی(به ترتیب بیان شده) داریم(یعنی اول ۱۵۰ و بعد ۳۵۰). توجه کنید که این دوتا بلاک حافظه از هم جدا هستند، یعنی انگار بلاک ۱۵۰ از آدرس x1 شروع و تا x2 ادامه دارد و بلاک ۳۵۰ از آدرس y1 شروع و تا y2 می‌باشد(چون هم گفتیم که بلاک ۱۵۰ قبل از ۳۵۰ قرار دارد پس y1 &gt; x2).برای این دوتاحافظه با درخواست‌هایی به اندازه‌های ۳۰۰، ۲۵، ۱۲۵ و ۵۰(با همین ترتیبی که گفته شد) مواجه می‌شویم. ببنیم که دو الگوریتم best fit و first fit چگونه این درخواست‌ها را مدیریت می‌کنند(توجه گردد که فرض بر این است که برای تخصیص حافظه به یک درخواست حتما باید تمام تمام خانه‌ها در کنار هم باشند).الگوریتم best fit: برای درخواست ۳۰۰، بلاک حافظه به اندازه ۳۵۰ را انتخاب می‌کند(از ۳۵۰ خانه حافظه ۳۰۰ خانه استفاده و ۵۰ تا اون هنوز خالی و میشه ازش استفاده کرد). درخواست ۲۵ وارد صف می‌شود. در حافظه یک خانه ۱۵۰ تایی و یک خانه ۵۰ تایی داریم. خانه ۵۰تایی انتخاب می‌گردد(باز هم ۲۵ تا از حافظه خالی می‌ماند و میشه ازش استفاده کرد). درخواست ۱۲۵ دریافت می‌گردد. خانه ۱۵۰تایی از حافظه انتخاب می‌شود. ۲۵ خانه باقیمانده به حافظه برمی‌گردد. یعنی الان ما دو بلاک حافظه به اندازه ۲۵تایی داریم. درخواست ۵۰ تایی وارد می‌شود اما به دلیل اینکه بلاک حافظه به اندازه ۵۰ نداریم(دو تا ۲۵ داریم که کنار هم نیستند). حافظه‌ای تخصیص داده نمی‌شود و خطای حافظه کافی در اختیار نیست raise می‌گردد.الگوریتم first fit: برای درخواست ۳۰۰تایی بلاک ۳۵۰ انتخاب و ۵۰تای آن به حافظه بازگشت داده می‌شود. برای درخواست ۲۵ بلاک حافظه ۱۵۰تایی انتخاب(چون در حافظه قبل از بلاک ۵۰ تایی قرار دارد) و ۱۲۵ تا آن بازگشت داده می‌شود. درخواست ۱۲۵ دریافت و بلاک ۱۲۵ تایی اختصاص داده می‌شود، در نهایت نیز درخواست ۵۰تایی دریافت و بلاک ۵۰تایی که از بلاک ۳۵۰ تایی باقی مانده بود تخصیص داده می‌شود.مثال ۲: دو بلاک خالی حافظه به ترتیب و به اندازه‌های ۲۰ و ۱۵ داریم(یعنی ۲۰ قبل از ۱۵ می‌باشد). در شکل پایین مستطیل سمت چپ بلاک ۲۰ تایی و مستطیل سمت راست مربوط به بلاک ۱۵تایی می‌باشد.حالت اول: اگر دو تا شی با اندازه‌های ۱۰ و ۲۰ برای حافظه درخواست بدهند(برای طولانی نشدن مطلب توضیح داده نمیشه ولی اگر مبهم بود تو کامنت‌ها بگین تا همونجا توضیح بدهم):https://www.cs.columbia.edu/~junfeng/10sp-w4118/lectures/l19-mem.pdfحالت دوم: اگر سه شی به اندازه‌های ۸، ۱۲ و ۱۳ برای تخصیص داشته باشیم:https://www.cs.columbia.edu/~junfeng/10sp-w4118/lectures/l19-mem.pdfالگوریتم‌های garbage collectorقبل از اینکه وارد بحث الگوریتم‌ها بشویم. باید با نظریه‌ی Generational آشنا و آن را بفهمیم. بر اساس نظریه نسل‌ها اکثر اشیا عمر طولانی نداشته و در همان لحظات ابتدایی پس از ایجاد شدنشون ازبین می‌روند. به همین دلیل بعضی از الگوریتم‌های garbage collector اشیا داخل حافظه heap را به دو دسته کلی تقسیم کنند: نسل جوان و نسل پیر.یکی از مزایای تقسیم کردن حافظه به دو بخش، اجرا کردن بیشتر الگوریتم  GC در بخشِ نسل جوان می‌باشد. این کار باعث می‌شود از جهتی میزان حافظه کمتری بررسی گردد(کل حافظه پیمایش نمی‌شود) و از طرف دیگر امکان پیدا کردن اشیا بی‌مصرف بیشتر شود(چون اکثر اشیا در همان لحظات ابتدایی ازبین میٰ‌روند)، که در نتیجه آزادسازی تعداد بیشتری از خانه‌های حافظه را خواهیم داشت. حس می‌کنم مطلب خیلی طولانی شده به خاطر همین دیگه بریم سراغ الگوریتم‌ها.الگوریتم‌ها۱− ردیابی(Tracing)در این سبک از الگوریتم‌، روش پیدا کردن اشیا بی‌مصرف براساس قابلیت دسترسی به آن شی می‌باشد. یعنی برای تشخیص اینکه یک شی هنوز قابل استفاده می‌باشد یا نه؟ اگر بتوانیم از طریق پیمایش گراف به آن شی برسیم یعنی هنوز بی‌مصرف نشده و قابلیت استفاده را دارد، در غیر این صورت اون شی باید ازبین برود. احتمالا الان دارین میگین که گراف از کجا اومد(اولا اینکه گراف یک نوع ساختمان داده‌ست که شبیه درخته ولی دور هم دارد) و پیمایش اون چطوریه(BFS یا DFS) و ...؟ اساس این سبک الگوریتم ایجاد یک گراف و پیمایش آن می‌باشد. به نقاط شروع این گراف‌ها root set گفته می‌شود که root set در واقع خانه‌هایی از حافظه هستند که می‌دونیم قابل دسترسی می‌باشند. حالا برای بررسی حافظه از این root set شروع به پیمایش می‌کنیم و به هر خانه‌ای که رسیدیم به عنوان یک خانه مفید حافظه که قابلیت استفاده را دارد علامت‌گذاری می‌کنیم، وقتی که پیمایش تمام شد، خانه‌هایی که علامت‌گذاری نشدند حذف می‌گردند. اینکه پیمایش به چه شکل انجام می‌شود به نحوه پیاده‌سازی الگوریتم بستگی دارد، اما یکی از معروف‌ترین پیاده‌سازی‌های این سبک(mark&amp;sweep) از DFS استفاده می‌کند. این الگوریتم چند نوع پیاده سازی دارد که یکی از معروف‌ترین‌ها و قدیمی‌ترین‌ها اسمش mark &amp; sweep هستش. برای همین این الگوریتم توضیح داده می‌شود، تقریبا بقیه هم تقریبا به این صورت عمل می‌کنند. الگوریتم mark &amp; sweep:این الگوریتم دو فاز دارد: فاز mark  و فاز sweep(جمله فاخری بود D:)! در این الگوریتم برای هر شی یک بیت درنظر گرفته می‌شود که مشخص می‌کند این شی از حافظه پاک شود یا نه؟ فاز mark: در ابتدای این فاز بیت نشانه‌گذاری در همه اشیا، به صفر تنظیم شده است به چه صورت؟ اگر یک شی جدید ایجاد شده باشد به صورت پیشفرض این بیت برای آن شی صفر می‌باشد و اگر شی از قبل وجود داشته باشد، در سریِ قبلِ اجرای الگوریتم، به صفر تغییر داده شده است. الگوریتم از root set شروع به پیمایش می‌کند به هر شی‌ای که می‌رسد بیت نشانه‌گذاری را به یک تغییر می‌دهد و این کار تا جایی ادامه پیدا می‌کند که دیگه همه خانه‌های قابل دسترسی پیمایش گردد. اگر بخواهیم کد را این فاز را شبیه‌سازی کنیم، خواهیم داشت:Mark(node)
     If markedBit(node) = false then
         markedBit(node) = true
         For each v referenced by node
              Mark(v)یک گره را دریافت می‌کنیم اگر بیت نشانه‌گذاریش صفر باشد، اون را به یک تبدیل می‌کنیم. سپس این کار را برای تک‌تک گره‌هایی که از گره فعلی قابل دسترسی هستند، انجام میدیم.فاز sweep: در این فاز اشیا بررسی شده و شی‌هایی که بیت نشانه‌گذاری‌شان صفر می‌باشد حذف می‌گردند. همچین بیت‌نشانه‌گذاری دیگر اشیا نیز از یک به صفر تنظیم می‌گردد تا اشیا برای سری بعد اجرای الگوریتم آماده باشد. شبه کد این فاز:Sweep():
   For each node in heap
       If markedBit(node) = true then
          markedBit(node) = false
       else
          heap.release(node)این عکس gif به صورت خلاصه این الگوریتم را بیان می‌کند.از معایب این روش:متوقف کردن اجرای برنامه اصلی برای بررسی حافظهایجاد شدن fragmentation خارجی۲− شمارش تعداد رفرنس‌ها(Reference counting)این الگوریتم از شمارش تعداد رفرنس‌ها(اشاره‌گر) به یک شی مشخص می‌کند که آیا یک شی بی‌مصرف می‌باشد یا نه؟ این عدد(که با اسم refcount شناخته می‌شود) با تعداد رفرنس‌های یک شی کاهش و افزایش داشته و وقتی که refcount یک شی به صفر می‌رسد، یعنی بی‌مصرف بوده و باید خانه‌های اشغال شده توسط آن شی به حافظه بازگردانده ‌شود. برای نگهداری اطلاعات رفرنس‌ها هم یک overhead خواهیم داشت. اگر بخواهیم که به صورت شماتیک این الگوریتم را نشان دهیم، خواهیم داشت:https://rebelsky.cs.grinnell.edu/Courses/CS302/99S/Presentations/GC/چندتا ویژگی داره که می‌توان به:سادگی: پیچیدگی خاصی ندارد و راحت پیاده‌سازی می‌گردد.سرعت مناسب: برنامه اصلی تنها برای چند لحظه کوتاه متوقف می‌شود و از این جهت مناسب می‌باشد.ازبین رفتن سریع اشیا بی‌مصرف: بازپس‌گیری حافظه به سرعت اتفاق می‌افتد.البته معایبی هم داره که یک موردش خیلی بده:حلقه بی‌نهایت: اگر رفرنس‌ها تشکیل یک دور را بدهند در این صورت این الگوریتم با شکست مواجه خواهد شد و refcount درست نخواهد بود(البته این مورد با کمک اجرای یک الگوریتم به اسم cycle رفع می‌گردد).مشکل اضافه و کم‌کردن تعداد رفرنس‌های یک شی: این مورد می‌تواند باعث pipeline bubbles گردد که البته تکنیک‌هایی برای رفع آن وجود دارد. به عنوان مثال کامپایلر می‌تواند افزایش و کاهش رفرنس‌های نزدیک به هم را در یک مرحله انجام دهد که این کمک کننده خواهد بود.این الگوریتم را می‌توان بر اساس وزن هم پیاده‌سازی کرد که بهش Weighted reference counting گفته می‌شود. برای مطالعه می‌تونید از این لینک و این لینک استفاده کنید.۳−الگوریتم Copyبرای این الگوریتم گاها از نام Stop and Copy هم استفاده می‌شود. الگوریتم به این صورت کار می‌کند که حافظه را به دو بخش تقسیم می‌کند که به یک بخش active(یا from-space) و به بخش دیگر idle(یا to-space) گفته می‌شود. سپس به هنگام تخصیص حافظه فقط از بخش active استفاده خواهد شد. روش تخصیص حافظه نیز خطی بوده و از ابتدا بخش شروع و رفته رفته به سمت انتهای حافظه حرکت خواهد کرد. موقعی که خانه‌های حافظه active دیگر جوابگوی تخصیص حافظه نبودند، برنامه stop و شروع به حذف اشیا بی‌مصرف خواهد نمود. شی‌هایی که هنوز قابلیت استفاده را دارند نیز در بخش Idle(از ابتدای این بخش) کپی می‌گردند. در آخر نیز حافظه بخش idle مرحله قبل به حافظه active و حافظه active مرحله قبل به idle تغییر پیدا خواهند کرد. البته نحوه پیدا کردن اشیا بی‌مصرف و کپی کردن دگیر اشیا نیز خود می‌تواند شامل روش‌های مختلفی داشته باشد. به عنوان مثال می‌توان از Cheney که یک الگوریتم به صورت BFS می‌باشد نام نمود(لینک دادم چون به نظرم مطلب طولانی شده). یکی از معایب این روش کاهش حجم حافظه و استفاده تنها از نصف آن می‌باشد. موردی دیگری که می‌توان به آن اشاره کرد، کپی کردن اشیا در هر چرخه اجرای الگوریتم می‌باشد.https://images.slideplayer.com/25/8257976/slides/slide_23.jpgسعی کردم برای منابع از دانشگاه‌های معتبر استفاده کنم تا احیانا موردی اشتباه گفته نشود، اما طبیعتا ممکن است خطا(یا خطاهایی) وجود داشته باشد، خوشحال می‌شوم اون‌ها را بدانم تا هم مطلب و هم سواد ناقصم را یکم اصلاح کنم. همین! امیدوارم که مفید بوده باشه.در خصوص اجرای برنامه‌ها و کلا ماجرای حافظه در سیستم‌عامل آقای عباس یزدان‌پناه یک سری ویدئو آموزشی خوب و مختصر و مفید را توی کانال یوتیوب خودشون تحت عنوان &quot;گردشی در سیستم‌عامل&quot; قرار دادند که توصیه می‌کنم، این سری ویدئوهارو حتما نگاه کنید، چون دانش خوبی را بدست می‌آورید.یک نمونه از ویدئوهاشون(IP خودتون رو تغییر بدین). https://youtu.be/vrMuzaowMjc لبخند بزنین لطفا :)منابع:https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/lectures/18/Slides18.pdfhttps://www.cs.columbia.edu/~junfeng/10sp-w4118/lectures/l19-mem.pdfhttps://dzone.com/articles/choosing-the-best-garbage-collection-algorithm-forhttps://www.geeksforgeeks.org/partition-allocation-methods-in-memory-management/https://en.wikipedia.org/wiki/Reference_countinghttps://rebelsky.cs.grinnell.edu/Courses/CS302/99S/Presentations/GC/http://wiki.dreamrunner.org/public_html/C-C++/Library-Notes/SimpleGarbageCollector.htmlhttps://en.wikipedia.org/wiki/Garbage_collection_(computer_science)</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Fri, 25 Dec 2020 18:09:24 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با pytest فریمورک محبوب این روزهای پایتون</title>
                <link>https://virgool.io/@vahid_fathi/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-pytest-%D9%81%D8%B1%DB%8C%D9%85%D9%88%D8%B1%DA%A9-%D9%85%D8%AD%D8%A8%D9%88%D8%A8-%D8%AA%D8%B3%D8%AA-%D8%A7%DB%8C%D9%86-%D8%B1%D9%88%D8%B2%D9%87%D8%A7%DB%8C-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-ilqeyrag5asi</link>
                <description>جلسه هفتگی آموزش تیم بود و امیر افیانیان درمورد pytest مطلبی رو ارائه داد. از اونجایی هم که حدود چهار یا پنچ ماه پیش یک مطلب ناقصی در رابطه با این موضوع نوشته بودم، ترغیب شدم کاملش کنم تا این به اشتراک‌گذاری دانش ادامه پیدا کنه. :)https://dev-to-uploads.s3.amazonaws.com/i/ls1nn7bpt6xfxtm6vbam.png
خب بریم سراغ pytest و ببینیم که دلیل محبوبیت این فریمورک چیه و چرا این روزها ازش زیاد استفاده میشه؟ تو این مطلب سعی می‌کنیم با بعضی از ویژگی‌های این فریم ورک آشنا بشیم تا ما هم بریم تو گروه اونایی که از این فریم ورک برای تست نویسی استفاده می‌کنند!!بیاین با اولین جمله‌ای که تو داکیومنت این فریمورک اومده شروع کنیم:The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries.یعنی دو خط تست بنویسی و تامام؟ بریم ببنیم چطوریاست که این طوریاست.(علیرضا دو خط!)بدلیل اینکه مطلب مربوط به نوشتن تست‌های باکیفیت برای فهم بهتر این مطلب می تونه کمک کننده باشه، و گاها هم بهش اشاره‌هایی میشه به همین دلیل لینک‌ش رو میزارم. https://vrgl.ir/xP4Lf ویژگی های pytestنکته: طبیعتا امکان گفتن همه ویژگی‌های pytest وجود نداره(به دلیل سواد ناقص من) و فقط مواردی که احتمالا بیشتر مفید هستد(و من باهاشون آشنا هستم)، گفته می شوند.راحتی در assertion:     این مورد ویژگی خاصی نیست! ولی خب باعث راحتی کار میشه. دیگه موقع تست‌نویسی نیازی نیست که نوع برابری رو هم مشخص کنیم. یعنی assertDict یا assertTrue و ... را نمی‌خواد و فقط با نوشتن assert می‌تونیم مقدار مورد انتظارمون رو با مقدار تست شده بررسی کنیم.به عنوان مثال تو unit test داشتیم:from unittest import TestCase

class TestUserInput(TestCase):
      def test_input_values_validation_true(self):
             # stuff related to create input_values
            self.assertTrue(validate_values(input_values))
حالا اگر بخوایم این مورد را با pytest بنویسیم خواهیم داشت:def test_input_values_validation_True():
      # stuff related to create input_values
      assert validate_values(input_values) == Truehttps://i.redd.it/mrsuwojbyju11.jpgاگر به این دو تیکه کد دقیق‌تر نگاه کنیم به یکی دیگه از ویژگی‌های pytest می‌رسیم که اونم میشه:کدهای Boilerplate کمتر:    اگر توجه کرده باشین تو تکه کد اول ما باید اول import unittest کنیم و بعد کلاس رو بنویسم و بعدش تازه می‌تونیم تست خودمون رو بنویسم ولی توی pytest حتی نیاز به import کردن هم نیست!! این مورد بعدا تو parameterize هم خودش رو نشون میده.امکان اجرای تست های نوشته شده با unittest و nose     یکی از دلایلی که مهاجرت به pytest رو راحت و کم هزینه‌تر میکنه اینکه اگر قبلا تست‌هایی رو با unitest و یا nose نوشته باشیم، بدون هیچ نگرانی بابت اون‌ها شروع به نوشتن تست‌های جدید با pytest می‌کنیم(واقعا امتیاز مهمیه).حالت‌های مختلف با parameterize    بعضی موارد توی کد هستند که باید با شرایط مختلف تست بشوند. مثلا با ورودی های مختلفی مثل عدد منفی، مثبت، رشته و ... . نکته‌ی اذیت کننده اینجاست که برای هر حالتی باید یک تست جداگانه نوشته بشه، که خیلی هم بهم دیگه شبیه هستند و این واقعا رو مخه. pytest این مورد به زیبایی و با parameterize حلش کرده. به عنوان مثال:from datetime import datetime, timedelta

import pytest
# یک نکته ریز 
# کردن ماژول‌ها، اون‌هایی که برای خود پایتون هستند اول میان import  موقع 
# نصب شدن و آخر سر هم ماژول‌های خودمون pip بعد اون پکیچ هایی که با 

testdata = [
    (datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
    (datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]

@pytest.mark.parametrize(&amp;quota,b,expected&amp;quot, testdata)
def test_timedistance(a, b, expected):
    diff = a - b
    assert diff == expected`تو کد بالا ما دو حالت (یا هر چندتا که دلمون بخواد) را به راحتی تست کردیم و چی از این بهتر؟استفاده از fixture    همون طور که قبلا گفتیم استفاده از توابع‌ setUp و tearDown باعث ناخوانایی کد میشه( اگر نمی‌دونین چرا یه به مطلب تست های باکیفیت سر بزنین. به صورت خلاصه چون به صورت ضمنی در هر تست وجود دارند). اما pytest برای فراهم کردن داده‌های موردنیاز از fixtureها استفاده می‌کنه.fixture علاوه بر اینکه به صورت صریح به تابع تست (کلاس تست یا کل حتی ماژول) پاس داده میشه(در واقع خیلی خوشگل و زیبا داره برامون dependency injection می‌کنه) و باعث خوانایی کد میشه. البته یک سری ویژگی دیگه هم داره، مثلا میشه با parameterize ترکیبش کرد تا کارمون راحت‌تر بشه و البته خیلی چیزهای دیگه. fixture با درخواست تست مرتبطش ایجاد و با تموم شدن scopeش از بین میره.برای scope هم پنج حالت داریم که عبارت‌اند از: − function(پیشفرض)− class− module− package− sessionالبته scope داینامیک هم داریم!بحث رو طولانی‌تر نکنیم و بریم سراغ مثال و کد:import pytest

from .my_module import TargetClass

@pytest.fixture
def target_class_instance():
     return TargetClass(&amp;quota input value&amp;quot)

def test_validation_input(target_class_instance):
        assert target_class_instance.validate() == Trueاز اونجایی که هدف این مطلب آشنایی با pytest هستش، به خاطر همین بیشتر از این به fixture نمی‌پردازیم. اما واقعا شاخه و برگ زیادی داره و حتی میشه یک یا چندتا مطلب برای fixture نوشت.توصیه می‌کنم به صورت تیتروار هم که شده این لینک رو مطالعه کنید.استفاده از mark     این ویژگی باعث میشه که به تست‌هامون مِتادیتا اضافه کنیم و براساس این مِتادیتا اتفاقاتی موقع اجرای تست‌ها بیوفته.به عنوان فرض کنید در لحظه بنا به بعضی از شرایط و اتفاقات لازم داریم که یک سری از تست ها skip بشن. خیلی راحت به اون تست‌ها mark مربوط یعنی skip رو می‌زنیم تا موقع موقع اجرا، این تست‌ها در نظر گرفته نشوند. یا یک سری تست باید در صورت برقراری برخی شرایط اجرا بشن(مثلا بر اساس نسخه پایتون). میایم از skipif استفاده می‌کنیم.import sys

import pytest

@pytest.mark.skipif(sys.version_info &lt; (3, 6), reason=&amp;quotrequires python3.6 or higher&amp;quot)
def test_function():
    ...تنظیمات(Config)   یکی از قابلیت های خیلی خوب pytest تنظیمات بی‌نظیرشه که باعث میشه سرعت کار ادم بیشتر بشه. این تنظیمات و فلگ‌های مختلفی که برای اجرای تست‌ها وجود داره، دست آدم رو باز و لذت کار رو بیشتر می‌کند. البته باعث تسریع کل فرآیند هم میشه، که این خودش در نهایت باعث کمک کردن به اصل fast عمو باب می‌کنه. به عنوان نمونه فقط به چند مورد اشاره می‌کنیم(دوست داشتین خودتون هم یک نگاه به داکیومنت‌ها بندازین).− امکان اجرای تست‌هایی که شامل یک سری رشته خاص هستند.− امکان توقف اجرای پروسه‌ی تست‌ بعد از چند(N) تست fail شده.− امکان مشخص کردن میزان verbosity خروجی− امکان اجرای خودکار pdb بعد از fail شدن تست و ... علاوه بر اینکه همه این ها امکان پاس دادن مستقیم از طریق command line رو دارند، می‌تونیم کانفیگ فایل خودمون رو هم داشته باشیم. خوبیه این کار چیه؟ به عنوان مثال ما همیشه موقع اجرای تست‌ها اون‌هارو با یک سری فلگ مشخص اجرا می‌کنیم اما از اینکه هر بار این فلگ‌ها رو بزنیم شاکی‌ایم و غر می‌زنیم. خیلی راحت فایل رو ایجاد می‌کنیم، تا موقع اجرا فلگ‌های مدنظرمون به صورت خودکار اضافه بشوند.# pytest.ini
[pytest]
minversion = 6.0
addopts = -ra -q
testpaths =
    tests
    integrationداشتن extensionهای مختلف        یکی از مواردی که باعث دوست داشتنی‌تر شدن این فریم‌ورک میشه وجود extensionهای کاربردیه اونه. به عنوان مثال pytest-mock یا pytest-cov که اکثرا مورد استفاده قرار می‌گیرند. یک استفاده خوبی که از ویژگی میشه داشت اینکه براساس نیاز خودمون extension بنویسیم. مثلا یک چیزی بنویسیم که تو CI نتیجه اجرای تست‌هامون رو تو سیستم tracking ثبت کنه(چقدر طول کشیده، fail یا warning داشتیم یا نه و ...) .چند مورد از extensionها که ممکنه به کارمون بیان:pytest-mock, pytest-cov, pytest-django, pytest-flask, pytest-xdisthttps://www.testbytes.net/wp-content/uploads/2018/06/Software-Testing-Memes-8.jpg 
اگر نکته‌ای هست که به نظرتون فراموش شده یا سوالی، موردی، یا کلا دوست داشتین یک چیزی بگین، خوشحال میشم تو کامنت‌ها بگین.همین. لبخند بزنین لطفا :)منابعhttps://docs.pytest.org/en/stable/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Mon, 19 Oct 2020 09:26:55 +0330</pubDate>
            </item>
                    <item>
                <title>نوشتن callback اختصاصی برای ansible</title>
                <link>https://virgool.io/@vahid_fathi/%D9%86%D9%88%D8%B4%D8%AA%D9%86-callback-%D8%A7%D8%AE%D8%AA%D8%B5%D8%A7%D8%B5%DB%8C-%D8%A8%D8%B1%D8%A7%DB%8C-ansible-kxgopqbqwzcp</link>
                <description>توجه: برای درک بهتر این مطلب نیاز هستش که با ansible آشنایی مختصری داشته باشین.https://d2c.io/post/extending-ansible-plugins-part-1انسیبل(Ansible) یکی از ابزارهایی که کاربردهای زیادی داره و روز به روز هم داره به محبوبیتش اضافه میشه. یکی از دلایلی که، این ابزار رو دوست داشتنی کرده، امکان استفاده از پلاگین های مختلف هستش. پلاگین هایی که از یک طرف روند انجام کار رو برای ما راحت تر و شیرین تر می کنند و از طرف دیگه زمان انجام کار رو سرعت می بخشند. به عنوان مثال اگر خروجی playbook رو به صورت json نیاز داشته باشیم، کافیه تا پلاگین مربوط به اون رو استفاده کنیم(option زیر رو به کامندمون اضافه می کنیم یا اینکه توی تنظیمات فرمت خروجی رو به json تنظیم می کنیم).ANSIBLE_STDOUT_CALLBACK=jsonبا توجه به چیزی که می بینیم انسیبل از callback برای تولید خروجی json استفاده می کنه. خب سوالی که پیش میاد، اینکه آیا میشه callback اختصاصی خودمون رو داشته باشیم و اگر آره چطوری؟ با توجه به عنوان پست، جواب مشخصا اره ست! اما بریم ببینیم که چطوری باید این کار رو انجام بدیم.https://memegenerator.net/img/instances/62753961/im-ready-lets-go.jpgمقدمهبرای شروع باید بدونیم که callbackها خودشون یک نوع plugin هستند. پس باید با قواعد نوشتن پلاگین ها آشنا بشیم. از اونجایی که انسیبل مستندات خوبی داره، درنتیجه برای جواب دادن به این سوال از خود سایت انسیبل کمک می گیریم:‌ به گفته مستندات موجود در سایت برای نوشتن پلاگین باید چهارتا شرط رو رعایت کنیم. این چهار شرط عبارت اند از:be written in Pythonمورد اول به دلیل اینکه PluginLoader بتونه اون رو لود کنه و به عنوان یک object پایتونی برگردونه(حواستون باشه که این پلاگین باید با نسخه های ۲٫۷ یا ۳٫۵ به بالاتر پایتون نوشته شده باشه. چون انسیبل با این نسخه ها سازگاره)raise errorsاگر موقع اجرای پلاگین اروری غیرمنتظره ای رخ داد باید از AnsibleError برای raise کردن اون استفاده کنین. به عنوان مثال :from ansible.module_utils._text import to_native

try:
    cause_an_exception()
except Exception as e:
    raise AnsibleError(&#039;Something happened, this was original exception: %s&#039; % to_native(e))
                   (چون ممکنه با نسخه های مختلف پایتون اجرا بشه به خاطر همین این شکلی نوشته شده)return strings in unicodeهر رشته ای که برگشت داده میشه باید در نوع unicode پایتون باشه تا بشه تو jinja2 استفاده ش کرد(برای این کار میشه از قابلیت خود ansible استفاده کرد):from ansible.module_utils._text import to_text
result_string = to_text(result_string)conform to Ansible’s configuration and documentation standardsاگر هم بخوایم که آپشن هایی از پلاگین مون قابلیت کانفیگ کردن رو داشته باشه باید اون هارو تو بخش DOCUMENTATION فایل پلاگین قرار بدیم:DOCUMENTATION = &#039;&#039;&#039;
  callback: timer
  callback_type: aggregate
  requirements:
    - whitelist in configuration
  short_description: Adds time to play stats
  version_added: &amp;quot2.0&amp;quot
  description:
      - This callback just adds total play duration to the play stats.
  options:
    format_string:
      description: format of the string shown to user at play end
      ini:
        - section: callback_timer
          key: format_string
      env:
        - name: ANSIBLE_CALLBACK_TIMER_FORMAT
      default: &amp;quotPlaybook run took %s days, %s hours, %s minutes, %s seconds&amp;quot
&#039;&#039;&#039;پیش نیازهای نوشتن پلاگین رو گفتیم اما بد نیست با انواع پلاگین هایی که توی انسیبل داریم هم آشنا بشیم:Action pluginCache pluginCallback pluginConnection pluginFilter pluginInventory pluginLookup pluginTest pluginVars pluginاز اونجایی که این مطلب تمرکزش روی callback plugin هستش به خاطر همین مستقیم سراغ این مورد میریم.نوشتن Callbackhttps://pbs.twimg.com/media/DV1ynIsWkAEszlV.jpgاین نوع پلاگین ها می تونن، موقعی که به رخدادهای(event) مختلف جواب داده میشه، رفتارهای جدیدی رو به انسیبل اضافه کنن. منظور از رخداد چیه مثلا وقتی playbook یا یک تسک شروع به کار میکنه و چیزهایی از این قبیل.اولین نکته برای نوشتن callback اینکه که باید از CallbackBase ارث بری کنیم. یعنی به این شکل:from ansible.plugins.callback import CallbackBase

class CallbackModule(CallbackBase):
    passتا جایی که من می دونم، اینکه چه رخدادهایی(events) رو در اختیار داریم رو انسیبل مشخص میکنه(یعنی برای هر چیزی نمیشه یا حداقل من اینطوری میدونم اگر اینطوری نیست خوشحال میشم تو کامنت ها به منم اطلاع بدین. البته ناگفته نمونه که انسیبل موارد خوبی و جامعی رو ارائه داده). حالا برای اینکه بتونیم رفتارهای مدنظرمون رو پیاده سازی کنیم باید توی callback خودمون توابع متناسب با اون رخداد رو شخصی سازی(customize) کنیم. لیست توابعی که در اختیار داریم رو می تونیم از فایل __init__.py تو lib/ansible/plugins/callback ببینیم. خوشبختانه اسم شون خیلی گویاست، به طوری که باعث شده موقع فراخوانی هر تابع و اینکه برای چه کاریه کاملا مشخص باشه.نکته: توابعی که با v2 شروع می شوند برای نسخه ۲٫۰ به بعد انسیبل هستند. https://github.com/ansible/ansible/blob/96b74d3e0b340f1bc6b3102d874f17516fe35e79/lib/ansible/plugins/callback/__init__.py مثال عملیفرض کنیم که یک playbook خیلی زمان گیر داریم برای همین اون رو اجرا و به امون خدا ولش می کنیم. حالا میخوایم بعد از تموم شدن اجرا، تسک های fail شده و unreachable برامون ایمیل بشه( خودِ انسیبل برای ایمیل یک پلاگین آماده داره ولی خب از اونجایی که  این سناریو باعث درک بهتر قضیه میشه از همین موضوع استفاده می کنیم. هدف بیشتر اینکه با فرایند آشنا بشیم و بفهمیم چه کارهایی میشه انجام داد و ایده بگیریم).برای اینکه در زمان صرفه جویی بشه کد رو میزاریم و بعد میریم سراغ اینکه ببینیم هر بخش چیکار میکنه(البته بخش اصلی تا حد خیلی خوبی مشخصه).# These imports are defined for every callback plugin I&#039;ve seen so far.
# If you don&#039;t import `absolute_import` standard library modules may be
# overriden by Ansible python modules with the same name. For example: I use the
# standard library `json` module, but Ansible has a callback plugin with the
# same name. When I excluded `absolute_import` and imported `json` the json
# module I got was Ansible&#039;s json module and not the standard library one.
# I&#039;m not sure why `division` and `print_function` need to be imported.
from __future__ import (absolute_import, division, print_function)
import time

from ansible.plugins.callback import CallbackBase

__metaclass__ = type

# Ansible documentation of the module. I&#039;m also not sure why this is required,
# but other plugins add documentation so it seems to be a standard.
DOCUMENTATION = &#039;&#039;&#039;
    callback: failed_tasks
&#039;&#039;&#039;

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_NAME = &#039;failed_tasks&#039;
    CALLBACK_NEEDS_WHITELIST = True

    def __init__(self):
        super(CallbackModule, self).__init__()
        self.playbook_info = dict()
        self.playbook_info[&#039;duration&#039;] = dict()
        self.failed_tasks = list()
        self.unreachable_tasks = list()

    def v2_playbook_on_play_start(self, play):
         self.playbook_info[&#039;name&#039;]: play.get_name(),
         self.playbook_info[&#039;id&#039;]: to_text(play._uuid),
         self.playbook_info[&#039;duration&#039;][&#039;start&#039;] = time.time()

    def v2_runner_on_failed(self, task_result, ignore_errors=False):
         self.failed_tasks.append(self._get_task_detail(task_result))

    def v2_runner_on_unreachable(self, task_result):
         self.unreachable_tasks.append(self._get_task_detail(task_result))
 
    def _get_task_detail(self, task_result):
         return {
                &amp;quothost&amp;quot: task_result._host.get_name(),
                 &amp;quotresult&amp;quot: task_result._result
         }

    def playbook_on_stats(self, task_result, ignore_errors=False):
         self.playbook_info[&#039;duration&#039;][&#039;end&#039;] = time.time()
         self.send_email()

    def send_email(self):
        # do email stuffs.از اونجایی که ما با نسخه ۲٫۰ به بعد انسیبل کار می کنیم به خاطر همین فقط از این رفتیم سراغ توابعی که با v2 شروع می شوند.کلا تو این بخش از کد بهترین چیزی که میتونه به ما کمک کنه تا  اینکه بفهمیم توی هر تابع(به کمک متغیرهای ورودی) به چه چیزهایی دسترسی داریم، دیباگ کردنه. به شخصِ از pdb و dir تو این مورد خیلی کمک گرفتم. به این نکته حواستون باشه.https://www.hackadda.com/media/blog/abhi/2020/02/07/debugging-meme.jpgتابع v2_playbook_on_play_start:این تابع موقع شروعِ اجراء ِ playbook فراخوانی میشه و آرگومان play رو هم به عنوان ورودی می گیره، که به کمک این آرگومان می تونیم به موارد مرتبط با playbook بدست پیدا کنیم. مواردی مثل vars و ... .تو کد ما، این تابع اسم playbook، به همراه id و زمان شروع به کار رو تنظیم کرده. نکته: تو این تابع برای دسترسی به موارد مختلف playbook و متعیرهاش از get_variable_manager زیاد استفاده می کنیم. یعنی:self.manager = play.get_variable_manager()
vars = self.manager. .... تابع v2_runner_on_failed:وقتی که رانر یک تسک رو اجرا کنه و اجرای اون با خطا مواجه بشه این تابع فراخوانی میشه(بله میدونم که از اسمش مشخصه ولی باید توضیح داده بشه). ما هم اسم تسک و علت اینکه خطا رخ داده رو ذخیره می کنیم.تابع v2_runner_on_unreachable:این تابع هم وقتی که یک تسک unreachable باشه فراخوانی میشه(به جان عمه م میدونم اسمش مشخصه ول کن!). مثل تابع fail اطلاعات تسک رو می گیریم  تا موقع ارسال ایمیل این اطلاعات رو داشته باشیم.طبیعتا اگر تسکی fail یا unreachable نشود، این دوتا هم تابع فراخوانی نمی شوند.تابع playbook_on_stats:(خب شما که کچل مون کرده بودی از اسمش مشخصه، از اسمش مشخصه، بگو ببینیم این کی فراخوانی میشه و به چه کاری میاد؟ نه بگو دیگه! چرا در و دیوار رو نیگا میکنی D:)این تابع بعد از همه فراخوانی میشه. به خاطر همین ما هم اومدیم زمان تموم شدن اجرا رو تنظیم کردیم و بعد هم گفتیم برو کارهای مرتبط با ارسال ایمیل رو شروع کن(از اونجایی که هدف نحوه ارسال ایمیل نیست کدش رو ننوشتیم).اما دوتا تابع وجود داره که توی کد ما نیستند ولی ممکنه زیاد استفاده بشوند به خاطر همین به اونا هم یک اشاره ی کوتاهی می کنیم.تابع playbook_on_task_start، طبیعتا موقع شروع هر تسک فراخوانی می شود. آرگومان های ورودیش هم به ترتیب task و بعدش is_conditional هستند.تابع v2_runner_on_start که از نسخه ۲٫۸ به بعد انسیبل اومد و موقعی که یک تسک تو یک host اجرا میشه فراخوانی میشه. یعنی اگر قرار باشه یک هاست ۴ تا تسک رو اجرا کنه، ۴ بار فراخوانی میشه. آرگومان های ورودیش هم به ترتیب host و پشت سر اون task هستش.کلی تابع دیگه هم هست. حتما یک نگاه به اون لینک بالایی بندازین.نکاتدرمورد callback ها چندتا نکته وجود داره که گفتنشون می تونه کمک کننده باشه و ما رو از ساعت ها دیباگ کردن و حرص خوردن(مخصوصا اینکه در ظاهر همه چی درست باشه ولی بازم کار نکنه D:) نجات بده.https://www.hackadda.com/media/blog/abhi/2020/02/07/when-you-finally-find-the-bug.jpgبرای stdout همزمان فقط یک دونه callback میشه تنظیم کرد. برای همین انسیبل متغیر CALLBACK_TYPE رو گذاشته تا این نوع callback رو از بقیه callback هارو تشخیص بده.متغیر های CALLBACK_NAME و CALLBACK_VERSION ضروری هستند و حتما باید باشند.اگر از callback_whitelist استفاده بشه، انسیبل به دنبال فایلی هم نام با چیزی که جلوی callback_whitelist نوشته شده می گرده و به مقدار متغیر CALLBACK_NAME داخل کد callback نگاه نمیکنه! یعنی اسم فایل هم تو این مورد مهم هستش.برای گرفتن اطلاعات از playbook یک همچین کارهایی رو می تونین بنویسین: manager = playbook.get_variable_manager()
vars = manager.get_vars()
host_vars = vars[&#039;hostvars&#039;]در نهایت اینکه تو دفعات اول و اینکه به چیزهایی دسترسی دارین، باید دیباگ کنید. پس با pdb و دیباگرها دوست باشین!همین. لبخند بزنین :)منابع:https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.htmlhttps://docs.ansible.com/ansible/devel/plugins/callback.htmlhttps://dev.to/jrop/creating-an-alerting-callback-plugin-in-ansible---part-i-1h0nhttps://hodovi.ch/blog/extending-ansible-with-callback-plugins/https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Mon, 28 Sep 2020 00:16:23 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با metaclass در پایتون</title>
                <link>https://virgool.io/@vahid_fathi/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-metaclass-%D8%AF%D8%B1-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-u5v4yvsahpxm</link>
                <description>اگر برنامه نویس پایتون باشین احتمالا تو کدهای مختلف با چیزی به اسم metaclass مواجه شدین و براتون هم سوال شده این چیه و چیکارا میکنه؟ تو این مطلب سعی می کنم تا جایی که می تونم این موضوع رو براتون توضیح بدم و همزمان خودم هم یادبگیرم. https://thewebtier.com/wp-content/uploads/2019/06/Important-Python-Tips-and-Tricks-for-Programmers.jpgنکته ۱: از اونجایی که دیگه پایتون ۲ توسعه داده نمیشه به خاطر همین تو این مطلب منظور از پایتون، پایتون۳ هستش.نکته ۲: به طور کلی خیلی وارد عمق ماجرا نمی شیم، اما از اون ور هم خیلی هم سطحی بحث رو جمع نمیکنیم(دوتا مثال بزنیم و تموم بشه بره). بیشتر هدف اینه با موضوع آشنا باشیم تا اگر در آینده به چالشی برخوردیم که متاکلاس می تونست کمک کننده باشه یک ایده ی کلی داشته باشیم.بزارین قبل از شروع یک نکته کلاسیک که در رابطه با این موضوع وجود داره رو نقل قول کنیم:Metaclasses are deeper magic than 99% user should ever worry about. If you wonder whether you need them, you don&#x27;t(the people who actually need them know with certainty that they need them, and don&#x27;t need an explanation about why).- Tim Peters (the Python guru who authored the Zen of Python)سوالی که پیش میاد اینه خب وقتی ۹۹ درصد نباید نگرانش باشیم چرا الان باید نگرانش بشیم؟ جواب ساده ست: کنجکاوی، میل به دانش و فهمیدن. این کنجکاوی نمیزاره آدم راحت بشینه و فیلمش رو ببینه. اون گوشه ذهن داد و هوار راه میندازه و هی میگه که برو یکم درموردش بخون، شاید چیز خوبیه و به دردمون خورد. اصلا شاید اون باگت رو همین موضوع حل میکنه(پیشنهاد وسوسه انگیزیه واقعا)!! البته شاید هم کسی تو جمع  مون باشه که نیاز پیدا کرده درمورد متاکلاس ها اطلاعات بدست بیاره. ما هم یکم درموردش می خونیم شاید کنجکاوی مون راضی شد و اجازه داد ادامه فیلم مون رو ببنیم. البته خدا رو چه دیدی شاید تونستیم باگمون رو هم حل کنیم(واقعیت اینه باگ ها هیچ وقت تموم نمیشن بلکه از باگی به باگی دیگه میریم. و.ف D:)!!https://media.makeameme.org/created/curiosity-is-not.jpgبرای شروع با تعریف این موضوع آشنا میشیم. طبق تعریف، metaclass یعنی:A metaclass is a class whose instances are classes.تعریف بالا میگه متاکلاس یک کلاسی هستش که کلاس ها نمونه های اون هستند! احتمالا الان با خودتون دارین میگین: یعنی چی؟ چطوری میشه؟ البته حق هم داریم و کاملا طبیعی هستش. اما اینکه بتونیم این تعریف و کلا متاکلاس رو متوجه بشیم، لازمه با کلاس و روند ایجاد اون بیشتر و دقیق تر آشنا بشیم . برای همین اول میریم سراغ اونا و بعد دوباره به این تعریف و توضیح متاکلاس برمی گردیم.اولین مبحثی هم که به عنوان پیش نیاز درموردش می خونیم، آشنایی با نحوه ایجاد شدن یک کلاس تو پایتون هستش. یعنی وقتی ما یک کلاس رو تعریف می کنیم، پایتون اون رو چطوری می فهمه و می سازه.نحوه ایجاد کلاساگر بخوایم نحوه ایجاد کلاس رو بدنیم باید با type آشنا بشیم. کلاس type (اگر میخوای بگین type تابعه باید بگم این کلاس به دلیل backward compatibility به این شکل هستش. لینک مستندات کلاس type) به صورت پیشفرض وظیفه ایجاد کلاس ها رو بر عهده داره. یعنی وقتی چیزی مثل تکه کد پایین رو داریم:class TestCalss:
      passاطلاعات لازم به کلاس type پاس داده میشه تا کلاس رو برامون ایجاد کنه. اما خب سوالی که مطرح میشه اینکه type چه اطلاعاتی رو برای ایجاد کلاس نیاز داره؟ و چه چیزهایی بهش پاس داده میشه؟کلاس type برای ساخت یک کلاس به سه تا چیز نیاز داره:اسم کلاسکلاس یا کلاس های پایه ای که از اون ها ارث میبره(در قالب tuple).ویژگی های کلاس(attribute) به صورت dict.یعنی کلاسی که بالا تعریف کردیم این طوری بهش پاس داده میشه:TestClass = type(&#039;TestClass&#039;, (object,), {}) # requires a name, bases, and a namespaceحالا اگر بیایم و type کلاس TestCalass رو بگیریم میشه:&gt;&gt;&gt; type(TestClass)
&lt;class &#039;type&#039;&gt;اگر هم type یک نمونه(instance) از کلاس TestClass را بگیریم، طبیعتا خواهیم داشت:&gt;&gt;&gt; test_obj = TestClass()
&gt;&gt;&gt; type(test_obj)
&lt;class &#039;__main__.TestClass&#039;&gt;یا اگر بخوایم یک کلاس با جزئیات داشته باشیم، میشه به این صورت عمل کرد:def test_method(obj):
      return &amp;quotHello World&amp;quot

AnotherTestClass = type(
         &#039;AnotherTestClass&#039;,   # name of the class
         (TestClass,),     # base classes[tuple].
          {       # attributes
                   &#039;variable_name1&#039;: 100,
                   &#039;method_name1&#039;: lambda x: x*2 
                   &#039;method_name2&#039;: test_method
          }
)چون داریم درمورد کلاس و type حرف میزنیم این تیکه کد رو هم ببنیم بد نیست:&gt;&gt;&gt; for t in int, float, dict, list, tuple:
...         print(type(t))
output:
&lt;class &#039;type&#039;&gt;
&lt;class &#039;type&#039;&gt;
&lt;class &#039;type&#039;&gt;
&lt;class &#039;type&#039;&gt;
&lt;class &#039;type&#039;&gt;
&gt;&gt;&gt; print(type(type))
&lt;class &#039;type&#039;&gt;یعنی همه چیز در نهایت به type برمیگرده و حتی خود type هم به type برمیگرده.به صورت شماتیک هم این همچنین چیزی رو خواهیم داشت:منبع: http://www.atalon.cz/om/what-is-a-metaclassبر اساس چیزهایی گفتیم و کدهایی که دیدیم، همچنین از اون طرف اگر تعریف متاکلاس رو دوباره یادمون بیاریم به این نتیجه میرسیم که، کلاس type همون متاکلاس پیشفرض کلاس های ما هستش چون کلاس هامون نمونه هایی از اون هستند.https://i.pinimg.com/originals/83/d3/ff/83d3ff94ca1e713991d758703c4e8c2e.jpgخب پس متوجه شدیم که type متاکلاس پیشفرض کلاس ها تو پایتونه، اما آیا میشه metaclass رو شخصی سازی(customize) کرد و متاکلاس خودمون رو بنویسیم؟جواب این سوال بله ست، اما برای نوشتن متاکلاس خودمون باید با روند ساخت یک نمونه از کلاس هم آشنا بشیم به خاطر همین تو بخش بعدی به این موضوع می پردازیم.روند ساخت نمونه کلاسموقعی که یک نمونه از کلاس میخواد ساخته بشه تابع __call__ فراخوانی میشه. این تابع هم میاد به ترتیب دو تابع __new__ و __init__ رو فراخوانی میکنه. برای درک بهتر موضوع، این دوتابع رو توضیح مختصری میدیم تا ببنیم به چه کاری میان.__new__ is the first step of instance creation. It&#x27;s called first, and is responsible for returning a new instance of your class.In contrast, __init__ doesn&#x27;t return anything; it&#x27;s only responsible for initializing the instance after it&#x27;s been created.Use __new__ when you need to control the creation of a new instance.Use __init__ when you need to control initialization of a new instance.In general, you shouldn&#x27;t need to override __new__ unless you&#x27;re subclassing an immutable type like str, int, unicode or tuple.احتمالا الان تابع __init__ و اینکه چرا متغیرهامون رو توش تعریف میکنیم منطقی تر به نظر میاد. یک نکته دیگه که وجود داره اینه که __new__ میتونه مواردی مانند __slots__(مطلب درمورد __slots__)، نام کلاس و حتی مواردی مثل کلاس های والد(parent) و... رو تغییر بده چون هنوز نمونه ی کلاس ساخته نشده، ولی __init__ به دلیل فراخوانی بعد از ساخته شدن نمونه این امکان رو نداره. به طور کلی اگر قرار چیزی رو تغییر بدیم بهتره از __new__ استفاده بشه در غیر این صورت منطقی تره که از __init__ استفاده کنیم. اینکه کی کلاس __new__ خود کلاس فراخوانی میشه کی برای والد و ... رو میتونین به کمک مثال هایی که اینجا گفته شده متوجه بشین(حتما یه نگاه بندازین). من بخوام بگم مطلب واقعا طولانی میشه.پس نتیجه گیری می کنیم که type داره از __new__ استفاده میکنه(به خاطر همین هستش که نمیشه تابع __new__ کلاس type رو override کرد و اگر همچنین کاری بکنیم به ارور میخوریم: TypeError: can&#x27;t set attributes of built-in/extension type &#x27;type&#x27;).به عنوان حسن ختام بخش پیش نیازها یکم تو این موضوع عمیق تر بشیم و به صورت خیلی خلاصه ببنیم موقع ساخت نمونه ی کلاس دقیقا چه مراحلی طی میشه: ۱. مشخص شدن متاکلاس مرتبط با کلاس(اینکه type هستش یا ...)۲. آماده سازی فضای نام(namespace) کلاس(متد __prepare__).۳. اجرا شدن بدنه کلاس.۴. در نهایت ساخته شدن نمونه کلاس.برای جمع بندی این بخش هم دیدن این عکس هم خالی از لطف نیستhttps://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/class-creation.pngاگر هم میخواین درمورد مراحل ساخته شدن نمونه یک کلاس تو پایتون بیشتر بخونین، می تونین از این مطلب کمک بگیرین.متاکلاس چیست؟به نظرم دیگه الان دید خوبی نسبت به کلاس، نحوه ساخته شدن و جریان کار پیدا کردیم و می تونیم بریم سراغ metaclass و اینکه چطوری metaclass خودمون رو بنویسیم.برگردیم به اون تعریفی که اول کار گفته شد و دوباره يادآوریش کنیم. یک نکته که هست اینه که برای متاکلاس، دو تعریف کلاسیک وجود داره و تعریف دوم هم گاها استفاده میشه. برای همین تو این بخش هر دو تعریف رو میاریم:(1)  A metaclass is a class whose instances are classes.(2)  A metaclass is the class of a class.اما از اونجایی که تعریف اول بیشتر استفاده میشه، شکل کامل این تعریف رو میگیم که اینطوریه:In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses. Among those that do, the extent to which metaclasses can override any given aspect of class behavior varies. Metaclasses can be implemented by having classes be first-class citizen, in which case a metaclass is simply an object that constructs classes(نکته: سازندگان پایتون تعریف دوم رو بیشتر دوست دارن(لینک این موضوع)! و خود آقای Quido van Rossum اینجا(آی پی تون رو به خارج از کشور تغییر بدین تا ۴۰۴ نگیرین!) درمورد متاکلاسِ پایتون توضیحاتی داده که جالب هستش. یک کتاب(Putting Metaclasses to Work) هم معرفی کرده که الهام بخش پیاده سازی کلاس های سبک جدید تو پایتون بوده.)اما برسیم به تعریف و توضیح بدیم که این تعریف از لحاظ منطقی چطوری ممکنه؟این تعریف رو میشه در ۴ مرحله توضیح داد:۱. هر شی یک نمونه از یک کلاس هستش(Every object is an instance of a class)۲. با توجه به قانون هر چیزی یک شی هستش پس کلاس ها خودشون هم یک شی هستند(everything is an object).۳. در نتیجه کلاس ها باید نمونه ای از یک کلاس باشند(classes must also be instances of classes).۴. کلاسی که بقیه کلاس نمونه آن هستند متاکلاس می باشد(A class whose instances are classes is called a metaclass).الان دیگه منطقی به نظر میاد نه؟خب کم کم قطعات پازل داره کنار هم قرار میگیره و موضوع برامون شفاف تر میشه.https://en.dopl3r.com/memes/dank/when-you-connect-the-first-two-pieces-in-a-5000-piece-puzzle-set-oh-yeah-its-all-coming-together/889081 تا الان− کلاس و نحوه ساخته شدنش رو فهمیدیم.− اینکه یک نمونه از کلاس چطوری ساخته میشه رو یاد گرفتیم.− با تعریف متاکلاس و اینکه به چه معنی هستش هم آشنا شدیم.خب نوبتی هم که باشه نوبت کد و دیدن ماجرا توی عمله.ایجاد metaclassبدون هیچ گونه توضیحی بریم سراغ کد و ببنیم که چطوری باید متاکلاس خودمون رو بنویسیم:class MyMetaClass(type):
     def __new__(cls, name, bases, attrs):
             obj = super().__new__(cls, name, bases, attrs)
             obj.PI = 3.14
             return objحالا کافیه موقع تعریف کلاس بهش بگیم که از MyMetaClass به عنوان متاکلاس استفاده کنه که برای اونم به این شکل عمل می کنیم:class TestClass(metaclass=MyMetaClass):
        pass
&gt;&gt;&gt; print(TestClass.PI)
 3.14از اونجایی که ممکنه سر و کارمون به پایتون۲ (هرچند دیگه توسعه نمیشه) بخوره، اشاره کوتاهی بهش میکنیم. توی پایتون ۲ برای مشحض کردن متاکلاس از __metaclass__  استفاده می کنیم. یعنی اینطوری:class TestClass:
    __metaclass__ = MyMetaClassبرگردیم به پایتون ۳ و ارث بری،  ما برای کلاس TestClass متاکلاس پیشفرض رو عوض کردیم و متاکلاس خودمون رو گذاشتیم. حالا اگر کلاسی از کلاس TestClass ارث بری کنه، از متاکلاس ما استفاده میکنه و درنتیجه متغیر PI رو خواهد داشت، یعنی:class AnotherTestClass(TestClass):
       pass
&gt;&gt;&gt; print(AnotherTestClass.PI)
 3.14اما ارث بری یک سوالی رو به وجود میاره و اونم اینکه اگر دوتا کلاس والد دوتا متاکلاس متفاوت داشته باشند، چطوری میشه؟منظور همچنین کدی هستش:class MetaA(type):
     pass

class MetaB(type):
     pass

class A(metaclass=MetaA):
     pass

class B(metaclass=MetaB):
     pass

class C(A, B):
    passبه نظرتون چه اتفاقی میوفته؟ جواب اینکه پایتون اجازه همچین کاری رو نمیده و ارور می گیریم. اروری هم که میگیریم این شکلیه:TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its basesاما میشه دورش زد! چطوری؟ به این شکل:class MetaA(type):
      pass

class MetaB(type):
      pass

class MixinMeta(MetaA, MetaB):
      pass
 
class A(metaclass=MetaA):
      pass  

class B(metaclass=MetaB):
      pass

class C(A, B, metaclass=MixinMeta):
     passنکته آخر هم اینکه اگر میخواین درمورد متاکلاس اطلاعات کامل و جامع تری رو بدست بیارین، مطلب پایین رو که به قول بچه های شرکت مطلب خیلی فاخریه رو بخونین. این مطلب اومده اول یک معرفی کلی انجام داده بعد بر اساس کتاب Putting Metaclasses to Work که گفتیم پایتون هم ازش ایده گرفته توضیحات جامعی داده. بعدشم رفته سراغ زبان های دیگه که متاکلاس دارن. مثل java, perl, smalltalk و... و یه توضیحات مختصری هم درباره روش اون ها داده و در نهایت مطلب رو جمع کرده.لینک مطلب فاخر!https://thumbs.gfycat.com/PointlessEcstaticAmurminnow-small.gifدرمورد پایتون ۳٫۶ به بعد:از این نسخه به بعد برای اینکه بشه روند ساخت کلاس رو راحت تر شخصی سازی(customization) کرد، تکنیک هایی ارائه شده که توی PEP-487 معرفی شدن. یکی از این موارد چیزیه به اسم __init_subclass__ که موقع ایجاد کلاس های فرزند فراخوانی میشه. به عنوان مثال:speakers = {}
class Speaker:
  # `name` is a custom argument
  def __init_subclass__(cls, name=None):
        if name is None:
              name = cls.__name__
        speakers[name] = cls

class Guido(Speaker):
        pass

class Beazley(Speaker, name=&#039;David Beazley&#039;):
        pass

speakers
# {&#039;Guido&#039;: __main__.Guido, &#039;David Beazley&#039;: __main__.Beazley}`به نظرم PEP-487 رو بخونین، نکات جالبی رو گفته. مثلا میگه یکی از مزایای این روش کاهش احتمال ارور conflict متاکلاس هستش.کاربردها:یک اشاره مختصر هم به کاربردهای متاکلاس ها داشته باشیم و بحث رو جمعش کنیم. از جمله کاربردهایی که میشه گفت:استفاده گسترده توی فریم ورک های مختلف. به عنوان مثال django یک نمونه خوبه که می تونین بررسیش کنین(حواستون باشه اون Meta که توی مدل ها و اینا می نویسیم فرق داره با این موضوع و اون برای مدیریت متادیتا هستش).کنترل روند ایجاد نمونه های کلاس(دلیلش هم که مشخصه).امکان اضافه کردن thread-safety پیاده سازی singletonو ... که می تونین سرچ کنین یا خودتون بهش فکر کنین و ببنین چه کارهایی میشه باهاش انجام داد.http://kenscourses.com/tc201winter2016/امیدوارم که این حس کنجکاوی ما راضی شده باشه و بتونیم ادامه فیلم مون رو ببنیم(حالا از خود این مطلب یک موردی رو پیدا می کنه و میگه برو اونو بخون)!شاد و خندون باشید :)منابع:https://docs.python.org/3.6/glossary.html#term-metaclasshttps://docs.python.org/3.6/reference/datamodel.html#metaclasseshttp://www.atalon.cz/om/what-is-a-metaclass/http://python-history.blogspot.com/2013/10/origin-of-metaclasses-in-python.htmlhttps://www.python-course.eu/python3_metaclasses.phphttps://realpython.com/python-metaclasses/https://en.wikipedia.org/wiki/Metaclasshttps://stackoverflow.com/questions/674304/why-is-init-always-called-after-newhttps://www.geeksforgeeks.org/__new__-in-python/https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/https://www.python.org/dev/peps/pep-3115/https://www.python.org/dev/peps/pep-0487/</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Fri, 17 Jul 2020 19:13:29 +0430</pubDate>
            </item>
                    <item>
                <title>چگونه unit test های با کیفیت بنویسیم(best practice)</title>
                <link>https://virgool.io/@vahid_fathi/%DA%86%DA%AF%D9%88%D9%86%D9%87-unit-test-%D9%87%D8%A7%DB%8C-%D8%A8%D8%A7-%DA%A9%DB%8C%D9%81%DB%8C%D8%AA-%D8%A8%D9%86%D9%88%DB%8C%D8%B3%DB%8C%D9%85best-practice-hhwad34yg7ul</link>
                <description>https://ucarecdn.com/39483ff6-4269-400d-bca7-1dd059c31c02/نوشتن unit test برای پروژه هامون(چه شخصی و چه بیزنسی) یکی از مهم ترین مواردیه که باید رعایتش کنیم . اما نکته ای که وجود داره اینکه گاها به تست ها و کیفیت اون ها زیاد توجه نمیشه و با نوشتن تست های بد بازم کار رو برای خودمون و یا بقیه سخت می کنیم. تغییر تو پروژه یکی از مواردیه که همیشه وجود داره(از رفع باگ گرفته تا اضافه کردن فیچر جدید). از اون طرف تست ها هم با توجه به شرایط جدید نیاز به تغییر پیدا میکنن(لزوما نه همیشه). حالا اگر تست های ما به شکلی نوشته شده باشند که ایجاد تغییر تو اونا سخت باشه چه اتفاقی میوفته؟ بعد از یکم وقت گذاشتن روی موضوع و نا امید شدن از تغییرش، یه نیگا به این ور و یه نیگا به اون ور و اون تست کامنت یا پاک میشه!!!!طبیعتا راه حلش هم اینکه تست های خوب و با کیفیت که بعدا دستمون رو تو پوست گردو نزاره، بنویسیم! در باب اهمیت تست نویسی خوب هم بگم که تو کتاب clean code هم گفته شده که اهمیت کد تست از کد خود محصول(production) کمتر نیست و تمام مواردی که باید برای نوشتن یک کد تمیز لازمه رو باید تو تست نویسی هم رعایت کنیم.تو این مطلب هم سعی شده چند نکته درباره تست نویسی خوب گفته بشه.نکته: اینکه چرا ما باید برای پروژه هامون تست بنویسیم و مورادی از این قبلی هدف این مطلب نیست. خوشبختانه پست های خیلی خوبی تو این زمینه نوشته شده که توصیه می کنم اگر با این موارد آشنا نیستین حتما درموردش سرچ و مطالعه کنین.نکات(best practices)حرف از کتاب clean code شد بیاین با نکات همین کتاب شروع کنیم. رابرت مارتین تو کتاب خودش از ۵ ویژگی برای تست خوب یاد میکنه که به اختصار اسمش رو گذاشته: F.I.R.S.T که در ادامه به ترتیب درموردشون حرف میزنیم. مورد اول Fast: تست باید سریع و در مدت زمان کمی اجرا بشه.       توضیح: هرچی تست هامون مدت زمان اجرای طولانی تر داشته باشند در نتیجه رغبت کمتری رو برای اجرای اون ها خواهیم داشت. بزارین این طوری توضیح بدم که فرض کنین داریم یه سری تغییرات رو تو پروژه انجام میدیم اصولا این طوریه که هر بخشی که تغییر میدیم باید تست اون بخش رو اجرا کنیم تا مشخص بشه که چیزی خراب شده یا نه؟ اما چون اجرای تست ها طول میکشه با خودمون می گیم که به جای اینکه تست ها رو ران کنم و منتظر بمونم می تونم تو این زمان تغییرات بیشتری رو اعمال کنم و آخر کار یک بار تست ها رو ران میکنم ببینم مشکلی هست یا نه! خب مشکل اینجاست که تو برنامه نویسی جزئیات خیلی تاثیرگذارن، به عنوان مثال تو دستور if شرط &gt; با &gt;= خیلی فرق میکنه، و موقع کد زدن هم اشتباهات کوچیک زیاد داریم. پس از اونجایی این جزئیات پس از تغییرات کوچیک بررسی نمیشن و بررسی اون ها رو به آخر کار محول می کنیم، این رفتار معمولا باعث میشه تست های بیشتری fail بشوند و از اون طرف چون یک سیستم از ماژول های مختلفی تشکیل شده، پس یک زنجیره ای از خطا ایجاد میشه. طبیعتا پیدا کردن و رفع باگ های ایجاد شده هم طاقت فرساتر و از لحاظ زمانی مدت بیشتری رو از ما خواهد گرفت. پس تست هامون باید تا جایی که می تونن سریع باشن تا ما رغبت بیشتری رو برای اجرای اون ها داشته باشیم.https://techgirlkb.guru/wp-content/uploads/2019/05/giphy-3.gifمورد دوم Isolated: تست ها باید ایزوله و مستقل از همدیگه باشند.      توضیح: تست باید کاملا مستقل از همدیگه کار کنند. تمام داده هایی که نیاز دارند رو بدون وابستگی به تست های دیگه مهیا کنند و ترتیب اجرای تست ها هم مهم نباشد. همچنین تک تک تست ها رو باید بتونیم به تنهایی اجرا کنیم. اگر این کار ممکن نیست پس تست رو اشتباه نوشتیم. فرض کنیم که ما چهارتا تست داریم(فقط ۴ تا!!!) و برای اینکه تست سوم درست کار کنه باید قبل از اجرای اون تست دوم اجرا شده باشه چون تست دوم یک متغیر محیطی رو تنظیم میکنه(فرض هم کنیم که تست با ترتیب اجرا میشن). چه اتفاقی میوفته اگر کد پروژه یکم تغییر کنه و دیگه به اون بخش تنظیم متغیر محیطی تو تست نیازی نداشته باشیم؟مورد سوم Repeatable: تست ها باید در تمامی محیط ها قابل اجرا باشند.      توضیح: تست ها نباید وابستگی به محیط و زمان اجرا داشته باشند. تست باید همیشه در هر محیط و زمانی از شبانه روز یک خروجی مشخص داشته باشد و این طوری نباشه که وقتی تو شب اجرا میشه یه خروجی بگیره و تو روز یه خروجی دیگه(درباره این موضوع تو ادامه مثال با کد هم میاریم).مورد چهارم Self-Validating: خروجی تست باید حتما به صورت pass یا fail باشه و نیاز به بررسی های بیشتر نداشته باشند.       توضیح: تست نباید این طوری باشه که برای بررسی fail شدن و نشدن اون بریم یک مورد دیگه بررسی کنیم مثلا یک فایل لاگ رو نگاه کنیم.مورد پنجم Timely: تست ها باید قبل از کد محصول نوشته شوند(همون TDD).       توضیح: نکته اول اینکه این مورد یکم سلیقه ای و به سیاست های خودتون و یا تیم هم وابستگی دارد. به طور مثال تیم هایی هستند که کد بدون تست رو اجازه push کردن نمیدن ولی تست هاشون لزوما قبلا از کد محصول نمی نویسند. در واقع اگر قرار باشه همیشه حتما برای کدهامون تست بنویسیم(تاکید میکنم همیشه) در این صورت میشه گفت نوشتن کد تست قبل از خود کد محصول بستگی به خودتون یا تیم تون دارد.    نکته برای این مورد به جای Timely گاها از Thorough رو هم استفاده میشه که در این صورت باید گفت:         تست باید تمام حالت ممکن رو پوشش بده. همه انواع داده های بزرگ، کوچک، معتبر، نامعتبر، شرایط          corner(در ادامه بیشتر توضیح میدیم)، انواع کاربرهای مختلف موجود در سیستم و... رو شامل بشود.نام گذاری تست ها:    توضیح: تست ها باید نام های مشخصی داشته باشند تا با نگاه کردن به اسم اون کامل مشخص باشه که برای چه موردی نوشته شده. برای یک نام گذاری خوب می تونیم از این روش استفاده کنیم:برای اسم هر تست سه بخش را داشته باشیم:  واحدی که قراره تست بشهسناریو ای که قراره تست بشهنتیجه ای که انتظار داریم از تست بگیریم.به عنوان مثال اسم test_single هیچ معنایی نداره ولی Add_SingleNumber_ReturnsSameNumber به خوبی مشخص برای چی نوشته شده است.داشتن ساختار در تست ها:    توصیح: بهش 3A هم گفته میشه که یعنی: Arrange, Act, Assert.م Arrange: اول شرایطی که تست نیاز داره رو مهیا می کنیم(ایجاد instanceها و ...)م Act: فراخوانی یا چیزی که قراره تست بشه رو انجام میدیم.م Assert: در نهایت نتیجه رو بررسی می کنیم.این کار باعث میشه خوانایی کد بیشتر باشه.نوشتن تست های کوچک    توضیح: تست های کوچک چندتا مزیت دارند: امکان ایجاد تغییر در کد را آسان تر می کنندبر روی موردی که قرار است تست شود تمرکز می کنیم تا پیاده سازی خود تست.امکان وجود باگ در تست کمتر می شود.تست های بزرگ معمولا بیش از یک چیز رو تست می کنند.استفاده از نامگذاری های خوب برای متغیرهای تست     توضیح: نام گذاری متغیر های تست به اندازه نام گذاری متغیر های کد محصول اهمیت دارد. این باعث خوانایی کد شده و به فهمیدن هدف تست نیز کمک می کند. در نتیجه در اینده اگر خودتون یا برنامه نویس دیگه ای به تست مراجعه کرد به راحتی اون رو بفهمه و تغییرش بده.عدم استفاده از دستورات منطقی در تست ها      توضیح: استفاده از دستورات if, while, for, switch در تست مشکلاتی را به همراه دارد که عبارت اند از: تمرکز برنامه نویس رو از نتیجه تست به پیاده سازی تست منتقل میکنه.وجود دستورات در شرط معمولا یعنی چندین حالت مختلف را تو یک تست بررسی کردن که درست نیست. استفاده از توابع helper به جای setup, teardown    توضیح: ما معمولا به همه متغیرهایی که توی setup تعریف شدن نیازی نداریم. و از اونجایی که setup, teardown قبل و بعد از هر تست فراخوانی می شود پس در تست ها متغیرهایی خواهیم داشت که به اون ها نیازی به آن ها نداریم. از اون طرف فراخوانی هر باره این دو تابع مخصوصا اگر کارای زیادی رو انجام میدن مدت زمان اجرا رو طولانی تر میکنه. نکته مهم دیگه کمتر شدن خوانایی کد تست هستش. چون این دو تابع به صورت صریح توی کد فراخوانی نشدن و به طور ضمنی فراخوانی می شوند در نتیجه موقع خوندن کد برنامه نویس باید حواسش دائما به این موضوع و مواردی که تو این دو تابع وجود دارند باشه. استفاده از توابع helper این موارد را به خوبی کنترل میکنه(البته این به معنی نیست که دو تابع کلا استفاده نکنیم بلکه می تونن کمک کننده باشند).مشکلات مربوط به موارد static در کد محصول     توضیح: بزارین با یک تکه کد این مورد را ببینیم:def get_time_of_day():
      current = datetime.now()
       if current.hour &gt; 12:
             return &amp;quotafternoon&amp;quot
       else:
              return &amp;quotmorning&amp;quotبه نظرتون اگر بخوایم برای این تابع تست بنویسم به چه مشکلی میخوریم؟ یادتونه تو ویژگی های F.I.R.S.T گفتیم که تست باید Repeatable باشه و در همه شرایط و زمان ها یک نتیجه رو بگیریم. خب تو این مورد اگر ما تست رو یک بار صبح و یک بار عصر اجرا کنیم نتیجه مختلفی رو میگیرم! برای حل این موضوع باید از if استفاده کنیم از اون طرف هم گفته بودیم که داشتن if تو تست خوب نیست، پس چیکار بکنیم؟ در واقع مشکل از تست نیست و از خود تابع هستش که درست پیاده سازی نشده. تابع باید به این شکل اصلاح بشه:def get_time_of_day(time_of_day):
     if time_of_day.hour &gt; 12:
          return &amp;quotafternoon&amp;quot
     else:
           return &amp;quotmorning&amp;quot الان اگر بخوایم این تابع رو تست کنیم خیلی راحت می تونیم زمان رو خودمون تنظیم و به تابع پاس بدیم و خروجی مورد انتظار رو assert کنیم.نوشتن تست برای باگ ها    توضیح: اگر در محصول ما باگی پیدا شد یعنی برای اون حالت تست نداریم. پس برای جلوگیری از این باگ در آینده باید برای اون تست بنویسیم و به مجموعه تست هامون اضافه ش کنیم.بررسی حالت های Corner و Edge      توضیح: خود پیدا کردن این حالت ها هم جالبه حتی(برای درک این موضوع لینک ها رو بخونین)!نکته آخر هم اینکه بعد از pull، قبل از push و بعد از هر تغییری تست ها باید اجرا بشوند.مواردی دیگه ای هم میشه مطرح کرد که دیگه شاید باعث بشه مطلب خیلی طولانی بشه به خاطر همین اگر دوست داشتین می تونین با سرچ کردن مطالعه شون کنین.https://i.imgur.com/44Rkxx5.gifهمین، شاد و خندون باشین و امیدوارم که تست هاتون کمتر fail بشن. :)منابع:http://agileinaflash.blogspot.com/2009/02/first.htmlhttps://github.com/ghsukumar/SFDC_Best_Practices/wiki/F.I.R.S.T-Principles-of-Unit-Testinghttps://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practiceshttps://www.toptal.com/qa/how-to-write-testable-code-and-why-it-matters</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Fri, 01 May 2020 13:50:08 +0430</pubDate>
            </item>
                    <item>
                <title>از celery بهتر استفاده کنیم(۲)</title>
                <link>https://virgool.io/@vahid_fathi/%D8%A7%D8%B2-celery-%D8%A8%D9%87%D8%AA%D8%B1-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%DA%A9%D9%86%DB%8C%D9%85%DB%B2-uhujrvvf0bca</link>
                <description>https://www.caktusgroup.com/blog/2016/10/18/dont-keep-important-data-your-celery-queue خود لینک عکس نکته داره :)لینک قسمت اولیه راست میریم سر اصل مطلب و ويژگی هایی که می تونه بهمون تو استفاده بهتر از celery کمک مون کنه رو مرور می کنیم.نکته مهم اینکه فرض می کنیم یه تسک با اسم test_task داریم و مثال ها بر اساس اون میریم جلو.این تسک مثلا این طوری تعریف شده:@celery.shared_task(bind=True)
def test_task(self, *arg, **kwargs):
     logger.log(self.request)
     ....۱) مخفی کردن اطلاعات حساس با استفاده از argsrepr و kwargsreprاز اونجایی که ممکنه بنا به دلایلی مجبور باشیم اطلاعات حساسی مثل نام کاربری، پسورد و... رو به یک تسک پاس بدیم و این اطلاعات هم جز اطلاعاتی هستن که نباید نه لاگ بشن و نه تو بخش هایی مثل مانیتورینگ و اینا دیده بشن پس باید یه جوری مخفی شون کنیم اینجاست که ما باید از argsrepr و kwargsrepr استفاده کنیم.test_task.apply_async((&amp;quotadmin&amp;quot, &amp;quotadmin&amp;quot), argsrepr=&#039;(&lt;secret-username&gt;, &lt;secret-password&gt;)&#039;)
یا 
test_task.s(password=&#039;12345678&#039;).set(
     kwargsrepr=repr({&#039;password&#039;: &#039;********&#039;})
).delay()نکته ای که باید بهش توجه کرد اینکه این اطلاعات همچنان برای کسی که به اون تسک دسترسی داره در دسترس هستش. یعنی اگر ما بیایم از redis استفاده کنیم هر کسی به redis دسترسی داشته باشه به این اطلاعات هم دسترسی داره به خاطر همین گفته میشه که از encryption/decryption استفاده بشه. لینک منبع۲) مانیتور کردن celeryراحت ترین کار استفاده از ابزار تحت وب flower هستش. لینک گیت هابش. امکاناتش رو بخونین متوجه میشین چه ابزار خوبیه و امار جالبی میده.(داکیومنت خود celery هم توضیحاتی درمورد مانتیورینگ داره)۳) ماجرای logger داخلی celeryدر این مورد من توضیح خاصی نمیدم! همین issue (البته این دوتا رو هم بخونین. لینک ۱ و لینک ۲) مربوط به خود celery رو تا اخر بخونین دستتون میاد ماجرا(دلیل اینکه توضیح ندادم اینکه اونجا خیلی بهتر و مفصل تر بحث و توضیح داده شده در نتیجه بهتر متوجه میشین). :)۴) استفاده از routingشما می تونین برای تسک ها صف های مختلفی داشته باشین و worker ها هم می تونین به یک یا چند تا صف consume کنن.test_task.apply_async((2, 2), queue=&#039;fake_queue&#039;)ما اگر برای تسک صفی رو مشخص نکنیم به صورت پیشفرض صف celery استفاده میشه و همچنین اگر برای worker هم صف رو مشخص نکنیم صف celery برای consume انتخاب می کنه اما اگر بخوایم صف های دیگه ای رو هم consume داشته باشیم می تونیم باید از Q− استفاده کنیم یعنی این طوری:celery -A proj worker -Q fake_queue, celeryالبته برای بحث routing این مثالی که اینجا زدیم ساده ترین مدل ممکن هستش و کارهای خیلی بهتر و جالبی میشه انجام داد. اما از اونجایی که هدف ما معرفی ویژگی هاست به خاطر همین برای مطالعه بیشتر لینک میزارم. :)لینک مرتبط با مباحث routing۵) تسک های زمان بندی شده با celery beatاگر تسک هایی داشته باشیم که باید در زمان بندی های خاصی اجرا بشن celery برای این نیاز هم تمهیداتی رو پیش بینی کرده.به نظرم توضیح خاصی نمی خواد و بریم سراغ مثالش:from celery import Celery
from celery.schedules import crontab

app = Celery()

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # Calls test(&#039;hello&#039;) every 10 seconds.
    sender.add_periodic_task(10.0, test.s(&#039;hello&#039;), name=&#039;add every 10&#039;)

    # Calls test(&#039;world&#039;) every 30 seconds
    sender.add_periodic_task(30.0, test.s(&#039;world&#039;), expires=10)

    # Executes every Monday morning at 7:30 a.m.
    sender.add_periodic_task(
        crontab(hour=7, minute=30, day_of_week=1),
        test.s(&#039;Happy Mondays!&#039;),
    )

@app.task
def test(arg):
    print(arg)برای شروع به کار سرویس celery beat هم داریم:celery -A proj beatالبته میشه به روش دیگه هم این کار رو انجام داد و اون استفاده از بخش config هستش. حتما این لینک رو مطالعه بخونین.۶) تست نویسیهمین طور که می دونیم دیگه دوره اون زمان که برنامه ها بدون تست نوشته می شدن سر اومده و باید برای بخش های مختلف برنامه مون تست داشته باشیم. خود celery هم این موضوع رو میدونه و این امکان رو فراهم کرده که براش تست بنویسیم.چند روش برای نوشتن تست برای celery داریم که خودتون نیگا کنین با هر کدوم راحت بودین اون رو انتخاب کنین. لینک تست نویسی برای celery.7) بخش کانفیگ و امکانات celeryنکته اینکه این سری مطالب رو تا چند قسمت دیگه هم میشه ادامه داد(بحث هایی مثل signal، امنیت، همزمانی و ...). از اونجایی هم که celery داکیومنت به نسبت خوبی داره(توجه کرده باشین تمام لینک ها از خود داکیومنتش بود)، به خاطر همین بهتره که دیگه مطلب رو ادامه ندیم و هر کسی که دوست داره از داکیومنت جامع تر و کامل تر بخونه.اما قبل تموم کردن مطلب توصیه می کنم که حتما بخش کانفیگ رو بخونین چون دید خوبی بهتون میده.اگر حوصله خوندش رو هم نداشتین حداقل به صورت تیتروار بخونین مطمئنا خیلی چیزا دستتون میاد. لینک بخش کانفیگهمین. شاد و خندون باشین :)</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Thu, 26 Mar 2020 19:31:21 +0430</pubDate>
            </item>
                    <item>
                <title>از celery بهتر استفاده کنیم(۱)</title>
                <link>https://virgool.io/@vahid_fathi/%D8%A7%D8%B2-celery-%D8%A8%D9%87%D8%AA%D8%B1-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%DA%A9%D9%86%DB%8C%D9%85%DB%B1-qksx3451coez</link>
                <description>https://www.caktusgroup.com/blog/2016/10/18/dont-keep-important-data-your-celery-queue   خود لینک عکس نکته داره :)مقدمهاگر با celery و کاربردش آشنا هستین می تونین مستقیم به بخش ویژگی ها برین و از اونجا بخونین. اما اگر آشنا نیستین همراه باشین.اول از همه: celery چیه؟احتمالا تا حالا موقع برنامه نویسی به مواردی برخوردین که مدت زمان اجرای اون بخش از کد طولانی هستش و یا به یک سرویس خارجی وابستگی داره(مثل ارسال ایمیل یا sms). اگر بخوایم یه مثال خیلی روتین بزنیم فرض کنیم که کاربر روی دکمه ایجاد گزارش کلیک می کنه. اینجاست که برنامه ما باید بره کلی اطلاعات جمع کنه و بعد با یک ساختار توی فایل بنویسه و در نهایت اون رو به کاربر تحویل بدیم. این کار علاوه بر اینکه از لحاظ میزان حجم کار یکم زیاد هستش و بار به سیستم تحمیل می کنه از لحاظ زمانی هم خیلی طول می کشه پس اینکه کل برنامه معطل اون بخش بمونه و حالت هنگ طور داشته باشه، از اون طرف هم کاربر هم بشینه زل بزنه به مانیتور تا این کار تموم بشه(تازه اگر برنامه تحت وب هم باشه بدتر چون نمیشه یک رکوئست رو برای مدت طولانی منتظر گذاشت و تایم اوت میخوریم) منطقی نیست! اما باید چیکار کرد؟اژدها وارد می شود(اژدها = celery)استفاده از celery(و به طور کلی task manager ها ) این امکان رو به ما میده تا بتونیم این طور کارها که طول میکشن رو تا وقتی که اماده میشن در پس زمینه اجرا کنیم.طبق گفته خود وبسایت celery:Celery is an asynchronous task queue/job queue based on distributed message passingکه طبق ادعاش :Celery is used in production systems to process millions of tasks a day.به طور کلی بخوایم بگیم در واقع ما میایم یک سری کار تعریف می کنیم و یک سری هم worker (هرچندتا که خودمون بخوایم) برای این کارها تعریف می کنیم و این worker ها میان و کارهارو از صف هایی که دارن برمیدارن و میرن انجام شون میدن.نکته: celery برای ایجاد صف به چیزهایی مثل rabbitmq یا redis  و... نیاز دارد.من بیشتر از این دیگه توضیح نمیدم. برای اشنایی بیشتر توی نت و خود ویرگول مطالب مختلفی هستش که خیلی خوب و جامع معرفیش کردن.ويژگی هااما celery یک سری ویژگی ها دارد که باعث می شود تا بتونیم به کمک اونا هم راحت تر باشیم و بهتر کارها رو مدیریت کنیم.۱) روش های تعریف کردن کار:به طور کلی تو celery دو نوع روش تعریف کار وجود داره:@shared_taskapp.taskتفاوت هاشون چیه:تقریبا میشه از اسم شون حدس زد، وقتی ما یه چیزی می نویستم که استفاده تو جاهای مختلف داره مثل موقع نوشتن یک library که می تونه به پروژه های مختلف اضافه بشه میایم از shared_task استفاده می کنیم. اما اگر یه چیزی توسعه میدیم که مختص کار خودمونه و قرار نیست جای دیگه ازش استفاده کنیم بهتره که حالت دوم استفاده کنیم. اگر کامل متوجه نشدین این لینک و این لینک رو یه نیگا بندازین.یک نکته: موقعی که کاری تعریف میشه می تونین مقدار bind رو برابر True قرار بدین تا به object اون کار هم دسترسی داشته باشین. البته در این صورت باید متغیر self رو به عنوان اولین متغیر اون تابع تون قرار بدین. یعنی این طوری میشه:app.task(bind=True)
def test_task(self, *args, **kwargs):
    ....حالا بدون bind:app.task()
def test_task(*args, **kwargs):
     .....۲) تعریف chainممکنه مواقعی پیش بیاد که یک سری کار داشته باشیم که باید تو ترتیب خاصی اجرا بشن و نتیجه کار قبلی به کار بعدی پاس داده بشه. این جور مواقع ست که chain به دردمون می خوره. دیدن مثال و لینک مرجع تو مثال پایین وقتی کار اول اجرا شد نتیجه به عنوان ارگومان کار دوم پاس داده میشه و همین طور الی آخر(پس کار دوم هم در واقع دوتا ورودی گرفته).res = chain(test_task.s(2, 2), test_task.s(3), test_task.s(2))()۳) تعریف groupاگر بخوایم که یک سری از کارها به صورت موازی انجام بشن. از group استفاده میکنیم. دیدن مثال و لینک مرجعres = group(test_task.s(i, i) for i in range(10))()۴) فراخوانی کارها:برای فراخوانی کارها می تونیم از دو روش استفاده کنیم:delayapply_asyncحالت اول در واقع همون حالت دوم هستش ولی با امکانات کمتر. یعنی موقعی که از این روش استفاده می کنیم در پشت صحنه همون حالت دوم اتفاق میوفته ولی خیلی ويژگی هاش رو از دست میدیم. تو این روش فقط ارگومان رو به اون کار پاس میدیم و تمام. دیگه به هیچ چیز دیگه ای دسترسی نداریم.حالت دوم ویژگی های بیشتری به ما میده یعنی علاوه بر اینکه که می تونیم ارگومان های اون کار رو بهش پاس بدیم برای اون کار موارد دیگه رو هم تنظیم کنیم. مواردی مثل اینکه چند بار تلاش انجام بشه برای انجام کار،  حداکثر مدت زمان اجرای کار چقدر باشه ویا برای اون کار callback تعریف کنیم.  لینک همه موارد در دسترسtest_task.apply_async(
                   args=(2,2),
                   expires=60,
                   countdown=10,
                   retry=True, retry_policy={
                         &#039;max_retries&#039;: 3,
                         &#039;interval_start&#039;:0.5,
                         &#039;interval_max&#039;:0.2
                   }۵) تعریف retryاز اسمش مشخصه اینکه مشخص کنیم یک کار اگر به صورت موفقیت امیزی اجرا نشد دوباره اجرا بشه. همچنین می تونیم مشخص کنیم چند بار تلاش کنه، بین هر تلاش چقدر فاصله بندازه، حداکثر فاصله بین تلاش ها چقدر باشه و اینکه برای چه نوع exceptionهایی دوباره تلاش کنه.@task(max_retries=10, autoretry_for=(Exception,), retry_backoff=60)۶) تعریف linkاگر بخوایم برای یک کار در صورتی که موفق بود یک call back(یعنی اگر فلان اتفاق اتفاد بعدش یک چیزی رو فراخوانی کن) تعریف کنیم. از link استفاده می کنیم. در این صورت نتیجه کار به هدفی که به عنوان link  گذاشتیم پاس داده میشه.تو مثال زیر اگر کار test_task با موفقیت اجرا بشه test_task_success فراخوانی خواهد شد.test_task.apply_asyc(args=(2, 2), link=test_task_success.s())۷) تعریف link_errorاگر موقع انجام کار با خطا مواجه شدی چه چیزی رو فراخوانی کن. که در این صورت هم id اون کار رو به هدف پاس میده. تو مثال زیر اگر کار موفقیت امیز بود که هیچی اگر نبود میاد test_task_failرو فراخوانی می کنه.test_task.apply_async(kwargs={&amp;quotarg1&amp;quot:2, &amp;quotarg2&amp;quot:2}, link_error=test_task_fail.s())برای اینکه مطلب طولانی و خسته کننده نشه اینجا تمومش می کنیم و اگر عمری بود تو یک مطلب دیگه ادامه ویژگی های celery رو با هم مرور می کنیم.راستی عیدتون مبارک باشه :)لینک قسمت دوم </description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Wed, 18 Mar 2020 23:12:46 +0330</pubDate>
            </item>
                    <item>
                <title>نکته هایی از پایتون؛ این قسمت __slots__</title>
                <link>https://virgool.io/@vahid_fathi/%D9%86%DA%A9%D8%AA%D9%87-%D9%87%D8%A7%DB%8C%DB%8C-%D8%A7%D8%B2-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86%D8%9B-%D8%A7%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-slots-oe9owxnngnc5</link>
                <description>سلام سعی می کنم نکته هایی از پایتون که به نظرم جالب هستن رو به اشتراک بزارم.این قسمت هم چیزی به اسم: __slots__ https://thewebtier.com/wp-content/uploads/2019/06/Important-Python-Tips-and-Tricks-for-Programmers.jpg خب اما این عزیز چیه؟پایتون به صورت پیش فرض ویژگی های کلاسی(instance attributes) یک شی (object) رو تو یه دیکشنری به اسم __dict__ ذخیره میکنه(البته تا لحظه نوشتن این متن که اینطوریه) یعنی اگر یه کلاس به اسم MyClass داشته باشیم و متغیر first_name هم یکی از ویژگی های این کلاس باشه با فراخوانی __dict__ ویژگی های اون شی رو بدست میاریم. یعنی:class MyClass(object):
     def __init__(self, f_name):
           self.first_name = f_name
test_obj = MyClass(&amp;quotvahid&amp;quot)
print(test_obj.__dict__)
خروجی میشه:
#{ &#039;first_name&#039;: &#039;vahid&#039; }یا می تونیم ویژگی هایی رو به صورت پویا به اون شی اضافه کنیم. مثلا:class MyClass(object):
      def __init__(self, f_name):
                  self.first_name = f_name
test_obj = MyClass(&amp;quotvahid&amp;quot)
test_obj.last_name = &amp;quotfathi&amp;quot   # dynamically add attributes
print(test_obj.__dict__) 
خروجی میشه: 
#{ &#039;first_name&#039;: &#039;vahid&#039;, &#039;last_name&#039;: &#039;fathi&#039; }اما مشکل کجاست؟ اینجاست که استفاده از دیکشنری برای ذخیره ویژگی ها باعث هدر رفت فضا میشه و می تونه باعث مشکل کمبود حافظه(RAM) موقعی که تعداد زیاد شی(object) داشته باشیم، بشه(منظور از تعداد زیاد هزاران و میلیون هاست).اژدها وارد می شود!خب مشکل رو گفتیم راه حل رو هم بگیم.داخل پرانتز و قبل از اینکه به مشکل حافظه بپردازیم باید بگم طبق گفته خود سازنده پایتون یعنی اقای Guido van Rossum (که اینجا می تونین بخونین) هدف نهایی __slots__ بحث performance بوده. این لینک هم رو یه مثال درمورد این موضوع داره.مشکل حافظه:اومدن گفتن یه کاری کنیم که میزان مصرف حافظه بهبود پیدا کنه و برای اینکار از __slots__  استفاده می کنیم. چرا باعث بهبود حافظه میشه چون __slots__ ویژگی هایی که هر شی باید داشته باشه رو به صورت دقیق مشخص میکنه مثلا:class MyClass(object):
    __slots__ = [&#039;first_name&#039;]
    def __init__(self, f_name):
        self.first_name = f_name
test_obj = MyClass(&amp;quotvahid&amp;quot)کد بالا درسته و به خوبی کار میکنه اما اگر بخوایم چیزی شبیه کد پایین بنویسیم به ارور می خوریم:test_obj.last_name = &amp;quotfathi&amp;quot
Traceback (most recent call last):
    File &amp;quottest.py&amp;quot, line 12, in &lt;module&gt;
           test_obj.last_name = &amp;quotfathi&amp;quot
AttributeError: &#039;MyClass&#039; object has no attribute &#039;last_name&#039;یه نکته ریز اینکه اگر __slots__ رو اینطوری بنویسم باز اضافه کردن پویا ویژگی رو خواهیم داشت __slots__ = [&#x27;first_name&#x27;, &#x27;__dict__&#x27;] نکته اخر:از پایتون ۳٫۳ به بعد این مشکل مصرف حافظه تا حدی حل شده و دلیلش هم Key-Sharing در دیکشنری هستش.With Python 3.3 Key-Sharing Dictionaries are used for the storage of objects. The attributes of the instances are capable of sharing part of their internal storage between each other, i.e. the part which stores the keys and their corresponding hashes. This helps to reduce the memory consumption of programs, which create many instances of non-builtin types.جمع بندی و اینکه اگر حل شده چرا گفتم؟فهمیدیم که ویژگی های شی تو __dict__ ذخیره میشن.چیزی به اسم __slots__ تو پایتون داریم.هدف نهایی آقای Guido van Rossum از __slots__ بحث performance بوده.افتخار آشنایی با Key-Sharing هم داشتیم.منابع:https://www.python-course.eu/python3_slots.phphttp://book.pythontips.com/en/latest/__slots__magic.htmlhttp://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.htmlhttps://stackoverflow.com/questions/472000/usage-of-slotsشاد و خندون باشین :)</description>
                <category>Vahid</category>
                <author>Vahid</author>
                <pubDate>Fri, 18 Oct 2019 17:39:36 +0330</pubDate>
            </item>
            </channel>
</rss>