Mohammad Javad Naderi
Mohammad Javad Naderi
خواندن ۹ دقیقه·۵ سال پیش

برخی امکانات جدید زبان پایتون

حدود دو ماه از انتشار پایتون ۳٫۸ می‌گذرد. در زمان انتشار پایتون ۳٫۸ در تیم فنی شرکت برخی از امکانات جدید پایتون (در نسخه‌های ۳٫۶، ۳٫۷ و ۳٫۸) را ارائه دادم. در این مطلب می‌خواهم بخش‌هایی از آن ارائه را معرفی کنم.

سعی می‌کنم کوتاه و مختصر بنویسم تا در کمترین زمان با این ویژگی‌ها آشنا شوید و برای تمرین بیشتر خودتان آن‌ها را امتحان کنید. به جای توضیحات مفصل، سعی کرده‌ام تکه‌کدهایی قرار دهم که با خواندن آن‌ها با امکانات جدید بهتر آشنا شوید.

تصویر از realpython.com
تصویر از realpython.com

دیکشنری ترتیب را حفظ می‌کند (پایتون ۳٫۷)

از پایتون ۳٫۶ به بعد، کلیدهای یک دیکشنری ترتیب وارد شدن به دیکشنری را حفظ می‌کنند. یعنی مثلاً وقتی با یک حلقه روی محتوای یک دیکشنری حرکت کنیم، می‌توانیم مطمئن باشیم که مقادیر را به همان ترتیبی که وارد دیکشنری شده‌اند می‌بینیم.

>>> d = {} >>> d[&quota&quot] = 1 >>> d[&quotb&quot] = 2 >>> d.items() dict_items([('a', 1), ('b', 2)]) # Order is guaranteed >>> d.keys() dict_keys(['a', 'b']) # Order is guaranteed

پیش از این اگر به این ویژگی نیاز داشتیم مجبور بودیم از نوع OrderedDict استفاده کنیم.

در پایتون ۳٫۶ نوع dict به روش دیگری پیاده‌سازی شده که علاوه بر حفظ ترتیب کلیدها، ۲۰ تا ۲۵ درصد حافظۀ کمتری مصرف می‌کند. ویژگی حفظ ترتیب کلیدها در پایتون ۳٫۶ جزء تعریف زبان نبود، و به خاطر پیاده‌سازی جدید ایجاد شد. اما در پایتون ۳٫۷، این ویژگی رسماً به تعریف (Specification) زبان اضافه شد.

ساخت رشته با f-string (پایتون ۳٫۶)

حتماً پیشوندهای r یا b را قبل از رشته‌ها دیده‌اید (که به ترتیب برای ایجاد raw string و دادۀ باینری یا bytes به کار می‌روند). اکنون شما را با پیشوند f آشنا می‌کنم که در پایتون ۳٫۶ اضافه شده است:

>>> name = &quotFred&quot >>> print(f&quotHe said his name is {name}.&quot) He said his name is Fred. >>> width = 10 >>> precision = 4 >>> value = decimal.Decimal(&quot12.34567&quot) >>> print(f&quotresult: {value:{width}.{precision}}&quot) # nested fields result: 12.35

همان‌طور که می‌بینید با استفاده از این ویژگی format کردن یک رشته و قرار دادن مقدار یک متغیر داخل آن بسیار ساده‌تر شده است. توجه کنید که لازم نیست داخل آکولاد تنها نام یک متغیر بیاید. بلکه می‌توان هر عبارتی را داخل آکولاد قرار داد. مثلاً:

>>> from math import sin, radians >>> theta = 30 >>> print(f&quotsin({theta} deg) = {sin(radians(theta)):.3f}&quot) sin(30 deg) = 0.500

در پایتون ۳٫۸ ویژگی جدیدی به f-string اضافه شده که برای دیباگ کردن خیلی به کار می‌آید. با قرار دادن یک = در انتهای عبارتی که درون آکولاد قرار داده‌اید، خود عبارت نیز در رشته ظاهر می‌شود:

>>> from math import sin, radians >>> theta = 30 >>> print(f'{theta=} {sin(radians(theta))=:.3f}') theta=30 sin(radians(theta))=0.500

عملگر Walrus (پایتون ۳٫۸)

در پایتون ۳٫۸ عملگر جدید := برای ایجاد عبارت انتسابی (Assignment Expression) به زبان پایتون اضافه شده است. این عملگر، Walrus (گراز دریایی) نام دارد، زیرا شکل این عملگر به چشم‌ها و دندان‌های گراز دریایی شباهت دارد :)

یک گراز دریایی
یک گراز دریایی

