ستاره ها در پایتون: چیست و چگونه از آنها استفاده کنیم

astriks-2
به این مقاله چند ستاره میدی؟
0 / 5 امتیاز: 4.2 رای 5

امتیاز شما به این مطلب:

فهرست عناوین :

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

عملگرهای * و ** طی سالها قابلیت‌های خود را گسترش داده‌اند. من تمام روش‌هایی که استفاده از این عملگرها را بررسی خواهم کرد و همچنین توضیح میدهم کدام یک فقط در نسخه‌های جدید پایتون قابل استفاده هستند. اگر در زمان پایتون 2 با این عملگرها آشنا شده‌اید، پیشنهاد میکنم حتماً این مقاله را مطالعه کنید زیرا پایتون 3 کاربردهای جدید زیادی برای این عملگرها اضافه کرده است.

چیزی که در مورد آن صحبت نمیکنم

وقتی در این مقاله از * و ** صحبت میکنم، در مورد عملگرهای prefix * و ** صحبت میکنم، نه عملگرهای infix. بنابراین من در مورد ضرب و توان صحبت نمیکنم:

				
					>>> 2 * 5
10
>>> 2 ** 5
32
				
			

استفاده از * و ** در پایتون

ارسال آرگومان‌ها به یک تابع

در پایتون، از * و ** برای ارسال آرگومان‌ها به یک تابع استفاده میشود. به عنوان مثال، args*لیستی از آرگومان‌های نامحدود را به تابع ارسال می‌کند و kwargs**دیکشنری‌ای از آرگومان‌های کلیدی را میپذیرد.

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

شما میتوانید از args*برای گرفتن آرگومان‌های نامحدود و از kwargs**برای گرفتن آرگومان‌های کلیدی استفاده کنید. این باعث انعطاف پذیری بیشتری در توابع شما میشود.

پذیرش آرگومان‌های فقط کلیدی

با استفاده از * در امضای تابع، میتوانید مطمئن شوید که تمامی آرگومان‌های بعد از آن به عنوان آرگومان‌های کلیدی پذیرش شوند.

ستاره برای باز کردن بسته بندی در فراخوانی تابع

هنگام فراخوانی یک تابع، عملگر * می تواند برای باز کردن یک تکرار در آرگومان های فراخوانی تابع استفاده شود:
				
					>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']

>>> print(fruits[0], fruits[1], fruits[2], fruits[3])
lemon pear watermelon tomato

# همین عملکرد با رویکرد جدید
>>> print(*fruits)
lemon pear watermelon tomato
				
			
خط print(*fruits) همه آیتم‌های موجود در لیست fruits را به عنوان آرگومان‌های جداگانه به تابع print ارسال می‌کند، بدون نیاز به دانستن تعداد آرگومان‌های موجود در لیست. توانایی ارسال همه آیتم‌های موجود در یک تکرار به‌عنوان آرگومان‌های جداگانه بدون * امکانپذیر نیست، مگر اینکه لیست طول ثابتی داشته باشد. اینجا یک مثال دیگر داریم:
				
					
def example(list_of_lists):
    return [
        list(row) for row in zip(*list_of_lists)
    ]
				
			

