danh sách hiểu so với lambda + bộ lọc


857

Tôi tình cờ thấy mình có một nhu cầu lọc cơ bản: Tôi có một danh sách và tôi phải lọc nó theo một thuộc tính của các mục.

Mã của tôi trông như thế này:

my_list = [x for x in my_list if x.attribute == value]

Nhưng sau đó tôi nghĩ, sẽ tốt hơn nếu viết nó như thế này?

my_list = filter(lambda x: x.attribute == value, my_list)

Nó dễ đọc hơn, và nếu cần cho hiệu suất, lambda có thể được lấy ra để đạt được thứ gì đó.

Câu hỏi là: có bất kỳ cảnh báo trong việc sử dụng cách thứ hai? Bất kỳ sự khác biệt hiệu suất? Tôi có thiếu hoàn toàn Pythonic Way ™ không và nên thực hiện theo cách khác (chẳng hạn như sử dụng itemgetter thay vì lambda)?


19
Một ví dụ tốt hơn sẽ là trường hợp bạn đã có một hàm được đặt tên độc đáo để sử dụng làm vị ngữ của bạn. Trong trường hợp đó, tôi nghĩ nhiều người sẽ đồng ý rằng nó filterdễ đọc hơn. Khi bạn có một biểu thức đơn giản có thể được sử dụng như trong một listcomp, nhưng phải được bọc trong lambda (hoặc được xây dựng tương tự ngoài chức năng partialhoặc operatorchức năng, v.v.) để chuyển đến filter, đó là khi listcomps thắng.
abarnert

3
Cần phải nói rằng trong Python3 ít nhất, sự trở lại của filterlà một đối tượng trình tạo bộ lọc không phải là một danh sách.
Matteo Ferla

Câu trả lời:


588

Thật kỳ lạ khi có bao nhiêu vẻ đẹp khác nhau đối với những người khác nhau. Tôi thấy việc hiểu danh sách rõ ràng hơn nhiều so với filter+ lambda, nhưng sử dụng bất cứ điều gì bạn thấy dễ dàng hơn.

Có hai điều có thể làm chậm việc sử dụng của bạn filter.

Đầu tiên là chức năng gọi hàm: ngay khi bạn sử dụng hàm Python (dù được tạo bởi defhoặc lambda), có khả năng bộ lọc sẽ chậm hơn mức hiểu danh sách. Nó gần như chắc chắn là không đủ để quan trọng và bạn không nên suy nghĩ nhiều về hiệu suất cho đến khi bạn tính thời gian cho mã của mình và thấy nó là một nút cổ chai, nhưng sự khác biệt sẽ ở đó.

Chi phí khác có thể áp dụng là lambda đang bị buộc phải truy cập vào một biến có phạm vi ( value). Điều đó chậm hơn so với việc truy cập một biến cục bộ và trong Python 2.x, việc hiểu danh sách chỉ truy cập các biến cục bộ. Nếu bạn đang sử dụng Python 3.x, việc hiểu danh sách sẽ chạy trong một chức năng riêng biệt nên nó cũng sẽ được truy cập valuethông qua việc đóng và sự khác biệt này sẽ không được áp dụng.

Tùy chọn khác để xem xét là sử dụng một trình tạo thay vì hiểu danh sách:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

Sau đó, trong mã chính của bạn (đó là nơi khả năng đọc thực sự quan trọng) bạn đã thay thế cả chức năng hiểu và lọc danh sách bằng một tên hàm có ý nghĩa hy vọng.


68
+1 cho máy phát điện. Tôi có một liên kết ở nhà với một bài thuyết trình cho thấy mức độ tuyệt vời của máy phát điện. Bạn cũng có thể thay thế việc hiểu danh sách bằng biểu thức trình tạo chỉ bằng cách thay đổi []thành (). Ngoài ra, tôi đồng ý rằng danh sách comp đẹp hơn.
Wayne Werner

1
Trên thực tế, không - bộ lọc nhanh hơn. Chỉ cần chạy một vài điểm chuẩn nhanh bằng cách sử dụng thứ gì đó như stackoverflow.com/questions/5998245/ trên
skqr