این ویژگی که در PEP 572 مطرح شد، یکی از جنجالی‌ترین ویژگی‌های پایتون بوده است. بحث‌هایی که مطرح شد در نهایت باعث شد Guido van Rossum خالق پایتون از سِمت خود به عنوان BDFL کناره‌گیری کند.

I'm tired, and need a very long break.
-- Guido van Rossum

با این عملگر می‌توانیم یک مقدار را به یک متغیر نسبت دهیم و همزمان از آن به عنوان یک عبارت (Expression) استفاده کنیم. تکه‌کد زیر را در نظر بگیرید که یک فایل را باز می‌کند و خط‌های آن را می‌خواند و پردازش می‌کند.

fp = open('some/file.txt') while True: line = fp.readline() if not line: break process(line)

با استفاده از عملگر := می‌توان کد بالا را به این صورت نوشت:

fp = open('some/file.txt') while line := fp.readline(): process(line)

در تکه‌کد بالا عبارت line := fp.readline() یک خط را از فایل می‌خواند، خروجی را در متغیر line قرار می‌دهد و همزمان این مقدار را در اختیار while می‌گذارد.

فرض کنید تابع fib را پیاده‌سازی کرده‌ایم که n را می‌گیرد و nاُمین عدد فیبوناچی را برمیگرداند. می‌خواهیم همۀ اعداد فیبوناچی کوچکتر از ۱۰۰۰ را چاپ کنیم:

i = 0 while (f := fib(i := i + 1)) < 1000: print(f)

کاربرد این عملگر در کار با کتابخانه re را در مثال زیر می‌بینید:

discount = 0.0 if m := re.search(r'(\d+)% discount', advertisement): discount = float(m.group(1)) / 100.0

در مثال زیر، بدون عملگر := مجبور بودیم تابع normalize را برای هر name دو بار صدا کنیم، یا اینکه از list comprehension استفاده نکنیم و لیست موردنظر را با یک حلقۀ for معمولی بسازیم.

[clean_name.title() for name in names if (clean_name := normalize('NFC', name)) in allowed_names]

برای آشنایی بیشتر با این عملگر توصیه می‌کنم ارائۀ Dustin Ingram در همایش PyCon 2019 را ببینید:

https://www.youtube.com/watch?v=6uAvHOKofws

پارامترهای positional-only (پایتون ۳٫۸)

شاید با پارامترهای keyword-only در پایتون آشنا باشید. در پایتون می‌توانیم تعدادی از پارامترهای یک تابع را به صورت keyword-only در نظر بگیریم. به این معنی که موقع صدازدن تابع، آن پارامترها حتماً باید به صورت keyword argument به تابع پاس داده شوند. در پایتون ۳٫۸، امکان مشخص کردن تعدادی از پارامترهای تابع به صورت positional-only نیز اضافه شده است.

تابع زیر را در نظر بگیرید. در این تابع، پارامترهای a و b، پارامترهای positional-only و پارامترهای e و f پارامترهای keyword-only هستند.

def f(a, b, /, c, d, *, e, f): ...

بنابراین هنگام صدا زدن تابع f، برای پاس دادن مقادیر a و b و e و f با محدودیت‌هایی روبرو هستیم. اما پارامترهای c و d می‌توانند به هر دو شکل positional و keyword پاس داده شوند.

سه حالت زیر را برای صدا زدن تابع f در نظر بگیرید:

f(10, 20, 30, d=40, e=50, f=60) # Valid f(10, b=20, c=30, d=40, e=50, f=60) # Invalid f(10, 20, 30, 40, 50, f=60) # Invalid

مورد دوم نادرست است. زیرا b به صورت keyword پاس داده شده است. مورد سوم نیز نادرست است زیرا e به صورت positional پاس داده شده است.

کاربرد اصلی این ویژگی این است که می‌توان خوانایی برنامه‌ها را افزایش داد. مثلاً تابع len را می‌توان به صورت زیر تعریف کرد تا امکان پاس دادن مقدار به صورت keyword وجود نداشته باشد (زیرا باعث کاهش خوانایی می‌شود):

def len(obj, /): # ... len(obj=&quotHello&quot) # Invalid. The &quotobj&quot keyword argument impairs readability

همچنین اگر پارامتری به این شکل تعریف شده باشد، بعداً می‌توان نام آن پارامتر را با خیال راحت تغییر داد. زیرا مطمئنیم که کدهایی که از تابع ما استفاده کرده‌اند، به نام پارامتر متکی نیستند.

بسیاری جاها نیز پارامترهای keyword-only خوانایی را افزایش می‌دهند. مثلاً:

def get_related_jobs(user, *, count=10, strict=False): # ... get_related_jobs(user1, 50, True) # Invalid. Not readable. What is 50? What is True? get_related_jobs(user1, count=50, strict=True) # Valid. More readable.

