سلام دوستان امیدوارم حالتون خوب باشه. درین پست قراره Structural pattern matching که در نسخه 3.10 پایتون به اون اضافه شده ارو مورد برسی قرار بدیم.
اگه بخوام به طور خلاصه بگم، خیلی شبیه به switch statement های زبان های برنامه نویسی خانواده C هستش ولی خیلی چیز های خفن تری برای ارائه داره که در ادامه همه رو مورد بحث قرار خواهیم داد
match subject: case <pattern_1>: <action_1> case <pattern_2>: <action_2> case <pattern_3>: <action_3> case _: <action_wildcard>
مثال بالا از خود وبسایت رسمی python.org گرفته شده. همون طور که می بینید این عبارت با کلمه match شروع میشه و در ادامه می تونیم subject رو با case های مختلف انطباق بدیم و روی هر کدوم که منطبق شد، action مربوط به اون قسمت رو انجام میدیم.
در بسیاری از زبان های برنامه نویسی (مخصوصا خانواده C) در انتهای case ها یک عبارت default وجود داره که در صورت عدم انطباق subject بر case ها، کد های مربوط به قسمت default اجرا میشن. در پایتون 3.10 هم اگر به جای استفاده از یک pattern در مقابل case ها، از یک "_" استفاده کنیم، درصورتی که subject به هیچ یک از pattern های موجود منطبق نشه، کد های مربوط به اون قسمت اجرا میشن. این هم ساده ترین استفاده از عبارت های match-case:
def http_error(status): match status: case 400: return "Bad request" case 404: return "Not found" case 418: return "I'm a teapot" case _: return "Something's wrong with the Internet" # from python.org
حالا می تونید تابع http_error رو با status code های مختلف فراخوانی کنید و error message های مربوطه رو دریافت کنید.
درصورتی که می خواهید عملیاتی مشترک رو برای چند pattern در نظر بگیرید.می تونید در عبارت های match-case از عملگر "|" برای تعریف چند pattern برای یک action استفاده کنید. مثل مثال زیر:
case 401 | 403 | 404: return "Not allowed" # copied from python.org
در اکثر زبان هایی که عبارت های switch-case دارند، باید از literal ها یا constant (ثابت) ها استفاده کرد. به طوری که شما نمی تونید در عبارت های switch-case از یک متغیر برای تعریف یک case استفاده کنید. اما پایتون بسیار انعطاف پذیر تر عمل کرده و به شما این امکان رو میده که از متغیر ها هم استفاده کنید.
بخش هایی که literal هستند با مقدار subject مقایسه میشن ولی قسمت های متغیر به صورت wildcard عمل می کنند و به هر مقداری منطبق میشن (تنها کاری که می کنن اینه که مقدار مربوطه رو در خودشون ذخیره می کنند که برای مواقعی که چندین مقدار در subject داریم (مثلا subject یک list یا tuple باشه) بسیار مفیده)
# point is an (x, y) tuple match point: case (0, 0): print("Origin") case (0, y): print(f"Y={y}") case (x, 0): print(f"X={x}") case (x, y): print(f"X={x}, Y={y}") case _: raise ValueError("Not a point") # copied from python.org
اگر برای ذخیره اطلاعات از class ها استفاده می کنید، باید بدونید که میشه class ها رو هم در pattern ها به این صورت استفاده کرد:
class Point: x: int y: int def location(point): match point: case Point(x=0, y=0): print("Origin is the point's location.") case Point(x=0, y=y): print(f"Y={y} and the point is on the y-axis.") case Point(x=x, y=0): print(f"X={x} and the point is on the x-axis.") case Point(): print("The point is located somewhere else on the plane.") case _: print("Not a point") # copied from python.org
همونطور که می بینید کلاس Point دو attribute به نام های x و y داره. حالا اگر بخواهیم مقادیر این دو attribute رو در pattern ها برسی کنیم، می تونیم اسم attribute ها به همراه مقادیر مورد انتظارمون رو به constructor کلاس Point بدیم.
درضمن درصورتی که می خواهید مقادیر attribute ها رو در متغیر های قابل استفاده در بدنه case ها ذخیره کنید، باید به جای مقدار اون attribute از یک identifier برای نام متغیر استفاده کنید. مثل مثال زیر:
case Point(x=xValue, y=yValue): # ^ ^ : actual attribute names along with variable names print (f"{xValue} {yValue}") # ^^^^^^ ^^^^^^ : use variables
به صورت پیش فرض وقتی شما دارید از class ها در pattern ها استفاده می کنید، باید اسم همه attribute هایی رو که می خواهید مورد برسی قرار بدید رو مشخص کنید. اما بعضی اوقات این اذیت کننده میشه و شاید ترجیح بدید از positional parameters استفاده کنید. در اون صورت باید به __match_args__ در کلاس مورد نظر مقدار بدید. مثلا اگه مقدار این attribute رو در کلاس Point برابر با ("x", "y") بزارید. اون موقع می تونید به این صورت از کلاس Point استفاده کنید:
# checks if x is equal to 1 and assigns the value of `y` to var Point(1, var) # ^ ^^^ # x, y # all do the same thing Point(1, y=var) Point(x=1, y=var) Point(y=var, x=1)
شما می تونید از pattern های تو در تو برای لیست های کوتاه استفاده کنید مثل مثال زیر:
match points: case []: print("No points in the list.") case [Point(0, 0)]: print("The origin is the only point in the list.") case [Point(x, y)]: print(f"A single point {x}, {y} is in the list.") case [Point(0, y1), Point(0, y2)]: print(f"Two points on the Y axis at {y1}, {y2} are in the list.") case _: print("Something else is found in the list.") # copied from python.org
شما می تونید از wildcard ها حتی در pattern های پیچیده تر استفاده کنید.
match book_details: case ("how to code better", "Ashkan Mohammadi", "21/06/05"): print ("found `how to code better` book") case (title, "Ashkan Mohammadi", _): print ("it is one of the books written by Ashkan Mohammadi") case ("Guns", *_): # * is used for packing the rest of the values print ("the book is all about guns and weapons")
متغیرbook_details یک tuple است که شامل (title, author, publication_date) یک کتاب میشه.
هرجایی که از "_" استفاده شده به این معنیه که مهم نیست در اینجا چه مقداری قرار داره.
"_" یک identifier صحیح محسوب میشه، یعنی شما می تونید متغیری رو تنها با "_" نام گذاری کنید. پس در واقع "_" یک متغیره ولی معنی "_" در بین برنامه نویس های پایتون به این معنیه که: این مقدار رو نادیده بگیر چون اهمیتی نداره
شما می تونید یک if clause به آخر case ها اضافه کنید که بهش guard میگن. اگر مقدار guard برابر با False باشه، پایتون از کل case (علی رغم این که منطبق شده باشه) صرف نظر می کنه و میره سراغ بعدی. توجه داشته باشید که انتساب مقادیر به متغیر ها قبل از چک کردن شرط if انجام میشه. یعنی می تونید از متغیر هایی که در pattern تعریف کردید در guard هم استفاده کنید:
match point: case Point(x, y) if x == y: print(f"The point is located on the diagonal Y=X at {x}.") case Point(x, y): print(f"Point is not on the diagonal.") # copied from python.org
خوب دوستان به انتهای این پست رسیدیم. امیدوارم براتون مفید بوده باشه. برای اطلاعات بیشتر می تونید به این صفحه مراجعه کنید.
شاید از این نوشته هم خوشتون اومد: