Есть ли существенная разница между перечислением аргументов и ключевым args
Метод передачи параметров с помощью keyword *args или **kwargs
.
В качестве примера реализации этих ключевых слов был реализован класс Car
:
class Car():
def __init__(self, *args):
self.speed = args[0]
self.color = args[1]
audi = Car(200, 'red')
bmw = Car(250, 'black')
mb = Car(190, 'white')
print(audi.color)
print(bmw.speed)
Как мы видим, при создании объекта класса Car
, были переданы параметры в метод __init__
и после присваивание значений из массива args
.
Вопрос:
Если мы используем вместо args
обычное перечисление аргументов, будет ли существенная разница или ничего не произойдет?
UPD: Исходя из ответов и комментарий я подумал об удачных примерах использования этих keywords
. В основном, была бы удобна передача всех аргументов в одну кучу и последующей записи или обработки. Например:
import logging
logging.basicConfig(filename='example.log',
level=logging.INFO)
def logger(func):
def log_func(*args):
logging.info(f'Running "{func.__name__}" with arguments {args}')
print(func(*args))
return log_func
def add(x, y):
return x+y
def sub(x, y):
return x-y
add_logger = logger(add)
sub_logger = logger(sub)
add_logger(3, 3)
add_logger(4, 5)
sub_logger(10, 5)
sub_logger(20, 10)
Получаем результат:
python .\ClosureFuntcionsPython.py
6
9
5
10
cat .\example.log
INFO:root:Running "add" with arguments (3, 3)
INFO:root:Running "add" with arguments (4, 5)
INFO:root:Running "sub" with arguments (10, 5)
INFO:root:Running "sub" with arguments (20, 10)
Ответы (1 шт):
Визуальные отличия
Давайте посмотрим, что будет, если мы не передадим ожидаемые аргументы в класс.
audi = Car()
В вашем классе будет вызвано исключение IndexError
, что сделает запутанным работу с классом, при работе с классом вам придётся каждый раз вспоминать, что ожидает увидеть на входе класс, или читать код этого класса. И тот, и другой варианты кажутся не практичными.
IndexError: tuple index out of range
А, использовав позиционные аргументы в классе, если забыть указать speed
и color
будет вызвано исключение TypeError
и будет понятно, что было не так сделано.
class Car():
def __init__(self, speed, color):
self.speed = speed
self.color = color
TypeError:
Car.__init__()
missing 2 required positional arguments: 'speed' and 'color'
Кроме того, как удачно подметил insolor, невозможно заранее узнать, какие аргументы нужно будет передать классу, что недопускает возможности использовать типизацию. Это сделает код нечитабельным и непригодным для дальнейшей поддержки как для самого автора, так и какого-нибудь контрибьютора.
По ту сторону байт-кода
Давайте же посмотрим, какие инструкции выполняются в обоих классах
- Рассмотрим первый класс, в котором мы получаем скорость и цвет из
args
import dis
class Car:
def __init__(self, *args):
self.speed = args[0]
self.color = args[1]
dis.dis(Car)
Результат:
Disassembly of __init__:
5 0 RESUME 0
6 2 LOAD_FAST 1 (args)
4 LOAD_CONST 1 (0)
6 BINARY_SUBSCR
10 LOAD_FAST 0 (self)
12 STORE_ATTR 0 (speed)
7 22 LOAD_FAST 1 (args)
24 LOAD_CONST 2 (1)
26 BINARY_SUBSCR
30 LOAD_FAST 0 (self)
32 STORE_ATTR 1 (color)
42 RETURN_CONST 0 (None)
На первый взгляд, ничего интересного. Но давайте внимательно посмотрим на то, как мы достаём нужные нам аргументы.
Мы загружаем args
, загружаем константу (индекс), затем через инструкцию BINARY_SUBSCR
мы ищем в кортеже нужный аргумент, такая инструкция имеет свой метод __getitem__
, полученный результат присваиваем атрибуту self.speed
и тоже самое для self.color
.
- А как это будет выглядеть в классе с позиционными аргументами?
import dis
class Car:
def __init__(self, speed, color):
self.speed = speed
self.color = color
dis.dis(Car)
Смотрим:
Disassembly of __init__:
11 0 RESUME 0
12 2 LOAD_FAST 1 (speed)
4 LOAD_FAST 0 (self)
6 STORE_ATTR 0 (speed)
13 16 LOAD_FAST 2 (color)
18 LOAD_FAST 0 (self)
20 STORE_ATTR 1 (color)
30 RETURN_CONST 0 (None)
Здесь мы напрямую загружаем значения в наши атрибуты.
Это означает, что класс с неименованными аргументами будет выполнять на несколько инструкций больше.
Гипотеза: класс с неименованными аргументами будет медленнее, чем класс с позиционнными аргументами
Производительность
Естественно, тесты слишком синтетические и результаты этих тестов будут очень далеки от реальности, но для показательного сравнения подойдут.
Замеры будем проводить с помощью timeit:
import timeit
class Car:
def __init__(self, *args):
self.speed = args[0]
self.color = args[1]
class Car1:
def __init__(self, speed, color):
self.speed = speed
self.color = color
def test_car():
Car(200, "red")
def test_car1():
Car1(200, "red")
timeit.timeit(test_car), timeit.timeit(test_car1)
Для класса с неименованными аргументами получили 0.13930917900142958 попугаев, а для класса с позиционными аргументами - 0.1009512620003079 попугаев.
Это означает, что гипотеза верна. Однако эта разница настолько микроскопическая в данном контексте, что её можно даже не учитывать. Но другой вопрос, когда у нас в классе будет использоваться в разы больше аргументов.