2012-06-25 34 views
22

câu hỏi đơn giản mà tôi không thể tìm thấy bất kỳ câu trả lời "tốt đẹp" của bản thân mình:Có cách nào tốt hơn để viết các câu lệnh "hoặc" liên tiếp trong Python không?

Hãy nói rằng tôi có điều kiện sau đây:

if 'foo' in mystring or 'bar' in mystring or 'hello' in mystring: 
    # Do something 
    pass 

Trường hợp số or tuyên bố có thể khá dài tùy thuộc vào tình huống.

Có cách nào "đẹp hơn" (hơn Pythonic) bằng cách viết này, mà không bị mất hiệu suất?

Nếu nghĩ đến việc sử dụng any() nhưng cần danh sách các phần tử giống như boolean, vì vậy tôi sẽ phải xây dựng danh sách đó trước tiên (đánh giá ngắn mạch trong quá trình), vì vậy tôi đoán nó kém hiệu quả hơn.

Cảm ơn bạn rất nhiều.

Trả lời

30

Một cách có thể là

if any(s in mystring for s in ('foo', 'bar', 'hello')): 
    pass 

Điều bạn lặp qua là một tuple, được xây dựng dựa trên tổng hợp các chức năng, vì vậy nó không phải là thua kém phiên bản gốc của bạn.

Nếu bạn lo ngại rằng các tuple sẽ trở nên quá dài, bạn có thể làm

def mystringlist(): 
    yield 'foo' 
    yield 'bar' 
    yield 'hello' 
if any(s in mystring for s in mystringlist()): 
    pass 
+1

Cảm ơn. Nhưng kỹ thuật đó không ngăn cản tối ưu hóa mạch ngắn? – ereOn

+2

Đó là máy phát điện, không phải danh sách. – johv

+9

Không. '(s trong mystring cho s trong 'foo', 'bar', 'hello')' là một biểu thức máy phát điện, có nghĩa là nó không được tính ngay lập tức như một toàn thể, chỉ theo yêu cầu. 'any()' dừng lặp lại khi nhìn thấy giá trị thực đầu tiên, vì vậy phần còn lại sẽ không bao giờ được kiểm tra. Đọc trên biểu thức máy phát. – Kos

7

này nghe có vẻ như một công việc cho một regex.

import re 

if re.search("(foo|bar|hello)", mystring): 
    # Do something 
    pass 

Nó cũng sẽ nhanh hơn. Đặc biệt là nếu bạn biên dịch regex trước thời hạn.

Nếu bạn đang tạo biểu thức chính quy tự động, bạn có thể sử dụng re.escape() để đảm bảo không có ký tự đặc biệt nào phá vỡ regex của bạn. Ví dụ, nếu words là một danh sách các chuỗi bạn muốn tìm kiếm, bạn có thể tạo ra mô hình của bạn như thế này:

pattern = "(%s)" % ("|".join(re.escape(word) for word in words),) 

Bạn cũng nên lưu ý rằng nếu bạn có m từ và chuỗi của bạn có n ký tự, ban đầu của bạn mã có độ phức tạp O(n*m), trong khi cụm từ thông dụng có độ phức tạp O(n). Mặc dù Python regex không thực sự là lý thuyết comp-sci thường xuyên biểu thức, và không phải là luôn luônO(n) phức tạp, trong trường hợp này đơn giản họ đang có.

+4

Nhưng bạn phải cẩn thận nếu bất kỳ "từ" bạn đang tìm kiếm chứa các ký tự regex đặc biệt –

+0

@gnibbler: True. Ngược lại, bạn có thể viết ít mã hơn bằng cách sử dụng đối sánh mẫu. Nếu bạn đang làm một cái gì đó như tự động tạo regex, bạn có thể sử dụng 're.escape()'. – cha0site

+0

Thật vậy, bạn có thể thêm câu trả lời đó vào câu trả lời –

2

Vì bạn đang xử lý từng từ với mystring, chắc chắn bạn có thể sử dụng tính năng bí ẩn làm bộ. Sau đó chỉ cần lấy ngã tư giữa các thiết lập có chứa các từ trong mystring và các nhóm đối tượng của chữ:

In [370]: mystring=set(['foobar','barfoo','foo']) 

In [371]: mystring.intersection(set(['foo', 'bar', 'hello'])) 
Out[371]: set(['foo']) 

của bạn logic 'hoặc' là các thành viên của giao điểm của hai bộ.

Sử dụng bộ cũng nhanh hơn. Dưới đây là thời gian tương đối vs một máy phát điện và biểu thức chính quy:

f1: generator to test against large string 
f2: re to test against large string 
f3: set intersection of two sets of words 

    rate/sec  f2  f1  f3 
f2 101,333  -- -95.0% -95.5% 
f1 2,026,329 1899.7%  -- -10.1% 
f3 2,253,539 2123.9% 11.2%  -- 

Vì vậy, một máy phát điện và các in hoạt động là 19x nhanh hơn so với một biểu thức chính quy và một ngã tư bộ là 21x nhanh hơn so với một regex và nhanh hơn so với một máy phát điện 11%.

Đây là mã đã tạo ra thời gian:

import re 

with open('/usr/share/dict/words','r') as fin: 
    set_words={word.strip() for word in fin} 

s_words=' '.join(set_words) 
target=set(['bar','foo','hello']) 
target_re = re.compile("(%s)" % ("|".join(re.escape(word) for word in target),)) 

gen_target=(word for word in ('bar','foo','hello')) 

def f1(): 
    """ generator to test against large string """   
    if any(s in s_words for s in gen_target): 
     return True 

def f2(): 
    """ re to test against large string """ 
    if re.search(target_re, s_words): 
     return True 

def f3(): 
    """ set intersection of two sets of words """ 
    if target.intersection(set_words): 
     return True 

funcs=[f1,f2,f3] 
legend(funcs) 
cmpthese(funcs)   
+0

Điều này khác với câu trả lời được chấp nhận như thế nào? Khác biệt duy nhất là bạn sử dụng 'bộ' thay thế của một 'tuple', nếu không nó là chính xác như nhau – cha0site

+0

@ cha0site: Câu trả lời được chấp nhận cũng đề xuất một hàm cho một danh sách lớn.Tôi nghĩ rằng một bộ là cách tốt hơn để như vậy.Điều này cũng đề xuất hai bộ - không sử dụng 'bất kỳ ' –

2

Nếu bạn có một danh tiếng của sản phẩm để kiểm tra chống lại, bạn cũng có thể viết nó như

if mystring in ['foo', 'bar', 'hello']: 

Bạn không thể có được lợi ích của việc đảm bảo thứ tự so sánh (Tôi không nghĩ rằng Python là cần thiết để kiểm tra các yếu tố danh sách từ trái sang phải) nhưng đó chỉ là vấn đề nếu bạn biết 'foo' có nhiều khả năng hơn 'bar'.

+0

Điều này không hoàn toàn giống nhau. Điều kiện trong câu hỏi cũng sẽ đúng khi 'mystring = 'hello world''. Điều này sẽ không. – Izkata

+0

Điểm tốt, cảm ơn - như bạn nói, không hoàn toàn giống nhau, và có lẽ không phù hợp với vấn đề cụ thể. – kimvanwyk

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