Cách pythonic để tránh các tham số mặc định là danh sách trống là gì?


117

Đôi khi có vẻ tự nhiên khi có một tham số mặc định là một danh sách trống. Tuy nhiên, Python cung cấp hành vi bất ngờ trong những tình huống này .

Nếu ví dụ, tôi có một hàm:

def my_func(working_list = []):
    working_list.append("a")
    print(working_list)

Lần đầu tiên được gọi, mặc định sẽ hoạt động, nhưng các cuộc gọi sau đó sẽ cập nhật danh sách hiện có (với một "a" mỗi cuộc gọi) và in phiên bản cập nhật.

Vậy, cách quan trọng để có được hành vi mà tôi mong muốn (một danh sách mới trên mỗi cuộc gọi) là gì?


17
nếu bất kỳ ai quan tâm đến lý do tại sao điều này xảy ra, hãy kiểm tra effbot.org/zone/default-values.htm
Ryan Haining

Hành vi tương tự cũng xảy ra cho các bộ, mặc dù bạn cần một ví dụ phức tạp hơn một chút để nó hiển thị dưới dạng lỗi.
abeboparebop

Khi các liên kết chết, hãy để tôi chỉ ra rõ ràng rằng đây là hành vi mong muốn. Các biến mặc định được đánh giá ở định nghĩa hàm (xảy ra lần đầu tiên nó được gọi) và KHÔNG phải mỗi khi hàm được gọi. Do đó, nếu bạn thay đổi một đối số mặc định có thể thay đổi thì bất kỳ lệnh gọi hàm tiếp theo nào cũng chỉ có thể sử dụng đối tượng đã biến đổi.
Moritz

Câu trả lời:


150
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    working_list.append("a")
    print(working_list)

Tài liệu nói rằng bạn nên sử dụng Nonelàm mặc định và kiểm tra rõ ràng cho nó trong phần thân của hàm.


1
Tốt hơn là nói: if working_list == Không có: hoặc nếu working_list: ??
John Mulder

2
Đây là cách ưa thích để làm điều đó trong python, ngay cả khi tôi không thích nó vì nó xấu xí. Tôi muốn nói rằng phương pháp hay nhất sẽ là "nếu working_list là Không có".
e-thoả mãn

21
Cách ưa thích trong ví dụ này là nói: nếu working_list là Không có. Người gọi có thể đã sử dụng một đối tượng giống như danh sách trống với một phần phụ tùy chỉnh.
tzot 14/12/08

5
Mohit Ranka: lưu ý rằng not working_list là True nếu độ dài của nó là 0. Điều này dẫn đến hành vi không nhất quán: nếu các hàm nhận được một danh sách có một số phần tử trong đó, người gọi của nó sẽ cập nhật danh sách của anh ta và nếu danh sách trống, nó sẽ không được chạm vào.
vincent

1
@PatrickT Công cụ phù hợp tùy thuộc vào từng trường hợp - một hàm varargs rất khác với một hàm lấy đối số danh sách (tùy chọn). Các tình huống mà bạn phải lựa chọn giữa chúng xuất hiện ít hơn bạn nghĩ. Varargs là tuyệt vời khi số lượng đối số thay đổi, nhưng được sửa khi mã là WRITTEN. Giống như ví dụ của bạn. Nếu đó là biến thời gian chạy, hoặc bạn muốn gọi f()trong danh sách, bạn phải gọi f(*l)là biến tổng. Tệ hơn nữa, việc triển khai mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])sẽ SUCK w / varargs. Tốt hơn nhiều nếu nó def mate(birds=[], bees=[]):.
FeRD

26

Các câu trả lời hiện tại đã cung cấp các giải pháp trực tiếp như được yêu cầu. Tuy nhiên, vì đây là một cạm bẫy rất phổ biến đối với các lập trình viên Python mới, nên cần thêm lời giải thích tại sao python hoạt động theo cách này, được tóm tắt độc đáo trong " Hướng dẫn dành cho người quá giang đối với Python " là " Đối số mặc định có thể thay đổi ": http: // docs .python-guide.org / vi / new / writing / gotchas /

Trích dẫn: " Các đối số mặc định của Python được đánh giá một lần khi hàm được xác định, chứ không phải mỗi lần hàm được gọi (giống như trong Ruby). Điều này có nghĩa là nếu bạn sử dụng đối số mặc định có thể thay đổi và biến đổi nó, bạn sẽ và có đã biến đổi đối tượng đó cho tất cả các lệnh gọi hàm trong tương lai "

Mã mẫu để triển khai nó:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

13

Không quan trọng trong trường hợp này, nhưng bạn có thể sử dụng nhận dạng đối tượng để kiểm tra Không có:

if working_list is None: working_list = []

