Toán tử dấu sao có nghĩa là gì trong một lệnh gọi hàm?


623

Không những gì *nhà điều hành có nghĩa là bằng Python, chẳng hạn như trong mã như zip(*x)hay f(**k)?

  1. Nó được xử lý nội bộ như thế nào trong trình thông dịch?
  2. Nó có ảnh hưởng đến hiệu suất không? Nó nhanh hay chậm?
  3. Khi nào thì hữu ích và khi nào thì không?
  4. Nó nên được sử dụng trong một khai báo hàm hoặc trong một cuộc gọi?

4
Tôi nghĩ điều này nên được diễn giải thành "cú pháp gọi hàm *". Họ không phải là nhà khai thác, mặc dù nó sẽ được gây nhầm lẫn vì có một ***điều hành mà không có gì để làm với cú pháp này.
Ian Bicking

1
@Ian Bicking: bạn hoàn toàn đúng, * và ** trong danh sách đối số là cú pháp thuần túy (mã thông báo).
P. Ortiz,

25
f(**k)sai nếu bạn hỏi tôi :)
Jean-François Fabre

1
Lưu ý: Đối với PEP 448: Các nội dung cụ thể về Mở gói Chung bổ sung (ví dụ [*a, b, *c]hoặc {**d1, **d2}), bạn sẽ muốn đọc dấu hoa thị trong bộ định nghĩa, danh sách và thiết lập, dấu hoa thị kép trong định nghĩa chính tả , dành riêng cho việc sử dụng bên ngoài các lệnh gọi hàm và định nghĩa hàm . Đối với PEP 3132 trước đó , hãy xem Phân chia nhiều gói trong Python khi bạn không biết độ dài chuỗi .
ShadowRanger

1
VTR - Đây không phải là bản sao của ** (dấu sao kép / dấu sao) và * (dấu sao / dấu sao) làm gì cho các tham số? vì câu hỏi đó chỉ là về các tham số, mặc dù câu trả lời cũng bao gồm các lệnh gọi hàm. Dấu hoa thị trong lệnh gọi hàm nên được đánh dấu là bản sao của câu hỏi này vì nó ít phổ biến hơn và câu trả lời hàng đầu kém hoàn chỉnh hơn.
wjandrea

Câu trả lời:


956

Dấu sao đơn *giải nén chuỗi / tập hợp thành các đối số vị trí, vì vậy bạn có thể làm điều này:

def sum(a, b):
    return a + b

values = (1, 2)

s = sum(*values)

Thao tác này sẽ giải nén bộ tuple để nó thực sự thực thi như sau:

s = sum(1, 2)

Dấu sao đôi **cũng làm như vậy, chỉ sử dụng từ điển và do đó, các đối số được đặt tên:

values = { 'a': 1, 'b': 2 }
s = sum(**values)

Bạn cũng có thể kết hợp:

def sum(a, b, c, d):
    return a + b + c + d

values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }
s = sum(*values1, **values2)

sẽ thực thi như:

s = sum(1, 2, c=10, d=15)

Cũng xem phần 4.7.4 - Giải nén danh sách đối số của tài liệu Python.


Ngoài ra, bạn có thể xác định các hàm nhận *x**yđối số, điều này cho phép một hàm chấp nhận bất kỳ số lượng đối số vị trí và / hoặc được đặt tên nào không được đặt tên cụ thể trong khai báo.

Thí dụ:

def sum(*values):
    s = 0
    for v in values:
        s = s + v
    return s

s = sum(1, 2, 3, 4, 5)

hoặc với **:

def get_a(**values):
    return values['a']

s = get_a(a=1, b=2)      # returns 1

điều này có thể cho phép bạn chỉ định một số lượng lớn các tham số tùy chọn mà không cần phải khai báo chúng.

Và một lần nữa, bạn có thể kết hợp:

def sum(*values, **options):
    s = 0
    for i in values:
        s = s + i
    if "neg" in options:
        if options["neg"]:
            s = -s
    return s

s = sum(1, 2, 3, 4, 5)            # returns 15
s = sum(1, 2, 3, 4, 5, neg=True)  # returns -15
s = sum(1, 2, 3, 4, 5, neg=False) # returns 15

4
tại sao bạn cần điều này, không thể hàm chỉ lặp qua danh sách được cung cấp mà không mở rộng nó?
Martin Beckett

29
Chắc chắn, nhưng sau đó bạn sẽ phải gọi nó: s = sum((1, 2, 3, 4, 5))hoặc s = sum([1, 2, 3, 4, 5]), *valuestùy chọn làm cho cuộc gọi trông giống như cần một số đối số, nhưng chúng được đóng gói thành một bộ sưu tập cho mã hàm.
Lasse V. Karlsen

11
Đây là lợi ích thực sự: bạn có thể viết các hàm không thể thực hiện được nếu bạn cần có số lượng đối số thay đổi. Ví dụ, hàm printf của C, có 1 + n đối số, rất khó viết như một bài tập cho bất kỳ lập trình viên mới bắt đầu nào. Trong Python, người mới bắt đầu có thể viết def printf (string_template, * args) và tiếp tục.
IceArdor

