Tại sao Python cho phép gọi hàm với số đối số sai?


80

Python là ngôn ngữ động đầu tiên của tôi. Gần đây tôi đã viết mã lệnh gọi hàm không chính xác khi cung cấp sai số đối số. Điều này không thành công với một ngoại lệ tại thời điểm hàm đó được gọi. Tôi mong đợi rằng ngay cả trong một ngôn ngữ động, loại lỗi này có thể được phát hiện khi tệp nguồn được phân tích cú pháp.

Tôi hiểu rằng loại đối số thực tế không được biết cho đến khi hàm được gọi, vì cùng một biến có thể chứa các giá trị thuộc bất kỳ loại nào tại các thời điểm khác nhau. Nhưng số lượng đối số được biết ngay sau khi tệp nguồn được phân tích cú pháp. Nó sẽ không thay đổi trong khi chương trình đang chạy.

Vì vậy, đây không phải là một câu hỏi triết học

Để giữ điều này trong phạm vi của Stack Overflow, hãy để tôi diễn đạt câu hỏi như thế này. Có một số tính năng mà Python cung cấp, yêu cầu nó trì hoãn việc kiểm tra số lượng đối số trong một lệnh gọi hàm cho đến khi mã thực sự thực thi?


17
Điều gì sẽ xảy ra nếu tên đề cập đến một chức năng khác hoàn toàn trong thời gian chạy?
jonrsharpe

cũng như đối số phân cấp, số lượng đối số để gọi f(*args)sẽ không được biết trong giai đoạn phân tích cú pháp.
Łukasz Rogalski

3
Cần lưu ý rằng, trong lý thuyết kiểu, kiểu giá trị bao gồm số lượng đối số, trong trường hợp là hàm.
PyRulez

9
Làm thế nào Python biết được bạn sẽ gọi hàm nào cho đến khi bạn thực sự cố gắng gọi nó? Nó không giống như các hàm trong Python có tên. (Đây không phải là mỉa mai)
user253751

1
@immibis: Chà, không. Họ có tên. Tuy nhiên, họ không đặc biệt gắn liền với những cái tên đó. Một hàm được định nghĩa là def foo(): whateverhas foo.__name__ == 'foo', nhưng điều đó sẽ không ngăn bạn thực hiện foo = some_other_functionhoặc bar = foo.
user2357112 hỗ trợ Monica

Câu trả lời:


146

Python không thể biết trước đối tượng nào bạn sẽ gọi, bởi vì là đối tượng động, bạn có thể hoán đổi đối tượng hàm . Bất cứ lúc nào. Và mỗi đối tượng này có thể có một số đối số khác nhau.

Đây là một ví dụ điển hình:

import random

def foo(): pass
def bar(arg1): pass
def baz(arg1, arg2): pass

the_function = random.choice([foo, bar, baz])
print(the_function())

Đoạn mã trên có 2 trong 3 cơ hội tạo ra một ngoại lệ. Nhưng Python không thể biết tiên nghiệm nếu điều đó xảy ra hay không!

Và tôi thậm chí còn chưa bắt đầu với việc nhập mô-đun động, tạo hàm động, các đối tượng có thể gọi khác (bất kỳ đối tượng nào có __call__phương thức đều có thể được gọi) hoặc đối số bắt tất cả ( *args**kwargs).

Nhưng để làm rõ hơn điều này, bạn nêu trong câu hỏi của mình:

Nó sẽ không thay đổi trong khi chương trình đang chạy.

Đây không phải là trường hợp, không phải trong Python, khi mô-đun được tải, bạn có thể xóa, thêm hoặc thay thế bất kỳ đối tượng nào trong không gian tên mô-đun, bao gồm cả các đối tượng hàm.


Tôi đã nhìn thấy bảng chức năng trước, vì vậy nó có thể không được khá nên ác. Ồ, khoan đã, các hàm trong bảng không sử dụng *đối số .... và đây là Python nên .... vâng, khá là cực đoan.
Ray Toal

3
Bây giờ nó là một chiếc áo phông. (Nếu không thể tìm ra cách để thêm ghi Nếu bạn muốn nó, cho tôi biết, và tôi có lẽ có thể tìm nó ra..)
PyRulez

@PyRulez: Áo phông của tôi có bản sao hình Unicorn của bài đăng phân tích cú pháp regex sử dụng một URL. Bạn chỉ có thể thêm https://stackoverflow.com/a/34567789và hoàn thành nó.
Martijn Pieters

4
@PyRulez: nhưng đây chỉ là danh sách công văn, thực sự. Các điểm là để minh họa mà bạn có thể sử dụng chức năng như các đối tượng, và bạn không thể biết lên phía trước cái nào được gọi. Nếu không, nó là một kỹ thuật hợp lý phổ biến .
Martijn Pieters

Có thể để giúp người dùng giải quyết vấn đề ban đầu: phát hiện lỗi trước khi mã chạy, việc sử dụng công cụ xác minh tĩnh có thể được đề cập.
Xavier Combelle

35

Số lượng đối số được truyền đã được biết, nhưng không phải là hàm thực sự được gọi. Xem ví dụ này:

def foo():
    print("I take no arguments.")

def bar():
    print("I call foo")
    foo()

Điều này có vẻ hiển nhiên, nhưng chúng ta hãy đặt chúng vào một tệp có tên "fubar.py". Bây giờ, trong một phiên Python tương tác, hãy thực hiện điều này:

>>> import fubar
>>> fubar.foo()
I take no arguments.
>>> fubar.bar()
I call foo
I take no arguments.

Đó là điều hiển nhiên. Bây giờ cho phần thú vị. Chúng tôi sẽ xác định một hàm yêu cầu số lượng đối số khác 0:

>>> def notfoo(a):
...    print("I take arguments!")
...

Bây giờ chúng tôi làm một cái gì đó được gọi là vá khỉ . Trên thực tế, chúng tôi có thể thay thế hàm footrong fubarmô-đun:

>>> fubar.foo = notfoo

Bây giờ, khi chúng ta gọi bar, một TypeErrorý chí sẽ được nâng lên; tên foobây giờ đề cập đến chức năng chúng tôi đã xác định ở trên thay vì chức năng ban đầu trước đây-được biết đến như- 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'

Vì vậy, ngay cả trong tình huống như thế này, nơi có vẻ rất rõ ràng rằng hàm được gọi fookhông có đối số, Python chỉ có thể biết rằng nó thực sự là foohàm đang được gọi khi nó thực thi dòng nguồn đó.

Đây là một thuộc tính của Python làm cho nó mạnh mẽ, nhưng nó cũng gây ra một số sự chậm chạp của nó. Trên thực tế, việc tạo các mô-đun ở chế độ chỉ đọc để cải thiện hiệu suất đã được thảo luận trên danh sách gửi thư ý tưởng python một thời gian trước, nhưng nó không nhận được bất kỳ hỗ trợ thực sự nào.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.