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

جرایم با تطبیق الگوی پایتون • هیلل وین

یکی از قطعات کوچک مورد علاقه من از پایتون است __subclasshook__. کلاس های پایه چکیده با __subclasshook__ می تواند تعریف کند که چه چیزی به عنوان زیر کلاس ABC محسوب می شود، حتی اگر هدف از ABC اطلاعی نداشته باشد. مثلا:

class PalindromicName(ABC):
   
  @classmethod
  def __subclasshook__(cls, C):
    name = C.__name__.lower()
    return name[::-1] == name

class Abba:
  ...

class Baba:
  ...

>>> isinstance(Abba(), PalindromicName)
True
>>> isinstance(Baba(), PalindromicName)
False

با این کار می توانید کارهای عجیب و غریب انجام دهید. در سال 2019 من از آن برای ایجاد انواع غیر یکنواخت استفاده کردم، جایی که چیزی به عنوان یک به حساب می آید NotIterable اگر این نمی کند را داشته باشند __iter__ روش. هیچ کاری خیلی شیطانی وجود نداشت که بتوانید با این کار انجام دهید: هیچ چیز در پایتون واقعاً با ABC ها تعامل نداشت و آسیبی که می توانید با کد تولید وارد کنید محدود می کرد.

سپس پایتون 3.10 تطبیق الگوی اضافه شده.

مروری سریع بر تطبیق الگو

از آموزش تطبیق الگو:

match command.split():
    case ["quit"]:
        print("Goodbye!")
        quit_game()
    case ["look"]:
        current_room.describe()
    case ["get", obj]:
        character.get(obj, current_room)

می توانید روی آرایه ها، فرهنگ لغت ها و اشیاء سفارشی مطابقت دهید. پایتون برای پشتیبانی از اشیاء منطبق استفاده می کند isinstance(obj, class)، که بررسی می کند

  1. اگر obj از نوع است class
  2. اگر obj یک زیرگروه متعدی از است class
  3. اگر class ABC است و a را تعریف می کند __subclasshook__ که با نوع آن مطابقت دارد obj.

این باعث شد به این فکر کنم که آیا ABC ها می توانند یک الگوی مشابه را “ربایش” کنند. چیزی مثل این:

from abc import ABC

class NotIterable(ABC):

    @classmethod
    def __subclasshook__(cls, C):
        return not hasattr(C, "__iter__")

def f(x):
    match x:
        case NotIterable():
            print(f"{x} is not iterable")
        case _:
            print(f"{x} is iterable")

if __name__ == "__main__":
    f(10)
    f("string")
    f([1, 2, 3])

اما مطمئناً پایتون این شیک‌نفری را کاهش می‌دهد، درست است؟

$ py10 abc.py
10 is not iterable
string is iterable
[1, 2, 3] is iterable

اوه

ای وای.

بدتر کردنش

تطبیق الگو نیز می تواند تخریب فیلدهای شی:

match event.get():
    case Click(position=(x, y)):
        handle_click_at(x, y)

ما فقط می توانیم میدان را بدست آوریم بعد از ما موضوع را تعیین کرده ایم. ما نمی‌توانیم «هیچ شی‌ای را که دارای آن است مطابقت دهیم foo فیلد… مگر اینکه از ABC استفاده کنیم.

from abc import ABC
from dataclasses import dataclass
from math import sqrt

class DistanceMetric(ABC):

    @classmethod
    def __subclasshook__(cls, C):
        return hasattr(C, "distance")

def f(x):
    match x:
        case DistanceMetric(distance=d):
            print(d)
        case _:
            print(f"{x} is not a point")

@dataclass
class Point2D:
    x: float
    y: float

    @property
    def distance(self):
        return sqrt(self.x**2 + self.y**2)

@dataclass
class Point3D:
    x: float
    y: float
    z: float

    @property
    def distance(self):
        return sqrt(self.x**2 + self.y**2 + self.z**2)

if __name__ == "__main__":
    f(Point2D(10, 10))
    f(Point3D(5, 6, 7))
    f([1, 2, 3])
14.142135623730951
10.488088481701515
[1, 2, 3] is not a point

بهتر می شود! در حالی که ABC تطابق را تعیین می کند، شی تصمیم می گیرد که ساختارشکن باشد، به این معنی که ما می توانیم کارهایی مانند این را انجام دهیم:

def f(x):
    match x:
        case DistanceMetric(z=3):
            print(f"A point with a z-coordinate of 3")
        case DistanceMetric(z=z):
            print(f"A point with a z-coordinate that's not 3")
        case DistanceMetric():
            print(f"A point without a z-coordinate")
        case _:
            print(f"{x} is not a point")

ترکیب کننده ها

تطبیق الگو انعطاف پذیر است اما نسبتاً محدود است. این فقط می تواند با نوع یک شی مطابقت داشته باشد، به این معنی که ما باید یک ABC جداگانه برای هر چیزی که می خواهیم آزمایش کنیم ایجاد کنیم. خوشبختانه، راهی برای دور زدن این موضوع وجود دارد. پایتون به صورت پویا تایپ می شود. در 99٪ مواقع این فقط به این معنی است که “اگر با خراب شدن چیزها در زمان اجرا مشکلی ندارید، به انواع ثابت نیاز ندارید”. اما آن همچنین به این معنی است که اطلاعات نوع در زمان اجرا وجود دارد و این انواع را می توان در زمان اجرا ایجاد کرد.

