لینک کوتاه مطلب : https://hsgar.com/?p=3924

چه چیزی در پایتون 3.11 وجود دارد؟

اولین نسخه بتا پایتون 3.11 منتشر شد و ویژگی‌های جذابی را برای ما به ارمغان می‌آورد. این همان چیزی است که می توانید انتظار داشته باشید که در سال 2022 نسخه پایتون را در اواخر امسال ببینید.

حتی پیام های خطای بهتر

پایتون 3.10 از جنبه‌های مختلف پیام‌های خطای بهتری به ما می‌دهد، اما پایتون 3.11 قصد دارد آنها را حتی بیشتر بهبود بخشد. برخی از مهم ترین مواردی که به پیام های خطا در پایتون 3.11 اضافه می شوند عبارتند از:

محل دقیق خطا در ردیابی

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

در اینجا یک مثال است:

def get_margin(data):
    margin = data['profits']['monthly'] / 10 + data['profits']['yearly'] / 2
    return margin

data = {
    'profits': {
        'monthly': 0.82,
        'yearly': None,
    },
    'losses': {
        'monthly': 0.23,
        'yearly': 1.38,
    },
}
print(get_margin(data))

این کد منجر به خطا می شود، زیرا یکی از این فیلدها در فرهنگ لغت است
None. این چیزی است که ما دریافت می کنیم:

Traceback (most recent call last):
  File "/Users/tusharsadhwani/code/marvin-python/mytest.py", line 15, in <module>
    print(get_margin(data))
  File "/Users/tusharsadhwani/code/marvin-python/mytest.py", line 2, in print_margin
    margin = data['profits']['monthly'] / 10 + data['profits']['yearly'] / 2
TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'

اما نمی توان از طریق خود ردیابی تشخیص داد که کدام قسمت از محاسبه باعث خطا شده است.

اما در 3.11:

Traceback (most recent call last):
  File "asd.py", line 15, in <module>
    print(get_margin(data))
          ^^^^^^^^^^^^^^^^
  File "asd.py", line 2, in print_margin
    margin = data['profits']['monthly'] / 10 + data['profits']['yearly'] / 2
                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'

واضح است که data['profits']['yearly'] بود None.

برای اینکه بتوانید این اطلاعات را ارائه دهید، end_line و end_col داده ها به اشیاء کد پایتون اضافه شد. شما همچنین می توانید به طور مستقیم از طریق به این اطلاعات دسترسی داشته باشید obj.__code__.co_positions() روش.

یادداشت هایی برای استثناها

برای غنی‌تر کردن ردیابی‌ها، Python 3.11 به شما امکان می‌دهد یادداشت‌هایی را به اشیاء استثنا اضافه کنید، که در استثناها ذخیره می‌شوند و زمانی که استثنا مطرح می‌شود، نمایش داده می‌شوند.

به عنوان مثال این کد را در نظر بگیرید، جایی که ما اطلاعات مهمی در مورد منطق تبدیل داده های API اضافه می کنیم:

def get_seconds(data):
    try:
        milliseconds = float(data['milliseconds'])
    except ValueError as exc:
        exc.add_note(
            "The time field should always be a number, this is a critial bug. "
            "Please report this to the backend team immediately."
        )
        raise  

    seconds = milliseconds / 1000
    return seconds

get_seconds({'milliseconds': 'foo'})  

این یادداشت اضافه شده درست در زیر پیام Exception چاپ می شود:

Traceback (most recent call last):
  File "asd.py", line 14, in <module>
    get_seconds({"milliseconds": "foo"})
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "asd.py", line 3, in get_seconds
    milliseconds = float(data["milliseconds"])
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: could not convert string to float: 'foo'
The time field should always be a number, this is a critial bug. Please report this to the backend team immediately.

ساخته شده است toml پشتیبانی

کتابخانه استاندارد اکنون دارای پشتیبانی داخلی برای خواندن فایل های TOML با استفاده از
tomllib مدول:

import tomllib

with open('.deepsource.toml', 'rb') as file:
    data = tomllib.load(file)

tomllib در واقع بر اساس یک کتابخانه تجزیه و تحلیل TOML منبع باز به نام است
tomli. و در حال حاضر، فقط خواندن فایل های TOML پشتیبانی می شود. اگر به جای آن نیاز به نوشتن داده در یک فایل TOML دارید، از آن استفاده کنید tomli-w بسته بندی

asyncio گروه های وظیفه

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

برای انجام این کار، باید وظایف را جمع آوری کرده و به آنها منتقل کنید asyncio.gatherدر اینجا یک مثال ساده از وظایف موازی در حال اجرا با gather عملکرد:

