Cách hiệu quả nhất để tạo câu lệnh if-elif-elif-else khi câu lệnh khác được thực hiện nhiều nhất?


99

Tôi có một câu lệnh if-elif-elif-else trong đó 99% thời gian, câu lệnh else được thực thi:

if something == 'this':
    doThis()
elif something == 'that':
    doThat()
elif something == 'there':
    doThere()
else:
    doThisMostOfTheTime()

Cấu trúc này được thực hiện rất nhiều , nhưng vì nó vượt qua mọi điều kiện trước khi nó chạm vào điều kiện khác, tôi có cảm giác nó không hiệu quả lắm, chứ đừng nói đến Pythonic. Mặt khác, nó cần biết nếu có bất kỳ điều kiện nào trong số đó được đáp ứng hay không, vì vậy nó nên thử nghiệm nó.

Có ai biết nếu và làm thế nào điều này có thể được thực hiện hiệu quả hơn hoặc đây chỉ đơn giản là cách tốt nhất có thể để làm điều đó?


Bạn có thể sortchuỗi những thứ bạn đang chạy if / else ... của mình, sao cho tất cả các phần tử mà một trong các điều kiện sẽ khớp với nhau đều ở một đầu và tất cả những phần còn lại ở đầu kia? Nếu vậy, bạn có thể xem liệu điều đó có nhanh hơn / thanh lịch hơn hay không. Nhưng hãy nhớ rằng, nếu không có vấn đề về hiệu suất, thì còn quá sớm để lo lắng về việc tối ưu hóa.
Patashu


4
Có điều gì mà ba trường hợp đặc biệt có điểm chung? Ví dụ, bạn có thể làm if not something.startswith("th"): doThisMostOfTheTime()và thực hiện một so sánh khác trong elsemệnh đề.
Tim Pietzcker

3
@ kramer65 Nếu đó là một chuỗi if / elif dài như vậy ... thì nó có thể chậm, nhưng hãy đảm bảo thực sự cấu hình mã của bạn và bắt đầu bằng cách tối ưu hóa bất kỳ phần nào cần nhiều thời gian nhất.
jorgeca

1
Các phép so sánh này chỉ được thực hiện một lần cho mỗi giá trị somethinghay các phép so sánh tương tự được thực hiện nhiều lần trên cùng một giá trị?
Chris Pitman

Câu trả lời:


98

Mật mã...

options.get(something, doThisMostOfTheTime)()

... trông giống như nó nên được nhanh hơn, nhưng nó thực sự là chậm hơn so với if... elif... elsexây dựng, bởi vì nó có để gọi một chức năng, có thể là một chi phí hiệu suất đáng kể trong một vòng lặp chặt chẽ.

Hãy xem xét những ví dụ này ...

1.py

something = 'something'

for i in xrange(1000000):
    if something == 'this':
        the_thing = 1
    elif something == 'that':
        the_thing = 2
    elif something == 'there':
        the_thing = 3
    else:
        the_thing = 4

2.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    the_thing = options.get(something, 4)

3.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    if something in options:
        the_thing = options[something]
    else:
        the_thing = 4

4.py

from collections import defaultdict

something = 'something'
options = defaultdict(lambda: 4, {'this': 1, 'that': 2, 'there': 3})

for i in xrange(1000000):
    the_thing = options[something]

... và lưu ý lượng thời gian CPU sử dụng ...

1.py: 160ms
2.py: 170ms
3.py: 110ms
4.py: 100ms

... sử dụng thời gian của người dùng từ time(1).

Tùy chọn số 4 không có thêm chi phí bộ nhớ để thêm một mục mới cho mỗi lần bỏ lỡ phím riêng biệt, vì vậy nếu bạn đang mong đợi một số lần bỏ lỡ khóa riêng biệt không giới hạn, tôi sẽ đi với tùy chọn số 3, đây vẫn là một cải tiến đáng kể trên cấu trúc ban đầu.


2
python có một câu lệnh chuyển đổi không?
nathan hayfield

ugh ... tốt cho đến nay thats điều duy nhất tôi đã nghe nói về python mà tôi không quan tâm cho ... đoán có được ràng buộc để được một cái gì đó
nathan hayfield

2
-1 Bạn nói rằng sử dụng a dictchậm hơn, nhưng sau đó thời gian của bạn thực sự cho thấy rằng đó là lựa chọn nhanh thứ hai.
Marcin

11
@Marcin Tôi đang nói rằng dict.get()chậm hơn, tức là 2.py- chậm nhất trong số chúng.
Aya

Đối với bản ghi, ba và bốn cũng nhanh hơn đáng kể so với việc ghi lại lỗi chính trong một cấu trúc thử / ngoại trừ.
Jeff

78

Tôi sẽ tạo một từ điển:

options = {'this': doThis,'that' :doThat, 'there':doThere}

Bây giờ chỉ sử dụng:

options.get(something, doThisMostOfTheTime)()

Nếu somethingkhông tìm thấy trong optionsdict thì dict.getsẽ trả về giá trị mặc địnhdoThisMostOfTheTime

Một số so sánh về thời gian:

Kịch bản:

from random import shuffle
def doThis():pass
def doThat():pass
def doThere():pass
def doSomethingElse():pass
options = {'this':doThis, 'that':doThat, 'there':doThere}
lis = range(10**4) + options.keys()*100
shuffle(lis)

