Эффективность заявления Python if - PullRequest
16 голосов
/ 29 марта 2010

Один из друзей (приятель-программист-питонист с низким уровнем навыков) попросил меня посмотреть код. Я заметил, что у него было 7 отдельных заявлений, которые в основном говорили.

if ( a and b and c):
    do something

Все операторы a, b, c проверяют их равенство или отсутствие для установки значений. Когда я посмотрел на него, то обнаружил, что из-за характера тестов я мог переписать весь логический блок в 2 ветви, которые никогда не проходили глубже, чем на 3, и редко проходили первый уровень (делая самый редкий тест вхождения первый).

if a:
    if b:
        if c:
    else:
        if c:
else:
    if b:
        if c:
    else:
        if c:

Мне логически кажется, что должно быть быстрее, если вы делаете меньше, более простые тесты, которые быстрее проваливаются и идут дальше. Мои настоящие вопросы

1) Когда я говорю «если и еще», должно ли «если» быть правдой, все остальное полностью игнорируется?

2) В теории будет

если (а и б и в)

займет столько времени, сколько три отдельных оператора if?

Ответы [ 8 ]

38 голосов
/ 29 марта 2010

Я бы сказал, что одиночный тест так же быстр, как и отдельные. Python также использует так называемую оценку короткого замыкания .

Для (a and b and c) это означает, что b или c больше не будут проверяться, если a равно false.

Аналогично, если у вас есть OR выражение (a or b) и a равно true, b никогда не оценивается.

Итак, чтобы подвести итог, пункты не терпят неудачу быстрее с разделением.

33 голосов
/ 29 марта 2010

if операторы пропустят все в else скобках, если оно будет иметь значение true. Следует отметить, что беспокойство по поводу такого рода проблем, если оно не выполняется миллионы раз за выполнение программы, называется «преждевременной оптимизацией» и его следует избегать. Если ваш код более понятен с тремя if (a and b and c) утверждениями, их следует оставить в.

19 голосов
/ 29 марта 2010

Код:

import dis

def foo():
  if ( a and b and c):
    pass
  else:
    pass

def bar():
  if a:
    if b:
      if c:
        pass

print 'foo():'
dis.dis(foo)
print 'bar():'
dis.dis(bar)

Выход:

foo():
  4           0 LOAD_GLOBAL              0 (a)
              3 JUMP_IF_FALSE           18 (to 24)
              6 POP_TOP             
              7 LOAD_GLOBAL              1 (b)
             10 JUMP_IF_FALSE           11 (to 24)
             13 POP_TOP             
             14 LOAD_GLOBAL              2 (c)
             17 JUMP_IF_FALSE            4 (to 24)
             20 POP_TOP             

  5          21 JUMP_FORWARD             1 (to 25)
        >>   24 POP_TOP             

  7     >>   25 LOAD_CONST               0 (None)
             28 RETURN_VALUE        
bar():
 10           0 LOAD_GLOBAL              0 (a)
              3 JUMP_IF_FALSE           26 (to 32)
              6 POP_TOP             

 11           7 LOAD_GLOBAL              1 (b)
             10 JUMP_IF_FALSE           15 (to 28)
             13 POP_TOP             

 12          14 LOAD_GLOBAL              2 (c)
             17 JUMP_IF_FALSE            4 (to 24)
             20 POP_TOP             

 13          21 JUMP_ABSOLUTE           29
        >>   24 POP_TOP             
             25 JUMP_ABSOLUTE           33
        >>   28 POP_TOP             
        >>   29 JUMP_FORWARD             1 (to 33)
        >>   32 POP_TOP             
        >>   33 LOAD_CONST               0 (None)
             36 RETURN_VALUE        

Таким образом, хотя настройка одинакова, очистка объединенного выражения выполняется быстрее, поскольку в стеке остается только одно значение.

8 голосов
/ 29 марта 2010

По крайней мере, в Python эффективность уступает читабельности, и «Плоская лучше, чем вложенная».

См. Дзен Python

3 голосов
/ 29 марта 2010

Если вас беспокоит, что b или c являются функциями, которые вызываются вместо просто оцениваемых переменных, этот код показывает, что короткое замыкание - ваш друг:

a = False
def b():
    print "b was called"
    return True

if a and b():
    print "this shouldn't happen"
else:
    print "if b was not called, then short-circuiting works"

печать

if b was not called, then short-circuiting works

Но если у вас есть код, который делает это:

a = call_to_expensive_function_A()
b = call_to_expensive_function_B()
c = call_to_expensive_function_C()

if a and b and c:
    do something...

тогда ваш код все еще вызывает все 3 дорогих функции. Лучше позволить Python быть Python:

if (call_to_expensive_function_A() and
    call_to_expensive_function_B() and
    call_to_expensive_function_C())
    do something...

, который будет вызывать столько дорогих функций, сколько необходимо для определения общего состояния.

Редактировать

Вы можете обобщить это с помощью встроенного all:

# note, this is a list of the functions themselves
# the functions are *not* called when creating this list
funcs = [function_A, function_B, function_C]

if all(fn() for fn in funcs):
    do something

Теперь, если вам нужно добавить другие функции или вы хотите изменить их порядок (возможно, function_A занимает очень много времени, и вы выиграете, отфильтровав случаи, которые сначала не пройдут function_B или function_C), вы просто обновите список funcs. all делает короткое замыкание так же, как если бы вы записали if как if a and b and c. (Если функции объединены или объединены, вместо них используйте any встроенный.)

3 голосов
/ 29 марта 2010

Я сомневаюсь, что вы увидите ощутимую разницу, поэтому я бы порекомендовал делать все, что делает код наиболее читабельным.

1 голос
/ 29 марта 2010

if (a and b and c) потерпит неудачу, если a ложно и не потрудится проверить b или c.

Тем не менее, я лично считаю, что вложенные условные выражения легче читать, чем 2 ^ n комбинаций условных выражений.

В общем, если вы хотите определить, какой способ сделать что-либо быстрее, вы можете написать простой тест, используя timeit .

0 голосов
/ 30 марта 2010

if (a and b and c) быстрее и лучше, ради «оптимизации кода настоящего программиста» и читабельности кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...