import asyncio

async def simulate_flight(city, departure_time, duration):
    await asyncio.sleep(departure_time)
    print(f"Flight for {city} departing at {departure_time}PM")

    await asyncio.sleep(duration)
    print(f"Flight for {city} arrived.")


flight_schedule = {
    'boston': [3, 2],
    'detroit': [7, 4],
    'new york': [1, 9],
}

async def main():
    tasks = []
    for city, (departure_time, duration) in flight_schedule.items():
        tasks.append(simulate_flight(city, departure_time, duration))

    await asyncio.gather(*tasks)
    print("Simulations done.")

asyncio.run(main())

اما اینکه خودتان لیستی از وظایف را تهیه کنید تا بتوانید منتظر آنها باشید کمی سخت است. بنابراین اکنون یک API جدید به آن اضافه شده است asyncio گروه های وظیفه نامیده می شوند:

import asyncio

async def simulate_flight(city, departure_time, duration):
    await asyncio.sleep(departure_time)
    print(f"Flight for {city} departing at {departure_time}PM")

    await asyncio.sleep(duration)
    print(f"Flight for {city} arrived.")


flight_schedule = {
    'boston': [3, 2],
    'detroit': [7, 4],
    'new york': [1, 9],
}

async def main():
    async with asyncio.TaskGroup() as tg:
        for city, (departure_time, duration) in flight_schedule.items():
            tg.create_task(simulate_flight(city, departure_time, duration))

    print("Simulations done.")

asyncio.run(main())

وقتی که asyncio.TaskGroup() مدیریت متن خارج می شود، اطمینان حاصل می کند که تمام وظایف ایجاد شده در داخل آن به پایان رسیده است.

پاداش: گروه های استثنایی

یک ویژگی مشابه نیز برای رسیدگی به استثنا در وظایف غیر همگام اضافه شده است، به نام گروه های استثنا.

فرض کنید که بسیاری از کارهای غیر همگام با هم اجرا می شوند و برخی از آنها خطاهایی را ایجاد می کنند. در حال حاضر سیستم مدیریت استثنا پایتون در این سناریو به خوبی کار نمی کند.

در اینجا یک نسخه نمایشی کوتاه از ظاهر آن با 3 کار همزمان خراب است:

import asyncio

def bad_task():
    raise ValueError("oops")

async def main():
    tasks = []
    for _ in range(3):
        tasks.append(asyncio.create_task(bad_task()))

    await asyncio.gather(*tasks)

asyncio.run(main())

وقتی این کد را اجرا می کنید:

$ python asd.py
Traceback (most recent call last):
  File "asd.py", line 13, in <module>
    asyncio.run(main())
  File "/usr/bin/python3.8/lib/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/bin/python3.8/lib/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "asd.py", line 9, in main
    tasks.append(asyncio.create_task(bad_task()))
  File "asd.py", line 4, in bad_task
    raise ValueError("oops")
ValueError: oops

هیچ نشانه ای وجود ندارد که 3 تا از این وظایف با هم اجرا می شدند. به محض اینکه اولی از کار می افتد، کل برنامه را از کار می اندازد.

اما در Python 3.11، رفتار کمی بهتر است:

import asyncio

async def bad_task():
    raise ValueError("oops")

async def main():
    async with asyncio.TaskGroup() as tg:
        for _ in range(3):
            tg.create_task(bad_task())

