Почему 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-идей некоторое время назад, но он не получил реальной поддержки.