2010-05-05 30 views
14

Tôi đang thực hiện một số công việc Python quan trọng về hiệu suất và muốn tạo một hàm loại bỏ một vài phần tử khỏi danh sách nếu chúng đáp ứng các tiêu chí nhất định. Tôi không muốn tạo bất kỳ bản sao nào của danh sách vì nó chứa rất nhiều đối tượng thực sự lớn.Python: tạo hàm để sửa đổi danh sách theo tham chiếu không có giá trị

năng tôi muốn thực hiện:

def listCleanup(listOfElements): 
    i = 0 
    for element in listOfElements: 
     if(element.meetsCriteria()): 
      del(listOfElements[i]) 
     i += 1 
    return listOfElements 

myList = range(10000) 
myList = listCleanup(listOfElements) 

Tôi không quen thuộc với các hoạt động ở mức độ thấp của Python. Danh sách của tôi có được chuyển qua giá trị hoặc tham chiếu không?

Tôi làm cách nào để thực hiện điều này nhanh hơn?

Có thể bằng cách nào đó mở rộng lớp danh sách và triển khai listCleanup() trong đó không?

myList = range(10000) 
myList.listCleanup() 

Thanks-

Jonathan

+0

Tôi nghĩ rằng bạn sẽ thấy rằng dòng suy nghĩ này gặp nhiều rắc rối hơn đáng giá. Chỉ cần sao chép danh sách, sửa đổi và trả lại bản sửa đổi. Sửa đổi một danh sách tại chỗ trong khi lặp nó chỉ là yêu cầu cho đau đầu. – jathanism

+1

'del' là một câu lệnh, không phải là một hàm. Không quấn đối số của nó trong dấu ngoặc đơn. – jemfinch

+1

Kích thước của đối tượng "trong danh sách" không liên quan vì Python không lưu trữ đối tượng trong danh sách; nó lưu trữ một tham chiếu đến đối tượng.Do đó, các vấn đề về hiệu suất có liên quan đến độ dài của danh sách và thuật toán được sử dụng để hoạt động trên danh sách chứ không phải là kích thước của các đối tượng được đề cập đến. – Nathan

Trả lời

29

Python đi tất cả mọi thứ cùng một cách, nhưng gọi đó là "giá trị" hoặc "bằng cách tham khảo" sẽ không rõ ràng tất cả mọi thứ lên, vì ngữ nghĩa của Python là khác nhau hơn so với ngôn ngữ mà các thuật ngữ đó thường áp dụng. Nếu tôi mô tả nó, tôi sẽ nói rằng tất cả đều là giá trị, và giá trị đó là một tham chiếu đối tượng. (Đây là lý do tại sao tôi không muốn nói nó!)

Nếu bạn muốn lọc ra một số nội dung từ một danh sách, bạn xây dựng một danh sách mới

foo = range(100000) 
new_foo = [] 
for item in foo: 
    if item % 3 != 0: # Things divisble by 3 don't get through 
     new_foo.append(item) 

hay, sử dụng cú pháp danh sách hiểu

new_foo = [item for item in foo if item % 3 != 0] 

Python sẽ không sao chép các đối tượng trong danh sách, nhưng thay vào đó cả hai foonew_foo sẽ tham chiếu cùng một đối tượng. (Python không bao giờ ngầm sao chép bất kỳ đối tượng nào.)


Bạn đã đề xuất bạn có mối quan tâm về hoạt động này.Sử dụng các câu lệnh del lặp đi lặp lại từ danh sách cũ sẽ dẫn đến mã không thành ngữ và khó hiểu hơn, nhưng nó sẽ giới thiệu hiệu suất bậc hai vì toàn bộ danh sách phải được thay đổi lại mỗi lần.