Bạn cũng có thể tận dụng cách toán tử boolean hoặc được định nghĩa trong python:

working_list = working_list or []

Mặc dù điều này sẽ hoạt động không mong muốn nếu người gọi cung cấp cho bạn một danh sách trống (được tính là sai) là working_list và mong đợi hàm của bạn sửa đổi danh sách mà anh ta đã cung cấp.


10

Nếu mục đích của hàm là sửa đổi tham số được truyền vào working_list, hãy xem câu trả lời của HenryR (= Không, kiểm tra Không có bên trong).

Nhưng nếu bạn không có ý định thay đổi đối số, chỉ cần sử dụng nó làm điểm bắt đầu cho một danh sách, bạn có thể chỉ cần sao chép nó:

def myFunc(starting_list = []):
    starting_list = list(starting_list)
    starting_list.append("a")
    print starting_list

(hoặc trong trường hợp đơn giản này, print starting_list + ["a"]nhưng tôi đoán đó chỉ là một ví dụ đồ chơi)

Nói chung, việc thay đổi các đối số của bạn là kiểu xấu trong Python. Các hàm duy nhất được mong đợi đầy đủ để thay đổi một đối tượng là các phương thức của đối tượng. Việc thay đổi một đối số tùy chọn thậm chí còn hiếm hơn - một tác dụng phụ chỉ xảy ra trong một số cuộc gọi có thực sự là giao diện tốt nhất?

  • Nếu bạn làm điều đó từ thói quen "xuất đối số" của C, điều đó hoàn toàn không cần thiết - bạn luôn có thể trả về nhiều giá trị dưới dạng một bộ giá trị.

  • Nếu bạn làm điều này để tạo hiệu quả một danh sách dài các kết quả mà không cần xây dựng danh sách trung gian, hãy xem xét việc viết nó như một trình tạo và sử dụng result_list.extend(myFunc())khi bạn đang gọi nó. Bằng cách này, quy ước gọi điện của bạn vẫn rất rõ ràng.

Một mô hình mà biến đổi một arg tùy chọn được thường xuyên thực hiện là một ẩn "ghi nhớ" arg trong các chức năng đệ quy:

def depth_first_walk_graph(graph, node, _visited=None):
    if _visited is None:
        _visited = set()  # create memo once in top-level call

    if node in _visited:
        return
    _visited.add(node)
    for neighbour in graph[node]:
        depth_first_walk_graph(graph, neighbour, _visited)

3

Tôi có thể lạc đề, nhưng hãy nhớ rằng nếu bạn chỉ muốn chuyển một số đối số thay đổi, thì cách *argshay ho là chuyển một bộ tuple hoặc một từ điển **kargs. Đây là tùy chọn và tốt hơn là cú pháp myFunc([1, 2, 3]).

Nếu bạn muốn vượt qua một tuple:

def myFunc(arg1, *args):
  print args
  w = []
  w += args
  print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]

Nếu bạn muốn chuyển từ điển:

def myFunc(arg1, **kargs):
   print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}

0

Đã có những câu trả lời tốt và đúng được cung cấp. Tôi chỉ muốn đưa ra một cú pháp khác để viết những gì bạn muốn làm mà tôi thấy đẹp hơn khi bạn muốn tạo một lớp với danh sách trống mặc định:

class Node(object):
    def __init__(self, _id, val, parents=None, children=None):
        self.id = _id
        self.val = val
        self.parents = parents if parents is not None else []
        self.children = children if children is not None else []

Đoạn mã này sử dụng cú pháp toán tử if else. Tôi đặc biệt thích nó vì nó có một chữ cái nhỏ gọn gàng không có dấu hai chấm, v.v. liên quan và nó gần giống như một câu tiếng Anh bình thường. :)

Trong trường hợp của bạn, bạn có thể viết

def myFunc(working_list=None):
    working_list = [] if working_list is None else working_list
    working_list.append("a")
    print working_list

-3

Tôi đã học lớp mở rộng UCSC Python for programmer

Điều nào đúng với: def Fn (data = []):

a) là một ý tưởng hay để danh sách dữ liệu của bạn bắt đầu trống với mọi cuộc gọi.

b) là một ý tưởng hay để tất cả các lệnh gọi đến hàm không cung cấp bất kỳ đối số nào trong cuộc gọi sẽ nhận được danh sách trống làm dữ liệu.

c) là một ý tưởng hợp lý miễn là dữ liệu của bạn là một danh sách các chuỗi.

d) là một ý tưởng tồi vì [] mặc định sẽ tích lũy dữ liệu và [] mặc định sẽ thay đổi với các lần gọi tiếp theo.

Câu trả lời:

d) là một ý tưởng tồi vì [] mặc định sẽ tích lũy dữ liệu và [] mặc định sẽ thay đổi với các lần gọi tiếp theo.

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.