def get():
    for x in lis:
        options.get(x, doSomethingElse)()

def key_in_dic():
    for x in lis:
        if x in options:
            options[x]()
        else:
            doSomethingElse()

def if_else():
    for x in lis:
        if x == 'this':
            doThis()
        elif x == 'that':
            doThat()
        elif x == 'there':
            doThere()
        else:
            doSomethingElse()

Các kết quả:

>>> from so import *
>>> %timeit get()
100 loops, best of 3: 5.06 ms per loop
>>> %timeit key_in_dic()
100 loops, best of 3: 3.55 ms per loop
>>> %timeit if_else()
100 loops, best of 3: 6.42 ms per loop

Đối với 10**5các khóa không tồn tại và 100 khóa hợp lệ ::

>>> %timeit get()
10 loops, best of 3: 84.4 ms per loop
>>> %timeit key_in_dic()
10 loops, best of 3: 50.4 ms per loop
>>> %timeit if_else()
10 loops, best of 3: 104 ms per loop

Vì vậy, đối với một từ điển thông thường, kiểm tra khóa bằng cách sử dụng key in optionslà cách hiệu quả nhất ở đây:

if key in options:
   options[key]()
else:
   doSomethingElse()

options = collections.defaultdict(lambda: doThisMostOfTheTime, {'this': doThis,'that' :doThat, 'there':doThere}); options[something]()hiệu quả hơn một chút.
Aya

Ý tưởng tuyệt vời, nhưng không thể đọc được. Ngoài ra, bạn có thể muốn tách optionsdict để tránh xây dựng lại nó, do đó di chuyển một phần (nhưng không phải tất cả) của logic ra xa điểm sử dụng. Tuy nhiên, mẹo hay!
Anders Johansson

7
bạn có biết liệu điều này có hiệu quả hơn không? Tôi đoán là nó chậm hơn vì nó đang thực hiện tra cứu băm hơn là kiểm tra một hoặc ba điều kiện đơn giản. Câu hỏi là về tính hiệu quả hơn là tính gọn nhẹ của mã.
Bryan Oakley

2
@BryanOakley Tôi đã thêm một số so sánh về thời gian.
Ashwini Chaudhary

1
thực sự nó sẽ hiệu quả hơn để làm try: options[key]() except KeyError: doSomeThingElse()(vì if key in options: options[key]()bạn đang tìm kiếm từ điển hai lần chokey
hardmooth

8

Bạn có thể sử dụng pypy?

Giữ mã gốc của bạn nhưng chạy nó trên pypy mang lại tốc độ tăng gấp 50 lần cho tôi.

CPython:

matt$ python
Python 2.6.8 (unknown, Nov 26 2012, 10:25:03)
[GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from timeit import timeit
>>> timeit("""
... if something == 'this': pass
... elif something == 'that': pass
... elif something == 'there': pass
... else: pass
... """, "something='foo'", number=10000000)
1.728302001953125

Pypy:

matt$ pypy
Python 2.7.3 (daf4a1b651e0, Dec 07 2012, 23:00:16)
[PyPy 2.0.0-beta1 with GCC 4.2.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``a 10th of forever is 1h45''
>>>>
>>>> from timeit import timeit
>>>> timeit("""
.... if something == 'this': pass
.... elif something == 'that': pass
.... elif something == 'there': pass
.... else: pass
.... """, "something='foo'", number=10000000)
0.03306388854980469

Chào Foz. Cảm ơn vì tiền hỗ trợ. Trên thực tế tôi đã sử dụng PyPy (tình yêu nó), nhưng tôi vẫn cần cải thiện tốc độ .. :)
kramer65

Chà! Trước đó, tôi đã thử tính toán trước một mã băm cho 'cái này', 'cái đó' và 'ở đó' - rồi so sánh mã băm thay vì chuỗi. Điều đó hóa ra chậm gấp đôi so với bản gốc, vì vậy có vẻ như so sánh chuỗi đã được tối ưu hóa khá tốt trong nội bộ.
foz

3

Dưới đây là một ví dụ về if với các điều kiện động được dịch sang từ điển.

selector = {lambda d: datetime(2014, 12, 31) >= d : 'before2015',
            lambda d: datetime(2015, 1, 1) <= d < datetime(2016, 1, 1): 'year2015',
            lambda d: datetime(2016, 1, 1) <= d < datetime(2016, 12, 31): 'year2016'}

def select_by_date(date, selector=selector):
    selected = [selector[x] for x in selector if x(date)] or ['after2016']
    return selected[0]

Đó là một cách, nhưng có thể không phải là cách khó thực hiện nhất vì khó đọc hơn đối với những người không thông thạo Python.


0

Mọi người cảnh báo về execvì lý do bảo mật, nhưng đây là một trường hợp lý tưởng cho nó.
Đó là một cỗ máy trạng thái dễ dàng.

Codes = {}
Codes [0] = compile('blah blah 0; nextcode = 1')
Codes [1] = compile('blah blah 1; nextcode = 2')
Codes [2] = compile('blah blah 2; nextcode = 0')

nextcode = 0
While True:
    exec(Codes[nextcode])
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.