2
@skqr tốt hơn là chỉ sử dụng timeit cho điểm chuẩn, nhưng vui lòng cho một ví dụ mà bạn thấy filternhanh hơn khi sử dụng chức năng gọi lại Python.
Duncan

8
@ tnq177 Đó là bài thuyết trình của David Beasley về máy phát điện - dabeaz.com/generators
Wayne Werner

2
@ VictorSchröder có, có lẽ tôi đã không rõ ràng. Những gì tôi đã cố gắng nói là trong mã chính bạn cần để có thể nhìn thấy bức tranh lớn hơn. Trong chức năng trợ giúp nhỏ bạn chỉ cần quan tâm đến một chức năng đó, những gì khác đang diễn ra bên ngoài có thể bị bỏ qua.
Duncan

237

Đây là một vấn đề hơi tôn giáo trong Python. Mặc dù Guido coi việc loại bỏ map, filterreducetừ Python 3 , đã có đủ của một phản ứng dữ dội mà cuối cùng chỉ reduceđược chuyển từ xây dựng-in để functools.reduce .

Cá nhân tôi thấy việc hiểu danh sách dễ đọc hơn. Rõ ràng hơn những gì đang xảy ra từ biểu thức [i for i in list if i.attribute == value]vì tất cả các hành vi nằm trên bề mặt không bên trong chức năng lọc.

Tôi sẽ không lo lắng quá nhiều về sự khác biệt hiệu suất giữa hai cách tiếp cận vì nó là cận biên. Tôi thực sự sẽ chỉ tối ưu hóa điều này nếu nó được chứng minh là nút cổ chai trong ứng dụng của bạn, điều không thể xảy ra.

Ngoài ra vì BDFL muốn filterđi từ ngôn ngữ nên chắc chắn điều đó sẽ tự động làm cho việc hiểu danh sách trở nên nhiều hơn Pythonic ;-)


1
Cảm ơn các liên kết đến đầu vào của Guido, nếu không có gì khác cho tôi, điều đó có nghĩa là tôi sẽ cố gắng không sử dụng chúng nữa, để tôi không có thói quen này và tôi sẽ không trở nên ủng hộ tôn giáo đó :)
bực bội

1
nhưng giảm là phức tạp nhất để làm với các công cụ đơn giản! bản đồ và bộ lọc là tầm thường để thay thế bằng sự hiểu biết!
njzk2

8
không biết giảm đã bị hạ cấp trong Python3. cảm ơn vì sự sáng suốt less () vẫn khá hữu ích trong điện toán phân tán, như PySpark. Tôi nghĩ đó là một sai lầm ..
Tagar

1
@Tagar bạn vẫn có thể sử dụng giảm bạn chỉ cần nhập nó từ
funcools

69

Vì bất kỳ sự khác biệt về tốc độ nào chắc chắn là rất nhỏ, nên việc sử dụng các bộ lọc hoặc hiểu danh sách có liên quan đến vấn đề sở thích hay không. Nói chung, tôi có xu hướng sử dụng hiểu (có vẻ đồng ý với hầu hết các câu trả lời khác ở đây), nhưng có một trường hợp tôi thích filter.

Một trường hợp sử dụng rất thường xuyên là kéo các giá trị của một số đối tượng X có thể lặp lại sang một vị từ P (x):

[x for x in X if P(x)]

nhưng đôi khi bạn muốn áp dụng một số chức năng cho các giá trị trước tiên:

[f(x) for x in X if P(f(x))]


Như một ví dụ cụ thể, hãy xem xét

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

Tôi nghĩ rằng điều này có vẻ tốt hơn so với sử dụng filter. Nhưng bây giờ hãy xem xét

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

Trong trường hợp này, chúng tôi muốn filterchống lại giá trị sau tính toán. Bên cạnh vấn đề tính toán khối lập phương hai lần (hãy tưởng tượng một phép tính đắt tiền hơn), còn có vấn đề viết biểu thức hai lần, vi phạm thẩm mỹ DRY . Trong trường hợp này tôi có thể sử dụng

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

7
Bạn sẽ không xem xét việc sử dụng nguyên tố thông qua việc hiểu danh sách khác? Chẳng hạn như[prime(i) for i in [x**3 for x in range(1000)]]
viki.omega9

