Что такое *args и **kwargs в определении функции?
Продолжаем сериал. Сами авторы опросника пишут, что "*args и **kwargs — это специальные параметры в Python, которые позволяют передавать переменное количество аргументов в функцию", что может ввести в заблуждение.
Думаете, args и kwargs - это специальные ключевые слова?
А вот и нет.
Есть только * и **, и нет никаких специальных параметров args и kwagrs. Они так названы только для удобства и в 99% случаев вы увидите эти названия в учебниках, но это сделано только потому, что "так принято". И это не ключевые слова.
Вот, смотрите:
class SomeClass():
····def __init__(self, *bbargs, **zzargs):
········print(f'{bbargs=}; {zzargs=}')
if __name__ == '__main__':
····c = SomeClass(1,2,3, a='a', c='d')
>>>bbargs=(1, 2, 3); zzargs={'a': 'a', 'c': 'd'}
Операторы * и ** используются всего лишь для упаковки и распаковки коллекций в Python, и вовсе не являются какими-то "специальными операторами для функций"!
Распаковка списков и кортежей
a, *b = (1, 2, 3, 4)
# первое значение будет a, все остальные списком станут b
# a == 1, b == [2,3,4]
*a, b = (1, 2, 3, 4)
# наоборот, сколько-то первых будут a, а последнее - точно b
# a == [1,2,3], b == 4
Распаковка словарей
params = {'a':1, 'b':2}
c = {'c':3, **params}
print(c)
>>>{'c': 3, 'a': 1, 'b': 2}
Хорошая ли практика передавать аргументы через args, kwargs?
Я считаю, что в большинстве случаев - нет.
Вот вам пример, допустим нам в классе нужен параметр адреса.
class SomeClass():
····def __init__(self, *bbargs, **zzargs):
········self.addr = zzargs.get('addr', '127.0.0.1') # 1
········self.addr = zzargs.get('addr', None) or '127.0.0.1' # 2
········print(self.addr)
c = SomeClass(1,2,3, a='a', c='d', addr='192.168.1.1')
Красиво, читаемо? Я считаю, что нет.
Кстати, вот ещё пример за гранью добра и зла:
class SomeClass():
····def __init__(self, *bbargs, **zzargs):
········self.__dict__.update(zzargs)
········# Так делать нельзя!
········print(self.addr) # '192.168.1.1' ;)
c = SomeClass(1,2,3, a='a', c='d', addr='192.168.1.1')
Для меня самый правильный вариант - по возможности принципиально избегать использования * и ** до тех пор, пока это не станет абсолютно необходимым:
class SomeClass():
····def __init__(self, addr:str='127.0.0.1') -> None:
········self.addr: str = addr
········print(self.addr)
Потому что так - красивей!
Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.