نگین موسوی حسابهای یک چاپخانه در قم را نگه میدارد. اسفند پارسال از چهل هزار سطر سفارش چاپ یک گزارش خواست که بگوید کدام مشتری چقدر بدهکار مانده است. اسکریپتی که نوشت جواب درست میداد و بیست و دو دقیقه طول میکشید. همان گزارش، بعد از چهار تغییر که هیچکدامشان نحو پایتون نبودند، هفت ثانیه گرفت.
هر چهار تغییر جملههایی هستند که پیش از پایتون هم درست بودند. اولی این است: رشته تغییرناپذیر است، پس هر افزودن به آن یک رشته تازه میسازد. حلقه نگین چهل هزار بار یک رشته کمی بلندتر ساخته و قبلی را دور ریخته بود، و کاری که شبیه یک بار نوشتن به نظر میرسید در عمل چهل هزار بار کپی کردن کل گزارش تا آن لحظه بود.
چیزی که در فارسی مبانی نامیده میشود، فصل اول نحو است
در فهرست دورههای فارسی، «مبانی برنامهنویسی» تقریباً همیشه نام ده جلسه اول یک دوره زبان است: متغیر، شرط، حلقه، تابع. آن ده جلسه نحو است و در هر زبان تازهای از نو خریده میشود، که کار نحو و اکوسیستم، هر بار یک زبان است. مبانی چیزی است که وقتی نحو تمام شد باقی میماند، و هشت خوشه زیر این صفحه همگی یک چیز را در اندازههای مختلف میپرسند: وقتی تعدادش بیشتر شد، این کار چقدر خرج برمیدارد.
هیچکدام از این هشتتا اولی نیست و ترتیبشان روی صفحه ترتیب یادگیری نیست. کسی که راهحلهای نامداری که به جای اختراع دوباره باید شناختشان را حفظ است و هرگز ندیده چیزی بیست و دو دقیقه طول بکشد، زیر فشار همان شکل گران را انتخاب میکند. نامها بعداً ارزان به دست میآیند و آن غریزه نه؛ غریزه با یک بار غافلگیر شدن ساخته میشود، و همین است که این موضوع را از بقیه برنامهنویسی، از اولین متغیر تا سیستمی که بالا آمده است جدا میکند.
چهار چیزی که گزارش نگین را از بیست و دو دقیقه به هفت ثانیه رساند
درست کردن اولی سه خط است و در هر زبانی همین سه خط با املای دیگر: به جای اینکه هر سطر به خود گزارش اضافه شود، تکهها در یک فهرست جمع میشوند و آخر کار یک بار به هم میچسبند.
parts = []for row in rows:parts.append(html_row(row))report = ''.join(parts)open('bedehi.html', 'w').write(report)
دومی حافظه بود و روی دستگاه خود نگین هرگز دیده نمیشد، چون فایل اسفند کوچک بود. خواندن خطبهخط حافظه ثابت میخواهد، خواندن یکجا به اندازه خود فایل. در پایتون تفاوتشان یک کلمه است: for row in f: فایل را همانطور که میرسد میخواند و هر بار یک سطر در حافظه دارد، و f.read() اول تمام آن را میآورد و بعد اجازه میدهد نگاهش کنید. روی فایل سه ماهه دومی سیصد مگابایت گرفت، و روی گزارش سالانه دستگاه شروع کرد به کند شدن بیآنکه هیچ خطایی چاپ شود.
سومی خطایی بود که شبیه خطای داده به نظر میرسید. نگین میخواست سفارشهای تکراری را بردارد و همانطور که روی فهرست حلقه میزد از آن حذف میکرد. فهرستی که وسط حلقه از آن حذف کنید، عضوهای بعدی را جا میاندازد: با برداشتن عضو پنجم، ششمی میآید سر جای پنجم و حلقه سراغ خانه ششم میرود، که حالا هفتمی است. برای نگین این یعنی از هر دو سفارش تکراری پشت سر هم یکی سالم میماند و به گزارش میرسید، و بدهی یک مشتری غلط درمیآمد بدون اینکه چیزی شکسته باشد.
راهی که تقریباً همه سراغش میروند مجموعه است، و کار میکند با عارضهای که همان لحظه دیده نمیشود. تبدیل فهرست به مجموعه تکراریها را حذف میکند و ترتیب را نگه نمیدارد. گزارش چاپخانه باید به ترتیب تاریخ سفارش باشد و بعد از این کار نبود، و چون خروجی همچنان معتبر به نظر میرسید سه هفته کسی متوجهش نشد. چیزی که هم تکراری را میگیرد و هم ترتیب را نگه میدارد dict.fromkeys(rows) است، که کلیدها را به ترتیب اولین بار دیده شدن نگه میدارد.
ترتیبی که در ایران تقریباً همیشه برعکس گرفته میشود
اینجا «ساختمان داده و الگوریتم» پیش از هر چیز نام یک درس دانشگاهی و یک ماده امتحانی است. نتیجهاش این است که نامها زودتر از هر تجربهای میرسند: کسی پشته و صف و درخت را برای امتحان از بر میکند، نمره خوبی هم میگیرد، و اولین باری که برنامهاش کند میشود گمان میکند زبان کند است و سراغ زبان بعدی میرود.
ترتیبی که جواب میدهد از پایین شروع میشود. آنچه میان فایل شما و پردازنده میگذرد اولین خوشهای است که با یک عدد سر و کار دارد، و بعد از آن انتخاب شکلی که مسئله را آسان میکند میآید، که همان چیزی است که چهار جمله بالا دربارهاش بودند.
جوابهای نامدار برای مسئلههایی که تکرار میشوند عکس این است و باید دیرتر باز شود. الگویی که پیش از دیدن مسئله خوانده شود جوابی است که هنوز هیچ خرابی به آن وصل نیست، و چیزی که از آن درمیآید برنامهای است با شش فایل، جایی که یکی کافی بود.
ترتیب
هر پله همان چیزی است که پله بعدی را معنادار میکند
این پلکان را تقریباً همه از وسط شروع میکنند، چون درس دانشگاهی از پله دوم آغاز میشود و از پله اول اسمی نمیبرد.
- ۰۱یک بار با یک عدد غافلگیر شویدببینید چیزی بیست و دو دقیقه طول میکشد که باید ثانیهای باشد. بدون این، هیچکدام از بالاییها نمینشیند.
- ۰۲بدانید هر شکل چه قیمتی داردرشته تغییرناپذیر است و فهرست نیست. نامها میتوانند صبر کنند، قیمتها نمیتوانند.
- ۰۳یک مسئله را دو بار ببینیدالگویی که پیش از بار دوم خوانده شود، جوابی است که هیچ خرابی به آن وصل نیست.
- ۰۴برای خواننده دوم بنویسیدعادتها وقتی معنا پیدا میکنند که فایل خودتان را بعد از هشت ماه باز کرده باشید.
نمیگوید هر پله چقدر طول میکشد. پله اول میتواند در یک بعدازظهر بیفتد و برای بیشتر آدمها سالها نمیافتد، چون کسی عمداً سراغش نمیرود.
ایست: گواهیای که میگوید مبانی تمام شده است
ایست این موضوع در فارسی یک جمله است: مبانی را گذراندهام. گواهی واقعی است، ده جلسه هم واقعاً گذرانده شده، و آنچه گذرانده شده نحو بوده. کسی که این جمله را باور کند دیگر سراغ این هشت خوشه نمیآید، چون فکر میکند پشت سرش است، و به جایش زبان دوم یا فریمورک را باز میکند.
نشانهاش را همین امروز میشود بررسی کرد: یک تکه از کد خودتان را باز کنید و بگویید اگر ورودی ده برابر شود چه چیزی ده برابر میشود و چه چیزی صد برابر. اگر جواب این است که باید اجرا کرد تا فهمید، آن ده جلسه نحو بوده و این هشت خوشه هنوز پیش روی شماست. جدا کردن این دو حالت از هم همان کاری است که پیدا کردنش، به جای حدس زدنش تمرینش میدهد.
سه ابزاری که با پایتون میآیند و به شبکه نیاز ندارند
تا ۲۰۲۶ هر سه جواب این صفحه داخل خود پایتوناند. timeit در کتابخانه استاندارد است و بحث میان دو نسخه از یک حلقه را در حدود ده ثانیه تمام میکند. جای عادت ساعت گذاشتن دور کل اسکریپت را گرفت، که بالا آمدن خود مفسر را هم اندازه میگیرد؛ روی کدی که سریع است تقریباً تمام آن عدد همان بالا آمدن است. روی خط فرمان: python3 -m timeit -s "from report import build" "build(rows)".
tracemalloc از نسخه 3.4 پایتون در سال ۲۰۱۴ در همان کتابخانه است و میگوید حافظه در کدام خط گرفته شده. جای چاپ کردن اندازه چند متغیر و حدس زدن بقیه را گرفت، و همان چیزی است که تفاوت خطبهخط و یکجا را از یک جمله به یک عدد تبدیل میکند.
cProfile هم در کتابخانه استاندارد است و میگوید وقت در کدام تابع رفته، نه اینکه کل برنامه چقدر طول کشیده: python3 -m cProfile -s cumtime report.py. جای گذاشتن چند دستور چاپ و دوباره اجرا کردن فایل را گرفت. هر سه با خود پایتون میآیند و هیچکدام یک بایت از مخزن بسته نمیخواهند، که برای خوانندهای که نصب کردن گرانترین قدمش است یک ویژگی است نه یک تصادف.
آنچه این چهار جمله تعیین نمیکنند این است که هشت ماه بعد، وقتی خودتان همین فایل را باز کنید، از کجا میفهمید آن حلقه عمداً این شکل را دارد. آن پرسش بعدی است و عادتهایی که تعیین میکنند بعداً کسی میتواند این را عوض کند یا نه جوابش را میدهد، همانطور که پرسش «این داده اصلاً کجا بماند» به ذخیره داده، جوری که بشود دوباره بیرونش کشید میرسد.