20
x*x*xkhông thể là số nguyên tố, vì nó có x^2xnhư là một yếu tố, ví dụ này không thực sự có ý nghĩa theo cách toán học, nhưng có lẽ nó vẫn hữu ích. (Có lẽ chúng ta có thể tìm thấy thứ gì đó tốt hơn?)
Zelphir Kaltstahl

3
Lưu ý rằng chúng ta có thể sử dụng biểu thức trình tạo thay thế cho ví dụ cuối cùng nếu chúng ta không muốn ăn hết bộ nhớ:prime_cubes = filter(prime, (x*x*x for x in range(1000)))
Mateen Ulhaq

4
@MateenUlhaq điều này có thể được tối ưu hóa để prime_cubes = [1]tiết kiệm cả chu kỳ bộ nhớ và cpu ;-)
Dennis Krupenik

7
@DennisKrupenik Hay đúng hơn,[]
Mateen Ulhaq

29

Mặc dù filtercó thể là "cách nhanh hơn", "cách Pythonic" sẽ không quan tâm đến những điều đó trừ khi hiệu suất là cực kỳ quan trọng (trong trường hợp đó bạn sẽ không sử dụng Python!).


9
Nhận xét muộn đối với một đối số thường thấy: Đôi khi, việc phân tích chạy trong 5 giờ thay vì 10 giờ sẽ khác biệt và nếu điều đó có thể đạt được bằng cách lấy một mã tối ưu hóa một giờ, nó có thể có giá trị (đặc biệt nếu một thoải mái với python và không với ngôn ngữ nhanh hơn).
bli

Nhưng quan trọng hơn là mã nguồn làm chúng ta chậm đến mức nào khi cố đọc và hiểu nó!
thoni56

20

Tôi nghĩ rằng tôi chỉ cần thêm rằng trong python 3, bộ lọc () thực sự là một đối tượng lặp, vì vậy bạn phải chuyển cuộc gọi phương thức bộ lọc của mình đến list () để xây dựng danh sách được lọc. Vì vậy, trong python 2:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

danh sách b và c có cùng giá trị và được hoàn thành trong cùng thời gian với bộ lọc () tương đương [x với x trong y nếu z]. Tuy nhiên, trong 3, cùng mã này sẽ để lại danh sách c chứa đối tượng bộ lọc, không phải danh sách được lọc. Để tạo ra các giá trị tương tự trong 3:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

Vấn đề là danh sách đó () lấy một lần lặp làm đối số của nó và tạo một danh sách mới từ đối số đó. Kết quả là việc sử dụng bộ lọc theo cách này trong python 3 mất tới hai lần so với phương thức [x for x in y if z] vì bạn phải lặp lại đầu ra từ bộ lọc () cũng như danh sách ban đầu.


13

Một sự khác biệt quan trọng là việc hiểu danh sách sẽ trả về một listlúc bộ lọc trả về một filtercái mà bạn không thể thao tác như một list(ví dụ: gọi lennó, không hoạt động với sự trở lại củafilter ).

Việc tự học của tôi đã đưa tôi đến một số vấn đề tương tự.

Điều đó đang được nói, nếu có một cách để có kết quả listtừ a filter, hơi giống như bạn sẽ làm trong .NET khi bạn làm lst.Where(i => i.something()).ToList(), tôi tò mò muốn biết điều đó.

EDIT: Đây là trường hợp của Python 3, không phải 2 (xem thảo luận trong các bình luận).


4
bộ lọc trả về một danh sách và chúng ta có thể sử dụng len trên nó. Ít nhất là trong Python 2.7.6 của tôi.
thiruvenkadam

7
Đây không phải là trường hợp của Python 3. a = [1, 2, 3, 4, 5, 6, 7, 8] f = filter(lambda x: x % 2 == 0, a) lc = [i for i in a if i % 2 == 0] >>> type(f) <class 'filter'> >>> type(lc) <class 'list'>
Adeynack

3
"Nếu có một cách để có danh sách kết quả ... tôi tò mò muốn biết nó". Chỉ cần gọi list()vào kết quả : list(filter(my_func, my_iterable)). Và tất nhiên bạn có thể thay thế listbằng set, hoặc tuple, hoặc bất cứ điều gì khác có thể lặp lại. Nhưng đối với bất kỳ ai khác ngoài các lập trình viên chức năng, trường hợp thậm chí còn mạnh hơn để sử dụng một sự hiểu biết danh sách hơn là filterchuyển đổi rõ ràng sang list.
Steve Jessop

