Чому Python дозволяє виклики функцій з неправильною кількістю аргументів?


80

Python - це моя перша динамічна мова. Нещодавно я закодував виклик функції, неправильно подавши неправильну кількість аргументів. Це не вдалося, за винятком під час виклику функції. Я очікував, що навіть у динамічній мові такий тип помилки можна виявити при синтаксичному аналізі вихідного файлу.

Я розумію, що тип фактичних аргументів невідомий, доки функція не буде викликана, оскільки одна і та ж змінна може містити значення будь-якого типу в різний час. Але кількість аргументів стає відомою, як тільки проаналізовано вихідний файл. Він не буде змінюватися під час роботи програми.

Так що це не філософське питання

Щоб зберегти це в межах Stack Overflow, дозвольте сформулювати запитання так. Чи існує якась функція, яку пропонує Python, яка вимагає відкласти перевірку кількості аргументів у виклику функції, доки код насправді не виконається?


17
Що робити, якщо назва повністю стосується іншої функції під час виконання?
jonrsharpe

ну, як контраргумент, підрахунок аргументів для виклику f(*args)не буде відомий на етапі аналізу.
Лукаш Рогальський

3
Слід зазначити, що в теорії типів тип значень включає кількість аргументів, у випадку функцій.
PyRulez 02

9
Як Python знає, яку функцію ви збираєтесь викликати, доки насправді не спробуєте викликати її? Це не так, як функції в Python мають імена. (Це не сарказм)
user253751

1
@immibis: Ну ні. Вони справді мають імена. Однак вони особливо не прив'язані до цих імен. Функція, визначена як def foo(): whateverhas foo.__name__ == 'foo', але це не завадить вам робити foo = some_other_functionабо bar = foo.
user2357112 підтримує Моніку

Відповіді:


146

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__методом можна викликати), або аргументів загального доступу ( *argsі **kwargs).

Але щоб зробити це додатково зрозумілим, у своєму питанні ви заявляєте:

Він не буде змінюватися під час роботи програми.

Це не так, не в Python, після завантаження модуля ви можете видалити, додати або замінити будь-який об'єкт у просторі імен модуля, включаючи об'єкти функцій.


Я вже бачив таблиці функцій, тож це може бути не настільки злим. О, почекайте, функції в таблиці не використовують *аргументи .... а це Python, так .... так, досить екстремально.
Ray Toal

3
Зараз це футболка. (Якщо не вдалося зрозуміти, як додати атрибуцію. Якщо ви цього хочете, скажіть мені, і я, напевно, можу це зрозуміти.)
PyRulez

@PyRulez: У моїй футболці з копією публікації аналізу регулярних виразів у формі Єдинорога використовується URL-адреса. Ви можете просто додати https://stackoverflow.com/a/34567789і закінчити з цим.
Мартін Пітерс

4
@PyRulez: але це справді лише диспетчерський список. Точка повинна показати , що ви можете використовувати функції як об'єкти, і ви не можете знати до фронту , який один називається. В іншому випадку це досить поширена техніка .
Мартін Пітерс

Можливо, для допомоги користувачеві у вихідній проблемі: виявлення помилки перед запуском коду, можна згадати про використання засобу статичної перевірки.
Xavier Combelle

35

Кількість переданих аргументів відома, але не функція, яка насправді викликається. Дивіться цей приклад:

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, 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-ideas деякий час тому, але реальної підтримки це не отримало.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.