Почему Python разрешает вызовы функций с неправильным количеством аргументов?
Python - мой первый динамический язык. Недавно я закодировал вызов функции неправильно, указав неправильное количество аргументов. Это не удалось с исключением в то время, когда вызывалась функция. Я ожидал, что даже на динамическом языке такая ошибка может быть обнаружена при анализе исходного файла.
Я понимаю, что фактические аргументы типа неизвестны до тех пор, пока не будет вызвана функция, потому что одна и та же переменная может содержать значения любого типа в разное время. Но аргумент число известен, как только анализируется исходный файл. Он не будет меняться во время работы программы.
Итак, это не философский вопрос
Чтобы сохранить это в области, позвольте мне рассказать о таком вопросе. Есть ли какая-то особенность, предлагаемая Python, которая требует от нее задержки проверки количества аргументов в вызове функции до тех пор, пока код действительно не выполнит?
Ответы
Ответ 1
Python не может знать, на каком объекте вы в конечном итоге назовете, потому что будучи динамическим, вы можете поменять объект функции. В любое время. И каждый из этих объектов может иметь различное количество аргументов.
Вот краткий пример:
import random
def foo(): pass
def bar(arg1): pass
def baz(arg1, arg2): pass
the_function = random.choice([foo, bar, baz])
print(the_function())
В приведенном выше коде есть вероятность появления исключения из 2 в 3. Но Python не может знать априори, если это будет так или нет!
И я даже не начал с импорта динамического модуля, генерации динамических функций, других вызываемых объектов (любой объект с помощью метода __call__
можно вызвать) или аргументов catch-all (*args
и **kwargs
).
Но чтобы сделать это более ясным, вы заявляете в своем вопросе:
Он не будет изменяться во время работы программы.
Это не так, а не на Python, после загрузки модуля вы можете удалить, добавить или заменить любой объект в пространстве имен модулей, включая объекты функции.
Ответ 2
Известно количество передаваемых аргументов, но не функция, которая фактически вызывается. См. Этот пример:
def foo():
print("I take no arguments.")
def bar():
print("I call foo")
foo()
Это может показаться очевидным, но давайте поместим их в файл под названием "fubar.py". Теперь, в интерактивном сеансе Python, сделайте следующее:
>>> import fubar
>>> fubar.foo()
I take no arguments.
>>> fubar.bar()
I call foo
I take no arguments.
Это было очевидно. Теперь для забавной части. Хорошо определите функцию, которая требует ненулевого количества аргументов:
>>> def notfoo(a):
... print("I take arguments!")
...
Теперь мы делаем что-то, что называется патчем обезьян. Фактически мы можем заменить функцию foo
в модуле fubar
:
>>> fubar.foo = notfoo
Теперь, когда мы назовем bar
, a TypeError
будет поднят; имя foo
теперь относится к функции, которую мы определили выше, вместо исходной функции, ранее известной как foo
.
>>> fubar.bar()
I call foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/horazont/tmp/fubar.py", line 6, in bar
foo()
TypeError: notfoo() missing 1 required positional argument: 'a'
Таким образом, даже в такой ситуации, когда может показаться очень очевидным, что вызываемая функция foo
не принимает аргументов, Python может только знать, что она фактически является функцией foo
, которая вызывается, когда она выполняет этот источник линия.
Это свойство Python, которое делает его мощным, но также вызывает некоторую медлительность. Фактически, создание модулей для чтения только для повышения производительности обсуждалось в списке рассылки python-идей некоторое время назад, но он не получил реальной поддержки.