آیا می توانیم از این برای تطبیق الگو استفاده کنیم؟ بیایید آن را امتحان کنیم:

def Not(cls):
    class _Not(ABC):
        @classmethod
        def __subclasshook__(_, C):
            return not issubclass(C, cls)
    return _Not

def f(x):
    match x:
        case Not(DistanceMetric)(): 
            print(f"{x} is not a point")
        case _:
            print(f"{x} is a point")

Not تابعی است که یک کلاس می گیرد، a را تعریف می کند جدید ABC، قلاب آن ABC را روی “هر چیزی که کلاس نیست” تنظیم می کند و سپس آن ABC را برمی گرداند.

ما این را امتحان می کنیم و…

    case Not(DistanceMetric)():
                            ^
SyntaxError: expected ':'

این یک خطا است! ما در نهایت به محدودیت های تطبیق الگو در ABC ها رسیده ایم. سپس دوباره، “فقط” یک خطای نحوی است. شاید اگر سینتکس را کمی تغییر دهیم کار کند؟

+   n = Not(DistanceMetric)
    match x:
-       case Not(DistanceMetric)(): 
+       case n(): 
PlanePoint(x=10, y=10) is a point
SpacePoint(x=5, y=6, z=7) is a point
[1, 2, 3] is not a point

موفقیت! و فقط برای آزمایش اینکه این قابل ترکیب است، اجازه دهید یک بنویسیم And.

from abc import ABC
from dataclasses import dataclass
from collections.abc import Iterable

def Not(cls):
    class _Not(ABC):
        @classmethod
        def __subclasshook__(_, C):
            return not issubclass(C, cls)
    return _Not

def And(cls1, cls2):
    class _And(ABC):
        @classmethod
        def __subclasshook__(_, C):
            return issubclass(C, cls1) and issubclass(C, cls2)
    return _And


def f(x):
    n = And(Iterable, Not(str))
    match x:
        case n():
            print(f"{x} is a non-string iterable")
        case str():
            print(f"{x} is a string")
        case _:
            print(f"{x} is a string or not-iterable")


if __name__ == "__main__":
    f("abc")
    f([1, 2, 3])

این به عنوان “””منتظره””” کار می کند.

ذخیره کردن همه چیز در اطراف من حاکم است

این باعث شد به این فکر کنم: اگر چه؟ __subclasshook__ تابع خالص نبود؟ آیا می توانم یک ABC درست کنم که با آن مطابقت داشته باشد اولین ارزش هر نوع ارسال شده است، اما موارد بعدی نه؟

from abc import ABC

class OneWay(ABC):
    seen_classes = set()

    @classmethod
    def __subclasshook__(cls, C):
        print(f"trying {C}")
        if C in cls.seen_classes:
            return False
        cls.seen_classes |= {C}
        return True




def f(x):
    match x:
        case OneWay():
            print(f"{x} is a new class")
        case _:
            print(f"we've seen {x}'s class before")


if __name__ == "__main__":
    f("abc")
    f([1, 2, 3])
    f("efg")

متأسفانه این همه بیهوده بود.

trying <class 'str'>
abc is a new class
trying <class 'list'>
[1, 2, 3] is a new class
efg is a new class

به نظر می رسد __subclasshook__ نتایج را برای یک بررسی نوع معین ذخیره می کند. CPython فرض می‌کند که مردم نمی‌خواهند عوارض جانبی را به گوشه‌های باطنی زبان وارد کنند. نشون بده چقدر آنها دانستن

با این حال، ما هنوز هم می توانیم با عوارض جانبی سرگرم شویم. این ABC اجازه می دهد تا از طریق هر نوع دیگر.

class FlipFlop(ABC):
    flag = False

    @classmethod
    def __subclasshook__(cls, _):
        cls.flag = not cls.flag
        return cls.flag

و این ABC از کاربر می پرسد که برای هر نوع چه کاری باید انجام دهد.

class Ask(ABC):
    first_class = None

    @classmethod
    def __subclasshook__(cls, C):
        choice = input(f"hey should I let {C} though [y/n]  ")
        if choice == 'y':
            print("okay we'll pass em through")
            return True
        return False

آنها را در یک الگوی مطابقت امتحان کنید. هر دو کار می کنند!

آیا باید از این استفاده کنم؟

خدایا نه

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

پس بله، شما هیچ چیز مفیدی یاد نگرفتید. من فقط چیزهای وحشتناک را دوست دارم ¯_(ツ)_/¯

با تشکر از پردراگ گرووسکی برای بازخورد عنوان از جنایات با Go Generics.

من نسخه اولیه این پست را در سایت خود به اشتراک گذاشتم خبرنامه هفتگی جایی که من پست های وبلاگ جدید را اعلام می کنم و مقالات اضافی می نویسم. اگر از این کار لذت بردید، چرا مشترک نمی شوید?



لینک منبع

ارسال یک پاسخ

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