در اینجا ما یک لیست از لیست ها را میپذیریم و یک لیست “تغییر یافته” از لیست ها را بر میگردانیم.

				
					>>> example([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
				
			

عملگر ** نیز کاری مشابه انجام میدهد، اما با آرگومان های keyword . عملگر ** به ما اجازه میدهد تا یک دیکشنری از جفت های کلید-مقدار را برداریم و آن را در آرگومان های کلمه کلیدی در یک فراخوانی تابع باز کنیم.

				
					>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> filename = "{year}-{month}-{day}.txt".format(**date_info)
>>> filename
'2020-01-01.txt'
				
			

یا مثالی دیگر:

				
					>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> numbers = [2, 1, 3, 4, 7]
>>> print(*numbers, *fruits)
2 1 3 4 7 lemon pear watermelon tomato
				
			

با توجه به تجربه من، استفاده از ** برای باز کردن آرگومان‌های کلیدواژه در فراخوانی تابع چندان رایج نیست. بیشتر این استفاده را هنگام تمرین وراثت می‌بینیم: فراخوانی به super() اغلب شامل هر دو * و ** می‌شود. از پایتون 3.5 به بعد، هر دو * و ** می‌توانند چندین بار در فراخوانی تابع استفاده شوند. استفاده از چند بار * گاهی اوقات می‌تواند مفید باشد.

				
					class Base:
    def __init__(self, **kwargs):
        self.base_attr = kwargs.get('base_attr', 'default value')

class Derived(Base):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.derived_attr = kwargs.get('derived_attr', 'default value')

# نمونه‌سازی
obj = Derived(base_attr='value1', derived_attr='value2')
print(obj.base_attr)   # خروجی: value1
print(obj.derived_attr)  # خروجی: value2

				
			
چندین بار استفاده از **  نیز مشابه به نظر میرسد:
				
					>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> filename = "{year}-{month}-{day}-{artist}-{title}.txt".format(
...     **date_info,
...     **track_info,
... )
>>> filename
'2020-01-01-Beethoven-Symphony No 5.txt'
				
			
هنگام استفاده از **باید مراقب باشید. توابع در پایتون نمیتوانند آرگومان کلیدواژه یکسانی داشته باشند که چندین بار مشخص شده باشد، بنابراین کلیدهای هر دیکشنری مورد استفاده با ** باید متمایز باشند، در غیر این صورت یک استثنا ایجاد می‌شود.
				
					def func(a, b, c):
    print(a, b, c)

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'b': 4}  # کلید 'b' تکراری است

# این فراخوانی خطا ایجاد می‌کند
try:
    func(**dict1, **dict2)
except TypeError as e:
    print(e)  # خروجی: func() got multiple values for keyword argument 'b'

				
			

ستاره برای بسته بندی آرگومان های داده شده به تابع

هنگام تعریف یک تابع، عملگر * میتواند برای گرفتن تعداد نامحدود از آرگومان‌های موقعیتی که به تابع داده می‌شود، استفاده شود. این آرگومان‌ها به صورت یک تاپل (tuple) در می‌آیند.

در مثال زیر، *args تمامی آرگومان‌های موقعیتی که به تابع example_func داده می‌شود را به عنوان یک تاپل جمع‌آوری می‌کند.

				
					def example_func(*args):
    print(args)

example_func(1, 2, 3)
# خروجی: (1, 2, 3)

# مثال بالا با خروجی آنپک شده
def example_func(*args):
    print(*args)
    
>>example_func(1, 2, 3)
# >> 1,2,3
				
			
توابع print و zip پایتون هر تعداد آرگومان موقعیتی را میپذیرد. استفاده از *  برای بسته بندی آرگومان به ما توانایی ایجاد تابع خودمان را میدهد که مانند print و zip، هر تعداد آرگومان را بپذیرد. عملگر ** جنبه دیگری نیز دارد: میتوانیم هنگام تعریف یک تابع، از ** استفاده کنیم تا هر آرگومان کلمه کلیدی داده شده به تابع را در یک فرهنگ لغت ثبت کنیم:
				
					def tag(tag_name, **attributes):
    attribute_list = [
        f'{name}="{value}"'
        for name, value in attributes.items()
    ]
    return f"<{tag_name} {' '.join(attribute_list)}>"
    
>>> tag('a', href="https://tecpro.ir")
'<a href="https://tecpro.ir">'

>>> tag('img', height=20, width=40, src="face.jpg")
'<img decoding="async" height="20" width="40"  data-src="face.jpg" class="lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">'

				
			

پذیرش آرگومان‌های فقط کلیدواژه در پایتون

از پایتون 3 به بعد، میتوانیم از یک نحو ویژه برای پذیرش آرگومان‌های فقط کلیدواژه استفاده کنیم. آرگومان‌های فقط کلیدواژه، آرگومان‌های تابعی هستند که تنها با استفاده از نحو کلیدواژه قابل تعیین هستند، به این معنی که نمیتوان آن‌ها را به صورت موقعیتی مشخص کرد. برای پذیرش این نوع آرگومان‌ها، میتوانیم آرگومان‌های نامگذاری شده را پس از استفاده از * در هنگام تعریف تابع خود قرار دهیم.

				
					def get_multiple(dictionary, *keys, default=None):
    return [
        dictionary.get(key, default) for key in keys
    ]
    
>>> fruits = {'lemon': 'yellow', 'orange': 'orange', 'tomato': 'red'}

>>> get_multiple(dictionary=fruits,'lemon', 'tomato', 'squash',  default='unknown')
['yellow', 'red', 'unknown']
				
			

آرگومان های فقط کلیدواژه بدون آرگومان های موقعیتی

این ویژگی آرگومان فقط کلمه کلیدی جالب است، اما اگر بخواهید بدون گرفتن آرگومان های موقعیتی نامحدود به آرگومان های فقط کلیدواژه نیاز داشته باشید، چه؟
پایتون این امکان را با یک نحو تا حدی عجیب *-on-its-own فراهم می کند:
				
					def with_previous(iterable, *, fillvalue=None):
    """Yield each iterable item along with the item before it."""
    ....previous = fillvalue
    ....for item in iterable:
    ........yield previous, item
    ........previous = item
				
			

این تابع یک آرگومان iterable را میپذیرد، که میتواند به صورت موقعیت (به عنوان اولین آرگومان) یا با نام آن و یک آرگومان fillvalue که یک آرگومان فقط کلمه کلیدی است مشخص شود. به این معنی است که ما میتوانیم with_previous را به این صورت فراخوانی کنیم:

				
					>>> list(with_previous([2, 1, 3], fillvalue=0))
[(0, 2), (2, 1), (1, 3)]
				
			
اما نه به این صورت:
				
					>>> list(with_previous([2, 1, 3], 0))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: with_previous() takes 1 positional argument but 2 were given

				
			
این تابع دو آرگومان میپذیرد و یکی از آنها fillvalue باید به عنوان آرگومان کلمه کلیدی مشخص شود. من معمولاً از آرگومان‌های فقط کلیدواژه برای گرفتن آرگومان‌های موقعیتی استفاده میکنم ، اما گاهی اوقات برای اجبار آرگومان برای استفاده از فقط با نام آن  از * میکنم. تابع مرتب‌ سازی داخلی پایتون در واقع از این رویکرد استفاده میکند. اگر به اطلاعات راهنما نگاه کنید موارد زیر را مشاهده خواهید کرد:
				
					>>> help(sorted)
Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.

    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.
				
			

ستاره در باز کردن بسته بندی تاپل

پایتون 3 همچنین روش جدیدی برای استفاده از عملگر * اضافه کرده که فقط تا حدودی به ویژگی های *-when-defining-a-function و *-when-calling-a-function مرتبط است. اپراتور * هم اکنون میتواند در باز کردن بسته بندی تاپل استفاده شود:
				
					>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> first, second, *remaining = fruits

>>> remaining
['watermelon', 'tomato']

>>> first, *remaining = fruits
>>> remaining
['pear', 'watermelon', 'tomato']

>>> first, *middle, last = fruits
>>> middle
['pear', 'watermelon']
				
			

اگر میپرسید «کجا میتوانم از این در کد خودم استفاده کنم»، به مثال زیر دقت کنید که چطور میتوانید به راحتی از این اپراتور برای آنپک کردن استفاده کنید:

				
					>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> ((first_letter, *remaining), *other_fruits) = fruits>>
>>> first_letter
['l']
>>> remaining
['e', 'm', 'o', 'n']
>>> other_fruits
['pear', 'watermelon', 'tomato']
				
			

پیشنهاد مطالعه: آموزش جامع تنظیمات HAProxy

کاربرد ستاره در لیست‌ها

پایتون 3.5 تعداد زیادی ویژگی جدید مرتبط با * را از طریق PEP 448 معرفی کرد. یکی از بزرگترین  این ویژگی‌ها، امکان استفاده از * برای ریختن یک تکرار در لیست جدید است. فرض کنید تابعی دارید که هر دنباله ای را میگیرد و لیستی را با دنباله و عکس آن دنباله به هم پیوسته برمیگرداند:

				
					
def example(sequence):
    return list(sequence) + list(reversed(sequence))

				
			
این تابع باید چند بار ورودی را به لیست تبدیل کند تا لیست ها را به هم متصل کند و نتیجه را برگرداند. در پایتون 3.5 میتوانیم اینگونه بنویسیم:
				
					def example(sequence):
....return [*sequence, *reversed(sequence)]

				
			

مثالی دیگر:

				
					def rotate_first_item(sequence):
....return [*sequence[1:], sequence[0]]

				
			
این تابع یک لیست جدید را برمیگرداند که در آن، اولین مورد در لیست داده شده (یا دنباله دیگر) به انتهای لیست جدید منتقل میشود. این استفاده از عملگر * یک راه عالی برای الحاق تکرارپذیری انواع مختلف به یکدیگر است. عملگر * برای هر تکراری کار میکند، در حالی که استفاده از عملگر ‘+’ فقط روی توالی‌های خاصی کار میکند که همه باید از یک نوع باشند. این فقط به ایجاد لیست ها نیز محدود نمیشود. همچنین میتوانیم تکرارپذیرها را در مجموعه‌ها یا تاپل‌های جدید بیاندازیم:
				
					>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> (*fruits[1:], fruits[0])
('pear', 'watermelon', 'tomato', 'lemon')
>>> uppercase_fruits = (f.upper() for f in fruits)
>>> {*fruits, *uppercase_fruits}
{'lemon', 'watermelon', 'TOMATO', 'LEMON', 'PEAR', 'WATERMELON', 'tomato', 'pear'}

				
			
توجه داشته باشید که آخرین خط بالا یک لیست و یک ژنراتور را می گیرد و آنها را در یک مجموعه جدید قرار می دهد. قبل از استفاده از *، قبلاً راه آسانی برای انجام این کار در یک خط کد وجود نداشت. قبلاً راهی برای انجام این کار وجود داشت، اما به خاطر سپردن یا کشف آن آسان نبود:
				
					>>> set().union(fruits, uppercase_fruits)
{'lemon', 'watermelon', 'TOMATO', 'LEMON', 'PEAR', 'WATERMELON', 'tomato', 'pear'}

				
			

دو ستاره در دیکشنری:

PEP 448 همچنین توانایی های ** را با اجازه دادن به این اپراتور برای استفاده از جفت های کلید-مقدار از یک فرهنگ لغت به یک فرهنگ لغت جدید گسترش داد:
				
					>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> all_info = {**date_info, **track_info}
>>> all_info
{'year': '2020', 'month': '01', 'day': '01', 'artist': 'Beethoven', 'title': 'Symphony No 5'}

				
			
این ویژگی را میتوان برای ادغام بیش از  دو دیکشنری با هم نیز استفاده کرد. برای مثال میتوانیم یک دیکشنری را کپی کنیم و در عین حال مقدار جدیدی به آن اضافه کنیم:
				
					>>> date_info = {'year': '2020', 'month': '01', 'day': '7'}
>>> event_info = {**date_info, 'group': "Python Meetup"}
>>> event_info
{'year': '2020', 'month': '01', 'day': '7', 'group': 'Python Meetup'}

				
			
یا کپی/ادغام دیکشنری ها در حالی که مقادیر خاصی را نادیده می گیرند:
				
					>>> event_info = {'year': '2020', 'month': '01', 'day': '7', 'group': 'Python Meetup'}
>>> new_info = {**event_info, 'day': "14"}
>>> new_info
{'year': '2020', 'month': '01', 'day': '14', 'group': 'Python Meetup'}
				
			

ستاره های پایتون قدرتمند هستند

عملگرهای * و ** پایتون فقط سینتکس شیرین نیستند. برخی از کارهایی که آنها به شما اجازه میدهند انجام دهید را میتوان از راه های دیگر به دست آورد، اما جایگزین های * و ** معمولاً دست و پا گیرتر و دارای منابع یادگیری کمتری هستند. و دستیابی به برخی از ویژگی هایی که آنها ارائه میدهند، بدون آنها به سادگی غیرممکن است: برای مثال هیچ راهی برای پذیرش تعدادی آرگومان موقعیتی برای یک تابع بدون * وجود ندارد. پس از خواندن تمام ویژگی های * و **، ممکن است تعجب کنید که نام این عملگرهای عجیب و غریب چیست. متأسفانه، آنها واقعاً اسامی مختصر ندارند. شنیده‌ام به اپراتور * «بسته‌بندی» و «باز کردن بسته‌بندی» می‌گویند. من همچنین شنیده ام که آن را “splat” (از دنیای روبی) مینامند و در پایتون به سادگی “ستاره” نامیده شده است. من تمایل دارم این عملگرها را “ستاره” و “ستاره دوگانه” یا “ستاره ستاره” بنامم. این آنها را از خویشاوندان پسوندشان متمایز نمیکند (ضرب و توان)، اما موضوع معمولاً مشخص میکند که در مورد عملگرهای prefix یا infix صحبت میکنیم. اگر * و ** را نمیفهمید یا نگران به خاطر سپردن تمام کاربردهای آنها هستید، این کار را نکنید! این اپراتورها کاربردهای زیادی دارند و به خاطر سپردن کاربرد خاص هر یک از آنها به اندازه درک این که چه زمانی بتوانید به این اپراتورها دسترسی پیدا کنید مهم نیست. من پیشنهاد میکنم از این مقاله به عنوان یک راهنما یا ساختن برگه راهنمای خود استفاده کنید تا به شما در استفاده از * و ** در پایتون کمک کند.

به این مقاله چند ستاره میدی؟
0 / 5 امتیاز: 4.2 رای 5

امتیاز شما به این مطلب:

مطلب بالا را با دوستان خود به اشتراک بگذارید!

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *