Checking for __debug__ and some other condition in Python

Question:

In Python sometimes I want to do something like (1)

if __debug__ and verbose: print "whatever"

If Python is run with -O, then I’d like for that whole piece of code to disappear, as it would if I just had (2)

if __debug__: print "whatever"

or even (3)

if __debug__:
    if verbose: print foo

However, that doesn’t seem to happen (see below). Is there a way I can get the run-time efficiency of #3 with compact code more like #1?

Here’s how I tested that I’m not getting the efficient code I want:

#!/usr/bin/python2.7

from dis import dis
import sys

cmds = ["""
def func ():
    if __debug__ and 1+1: sys.stdout.write('spam')""",   """
def func():
    if __debug__: sys.stdout.write('ham')""",     """
def func():
    __debug__ and sys.stdout.write('eggs')"""]

print "__debug__ is", __debug__, "nnn"

for cmd in cmds:
    print "*"*80, "nSource of {}nncompiles to:".format(cmd)
    exec(cmd)
    dis(func)
    print "n"*4

Running this gives

__debug__ is False 



******************************************************************************** 
Source of 
def func ():
    if __debug__ and 1+1: sys.stdout.write('spam')

compiles to:
  3           0 LOAD_GLOBAL              0 (__debug__)
              3 POP_JUMP_IF_FALSE       31
              6 LOAD_CONST               3 (2)
              9 POP_JUMP_IF_FALSE       31
             12 LOAD_GLOBAL              1 (sys)
             15 LOAD_ATTR                2 (stdout)
             18 LOAD_ATTR                3 (write)
             21 LOAD_CONST               2 ('spam')
             24 CALL_FUNCTION            1
             27 POP_TOP             
             28 JUMP_FORWARD             0 (to 31)
        >>   31 LOAD_CONST               0 (None)
             34 RETURN_VALUE        





******************************************************************************** 
Source of 
def func():
    if __debug__: sys.stdout.write('ham')

compiles to:
  3           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE        





******************************************************************************** 
Source of 
def func():
    __debug__ and sys.stdout.write('eggs')

compiles to:
  3           0 LOAD_GLOBAL              0 (__debug__)
              3 JUMP_IF_FALSE_OR_POP    21
              6 LOAD_GLOBAL              1 (sys)
              9 LOAD_ATTR                2 (stdout)
             12 LOAD_ATTR                3 (write)
             15 LOAD_CONST               1 ('eggs')
             18 CALL_FUNCTION            1
        >>   21 POP_TOP             
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE        
Asked By: user1717856

||

Answers:

No, you can’t. Python’s compiler is not nearly smart enough to detect in what cases it could remove the code block and if statement.

Python would have to do a whole lot of logic inference otherwise. Compare:

if __debug__ or verbose:

with

if __debug__ and verbose:

for example. Python would have to detect the difference between these two expressions at compile time; one can be optimised away, the other cannot.

Note that the difference in runtime between code with and without if __debug__ statements is truly minute, everything else being equal. A small constant value test and jump is not anything to fuss about, really.

Answered By: Martijn Pieters

You can do this since Python 3.10+. As long as you keep the if condition from getting too complicated and put the check on __debug__ first, the newer python versions behave as you’d want it to. Here’s a modified version of the original code:

from dis import dis
import sys

def func_debug_only(x):
    if __debug__:
        print('spam')

def func_debug_and_const(x):
    if __debug__ and True:
        print('spam')

def func_const_and_debug(x):
    if True and __debug__: # Using const int instead of True is identical
        print('spam')

def func_truth_and_debug(x):
    if 1==1 and __debug__: # -O doesn't remove this
        print('spam')

def func_debug_and_var(x):
    if __debug__ and x: # -O removes this
        print('spam')

def func_var_and_debug(x):
    # -O doesn't remove this; This makes sense as using x in the if
    # statement might trigger x.__bool__() or x.__len__() that might
    # have unpredictable (to the interpreter side effects). Even if
    # the if condition fails, order of condition evaluation would
    # mean that bool(x) is evaluated.
    if x and __debug__:
        print('spam')

def func_debug_nested(x):
    if __debug__:
        if x:
            print('spam')

def func_debug_nested2(x):
    if x: # Doesn't completely go away; 3.12 generates slightly smaller code than 3.10
        if __debug__:
            print('spam')

print("__debug__ is", __debug__, "on version", sys.version_info)
for fn in [func_debug_only, func_debug_and_const, func_const_and_debug, func_truth_and_debug, func_debug_and_var, func_var_and_debug, func_debug_nested, func_debug_nested2]:
    print('n', fn.__name__)
    dis(fn)

This gives you:

__debug__ is False on version sys.version_info(major=3, minor=10, micro=5, releaselevel='final', serial=0)

 func_debug_only
  5           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

 func_debug_and_const
  9           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

 func_const_and_debug
 13           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

 func_truth_and_debug
 17           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE        6 (to 12)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
        >>   12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

 func_debug_and_var
 21           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

 func_var_and_debug
 25           0 LOAD_FAST                0 (x)
              2 POP_JUMP_IF_FALSE        4 (to 8)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
        >>    8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

 func_debug_nested
 29           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

 func_debug_nested2
 34           0 LOAD_FAST                0 (x)
              2 POP_JUMP_IF_FALSE        4 (to 8)

 35           4 LOAD_CONST               0 (None)
              6 RETURN_VALUE

 34     >>    8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
Answered By: Bhu
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.