نکته: نام پارامترهای positional-only همچنان می‌تواند در kwargs مورد استفاده قرار بگیرد:

def counter(iterable, /, **kwargs): # ... counter(mylist, a=1, iterable=2) # Valid

تابع breakpoint (پایتون ۳٫۷)

از پایتون ۳٫۷ به بعد برای دیباگ کردن بخشی از کد می‌توانید تابع جدید breakpoint را در آن‌جا صدا کنید و برنامه را اجرا کنید. به محض رسیدن برنامه به آن خط، شما به دیباگر پایتون (pdb) هدایت می‌شوید و می‌توانید به راحتی مقادیر متغیرها را درون shell آن بررسی کنید. در انتها نیز با اجرای دستور continue از pdb خارج می‌شوید و ادامۀ برنامه اجرا می‌شود.

اگر یک توسعه‌دهندۀ Django هستید نیز می‌توانید از این تابع استفاده کنید و قبل از render شدن تمپلت، مقادیر متغیرها و queryset ها را درون shell بررسی کنید.

قبل از پایتون ۳٫۷ نیز این امکان وجود داشت اما کدهای بیشتری لازم داشت. اکنون کافی‌است یک تابع ساده را صدا کنید.

خوانایی بیشتر اعداد با _ (پایتون ۳٫۶)

در پایتون ۳٫۶ می‌توانید برای خوانایی بیشتر اعداد، در میان ارقام آن کاراکتر _ قرار دهید.

iran_surface = 1_648_195

کلاس داده (Data Class) (پایتون ۳٫۷)

در پایتون ۳٫۷ دکوراتوری به نام dataclass برای کلاس‌ها اضافه شده که تعدادی متد مانند سازنده (__init__)، متد __repr__، متد __eq__ و متد __hash__ را به صورت خودکار به کلاس اضافه می‌کند. به این ترتیب به سرعت و سادگی می‌توان یک کلاس برای نگهداری داده ایجاد کرد. مثال زیر را ببینید:

from dataclasses import dataclass @dataclass class Point: x: float y: float z: float = 0.0 >>> p = Point(1.5, 2.5) >>> print(p) Point(x=1.5, y=2.5, z=0.0) >>> p2 = Point(1.5, 2.5, z=0) >>> p == p2 True

ماژول secrets (پایتون ۳٫۶)

با این ماژول می‌توان به سادگی مقادیر شبه‌تصادفی که از لحاظ رمزنگاری قوی و امن هستند (به عبارت دیگر cryptographically strong هستند) برای کاربردهای امنیتی تولید کرد. نام این ماژول، به خوبی نمایانگر هدف آن است.

یادآوری: برای کاربردهای امنیتی و رمزنگاری نباید از ماژول random برای تولید اعداد تصادفی استفاده کرد. مقادیری که توسط random تولید می‌شود برای کاربردهای امنیتی مناسب نیست. البته برعکس آن نیز صادق است. نیاز نیست برای تولید یک عدد تصادفی برای یک کاربرد ساده از ماژول secrets استفاده کرد، زیرا هزینۀ تولید عدد تصادفی با این ماژول بیشتر از ماژول random است.

در مثال زیر نحوۀ تولید یک توکن ۱۶ بایتی با استفاده از secrets را می‌بینید:

>>> import secrets >>> secrets.token_bytes(16) b'\xebr\x17D*t\xae\xd4\xe3S\xb6\xe2\xebP1\x8b' >>> secrets.token_hex(16) 'f9bf78b9a18ce6d46a0cd2b0b86df9da'

امکانات دیگر

امکانات مختلف دیگری در نسخه‌های جدید پایتون (مثلاً در ماژول typing) اضافه شده است که به علت طولانی شدن این مطلب به آن‌ها نپرداخته‌ام. برای آشنایی با آن‌ها، می‌توانید ارائه‌ای که منبع این مطلب بوده را ببینید:

https://querateam.github.io/talks/presentations/New%20Python%20Features/

یا این مطلب از وب‌سایت Real Python را مطالعه کنید:

https://realpython.com/python38-new-features/

یا مستندات پایتون (لینک‌های بخش منابع) را مطالعه کنید.


منابع

https://docs.python.org/3/whatsnew/3.8.html
https://docs.python.org/3/whatsnew/3.7.html
https://docs.python.org/3/whatsnew/3.6.html
https://querateam.github.io/talks/presentations/New%20Python%20Features/

پایتونبرنامه نویسیکامپیوترمهندسیآموزش
هم‌بنیان‌گذار Quera، یک مهندس نرم‌افزار دانشگاهی در تلاش برای تبدیل شدن به یک مهندس واقعی
شاید از این پست‌ها خوشتان بیاید