Vraag Python als statement-efficiëntie


Een vriend (mede-laagdrempelige recreatieve python-scripter) heeft me gevraagd een code te bekijken. Ik merkte dat hij 7 afzonderlijke uitspraken had die eigenlijk zeiden.

if ( a and b and c):
    do something

de uitspraken a, b, c testten allemaal hun gelijkheid of gebrek aan om waarden in te stellen. Toen ik ernaar keek, ontdekte ik dat vanwege de aard van de tests, ik het hele logicablok kon herschrijven in 2 takken die nooit meer dan 3 diep zijn gegaan en zelden voorbij het eerste niveau zijn gekomen (waardoor de meest zeldzame gebeurtenis uittest) eerste).

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

Voor mij lijkt het logisch dat het sneller zou moeten zijn als je minder, eenvoudigere tests uitvoert die sneller falen en verder gaan. Mijn echte vragen zijn

1) Wanneer ik zeg als en anders, zou het als het waar is, dan wordt het anders volledig genegeerd?

2) In theorie zou dat

if (a en b en c)

evenveel tijd nemen als de drie afzonderlijke if-statements zouden doen?


11
2018-03-29 15:25


oorsprong


antwoorden:


if statements slaan alles over in een else bracket als het evalueert naar true. Opgemerkt moet worden dat zorgen maken over dit soort problemen, tenzij het miljoenen keren per programma-uitvoering is gedaan, "voortijdige optimalisatie" wordt genoemd en moet worden vermeden. Als uw code duidelijker is met drie if (a and b and c) verklaringen, ze moeten worden achtergelaten.


29
2018-03-29 15:31



Ik zou zeggen dat de enkele test net zo snel is als de afzonderlijke tests. Python maakt ook gebruik van zogenaamde kortsluiting evaluatie.

Dat betekent voor (a and b and c), dat b of c zou niet meer worden getest als a is false.

Vergelijkbaar, als je een hebt OR uitdrukking (a or b) en a is true, b wordt nooit geëvalueerd.

Dus om samen te vatten, de clausules niet mislukken sneller met scheiding.


32
2018-03-29 15:32



Code:

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)

Output:

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        

Dus, hoewel de instelling hetzelfde is, is de opschoning voor de gecombineerde uitdrukking sneller omdat er slechts één waarde op de stapel wordt achtergelaten.


18
2018-03-29 15:32



Ten minste in python is efficiëntie de tweede tot leesbaarheid en "Flat is beter dan genest".

Zien De Zen van Python


8
2018-03-29 15:32



Ik betwijfel of je een meetbaar verschil zou zien, dus ik zou aanraden om alles te doen wat de code zo leesbaar mogelijk maakt.


3
2018-03-29 15:32



Als u zich zorgen maakt over b of c als functies die worden aangeroepen in plaats van alleen variabelen die worden geëvalueerd, geeft deze code aan dat kortsluiting uw vriend is:

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"

prints

if b was not called, then short-circuiting works

Maar als je code hebt die dit doet:

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...

dan roept je code nog steeds alle 3 dure functies op. Beter om Python Python te laten zijn:

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

die alleen zoveel dure functies zal bellen als nodig is om de algehele conditie te bepalen.

Bewerk

U kunt dit generaliseren met behulp van de all ingebouwd:

# 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

Nu, als u andere functies moet toevoegen of ze opnieuw wilt ordenen (misschien function_A is erg tijdrovend, en je zou er baat bij hebben om gevallen te filteren die falen function_B of function_C eerst), werk je gewoon het funcs lijst. all kortsluiting maakt net alsof u het als zo had gespecificeerd if a and b and c. (Gebruik als functies zijn samengevoegd any in plaats daarvan gebouwd.)


3
2018-03-29 17:18



if (a and b and c)zal falen als a is vals, en niet de moeite van het controleren b of c.

Dat gezegd hebbende, heb ik persoonlijk het gevoel dat geneste voorwaarden gemakkelijker te lezen zijn dan 2 ^ n combinaties van conditionals.

Over het algemeen kunt u, als u wilt bepalen welke manier van doen het snelst is, een eenvoudige benchmark schrijven met timeit.


1
2018-03-29 15:30