Để giải quyết hiệu suất:

  • Nhận nó lên và chạy. Bạn không thể hiểu được hiệu suất của bạn là như thế nào trừ khi bạn có mã hoạt động. Điều này cũng sẽ cho bạn biết đó là tốc độ hoặc không gian mà bạn phải tối ưu hóa; bạn đề cập đến mối quan tâm về cả hai trong mã của bạn, nhưng đôi khi tối ưu hóa liên quan đến việc nhận được một với chi phí của người khác.

  • Tiểu sử. Bạn có thể sử dụng the stdlib tools để có hiệu suất kịp thời. Có nhiều trình biên tập bộ nhớ của bên thứ ba khác nhau có thể hữu ích nhưng không hoàn toàn phù hợp để làm việc.

  • Đo lường.Time hoặc sửa lại bộ nhớ khi bạn thực hiện thay đổi để xem liệu thay đổi có cải thiện hay không và nếu có thì cải tiến đó là gì.

  • Để làm cho mã của bạn nhạy cảm với bộ nhớ hơn, bạn thường muốn thay đổi mô hình về cách lưu trữ dữ liệu của mình, chứ không phải microoptimizastions như không xây dựng danh sách thứ hai để lọc. (Điều này cũng đúng với thời gian, thực sự: việc thay đổi thành một thuật toán tốt hơn sẽ hầu như luôn mang đến sự tăng tốc tốt nhất. Tuy nhiên, khó tổng quát hơn về tối ưu hóa tốc độ).

    Một số mô hình thông thường chuyển sang tối ưu hóa tiêu thụ bộ nhớ trong Python bao gồm

    1. Sử dụng Máy phát điện. Máy phát điện có thể lặp lại lười biếng: chúng không tải toàn bộ danh sách vào bộ nhớ cùng một lúc, chúng sẽ tìm ra các mục tiếp theo của chúng đang bay. Để sử dụng máy phát điện, các đoạn ở trên sẽ giống như

      foo = xrange(100000) # Like generators, xrange is lazy 
      def filter_divisible_by_three(iterable): 
          for item in foo: 
           if item % 3 != 0: 
            yield item 
      
      new_foo = filter_divisible_by_three(foo) 
      

      hay, sử dụng cú pháp biểu hiện máy phát điện,

      new_foo = (item for item in foo if item % 3 != 0) 
      
    2. Sử dụng numpy cho chuỗi đồng nhất, đặc biệt là những người có số-mathy. Điều này cũng có thể tăng tốc mã thực hiện rất nhiều hoạt động vectơ.

    3. Lưu trữ dữ liệu vào đĩa, chẳng hạn như trong cơ sở dữ liệu.

+1

pb [r] v (pass-by- [reference] -value) thực sự có thể được áp dụng cho nhiều, nhiều ngôn ngữ bao gồm (nhưng không giới hạn) Ruby, Java và C#. (Mỗi bộ tinh tế/cơ khí hơi khác nhau dựa trên 'loại' được thông qua). Tuy nhiên, tôi thích nói "một đối tượng là chính nó" và "một đối tượng không được sao chép/nhân bản/sao chép hoàn toàn khi gọi một hàm" (cho các kiểu không thay đổi/val, ngay cả khi đây là một lời nói dối, ngữ nghĩa hoạt động tương tự) khi thảo luận pb [r] v ngữ nghĩa. Nó là rất nhất quán trên hầu hết các ngôn ngữ mệnh lệnh cho phép một "nhìn quá khứ" tài liệu tham khảo/con trỏ khi giao dịch với ngôn ngữ cấp cao. –

+1

nó có thể là một ideo tốt để sử dụng python 'timeit' cho hồ sơ. – kriss

+0

Các điều khoản này chắc chắn có thể được áp dụng cho nhiều ngôn ngữ. Theo kinh nghiệm của tôi, khi ai đó nghe rằng Python là một hay khác, họ nghĩ rằng điều này đòi hỏi những thứ không đúng sự thật. Phân loại Python là "truyền theo giá trị" thường khiến mọi người nghĩ rằng họ có thể sử dụng các phím tắt tinh thần dựa vào thông tin bổ sung về ngữ nghĩa của Python không đúng. –

6

Trong Python, danh sách luôn đi ngang qua tham khảo.

Kích thước của các đối tượng trong danh sách không ảnh hưởng đến hiệu suất danh sách, vì danh sách chỉ lưu trữ tham chiếu đến đối tượng. Tuy nhiên, số lượng mục trong danh sách không ảnh hưởng đến hiệu suất của một số thao tác - chẳng hạn như xóa phần tử, là O (n).

