56

Programming in C I used to have code sections only used for debugging purposes (logging commands and the like). Those statements could be completely disabled for production by using #ifdef pre-processor directives, like this:

 #ifdef MACRO

 controlled text

 #endif /* MACRO */

What is the best way to do something similar in python?

qrdl
  • 34,062
  • 14
  • 56
  • 86
blueFast
  • 41,341
  • 63
  • 198
  • 344
  • I haven't used Python, but for logging, it looks like `Logger.isEnabledFor()` and `Logger.debug()` might work well together. – chris Sep 21 '12 at 05:12
  • Related (and adding as a comment here to make it easier to find; it's lifted from one of the answers below): http://stackoverflow.com/questions/482014/how-would-you-do-the-equivalent-of-preprocessor-directives-in-python – John Y Nov 21 '14 at 22:56

8 Answers8

40

If you just want to disable logging methods, use the logging module. If the log level is set to exclude, say, debug statements, then logging.debug will be very close to a no-op (it just checks the log level and returns without interpolating the log string).

If you want to actually remove chunks of code at bytecode compile time conditional on a particular variable, your only option is the rather enigmatic __debug__ global variable. This variable is set to True unless the -O flag is passed to Python (or PYTHONOPTIMIZE is set to something nonempty in the environment).

If __debug__ is used in an if statement, the if statement is actually compiled into only the True branch. This particular optimization is as close to a preprocessor macro as Python ever gets.

Note that, unlike macros, your code must still be syntactically correct in both branches of the if.


To show how __debug__ works, consider these two functions:

def f():
    if __debug__: return 3
    else: return 4

def g():
    if True: return 3
    else: return 4

Now check them out with dis:

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (3)
              3 RETURN_VALUE        
>>> dis.dis(g)
  2           0 LOAD_GLOBAL              0 (True)
              3 JUMP_IF_FALSE            5 (to 11)
              6 POP_TOP             
              7 LOAD_CONST               1 (3)
             10 RETURN_VALUE        
        >>   11 POP_TOP             

  3          12 LOAD_CONST               2 (4)
             15 RETURN_VALUE        
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE        

As you can see, only f is "optimized".

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • Food for thought: `globals()['__debug__'] = False; if __debug__: print 42` prints 42 :) – nneonneo Sep 21 '12 at 05:47
  • 1
    Thanks, very helpful description of `__debug__`. You say logging.debug would be very close to a no-op. What about logging commands which have big dictionaries / objects? Would that not require certain processing (stack management?) during parameter passing, even if the logging module decides to discard the message and return immediately? – blueFast Sep 21 '12 at 05:51
  • 1
    `logging.debug("x=%s y=%s z=%s", x, y, z)` passes references to the string, `x`, `y`, and `z` to the function (four platform pointers, packed into a `tuple`). It doesn't matter what `x`, `y` or `z` are; it's exactly the same overhead (Python passes *everything* by reference). – nneonneo Sep 21 '12 at 05:53
  • 1
    ok, I think I can probably live with that. I assume the overhead of passing N references is really minimal. Except that I have been so stupid to do `log.debug('%s' % (big_object))` in lots of places, and changing that will not be easy. And last question: in case I decide to use `__debug__`, I guess I should precede my logging statements with `if __debug__:` and run with `python -O`? I have a problem here: my python scripts are "crunchbanged" (#!/usr/bin/env python), and I call them by name. How would I pass the -O flag here? – blueFast Sep 21 '12 at 05:57
  • 1
    Using interpolation in logging calls is an extremely common mistake, so don't fret. At least it's reasonably easy to `grep` for. If you can affect your environment, you can use `PYTHONOPTIMIZE=1` (I amended my answer); otherwise, run `python` through a wrapper of your own devising. Note that there are other alternatives to `__debug__` such as @6502's note about conditional definition, so you should consider `__debug__` a sort of last-resort way to squeeze out extra performance. – nneonneo Sep 21 '12 at 06:01
  • Note that you should see an immediate performance benefit just from changing `logging.debug("%s" % obj)` to `logging.debug("%s", obj)`, so you may not need `__debug__` after that. – nneonneo Sep 21 '12 at 06:02
  • 1
    Sure, I have already considered that. That is what I *really* need to do, but I am not sure I can automatically edit all instances (I have over 30000 lines of code). And I am specially worried that I will edit the *wrong* instances. So I need to do this with care. I was now looking for a simple solution to my problem. Maybe `__debug__` is the easiest thing to implement. – blueFast Sep 21 '12 at 06:14
13

It is important to understand that in Python def and class are two regular executable statements...

import os

if os.name == "posix":
    def foo(x):
        return x * x
else:
    def foo(x):
        return x + 42
...

so to do what you do with preprocessor in C and C++ you can use the the regular Python language.

Python language is fundamentally different from C and C++ on this point because there exist no concept of "compile time" and the only two phases are "parse time" (when the source code is read in) and "run time" when the parsed code (normally mostly composed of definition statements but that is indeed arbitrary Python code) is executed.

I am using the term "parse time" even if technically when the source code is read in the transformation is a full compilation to bytecode because the semantic of C and C++ compilation is different and for example the definition of a function happens during that phase (while instead it happens at runtime in Python).

Even the equivalent of #include of C and C++ (that in Python is import) is a regular statement that is executed at run time and not at compile (parse) time so it can be placed inside a regular python if. Quite common is for example having an import inside a try block that will provide alternate definitions for some functions if a specific optional Python library is not present on the system.

Finally note that in Python you can even create new functions and classes at runtime from scratch by the use of exec, not necessarily having them in your source code. You can also assemble those objects directly using code because classes and functions are indeed just regular objects (this is normally done only for classes, however).

There are some tools that instead try to consider def and class definitions and import statements as "static", for example to do a static analysis of Python code to generate warnings on suspicious fragments or to create a self-contained deployable package that doesn't depend on having a specific Python installation on the system to run the program. All of them however need to be able to consider that Python is more dynamic than C or C++ in this area and they also allow adding exceptions for where the automatic analysis will fail.

Gringo Suave
  • 29,931
  • 6
  • 88
  • 75
6502
  • 112,025
  • 15
  • 165
  • 265
  • Thanks for the general explanation. So what you are suggesting is to replace my loggers with a simple function for production, which throws away the messages? Interesting approach, but it seems a bit convoluted, and I do not see any advantage to using the logging module, which is basically doing the same: throwing away the messages if the level is not allowed. – blueFast Sep 21 '12 at 06:08
  • 1
    Unfortunately Python doesn't support macros so your logging facilities calls (or the standard logging module calls) will always be compiled in and executed unless you really want to go down the black magic path to remove them. This shouldn't be much of a concern for the call itself (if you chose Python then for sure raw execution speed is not your main concern) but can be annoying for evaluation of logging parameters if they are costly to compute. – 6502 Sep 21 '12 at 06:23
9

Here is an example that I use to distinguish between Python 2 & 3 for my Python Tk programs:


import sys
if sys.version_info[0] == 3:
    from tkinter import *
    from tkinter import ttk
else:
    from Tkinter import *
    import ttk

""" rest of your code """

Hope that is a useful illustration.

Richard
  • 91
  • 1
  • 1
7

As far as I am aware, you have to use actual if statements. There is no preprocessor, so there is no analogue to preprocessor directives.

Edit: Actually, it looks like the top answer to this question will be more illuminating: How would you do the equivalent of preprocessor directives in Python?

Supposedly there is a special variable __debug__ which, when used with an if statement, will be evaluated once and then not evaluated again during execution.

Community
  • 1
  • 1
Andrew Gorcester
  • 19,595
  • 7
  • 57
  • 73
  • 1
    `__debug__` definitely exists, and it's definitely *very* special. You can't assign to it (that's a *`SyntaxError`*), and it does do exactly what is described at the other question when used with `if`. – nneonneo Sep 21 '12 at 05:27
  • This looks like an interesting approach. So I should precede my logging statements with `if __debug__:` and run with `python -O`? I have a problem here: my python scripts are "crunchbanged" (`#!/usr/bin/env python`). How would I pass the `-O` flag here? And another question: would `__debug__` completely avoid processing the related statements, even in the syntax parsing phase? – blueFast Sep 21 '12 at 05:47
  • No, `__debug__` doesn't free you from being syntactically correct (unlike macros). It does, however, exclude code from the generated bytecode (unlike basically everything else in Python). – nneonneo Sep 21 '12 at 05:50
2

There is no direct equivalent that I'm aware of, so you might want to zoom-out and reconsider the problems that you used to solve using the preprocessor.

If it's just diagnostic logging you're after then there is a comprehensive logging module which should cover everything you wanted and more.

http://docs.python.org/library/logging.html

What else do you use the preprocessor for? Test configurations? There's a config module for that.

http://docs.python.org/library/configparser.html

Anything else?

John Mee
  • 50,179
  • 34
  • 152
  • 186
  • Mostly for logging, and I am using the logging module for that. The problem is that my log statements are sometimes very heavy, including passing of big objects. I want to *completely* remove all parsing of those log statements for production. I do not know that I can do that with the logging module, since even if the logging level is decreased, the logging module still will be called in order to decide whether or not to show the logging messages. – blueFast Sep 21 '12 at 05:42
  • It sounds like you want to control whether or not the logged value gets evaluated in the first place. I don't think Python's *logger* module has a facility for this. You might be able to do something like have a helper function, combined with putting the logged expressions in thunks: *def myLog(thunk): logger.debug(thunk())*. Usage would then look something like *myLog(lambda: repr(someValue))* where we explicitly put lambdas to delay evaluation. Then *myLog* can be customized to check whether or not you really want to log. – dyoo Sep 21 '12 at 06:20
0

If you are using #ifdef to check for variables that may have been defined in the scope above the current file, you can use exceptions. For example, I have scripts that I want to run differently from within ipython vs outside ipython (show plots vs save plots, for example). So I add

     ipy = False
     try:
        ipy = __IPYTHON__
     except NameError:
        pass

This leaves me with a variable ipy, which tells me whether or not __IPYTHON__ was declared in a scope above my current script. This is the closest parallel I know of for an #ifdef function in Python.

For ipython, this is a great solution. You could use similar constructions in other contexts, in which a calling script sets variable values and the inner scripts check accordingly. Whether or not this makes sense, of course, would depend on your specific use case.

mmdanziger
  • 4,466
  • 2
  • 31
  • 47
-1

If you're working on Spyder, you probably only need this:

try:
    print(x)
except:
    #code to run under ifndef
    x = "x is defined now!"
#other code

The first time, you run your script, you'll run the code under #code to run under ifndef, the second, you'll skip it. Hope it works:)

Max Wong
  • 694
  • 10
  • 18
-1

This can be achieved by passing command line argument as below:

import sys

my_macro = 0

if(len(sys.argv) > 1):
    for x in sys.argv:
        if(x == "MACRO"):
            my_macro = 1

if (my_macro == 1):
    controlled text

Try running the following script and observe the results after this:

python myscript.py MACRO

Hope this helps.

DesiKeki
  • 656
  • 8
  • 9