asyncio.run(main())
$ python asd.py
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 1, in <module>
  |   File "/usr/local/lib/python3.11/asyncio/runners.py", line 181, in run
  |     return runner.run(main)
  |            ^^^^^^^^^^^^^^^^
  |   File "/usr/local/lib/python3.11/asyncio/runners.py", line 115, in run
  |     return self._loop.run_until_complete(task)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "/usr/local/lib/python3.11/asyncio/base_events.py", line 650, in run_until_complete
  |     return future.result()
  |            ^^^^^^^^^^^^^^^
  |   File "<stdin>", line 2, in main
  |   File "/usr/local/lib/python3.11/asyncio/taskgroups.py", line 139, in __aexit__
  |     raise me from None
  |     ^^^^^^^^^^^^^^^^^^
  | ExceptionGroup: unhandled errors in a TaskGroup (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 2, in bad_task
    | ValueError: oops
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 2, in bad_task
    | ValueError: oops
    +---------------- 3 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 2, in bad_task
    | ValueError: oops
    +------------------------------------

اکنون استثنا به ما می گوید که ما 3 خطا در ساختاری به نام an پرتاب کرده ایم ExceptionGroup.

مدیریت استثنا با این گروه های استثنایی نیز جالب است، شما می توانید هر دو را انجام دهید except ExceptionGroup برای گرفتن همه استثناها در یک حرکت:

try:
    asyncio.run(main())
except ExceptionGroup as eg:
    print(f"Caught exceptions: {eg}")
$ python asd.py
Caught exceptions: unhandled errors in a TaskGroup (3 sub-exceptions)

یا می توانید آنها را بر اساس نوع استثنا و با استفاده از جدید بگیرید except*
نحو:

try:
    asyncio.run(main())
except* ValueError as eg:
    print(f"Caught ValueErrors: {eg}")
$ python asd.py
Caught ValueErrors: unhandled errors in a TaskGroup (3 sub-exceptions)

بهبودهای تایپ

در typing ماژول شاهد به‌روزرسانی‌های جالب زیادی در این نسخه بود. در اینجا برخی از هیجان انگیزترین آنها آورده شده است:

ژنریک های واریادیک

پشتیبانی از ژنریک های variadic به آن اضافه شده است typing ماژول در پایتون 3.11.

معنی آن این است که اکنون می‌توانید انواع عمومی را تعریف کنید که می‌توانند تعداد دلخواه انواع را در آن‌ها داشته باشند. برای تعریف روش های عمومی برای داده های چند بعدی مفید است.

مثلا:

from typing import Generic
from typing_extensions import TypeVarTuple, Unpack

Shape = TypeVarTuple('Shape')

class Array(Generic[Unpack[Shape]]):
    ...


items: Array[int] = Array()


market_prices: Array[int, int, float] = Array()


def double(array: Array[Unpack[Shape]]) -> Array[Unpack[Shape]]:
    ...


def get_values(array: Array[int, int, *Shape]) -> Array[*Shape]:
    ...


vector_space: Array[int, int, complex] = Array()
reveal_type(get_values(vector_space))  

ژنریک های متغیر می توانند برای تعریف توابعی که روی داده های N بعدی نگاشت می شوند واقعا مفید باشند. این ویژگی می تواند کمک زیادی به بررسی نوع پایگاه های کد که به کتابخانه های علم داده مانند numpy یا tensorflow.

جدید Generic[*Shape] سینتکس فقط در پایتون 3.11 پشتیبانی می شود. برای استفاده از این ویژگی در پایتون 3.10 و پایین تر، می توانید از typing.Unpack ساخته شده به جای: Generic[Unpack[Shape]].

singledispatch اکنون از اتحادیه ها حمایت می کند

functools.singledispatch روشی منظم برای انجام اضافه بار توابع در پایتون، بر اساس نکات نوع. با تعریف یک تابع عمومی و تزئین آن کار می کند @singledispatch. سپس می توانید انواع تخصصی آن تابع را بر اساس نوع آرگومان های تابع تعریف کنید:

from functools import singledispatch


@singledispatch
def half(x):
    """Returns the half of a number"""
    return x / 2

@half.register
def _(x: int):
    """For integers, return an integer"""
    return x // 2

@half.register
def _(x: list):
    """For a list of items, get the first half of it."""
    list_length = len(x)
    return x[: list_length // 2]

                            
print(half(3.6))            
print(half(15))             
print(half([1, 2, 3, 4]))   

با بررسی نوع داده شده به آرگومان های تابع، singledispatch
می تواند توابع عمومی ایجاد کند، و یک روش غیر شی گرا برای انجام اضافه بار تابع ارائه دهد.

اما این همه خبر قدیمی است. آنچه Python 3.11 به ارمغان می آورد این است که اکنون، می توانید انواع اتحادیه را برای این آرگومان ها ارسال کنید. به عنوان مثال، برای ثبت یک تابع برای همه انواع اعداد، قبلاً باید آن را به طور جداگانه برای هر نوع انجام دهید، مانند float، complex یا Decimal:

@half.register
def _(x: float):
    return x / 2

@half.register
def _(x: complex):
    return x / 2

@half.register
def _(x: decimal.Decimal):
    return x / 2

اما اکنون، می توانید همه آنها را در یک اتحادیه مشخص کنید:

@half.register
def _(x: float | complex | decimal.Decimal):
    return x / 2

و کد دقیقاً همانطور که انتظار می رود کار خواهد کرد.

Self نوع

قبلاً، اگر مجبور بودید یک متد کلاس تعریف کنید که یک شی از خود کلاس را برمی گرداند، اضافه کردن انواع برای آن کمی عجیب بود، چیزی شبیه به این خواهد بود:

from typing import TypeVar

T = TypeVar('T', bound=type)

class Circle:
    def __init__(self, radius: int) -> None:
        self.radius = radius

    @classmethod
    def from_diameter(cls: T, diameter) -> T:
        circle = cls(radius=diameter/2)
        return circle

برای اینکه بتوانید بگویید یک متد همان نوع کلاس را برمی گرداند، باید a را تعریف کنید TypeVar، و بگویید که متد همان نوع را برمی گرداند T به عنوان خود کلاس فعلی.

اما با Self نوع، هیچ کدام مورد نیاز نیست:

from typing import Self

class Circle:
    def __init__(self, radius: int) -> None:
        self.radius = radius

    @classmethod
    def from_diameter(cls, diameter) -> Self:
        circle = cls(radius=diameter/2)
        return circle

Required[] و NotRequired[]

TypedDict برای افزودن اطلاعات نوع به یک پایگاه کد که از دیکشنری ها به شدت برای ذخیره داده ها استفاده می کند، واقعا مفید است. در اینجا نحوه استفاده از آنها آورده شده است:

from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

user : User = {'name': "Alice", 'age': 31}
reveal_type(user['age'])  

با این حال، TypedDicts یک محدودیت داشت، که در آن شما نمی‌توانید پارامترهای اختیاری را در یک فرهنگ لغت داشته باشید، مانند پارامترهای پیش‌فرض در تعاریف تابع.

به عنوان مثال، می توانید این کار را با a انجام دهید NamedTuple:

from typing import NamedTuple

class User(NamedTuple):
    name: str
    age: int
    married: bool = False

marie = User(name='Marie', age=29, married=True)
fredrick = User(name='Fredrick', age=17)  

با a امکان پذیر نبود TypedDict (حداقل بدون تعریف چند مورد از اینها TypedDict انواع). اما اکنون، می توانید هر فیلدی را به عنوان علامت گذاری کنید NotRequired، برای نشان دادن اینکه فرهنگ لغت اشکالی ندارد که آن فیلد را نداشته باشد:

from typing import TypedDict, NotRequired

class User(TypedDict):
    name: str
    age: int
    married: NotRequired[bool]

marie: User = {'name': 'Marie', 'age': 29, 'married': True}
fredrick : User = {'name': 'Fredrick', 'age': 17}  

NotRequired زمانی که بیشتر فیلدهای فرهنگ لغت شما مورد نیاز است، با داشتن چند فیلد غیر ضروری بسیار مفید است. اما، برای مورد مخالف، می توانید بگویید
TypedDict برای در نظر گرفتن هر فیلد به صورت غیر پیش فرض و سپس استفاده از
Required برای علامت گذاری فیلدهای مورد نیاز

به عنوان مثال، این همان کد قبلی است:

from typing import TypedDict, Required


class User(TypedDict, total=False):
    name: Required[str]
    age: Required[int]
    married: bool  

marie: User = {'name': 'Marie', 'age': 29, 'married': True}
fredrick : User = {'name': 'Fredrick', 'age': 17}  

contextlib.chdir

contextlib یک افزونه کوچک به آن دارد که یک مدیریت زمینه نامیده می شود
chdir. تنها کاری که انجام می دهد این است که دایرکتوری کاری فعلی را به دایرکتوری مشخص شده در مدیریت متن تغییر می دهد و هنگام خروج آن را به حالت قبل برمی گرداند.

یکی از موارد استفاده بالقوه می‌تواند این باشد که به جایی که گزارش‌ها را می‌نویسید تغییر مسیر دهید:

import os

def write_logs(logs):
    with open('output.log', 'w') as file:
        file.write(logs)


def foo():
    print("Scanning files...")
    files = os.listdir(os.curdir)  
    logs = do_scan(files)

    print("Writing logs to /tmp...")
    with contextlib.chdir('/tmp'):
        write_logs(logs)

    print("Deleting files...")
    files = os.listdir(os.curdir)
    do_delete(files)

به این ترتیب، لازم نیست نگران تغییر و برگرداندن دایرکتوری فعلی به صورت دستی باشید، مدیر زمینه این کار را برای شما انجام خواهد داد.

خلاصه

علاوه بر همه اینها، یک گیلاس در بالای صفحه وجود دارد: پایتون نیز در این نسخه به طور متوسط ​​22٪ سریعتر شده است. حتی ممکن است تا زمان انتشار نهایی در حدود اکتبر سریعتر شود!

همچنین، کار بر روی Python 3.12 از قبل شروع شده است. اگر می‌خواهید با آخرین پیشرفت‌های زبان به‌روز بمانید، می‌توانید آن را بررسی کنید
کشش درخواست ها صفحه در مخزن GitHub پایتون.

لینک منبع

ارسال یک پاسخ

آدرس ایمیل شما منتشر نخواهد شد.