10

Tôi tìm thấy cách thứ hai dễ đọc hơn. Nó cho bạn biết chính xác ý định là gì: lọc danh sách.
PS: không sử dụng 'list' làm tên biến


7

nói chung filterlà nhanh hơn một chút nếu sử dụng chức năng dựng sẵn.

Tôi hy vọng việc hiểu danh sách sẽ nhanh hơn một chút trong trường hợp của bạn


Bộ lọc python -m timeit '(lambda x: x trong [1,2,3,4,5], phạm vi (10000000)) '10 vòng, tốt nhất là 3: 1,44 giây trên mỗi vòng lặp python -m timeit' [x cho x trong phạm vi (10000000) nếu x trong [1,2,3,4,5]] '10 vòng, tốt nhất là 3: 860 msec mỗi vòng Không thực sự?!
giaosudau

@sepdau, hàm lambda không phải là nội trang. Khả năng hiểu danh sách đã được cải thiện trong 4 năm qua - hiện tại sự khác biệt là không đáng kể ngay cả với các hàm dựng sẵn
John La Rooy

7

Bộ lọc chỉ có vậy. Nó lọc ra các yếu tố của một danh sách. Bạn có thể thấy định nghĩa đề cập giống nhau (trong liên kết tài liệu chính thức tôi đã đề cập trước đó). Trong khi đó, việc hiểu danh sách là thứ tạo ra một danh sách mới sau khi hành động theo thứ gì đó trong danh sách trước đó. , giả sử, một kiểu dữ liệu hoàn toàn mới. Giống như chuyển đổi số nguyên thành chuỗi, v.v.)

Trong ví dụ của bạn, tốt hơn là sử dụng bộ lọc hơn là hiểu danh sách, theo định nghĩa. Tuy nhiên, nếu bạn muốn, giả sử other_attribution từ các thành phần danh sách, trong ví dụ của bạn sẽ được truy xuất dưới dạng danh sách mới, sau đó bạn có thể sử dụng mức độ hiểu danh sách.

return [item.other_attribute for item in my_list if item.attribute==value]

Đây là cách tôi thực sự nhớ về bộ lọc và danh sách hiểu. Xóa một vài thứ trong danh sách và giữ nguyên các yếu tố khác, sử dụng bộ lọc. Sử dụng một số logic của riêng bạn tại các yếu tố và tạo một danh sách xuống nước phù hợp cho một số mục đích, sử dụng hiểu danh sách.


2
Tôi sẽ rất vui khi biết lý do bỏ phiếu để tôi sẽ không lặp lại nó ở bất cứ đâu trong tương lai.
thiruvenkadam

định nghĩa của bộ lọc và hiểu danh sách là không cần thiết, vì ý nghĩa của chúng không được tranh luận. Rằng việc hiểu danh sách chỉ nên được sử dụng cho các danh sách mới của Wikipedia được trình bày nhưng không được tranh luận.
Agos 2/2/2015

Tôi đã sử dụng định nghĩa để nói rằng bộ lọc cung cấp cho bạn danh sách có cùng các phần tử đúng với trường hợp nhưng với việc hiểu danh sách, chúng ta có thể tự sửa đổi các phần tử, như chuyển đổi int thành str. Nhưng điểm thực hiện :-)
thiruvenkadam 2/2/2015

4

Đây là một đoạn ngắn tôi sử dụng khi tôi cần lọc một cái gì đó sau khi hiểu danh sách. Chỉ là sự kết hợp của bộ lọc, lambda và danh sách (còn được gọi là sự trung thành của một con mèo và sự sạch sẽ của một con chó).

Trong trường hợp này, tôi đang đọc một tập tin, tước bỏ các dòng trống, nhận xét các dòng và bất cứ điều gì sau khi nhận xét về một dòng:

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]

Điều này đạt được rất nhiều trong rất ít mã thực sự. Tôi nghĩ rằng nó có thể là một chút quá nhiều logic trong một dòng để dễ hiểu và dễ đọc là những gì được tính mặc dù.
Zelphir Kaltstahl

