Việc chấp nhận các đối số vị trí tùy chọn [thường được gọi là star args trong tham chiếu đến tên thông thường của tham số, *args] có thể làm cho lệnh gọi hàm rõ ràng hơn và loại bỏ nhiễu thị giác
Ví dụ: giả sử bạn muốn ghi lại một số thông tin gỡ lỗi. Với số lượng đối số cố định, bạn sẽ cần một hàm nhận thông báo và danh sách giá trị
def
log[message, values]:if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
, [1
,2
]] log['Hi there'
, []] >>> My numbers are: 1, 2 Hi there
Phải vượt qua một danh sách trống khi bạn không có giá trị để ghi lại là cồng kềnh và ồn ào. Sẽ tốt hơn nếu loại bỏ hoàn toàn đối số thứ hai. Bạn có thể làm điều này trong Python bằng cách thêm tiền tố vào tên tham số vị trí cuối cùng bằng *. Tham số đầu tiên cho thông điệp tường trình là bắt buộc, trong khi bất kỳ số lượng đối số vị trí nào tiếp theo là tùy chọn. Thân hàm không cần thay đổi, chỉ những người gọi mới làm
def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Nếu bạn đã có một danh sách và muốn gọi một hàm đối số biến như nhật ký, bạn có thể thực hiện việc này bằng cách sử dụng toán tử *. Điều này hướng dẫn Python chuyển các mục từ chuỗi dưới dạng đối số vị trí
favorites=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Có hai vấn đề với việc chấp nhận một số lượng đối số vị trí khác nhau
Vấn đề đầu tiên là các đối số của biến luôn được chuyển thành một bộ trước khi chúng được chuyển đến hàm của bạn. Điều này có nghĩa là nếu người gọi hàm của bạn sử dụng toán tử * trên trình tạo, nó sẽ được lặp lại cho đến khi hết. Bộ kết quả sẽ bao gồm mọi giá trị từ trình tạo, điều này có thể tiêu tốn nhiều bộ nhớ và khiến chương trình của bạn gặp sự cố
def
my_generator[]:for
iin
range
[10
]:yield i
def
my_func[*
args]:=
my_generator[] my_func[*
it] >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Các hàm chấp nhận *args là tốt nhất cho các tình huống mà bạn biết số lượng đầu vào trong danh sách đối số sẽ khá nhỏ. Thật lý tưởng cho các lệnh gọi hàm chuyển nhiều tên biến hoặc tên biến lại với nhau. Nó chủ yếu là để thuận tiện cho lập trình viên và khả năng đọc mã
Vấn đề thứ hai với *args là bạn không thể thêm đối số vị trí mới vào chức năng của mình trong tương lai mà không di chuyển mọi người gọi. Nếu bạn cố gắng thêm một đối số vị trí ở phía trước danh sách đối số, những người gọi hiện có sẽ bị ngắt một cách tinh vi nếu chúng không được cập nhật
def
log[sequence, message,*
values]:if not
values:'%s: %s'
%
[sequence, message]]else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s: %s'
%
[sequence, message, values_str]] log[1
,'Favorites'
,7
,33
]# New usage is OK
log['Favorite numbers'
,7
,33
]# Old usage breaks
>>> 1: Favorites: 7, 33 Favorite numbers: 7: 33
Vấn đề ở đây là lệnh gọi log thứ hai đã sử dụng 7 làm tham số thông báo vì đối số trình tự không được cung cấp. Những lỗi như thế này rất khó theo dõi vì mã vẫn chạy mà không đưa ra bất kỳ ngoại lệ nào. Để tránh hoàn toàn khả năng này, bạn nên sử dụng các đối số chỉ có từ khóa khi bạn muốn mở rộng các hàm chấp nhận *args [xem Mục 21. “Thực thi sự rõ ràng với các đối số chỉ có từ khóa”]
Những điều cần ghi nhớ
- Các hàm có thể chấp nhận một số lượng đối số vị trí khác nhau bằng cách sử dụng *args trong câu lệnh def
- Bạn có thể sử dụng các mục từ một chuỗi làm đối số vị trí cho một hàm với toán tử *
- Sử dụng toán tử * với trình tạo có thể khiến chương trình của bạn hết bộ nhớ và gặp sự cố
- Việc thêm các tham số vị trí mới vào các hàm chấp nhận *args có thể gây ra các lỗi khó tìm
mục 19. Cung cấp hành vi tùy chọn với các đối số từ khóa
Giống như hầu hết các ngôn ngữ lập trình khác, việc gọi hàm trong Python cho phép truyền đối số theo vị trí
def
remainder[number, divisor]:return
number%
divisorassert
remainder[20
,7
]==
6
Tất cả các đối số vị trí cho các hàm Python cũng có thể được chuyển theo từ khóa, trong đó tên của đối số được sử dụng trong một phép gán trong dấu ngoặc đơn của lệnh gọi hàm. Các đối số từ khóa có thể được chuyển theo bất kỳ thứ tự nào miễn là tất cả các đối số vị trí bắt buộc được chỉ định. Bạn có thể trộn và kết hợp các đối số từ khóa và vị trí. Các cuộc gọi này là tương đương
remainder[20
,7
] remainder[20
, divisor=
7
] remainder[number=
20
, divisor=
7
] remainder[divisor=
7
, number=
20
]
Đối số vị trí phải được chỉ định trước đối số từ khóa
remainder[number=
20
,7
] >>> SyntaxError: non-keyword arg after keyword arg
Mỗi đối số chỉ có thể được chỉ định một lần
________số 8Tính linh hoạt của các đối số từ khóa mang lại ba lợi ích đáng kể
Ưu điểm đầu tiên là các đối số từ khóa làm cho lệnh gọi hàm rõ ràng hơn đối với những người mới đọc mã. Với phần còn lại của cuộc gọi [20, 7], không rõ đối số nào là số và số nào là số chia nếu không xem xét việc triển khai phương thức số dư. Trong cuộc gọi với các đối số từ khóa, số=20 và số chia=7 làm cho nó rõ ràng ngay lập tức tham số nào đang được sử dụng cho từng mục đích
Tác động thứ hai của các đối số từ khóa là chúng có thể có các giá trị mặc định được chỉ định trong định nghĩa hàm. Điều này cho phép một chức năng cung cấp các khả năng bổ sung khi bạn cần nhưng cho phép bạn chấp nhận hành vi mặc định trong hầu hết thời gian. Điều này có thể loại bỏ mã lặp đi lặp lại và giảm tiếng ồn
Ví dụ: giả sử bạn muốn tính tốc độ chất lỏng chảy vào thùng. Nếu thùng cũng nằm trên cân, thì bạn có thể sử dụng chênh lệch giữa hai lần đo trọng lượng ở hai thời điểm khác nhau để xác định tốc độ dòng chảy
def
flow_rate[weight_diff, time_diff]:return
weight_diff/
time_diff weight_diff=
0.5
time_diff=
3
flow=
flow_rate[weight_diff, time_diff]'%.3f kg per second'
%
flow] >>> 0.167 kg per second
Trong trường hợp điển hình, sẽ rất hữu ích khi biết tốc độ dòng chảy tính bằng kilôgam trên giây. Vào những lúc khác, sẽ rất hữu ích khi sử dụng các phép đo cảm biến cuối cùng để tính gần đúng các thang thời gian lớn hơn, chẳng hạn như giờ hoặc ngày. Bạn có thể cung cấp hành vi này trong cùng một chức năng bằng cách thêm một đối số cho hệ số tỷ lệ khoảng thời gian
0def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Vấn đề là bây giờ bạn cần chỉ định đối số khoảng thời gian mỗi khi bạn gọi hàm, ngay cả trong trường hợp phổ biến là tốc độ dòng chảy trên giây [trong đó khoảng thời gian là 1]
1def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Để làm cho điều này bớt ồn ào hơn, tôi có thể đặt giá trị mặc định cho đối số khoảng thời gian
2def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Đối số thời gian bây giờ là tùy chọn
3def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Điều này hoạt động tốt đối với các giá trị mặc định đơn giản [nó trở nên phức tạp đối với các giá trị mặc định phức tạp—xem Mục 20. “Sử dụng Không có và Tài liệu để Chỉ định Đối số Mặc định Động”]
Lý do thứ ba để sử dụng các đối số từ khóa là chúng cung cấp một cách mạnh mẽ để mở rộng các tham số của hàm trong khi vẫn tương thích ngược với các trình gọi hiện có. Điều này cho phép bạn cung cấp chức năng bổ sung mà không phải di chuyển nhiều mã, giảm khả năng phát sinh lỗi
Ví dụ: giả sử bạn muốn mở rộng hàm flow_rate ở trên để tính tốc độ dòng chảy theo đơn vị trọng lượng ngoài kilôgam. Bạn có thể làm điều này bằng cách thêm một tham số tùy chọn mới cung cấp tỷ lệ chuyển đổi cho các đơn vị đo lường ưa thích của bạn
4def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Giá trị đối số mặc định cho units_per_kg là 1, làm cho đơn vị trọng lượng được trả về vẫn là kilôgam. Điều này có nghĩa là tất cả những người gọi hiện tại sẽ không thấy thay đổi về hành vi. Người gọi mới đến flow_rate có thể chỉ định đối số từ khóa mới để xem hành vi mới
5def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Vấn đề duy nhất với phương pháp này là các đối số từ khóa tùy chọn như thời gian và units_per_kg vẫn có thể được chỉ định làm đối số vị trí
6def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Việc cung cấp các đối số tùy chọn theo vị trí có thể gây nhầm lẫn vì không rõ giá trị 3600 và 2 là gì. 2 tương ứng với. Cách tốt nhất là luôn chỉ định các đối số tùy chọn bằng cách sử dụng tên từ khóa và không bao giờ chuyển chúng dưới dạng đối số vị trí
Những điều cần ghi nhớ
- Đối số hàm có thể được chỉ định theo vị trí hoặc theo từ khóa
- Các từ khóa làm rõ mục đích của từng đối số khi nó sẽ gây nhầm lẫn chỉ với các đối số vị trí
- Các đối số từ khóa với các giá trị mặc định giúp dễ dàng thêm các hành vi mới vào một hàm, đặc biệt là khi hàm đó có các trình gọi hiện có
- Các đối số từ khóa tùy chọn phải luôn được chuyển theo từ khóa thay vì theo vị trí
mục 20. Sử dụng Không có và Tài liệu để Chỉ định Đối số Mặc định Động
Đôi khi bạn cần sử dụng loại không tĩnh làm giá trị mặc định của đối số từ khóa. Ví dụ: giả sử bạn muốn in các thông báo ghi nhật ký được đánh dấu bằng thời gian của sự kiện đã ghi. Trong trường hợp mặc định, bạn muốn thông báo bao gồm thời gian khi chức năng được gọi. Bạn có thể thử cách tiếp cận sau, giả sử rằng các đối số mặc định được đánh giá lại mỗi khi hàm được gọi
7def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Dấu thời gian giống nhau vì datetime. now chỉ được thực hiện một lần duy nhất. khi chức năng được xác định. Các giá trị đối số mặc định chỉ được đánh giá một lần cho mỗi lần tải mô-đun, điều này thường xảy ra khi chương trình khởi động. Sau khi mô-đun chứa mã này được tải, ngày giờ. bây giờ đối số mặc định sẽ không bao giờ được đánh giá lại
Quy ước để đạt được kết quả mong muốn trong Python là cung cấp giá trị mặc định là Không và ghi lại hành vi thực tế trong chuỗi tài liệu [xem Mục 49. “Viết tài liệu cho mọi chức năng, lớp và mô-đun”]. Khi mã của bạn nhìn thấy giá trị đối số là Không, bạn sẽ phân bổ giá trị mặc định tương ứng
8def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Bây giờ dấu thời gian sẽ khác
9def
log[message,*
values]:# The only difference
if not
values:else
: values_str=
', '
.join[str
[x]for
xin
values]'%s: %s'
%
[message, values_str]] log['My numbers are'
,1
,2
] log['Hi there'
]# Much better
>>> My numbers are: 1, 2 Hi there
Sử dụng Không cho các giá trị đối số mặc định đặc biệt quan trọng khi các đối số có thể thay đổi. Ví dụ: giả sử bạn muốn tải một giá trị được mã hóa dưới dạng dữ liệu JSON. Nếu giải mã dữ liệu không thành công, bạn muốn một từ điển trống được trả về theo mặc định. Bạn có thể thử phương pháp này
favorites0=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Vấn đề ở đây giống như datetime. bây giờ ví dụ trên. Từ điển được chỉ định cho mặc định sẽ được chia sẻ bởi tất cả các cuộc gọi để giải mã vì các giá trị đối số mặc định chỉ được đánh giá một lần [tại thời điểm tải mô-đun]. Điều này có thể gây ra hành vi cực kỳ đáng ngạc nhiên
favorites1=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Bạn sẽ mong đợi hai từ điển khác nhau, mỗi từ điển có một khóa và giá trị. Nhưng sửa đổi cái này dường như cũng sửa đổi cái kia. Thủ phạm là cả foo và bar đều bằng tham số mặc định. Chúng là cùng một đối tượng từ điển
favorites2=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Cách khắc phục là đặt giá trị mặc định của đối số từ khóa thành Không và sau đó ghi lại hành vi trong chuỗi tài liệu của hàm
favorites3=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Bây giờ, chạy cùng mã kiểm tra như trước sẽ tạo ra kết quả như mong đợi
favorites4=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Những điều cần ghi nhớ
- Đối số mặc định chỉ được đánh giá một lần. trong khi định nghĩa chức năng tại thời điểm tải mô-đun. Điều này có thể gây ra các hành vi kỳ lạ đối với các giá trị động [như {} hoặc []]
- Sử dụng Không làm giá trị mặc định cho các đối số từ khóa có giá trị động. Ghi lại hành vi mặc định thực tế trong chuỗi tài liệu của hàm
mục 21. Thực thi rõ ràng với các đối số chỉ từ khóa
Truyền đối số theo từ khóa là một tính năng mạnh mẽ của các hàm Python [xem Mục 19. “Cung cấp hành vi tùy chọn với các đối số từ khóa”]. Tính linh hoạt của các đối số từ khóa cho phép bạn viết mã rõ ràng cho các trường hợp sử dụng của bạn
Ví dụ: giả sử bạn muốn chia một số cho một số khác nhưng hãy cẩn thận với các trường hợp đặc biệt. Đôi khi bạn muốn bỏ qua các ngoại lệ ZeroDivisionError và trả về vô cùng thay thế. Những lần khác, bạn muốn bỏ qua các ngoại lệ OverflowError và thay vào đó trả về 0
favorites5=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Sử dụng chức năng này rất đơn giản. Cuộc gọi này sẽ bỏ qua lỗi tràn float từ phép chia và sẽ trả về 0
favorites6=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Cuộc gọi này sẽ bỏ qua lỗi chia cho 0 và sẽ trả về vô cùng
favorites7=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Vấn đề là rất dễ nhầm lẫn vị trí của hai đối số Boolean kiểm soát hành vi bỏ qua ngoại lệ. Điều này có thể dễ dàng gây ra các lỗi khó theo dõi. Một cách để cải thiện khả năng đọc của mã này là sử dụng đối số từ khóa. Theo mặc định, chức năng có thể quá thận trọng và luôn có thể tăng lại các ngoại lệ
favorites8=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Sau đó, người gọi có thể sử dụng các đối số từ khóa để chỉ định cờ bỏ qua nào họ muốn lật cho các hoạt động cụ thể, ghi đè hành vi mặc định
favorites9=
[7
,33
,99
] log['Favorite colors'
,*
favorites] >>> Favorite colors: 7, 33, 99
Vấn đề là, vì các đối số từ khóa này là hành vi tùy chọn, nên không có gì bắt buộc người gọi hàm của bạn sử dụng đối số từ khóa cho rõ ràng. Ngay cả với định nghĩa mới của safe_division_b, bạn vẫn có thể gọi nó theo cách cũ với các đối số vị trí
0def
my_generator[]:for
iin
range
[10
]:yield i
def
my_func[*
args]:=
my_generator[] my_func[*
it] >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Với các chức năng phức tạp như thế này, tốt hơn hết là yêu cầu người gọi rõ ràng về ý định của họ. Trong Python 3, bạn có thể yêu cầu sự rõ ràng bằng cách xác định các hàm của mình bằng các đối số chỉ có từ khóa. Các đối số này chỉ có thể được cung cấp theo từ khóa, không bao giờ theo vị trí
Ở đây, tôi định nghĩa lại hàm safe_division để chấp nhận đối số chỉ từ khóa. Biểu tượng * trong danh sách đối số cho biết phần cuối của đối số vị trí và phần đầu của đối số chỉ từ khóa
1def
my_generator[]:for
iin
range
[10
]:yield i
def
my_func[*
args]:=
my_generator[] my_func[*
it] >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Bây giờ, gọi hàm với các đối số vị trí cho các đối số từ khóa sẽ không hoạt động
2def
my_generator[]:for
iin
range
[10
]:yield i
def
my_func[*
args]:=
my_generator[] my_func[*
it] >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Đối số từ khóa và giá trị mặc định của chúng hoạt động như mong đợi
3def
my_generator[]:for
iin
range
[10
]:yield i
def
my_func[*
args]:=
my_generator[] my_func[*
it] >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Đối số chỉ từ khóa trong Python 2
Thật không may, Python 2 không có cú pháp rõ ràng để chỉ định các đối số chỉ từ khóa như Python 3. Nhưng bạn có thể đạt được cùng một hành vi tăng TypeErrors cho các lệnh gọi hàm không hợp lệ bằng cách sử dụng toán tử ** trong danh sách đối số. Toán tử ** tương tự như toán tử * [xem Tiết 18. “Reduce Visual Noise with Variable Positional Arguments”], ngoại trừ việc thay vì chấp nhận số lượng đối số vị trí thay đổi, nó chấp nhận bất kỳ số lượng đối số từ khóa nào, ngay cả khi chúng không được xác định
4def
my_generator[]:for
iin
range
[10
]:yield i
def
my_func[*
args]:=
my_generator[] my_func[*
it] >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Để safe_division nhận các đối số chỉ có từ khóa trong Python 2, bạn có hàm accept **kwargs. Sau đó, bạn bật các đối số từ khóa mà bạn mong đợi từ từ điển kwargs, sử dụng đối số thứ hai của phương thức pop để chỉ định giá trị mặc định khi thiếu khóa. Cuối cùng, bạn đảm bảo rằng không còn đối số từ khóa nào trong kwargs để ngăn người gọi cung cấp đối số không hợp lệ