Như đã viết, listCleanup là trường hợp xấu nhất O (n ** 2), vì bạn có phép toán O (n) del trong vòng lặp có khả năng là O (n).

Nếu thứ tự của các yếu tố không quan trọng, bạn có thể sử dụng loại được xây dựng trong set thay vì danh sách. Các set có O (1) xóa và chèn. Tuy nhiên, bạn sẽ phải đảm bảo rằng các đối tượng của bạn là không thay đổi và có thể băm.

Nếu không, bạn nên tạo lại danh sách. Đó là O (n), và thuật toán của bạn cần phải có ít nhất O (n) vì bạn cần kiểm tra mọi phần tử. Bạn có thể lọc danh sách trong một dòng như thế này:

listOfElements[:] = [el for el in listOfElements if el.MeetsCriteria()] 
+0

Switiching từ một 'danh sách' thành tập hợp' có lẽ không phải là một cách tuyệt vời để cố gắng tiết kiệm bộ nhớ. ';)' –

+1

Đoạn mã của bạn không thực sự làm bất cứ điều gì tiết kiệm bộ nhớ hoặc có lợi hơn các kỹ thuật Python chuẩn như 'listOfElements = [el cho el trong listOfElements nếu el.MeetsCriteria()]' theo như tôi có thể nói . –

+0

@ Giống như tôi đồng ý. Tôi sửa nó rồi. –

0

Chỉ cần được rõ ràng:

def listCleanup(listOfElements): 
    i = 0 
    for element in listOfElements: 
     if(element.meetsCriteria()): 
      del(listOfElements[i]) 
     i += 1 
    return listOfElements 

myList = range(10000) 
myList = listCleanup(listOfElements) 

cũng giống như

def listCleanup(listOfElements): 
    i = 0 
    for element in listOfElements: 
     if(element.meetsCriteria()): 
      del(listOfElements[i]) 
     i += 1 

myList = range(10000) 
listCleanup(listOfElements) 

?

+0

Vâng, đúng vậy. Họ làm điều tương tự. –

+0

@Daniel: có nếu bạn sửa lỗi đánh máy, dòng cuối cùng phải là 'listCleanup (myList)' – kriss

+0

Hãy nhớ rằng, mọi "tên" trong Python chỉ là một tham chiếu. Các đối tượng có thể thay đổi được sửa đổi tại chỗ trừ khi bạn sao chép chúng một cách rõ ràng. – jathanism

2

Có vẻ như tối ưu hóa sớm. Bạn nên cố gắng hiểu rõ hơn về cách python hoạt động trước khi cố gắng tối ưu hóa.

Trong trường hợp cụ thể này, bạn không cần phải lo lắng về kích thước đối tượng.Sao chép một danh sách là sử dụng danh sách hiểu hoặc lát sẽ chỉ thực hiện sao chép bề mặt (sao chép tham chiếu đến các đối tượng ngay cả khi thuật ngữ không thực sự áp dụng tốt cho python). Nhưng số lượng các mục trong danh sách có thể quan trọng vì del là O (n). Có thể có các giải pháp khác, như thay thế một mục bằng Không hoặc một đối tượng Null thông thường hoặc sử dụng cấu trúc dữ liệu khác như tập hợp hoặc từ điển mà chi phí xóa mục thấp hơn nhiều.

1

sửa đổi cấu trúc dữ liệu của bạn khi bạn đang lặp lại nó giống như tự bắn mình vào chân ... lặp lại không thành công. bạn cũng có thể theo lời khuyên của người khác và chỉ cần thực hiện một danh sách mới:

myList = [element for element in listOfElements if not element.meetsCriteria()] 

danh sách cũ - nếu không có tài liệu tham khảo khác để nó - sẽ được deallocated và bộ nhớ được tái sinh. tốt hơn, thậm chí không tạo một bản sao của danh sách. thay đổi ở trên để một biểu thức phát cho một phiên bản bộ nhớ thân thiện hơn:

myList = (element for element in listOfElements if not element.meetsCriteria()) 