Bạn có thể viết cái này nhưfile_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
Steve Jessop

4

Ngoài câu trả lời được chấp nhận, có một trường hợp góc khi bạn nên sử dụng bộ lọc thay vì hiểu danh sách. Nếu danh sách không thể xóa được, bạn không thể xử lý trực tiếp bằng cách hiểu danh sách. Một ví dụ trong thế giới thực là nếu bạn sử dụng pyodbcđể đọc kết quả từ cơ sở dữ liệu. Các fetchAll()kết quả từ cursorlà một danh sách không thể bỏ qua. Trong tình huống này, để thao tác trực tiếp trên các kết quả được trả về, nên sử dụng bộ lọc:

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

Nếu bạn sử dụng hiểu danh sách ở đây, bạn sẽ gặp lỗi:

LoạiError: loại không thể xóa: 'danh sách'


1
tất cả các danh sách đều không thể xóa được, >>> hash(list()) # TypeError: unhashable type: 'list'thứ hai hoạt động tốt:processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
Thomas Grainger

"Nếu danh sách không thể xóa được, bạn không thể trực tiếp xử lý nó với sự hiểu biết danh sách." Điều này là không đúng sự thật, và tất cả các danh sách là không thể xóa được.
juanpa.arrivillaga

3

Tôi phải mất một thời gian để làm quen với higher order functions filtermap. Vì vậy, tôi đã quen với chúng và tôi thực sự thích filtervì rõ ràng là nó lọc bằng cách giữ bất cứ điều gì là sự thật và tôi cảm thấy mát mẻ rằng tôi biết một số functional programmingđiều khoản.

Sau đó tôi đọc đoạn văn này (Sách lưu loát Python):

Các chức năng bản đồ và bộ lọc vẫn là các nội dung trong Python 3, nhưng kể từ khi giới thiệu về khả năng hiểu danh sách và các áp lực của trình tạo, chúng không quan trọng. Một listcomp hoặc một genEx thực hiện công việc của bản đồ và bộ lọc kết hợp, nhưng dễ đọc hơn.

Và bây giờ tôi nghĩ, tại sao phải bận tâm với khái niệm filter/ mapnếu bạn có thể đạt được nó với các thành ngữ đã được phổ biến rộng rãi như hiểu danh sách. Hơn nữa mapsfilterslà loại chức năng. Trong trường hợp này tôi thích sử dụng Anonymous functionslambdas.

Cuối cùng, chỉ vì mục đích thử nghiệm, tôi đã hẹn giờ cho cả hai phương thức ( maplistComp) và tôi không thấy bất kỳ sự khác biệt nào về tốc độ có thể biện minh cho việc đưa ra lập luận về nó.

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602

0

Thật kỳ lạ trên Python 3, tôi thấy bộ lọc hoạt động nhanh hơn so với việc hiểu danh sách.

Tôi luôn nghĩ rằng việc hiểu danh sách sẽ hiệu quả hơn. Một cái gì đó như: [tên cho tên trong brand_names_db nếu tên không phải là không] Mã byte được tạo ra tốt hơn một chút.

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

Nhưng chúng thực sự chậm hơn:

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214

8
So sánh không hợp lệ . Đầu tiên, bạn không chuyển chức năng lambda sang phiên bản bộ lọc, điều này làm cho nó mặc định cho chức năng nhận dạng. Khi xác định if not Nonetrong phần hiểu danh sách, bạn đang xác định hàm lambda (chú ý MAKE_FUNCTIONcâu lệnh). Thứ hai, kết quả là khác nhau, vì phiên bản hiểu danh sách sẽ chỉ loại bỏ Nonegiá trị, trong khi phiên bản bộ lọc sẽ loại bỏ tất cả các giá trị "giả". Có nói rằng, toàn bộ mục đích của microbenchmarking là vô ích. Đó là một triệu lần lặp, lần 1k mục! Sự khác biệt là không đáng kể .
Victor Schröder

-7

Tôi lấy

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]

3
ikhông bao giờ được cho là một dict, và không cần thiết limit. Ngoài ra, điều này khác với những gì OP đề xuất và cách trả lời câu hỏi?
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.