Việc gán giá trị mặc định cho đối số từ khóa là rất phổ biến khi xác định hàm Python. Vì nó có thể tránh được các lỗi tiềm ẩn, nên phương pháp này được khuyến khích thực hiện nếu chúng ta không thể chắc chắn liệu đối số từ khóa có luôn nhận được giá trị hay không
Gần đây tôi đã tìm thấy một lỗi khó chịu trong một số mã Python do sử dụng sai đối số mặc định. Nếu bạn đã biết mọi thứ về các đối số mặc định và chỉ muốn cười vào lỗi hài hước này, vui lòng chuyển đến cuối bài viết. Than ôi, đoạn mã do tôi viết, nhưng tôi khá chắc chắn rằng ngày hôm đó tôi đã bị thay thế bởi một bản sao độc ác của chính mình. Bạn biết đấy, đôi khi nó xảy ra. =]
Bài đăng này chỉ tóm tắt những điều cơ bản về các đối số tiêu chuẩn và mặc định trong các hàm Python và cảnh báo những cạm bẫy có thể xảy ra trong mã của bạn. Nếu bạn mới tiếp cận Python và bắt đầu viết các hàm, tôi thực sự khuyên bạn nên kiểm tra hướng dẫn sử dụng Python chính thức nói gì về chúng tại các liên kết sau. và
Đánh giá nhanh các chức năng
Python là một ngôn ngữ hướng đối tượng mạnh mẽ, đẩy mô hình lập trình này lên mức tối đa. Nhưng lập trình hướng đối tượng vẫn dựa trên khái niệm hàm, đó là thứ bạn có thể sử dụng để xử lý dữ liệu. Python có một khái niệm tổng quát hơn về callable object, đó là mọi đối tượng có thể gọi được, nghĩa là áp dụng trên một số dữ liệu
Các hàm trong Python là các đối tượng có thể gọi được và thoạt nhìn chúng hoạt động giống như các hàm trong các ngôn ngữ khác. Họ chỉ lấy một số dữ liệu, được gọi là đối số, xử lý chúng và trả về kết quả [là
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
1 nếu không có câu lệnh def log[message=None]:
if message:
print["LOG: {0}".format[message]]
2 nào được đưa ra]Các đối số được khai báo [khi hàm được xác định] dưới dạng trình giữ chỗ cho các đối tượng thực sẽ được truyền khi hàm được gọi. Trong Python, bạn không khai báo loại đối số [chẳng hạn như bạn làm trong C hoặc Java] vì triết lý Python dựa trên tính đa hình
Hãy nhớ rằng các biến Python là các tham chiếu, đó là địa chỉ bộ nhớ của các biến thực tế. Điều này có nghĩa là các hàm Python luôn hoạt động giống như được "chuyển theo địa chỉ" [sử dụng danh pháp C/C++]. Khi bạn gọi một hàm, bạn không sao chép giá trị của các đối số vào phần giữ chỗ của hàm. Thay vào đó, bạn đang chỉ trình giữ chỗ cho chính biến đó. Điều này có một hệ quả rất quan trọng. bạn có thể thay đổi giá trị của biến từ bên trong hàm
Các tài liệu tham khảo đóng một vai trò rất lớn trong Python, là xương sống của cách tiếp cận đa hình đầy đủ của nó. Kiểm tra liên kết này để được giải thích rõ hơn về chủ đề rất quan trọng này
Để kiểm tra sự hiểu biết của bạn về hành vi cơ bản này của ngôn ngữ, hãy thử làm theo mã đơn giản này [trong đó biến ph là viết tắt của "placeholder"]
>>> def print_id[ph]:
.. print[hex[id[ph]]]
..
>>> a = 5
>>> print[hex[id[a]]]
0x84ab460
>>> print_id[a]
0x84ab460
>>>
>>> def alter_value[ph]:
.. ph = ph + 1
.. return ph
..
>>> b = alter_value[a]
>>> b
6
>>> a
5
>>> hex[id[a]]
'0x84ab460'
>>> hex[id[b]]
'0x84ab470'
>>>
>>> def alter_value[ph]:
.. ph.append[1]
.. return ph
..
>>> a = [1,2,3]
>>> b = alter_value[a]
>>> a
[1, 2, 3, 1]
>>> b
[1, 2, 3, 1]
>>> hex[id[a]]
'0xb701f72c'
>>> hex[id[b]]
'0xb701f72c'
>>>
Nếu bạn không ngạc nhiên với những gì đang xảy ra ở đây thì bạn đã nắm được một trong những điều quan trọng nhất trong Python và có thể yên tâm bỏ qua phần giải thích sau
Hàm
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
3 chứng minh rằng các trình giữ chỗ bên trong các hàm là cùng một biến được truyền vào thời gian chạy [địa chỉ bộ nhớ của chúng khớp với nhau]Hai phiên bản của
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
4 có nghĩa là các hàm cố gắng thay đổi giá trị của đối số đã truyền. Như bạn có thể thấy phiên bản đầu tiên của def log[message=None]:
if message:
print["LOG: {0}".format[message]]
4 không thành công trong việc thay đổi giá trị của biến def log[message=None]:
if message:
print["LOG: {0}".format[message]]
6, trong khi phiên bản thứ hai thì không. Tại sao? . Mặt khác, danh sách không phải là bất biến, do đó, chức năng thực hiện những gì tên của nó cam kết. [ở đây bạn tìm thêm chi tiết về các loại bất biến]Còn rất nhiều điều để nói về các hàm trong Python, nhưng đây là những viên gạch cơ bản về các đối số tiêu chuẩn
Giá trị đối số mặc định
Thỉnh thoảng, bạn cần xác định một hàm có thể chấp nhận một đối số và sẽ hoạt động khác đi cho dù có hay không có đối số. Nếu một ngôn ngữ không cung cấp hỗ trợ cho những trường hợp như vậy, bạn chỉ có hai lựa chọn. cách thứ nhất là xác định hai hàm khác nhau và quyết định hàm nào phù hợp nhất để gọi mỗi lần, cách thứ hai là buộc sự hiện diện của đối số, chọn giá trị "null" báo hiệu rằng không được sử dụng đối số . Cả hai giải pháp đều khả thi nhưng chưa tối ưu
Python, giống như các ngôn ngữ khác, cung cấp hỗ trợ cho các giá trị đối số mặc định, đó là các đối số hàm có thể được người gọi chỉ định hoặc để trống để tự động nhận giá trị được xác định trước
Một ví dụ rất đơn giản [và khá vô dụng] về giá trị mặc định như sau
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
Chức năng này có thể được chạy với một đối số [có thể là
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
1]def log[message=None]:
if message:
print["LOG: {0}".format[message]]
1nhưng cũng có thể được gọi mà không có đối số, trong trường hợp đó, nó sẽ nhận giá trị mặc định được đặt trong nguyên mẫu của nó [
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
1, trong trường hợp này]def log[message=None]:
if message:
print["LOG: {0}".format[message]]
3Bạn có thể tìm thấy nhiều ví dụ thú vị hơn trong thư viện chuẩn, chẳng hạn như trong hàm
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
11 [kiểm tra phần ]def log[message=None]:
if message:
print["LOG: {0}".format[message]]
5Rõ ràng từ nguyên mẫu rằng một lệnh gọi như
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
12 che giấu rất nhiều đối số bằng cách chuyển cho chúng các giá trị mặc định [def log[message=None]:
if message:
print["LOG: {0}".format[message]]
13, def log[message=None]:
if message:
print["LOG: {0}".format[message]]
14, def log[message=None]:
if message:
print["LOG: {0}".format[message]]
15, v.v.] và làm cho hàm trở nên rất đơn giản để sử dụng trong trường hợp sử dụng tiêu chuẩn của nóNhư bạn thấy từ hàm dựng sẵn
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
11, chúng ta có thể sử dụng cả đối số tiêu chuẩn và mặc định trong một hàm, nhưng thứ tự xuất hiện của chúng trong nguyên mẫu hàm là cố định. tất cả các đối số tiêu chuẩn trước, sau đó là các đối số mặc định>>> def print_id[ph]:
.. print[hex[id[ph]]]
..
>>> a = 5
>>> print[hex[id[a]]]
0x84ab460
>>> print_id[a]
0x84ab460
>>>
>>> def alter_value[ph]:
.. ph = ph + 1
.. return ph
..
>>> b = alter_value[a]
>>> b
6
>>> a
5
>>> hex[id[a]]
'0x84ab460'
>>> hex[id[b]]
'0x84ab470'
>>>
>>> def alter_value[ph]:
.. ph.append[1]
.. return ph
..
>>> a = [1,2,3]
>>> b = alter_value[a]
>>> a
[1, 2, 3, 1]
>>> b
[1, 2, 3, 1]
>>> hex[id[a]]
'0xb701f72c'
>>> hex[id[b]]
'0xb701f72c'
>>>
1Lý do rất dễ tìm ra. nếu chúng ta đặt một đối số có giá trị mặc định trước một giá trị tiêu chuẩn, ngôn ngữ sẽ không có cách nào để hiểu liệu đối số có giá trị mặc định đã được khởi tạo hay chưa. Ví dụ, xét định nghĩa hàm sau
>>> def print_id[ph]:
.. print[hex[id[ph]]]
..
>>> a = 5
>>> print[hex[id[a]]]
0x84ab460
>>> print_id[a]
0x84ab460
>>>
>>> def alter_value[ph]:
.. ph = ph + 1
.. return ph
..
>>> b = alter_value[a]
>>> b
6
>>> a
5
>>> hex[id[a]]
'0x84ab460'
>>> hex[id[b]]
'0x84ab470'
>>>
>>> def alter_value[ph]:
.. ph.append[1]
.. return ph
..
>>> a = [1,2,3]
>>> b = alter_value[a]
>>> a
[1, 2, 3, 1]
>>> b
[1, 2, 3, 1]
>>> hex[id[a]]
'0xb701f72c'
>>> hex[id[b]]
'0xb701f72c'
>>>
2khi gọi hàm
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
17 chúng ta sẽ chuyển đối số nào? >>> def print_id[ph]:
.. print[hex[id[ph]]]
..
>>> a = 5
>>> print[hex[id[a]]]
0x84ab460
>>> print_id[a]
0x84ab460
>>>
>>> def alter_value[ph]:
.. ph = ph + 1
.. return ph
..
>>> b = alter_value[a]
>>> b
6
>>> a
5
>>> hex[id[a]]
'0x84ab460'
>>> hex[id[b]]
'0x84ab470'
>>>
>>> def alter_value[ph]:
.. ph.append[1]
.. return ph
..
>>> a = [1,2,3]
>>> b = alter_value[a]
>>> a
[1, 2, 3, 1]
>>> b
[1, 2, 3, 1]
>>> hex[id[a]]
'0xb701f72c'
>>> hex[id[b]]
'0xb701f72c'
>>>
8Đánh giá đối số mặc định
Các đối số mặc định có thể được cung cấp dưới dạng các giá trị đơn giản hoặc là kết quả của một lệnh gọi hàm, nhưng kỹ thuật sau cần một cảnh báo rất lớn
Mặc dù các giá trị đơn giản được mã hóa cứng, do đó không cần đánh giá ngoại trừ được thực hiện tại thời điểm biên dịch, các lệnh gọi hàm dự kiến sẽ được thực thi trong thời gian chạy [kiểm tra nhận xét này trên Reddit để được giải thích rõ hơn về vấn đề này]. Vì vậy, chúng tôi có thể viết
>>> def print_id[ph]:
.. print[hex[id[ph]]]
..
>>> a = 5
>>> print[hex[id[a]]]
0x84ab460
>>> print_id[a]
0x84ab460
>>>
>>> def alter_value[ph]:
.. ph = ph + 1
.. return ph
..
>>> b = alter_value[a]
>>> b
6
>>> a
5
>>> hex[id[a]]
'0x84ab460'
>>> hex[id[b]]
'0x84ab470'
>>>
>>> def alter_value[ph]:
.. ph.append[1]
.. return ph
..
>>> a = [1,2,3]
>>> b = alter_value[a]
>>> a
[1, 2, 3, 1]
>>> b
[1, 2, 3, 1]
>>> hex[id[a]]
'0xb701f72c'
>>> hex[id[b]]
'0xb701f72c'
>>>
9mong đợi chức năng
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
32 cung cấp chính xác thời gian hiện tại mỗi khi chúng tôi gọi nó. Thật không may, điều này không hoạt động. các đối số mặc định được ước tính tại thời điểm xác định [ví dụ: khi bạn nhập mô-đun lần đầu tiên] và kết quả của các lệnh gọi liên tiếp làdef log[message=None]:
if message:
print["LOG: {0}".format[message]]
1Nếu bạn đặt giá trị mặc định cho một thể hiện của lớp, kết quả có thể còn xa lạ hơn, như bạn có thể đọc trong Hướng dẫn về Python của Người đi nhờ xe. Theo đề xuất của tài nguyên sau, giải pháp thông thường là thay thế giá trị mặc định bằng
def log[message=None]:
if message:
print["LOG: {0}".format[message]]
1 và kiểm tra giá trị của đối số bên trong hàmdef log[message=None]:
if message:
print["LOG: {0}".format[message]]
0kết luận
Các đối số mặc định có thể đơn giản hóa đáng kể các API, miễn là bạn chú ý đến "điểm lỗi" duy nhất của chúng, thời gian đánh giá. Đáng ngạc nhiên, một trong những điều cơ bản nhất trong Python, đối số hàm và tham chiếu, là một trong những nguồn gây ra lỗi lớn nhất, đôi khi đối với các lập trình viên có kinh nghiệm. Tôi khuyên bạn nên dành thời gian nghiên cứu tài liệu tham khảo và tính đa hình
cập nhật
2015-06-10. brandjon đã thêm một số thông tin hữu ích tại đây, giải thích cách CPython xử lý các hàm và giá trị đơn giản. Tôi đã thêm liên kết vào phần nhận xét