tất cả các truy cập đối tượng Python là bằng cách tham khảo. các đối tượng được tạo ra và các biến chỉ là tham chiếu đến các đối tượng đó. tuy nhiên, nếu ai đó muốn hỏi câu hỏi thuần túy, "loại ngữ nghĩa cuộc gọi nào sử dụng Python, gọi theo tham chiếu hoặc gọi theo giá trị?" câu trả lời sẽ phải là, "Không ... và cả hai." lý do là bởi vì các quy ước gọi là ít quan trọng đối với Python hơn kiểu đối tượng.

nếu đối tượng có thể thay đổi, nó có thể được sửa đổi bất kể phạm vi bạn đang ở ... miễn là bạn có tham chiếu đối tượng hợp lệ, đối tượng có thể thay đổi. nếu đối tượng là không thay đổi, thì đối tượng đó không thể thay đổi bất kể bạn ở đâu hoặc bạn tham chiếu gì.

1

Xóa các phần tử danh sách tại chỗ là có thể, nhưng không phải bằng cách chuyển tiếp qua danh sách. Mã của bạn chỉ đơn giản là không hoạt động - khi danh sách co lại, bạn có thể bỏ qua phần tử kiểm tra. Bạn cần phải lùi lại, để phần co lại phía sau bạn, với mã khá kinh khủng. Trước khi tôi cho bạn thấy điều đó, có một số cân nhắc sơ bộ:

Trước tiên, rác thải được đưa vào danh sách như thế nào? Phòng bệnh hơn chữa bệnh.

Thứ hai, số lượng phần tử trong danh sách và phần trăm nào có thể cần xóa? Tỷ lệ phần trăm càng cao thì khả năng tạo danh sách mới càng lớn.

OK, nếu bạn vẫn muốn làm điều đó tại chỗ, chiêm nghiệm này:

def list_cleanup_fail(alist, is_bad): 
    i = 0 
    for element in alist: 
     print "i=%d alist=%r alist[i]=%d element=%d" % (i, alist, alist[i], element) 
     if is_bad(element): 
      del alist[i] 
     i += 1 

def list_cleanup_ok(alist, is_bad): 
    for i in xrange(len(alist) - 1, -1, -1): 
     print "i=%d alist=%r alist[i]=%d" % (i, alist, alist[i]) 
     if is_bad(alist[i]): 
      del alist[i] 

def is_not_mult_of_3(x): 
    return x % 3 != 0 

for func in (list_cleanup_fail, list_cleanup_ok): 
    print 
    print func.__name__ 
    mylist = range(11) 
    func(mylist, is_not_mult_of_3) 
    print "result", mylist 

và đây là kết quả:

list_cleanup_fail 
i=0 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=0 element=0 
i=1 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=1 element=1 
i=2 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=3 element=3 
i=3 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=4 element=4 
i=4 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=6 element=6 
i=5 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=7 element=7 
i=6 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=9 element=9 
i=7 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=10 element=10 
result [0, 2, 3, 5, 6, 8, 9] 

list_cleanup_ok 
i=10 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=10 
i=9 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=9 
i=8 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=8 
i=7 alist=[0, 1, 2, 3, 4, 5, 6, 7, 9] alist[i]=7 
i=6 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=6 
i=5 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=5 
i=4 alist=[0, 1, 2, 3, 4, 6, 9] alist[i]=4 
i=3 alist=[0, 1, 2, 3, 6, 9] alist[i]=3 
i=2 alist=[0, 1, 2, 3, 6, 9] alist[i]=2 
i=1 alist=[0, 1, 3, 6, 9] alist[i]=1 
i=0 alist=[0, 3, 6, 9] alist[i]=0 
result [0, 3, 6, 9] 
2

Tôi không nghĩ rằng bất cứ ai đề cập thực sự sử dụng bộ lọc . Vì rất nhiều câu trả lời đến từ những người được kính trọng, tôi chắc chắn rằng tôi là người thiếu thứ gì đó. Ai đó có thể giải thích điều gì sẽ sai với điều này:

new_list = filter(lambda o: o.meetsCriteria(), myList)

Các vấn đề liên quan