1
Điều gì xảy ra nếu bạn (vô tình có lẽ: p) giải nén một từ điển chỉ có một * thay vì hai? Nó dường như làm một cái gì đó, có vẻ như một tuple xuất hiện, nhưng không quá rõ ràng nó là gì. (chỉnh sửa: ok Tôi nghĩ câu trả lời là nó chỉ giải nén các phím, các giá trị bị loại bỏ)
Ben Farmer

1
Ví dụ cuối cùng ngụ ý rằng * và ** không chỉ thực hiện việc giải nén mà còn đóng gói! Xem trang xuất sắc này codingame.com/playgrounds/500/…
HCChen 17/02/19

46

Một điểm nhỏ: đây không phải là toán tử. Các toán tử được sử dụng trong các biểu thức để tạo các giá trị mới từ các giá trị hiện có (ví dụ: 1 + 2 trở thành 3. * Và ** ở đây là một phần của cú pháp khai báo và gọi hàm.


6
Lưu ý rằng tài liệu Python gọi * trong ngữ cảnh này là một toán tử; Tôi đồng ý, đó là loại gây hiểu lầm.
Christophe

Cảm ơn. Tôi đã tìm kiếm sự giải thích rõ ràng về điều này trong tài liệu tham khảo về python nhưng vẫn không thấy nó. Vì vậy, quy tắc cho các lời gọi hàm về cơ bản là "*" hoặc "**" ở đầu một biểu thức trong một lời gọi hàm gây ra loại mở rộng này?
nealmcb

20

Tôi thấy điều này đặc biệt hữu ích khi bạn muốn 'lưu trữ' một lệnh gọi hàm.

Ví dụ: giả sử tôi có một số bài kiểm tra đơn vị cho một hàm 'add':

def add(a, b): return a + b
tests = { (1,4):5, (0, 0):0, (-1, 3):3 }
for test, result in tests.items():
   print 'test: adding', test, '==', result, '---', add(*test) == result

Không có cách nào khác để gọi add, ngoài việc làm thủ công một cái gì đó như add (test [0], test [1]), điều này thật tệ. Ngoài ra, nếu có một số biến thay đổi, mã có thể trở nên khá xấu với tất cả các câu lệnh if mà bạn cần.

Một nơi khác, điều này hữu ích là để xác định các đối tượng Factory (đối tượng tạo đối tượng cho bạn). Giả sử bạn có một lớp Factory nào đó, tạo ra các đối tượng Xe và trả về chúng. Bạn có thể làm cho nó để myFactory.make_car ('red', 'bmw', '335ix') tạo Car ('red', 'bmw', '335ix'), sau đó trả về nó.

def make_car(*args):
   return Car(*args)

Điều này cũng hữu ích khi bạn muốn gọi một phương thức khởi tạo của lớp cha.


4
Tôi thích những ví dụ của bạn. Nhưng, tôi nghĩ -1 + 3 == 2.
eksortso

5
Tôi cố tình đặt một cái gì đó vào đó sẽ không thành công :)
Donald Miner

19

Nó được gọi là cú pháp cuộc gọi mở rộng. Từ tài liệu :

Nếu biểu thức cú pháp * xuất hiện trong lệnh gọi hàm, biểu thức phải đánh giá thành một chuỗi. Các phần tử từ chuỗi này được coi như thể chúng là các đối số vị trí bổ sung; nếu có các đối số vị trí x1, ..., xN và biểu thức ước lượng là một chuỗi y1, ..., yM, điều này tương đương với một lệnh gọi với M + N đối số vị trí x1, ..., xN, y1 ,. .., yM.

và:

Nếu biểu thức cú pháp ** xuất hiện trong lệnh gọi hàm, biểu thức phải đánh giá thành một ánh xạ, nội dung của nó được coi là đối số từ khóa bổ sung. Trong trường hợp từ khóa xuất hiện trong cả biểu thức và dưới dạng đối số từ khóa rõ ràng, ngoại lệ TypeError sẽ được đưa ra.


3
Chỉ cần thêm chú thích cuối trang vào câu trả lời trong sách giáo khoa - trước khi hỗ trợ cú pháp đến, chức năng tương tự đã đạt được với apply()chức năng tích hợp
Jeremy Brown

18

Trong một lệnh gọi hàm, dấu sao đơn biến một danh sách thành các đối số riêng biệt (ví dụ zip(*x)giống như zip(x1,x2,x3)if x=[x1,x2,x3]) và dấu sao kép biến một từ điển thành các đối số từ khóa riêng biệt (ví dụ f(**k)giống như f(x=my_x, y=my_y)if k = {'x':my_x, 'y':my_y}.

Trong định nghĩa hàm thì ngược lại: dấu sao đơn biến một số đối số tùy ý thành danh sách và dấu sao kép biến một số đối số từ khóa tùy ý thành từ điển. Ví dụ: def foo(*x)"foo có một số lượng đối số tùy ý và chúng sẽ có thể truy cập được thông qua danh sách x (tức là nếu người dùng gọi foo(1,2,3), xsẽ được [1,2,3])" và def bar(**k)thanh có nghĩa là "lấy một số lượng đối số từ khóa tùy ý và chúng sẽ có thể truy cập được thông qua từ điển k (tức là nếu người dùng gọi bar(x=42, y=23), ksẽ được {'x': 42, 'y': 23}) ”.


3
Một nhận xét (rất) muộn, nhưng tôi tin rằng từ def foo(*x)* x đưa ra một bộ giá trị, không phải một danh sách.
jeremycg
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.