Manual for the 'bobs' BitBoost Python Obfuscator for Obfuscating Python Source Code: Version 3.3

Chris Niswander

for obfuscator version 3.3

manual date 2017-01-31

© Copyright 2017 by Chris Niswander and BitBoost (

1. Scope of This Document

This is a manual for version 3.3 of the 'bobs' Python source code obfuscator by Chris Niswander.

Other Python obfuscation tools by Chris Niswander are not covered by this manual.

2. Summary of Benefits

The obfuscator can provide two main benefits:

The obfuscator outputs Python source code:

The obfuscator can systematically change to gibberish most of the identifiers defined by your code: variable names, function names, function argument names, class names, class member names (data, methods, and method arguments), object names, and module names.

A body of code consisting of multiple directories, constituting many packages and subpackages, can be obfuscated as a single unit. These (sub)directory/(sub)package names can themselves be obfuscated in the output code.

The obfuscator recognizes through a general automated process which identifiers in your code can safely be changed without harming code functionality.

You have options to override the obfuscator's automatic judgement in the cases you specify, to selectively control which identifiers are changed and which are preserved by the obfuscation process.

Comments and docstrings are removed and/or replaced. Module docstrings can optionally be replaced by your up-to-date copyright notices.

In case your code might include bugs, the obfuscator includes a utility that Python code's author/publisher can use for de-obfuscating tracebacks.

Additional features, options, and use scenarios are discussed in more detailed parts of this manual.

3. Versions of Python and Python Features Supported by the Obfuscator

The obfuscator runs on Windows, Linux/BSD, and Mac OS X (Apple Macintosh.) Some other platforms may also be supported.

The obfuscator runs under Python 2.5, 2.6, and 2.7.

All features of Python 2.5 are supported, and almost all features of Python 2.6 and/or 2.7 are also supported.

For example, from __future__ import absolute_import is fully supported. Absolute imports and explicit relative imports are supported. Older-style imports are also supported. from __future__ import print_function is best supported when running in Python 2.7 or later.

Some features of Python 3 (devised for and made standard features of the Python language in Python 3) are best supported by using from __future__ import ... statements in your code to make your code compatible with a broader range of Python versions. See the appropriate subsection of this manual for details.

For more information, see Supported and Unsupported Python Language Features, and Obfuscating code that uses some Python 3 features while obfuscating with a Python 2 obfuscator.

4. Definitions (in logical order of presentation)

4.1. module

In Python, a "module" normally is a single file of source code (or is a special form corresponding to that single file of source code, e.g. a .pyc file.)

A file named corresponds to a module named foo .

4.2. package

In Python, a "package" is an entire directory of files of source code, which can be imported as a unit. To permit importation as a package, one of the files in the directory is an file.

For more information about the obfuscator's handling of packages and subpackages, see Obfuscating Packages and Subpackages as a Single Unit, in Which Even Inter-Package Interfaces Are Themselves Obfuscated.

4.3. corpus

In our jargon, a body of code that you want to obfuscate.

A corpus might be a program or a package. While the manual often refers to "your program", the same obfuscation method normally will work on a corpus that is a single-directory package. (See above for definition of "package".)

If the corpus' directory includes subdirectories that are actually packages in their own right, those subdirectories are part of the corpus (as of version 3.0 of the obfuscator.)

Just as the pre-obfuscated corpus did, a properly obfuscated corpus will continue to work correctly when used with the nonobfuscated packages.

4.4. project settings file

also known as a .poi file:

4.5. .poi file

a file which, through settings/parameters it specifies, instructs the obfuscator to obfuscate a corpus according to your wishes.

4.6. identifier

An identifier might be a variable name, a module name, a class name, an object name, a function name, a method name, a package name, etc.

An identifier is a name used in/by software code.

4.7. 'sacred' (regarding identifiers)

In our obfuscation jargon, a 'sacred' identifier is one that the obfuscator shall not change/replace.

The obfuscator attempts to automatically treat identifiers as sacred if they appear to be needed to interface to code outside your corpus.

Some examples of identifiers likely to be sacred by default:

(Many other identifiers might be sacred by default, depending on which non-obfuscated modules and packages your corpus imports directly or indirectly. You can also manually specify whether an indentifier is to be sacred or profane.)

4.8. 'profane' (regarding identifiers)

In our obfuscation jargon, a 'profane' identifier is one that the obfuscator shall almost certainly change/replace.

Usually profane identifiers are identifiers defined by your own code, that are not obviously used/needed by sacred code.

5. Installing the Obfuscator

The obfuscator is provided as a collection of .pyc files that a Python interpreter can run. You might copy these files to a directory on the machine where you wish to run the obfuscator. If the files are supplied to you on a CD, you can even run them directly on (from) the CD.

Use the .pyc files that are appropriate for your version of Python. Supported versions of Python are 2.5, 2.6, and 2.7. The .pyc files for each version of Python are delivered to you in their own separate directory or in their own separate zipfile (or other type of archive file as appropriate to your case). (In some cases, the three individual zipfiles might be delivered within a combined zipfile with a name like ''.)

You may add the obfuscator files' directory to your PYTHONPATH shell variable, or when you run the obfuscator you may simply specify it by pathname so it will be found.

5.1. Installing Optional Obfuscator Files

If you are using advanced features of the obfuscator that require be imported by your code, you can copy that file directly into your project's source code directory (or directories).

Alternatively, you could copy into any directory that you have in your PYTHONPATH, so it will be available to your corpus.

A copy of the file is provided within this manual.

6. Running the Obfuscator

Before you run the obfuscator you will have to create a .poi file specifying what the obfuscator does for your project. (Creating .poi files is explained in the Beginning Use Cases: Let's Obfuscate and Project Settings File (.poi file) sections of this manual.)

Windows example of command line to run the obfuscator, if d:\ob-4py27 is where you happen to have placed the obfuscator .pyc files for Python 2.7:

      python d:\ob-4py27\ someprojectfile.poi 

a similar UNIX example:

      python ~/ob-4py27/ someprojectfile.poi

If you don't specify the the .poi file on the command line, the obfuscator will prompt you for it.

7. Beginning Use Cases: Let's Obfuscate!

7.1. Simplest Possible Case

Suppose you have a program called, that you want to obfuscate into a program called before you release it to the public.

With a text editor you can make a .poi file that contains the following instructions to the obfuscator. Note that the .poi file is simply Python source code, using Python data types.

The name of a .poi file may include letters, numerals, and underscores. Examples:

Any relative pathnames (of directories or files) specified in the .poi file are assumed to be specified relative to the location of the .poi file itself. On Windows, any absolute pathnames should not be in UNC form: for example, 'X:\\foo\\' is ok, but '\\\\SRVR19\\DRIVE5\\foo\\' is not ok.

For example, here's a .poi file that obfuscates a program '' to an output program called '' in subdirectory 'out'. Any other modules in the same directory that are imported by (imported directly or indirectly) are automatically included in the obfuscated output.

        # main file of the program to be obfuscated.  
        # (You can leave off the .py extension.)
        input_root_module = 'simple'    
        # name for program's main file once obfuscated.  
        # (You can leave off the .py extension.)
        output_root_module = 'simple_public'
        # where to look for corpus code to obfuscate, 
        # and where to put the obfuscated code.  
        # Takes code from directory '.' 
        # and writes obfuscated code to directory 'out'
        project_dirs_in2out = ['.', './out']
        # easiest way to handle keyword arguments and get fully working code.
        keyword_arguments_shortcut = True

To produce an obfuscated program in subdirectory out, run the obfuscator, and when it prompts you, enter the pathname of the .poi file.

Alternatively, you can specify the .poi file on the command line, for example:

      python bobs.pyc simple_public_1.poi

7.2. If Your Corpus Includes Function/Method Calls With Keyword Arguments

7.2.1. The Easiest Method: the keyword_arguments_shortcut .poi file parameter

The single easiest way to handle your code's use of keyword arguments in function and method calls is to use this setting in your .poi file:

      keyword_arguments_shortcut = True

The only reason this isn't the default setting is to provide reverse compatibility to people using project files that they developed for older versions of the obfuscator.

This setting prevents any keyword argument's name from being obfuscated, wherever it might appear in your code, if the keyword argument name isn't always made profane by associating it with OB_KEYARG() whenever it occurs in in function/method calls.

To get more obfuscation of some argument names in your code than this setting alone provides, see the other techniques discussed in Keyword Arguments: Intermediate And Advanced Techniques.

7.3. Simple Easy Use Case That Applies to Most Smallish Programs

For most programs you'll want a few more settings in your .poi file.

To emplace a copyright notice into the module docstring (aka "Documentation String") of each file of your obfuscated code, where reverse engineers could easily see it, you'll want to add the setting message_for_module_docstring.


          message_for_module_docstring = \
                    ( C ) Copyright 2003-2012 by Bob Smith.  All Rights Reserved. 
                    Please see for more information.

If your obfuscated program crashes, the user will probably get a "traceback" message showing the state of your program's call stack when it crashed. If the user sends you the traceback message (possibly by email?), the message will look relatively cryptic, because identifiers will be obfuscated.

When you obfuscate your program, you can also produce a utility script that deobfuscates tracebacks, by using the trfix setting in your .poi file.


            # produces,
            # in subdirectory my_secret_troubleshooting_tools
            trfix = 'my_secret_troubleshooting_tools/'  

Feed the traceback text into the utility's stdin, followed by a newline/return, and the deobfuscated traceback will be written to stdout.

For more information, see the trfix entry in the Project Settings File -- Introductory Optional Settings section of this manual .

Related settings include makefix and revdict.

It is possible that the obfuscator might incorrectly obfuscate some identifier name in your corpus that it should not obfuscate, or might fail to obfuscate an identifier that it should obfuscate. In this case, you might want to use the .poi file settings profane_names and/or sacred_names

See the Project Settings File -- Introductory Optional Settings section of this manual for instructions.

7.4. Releasing Only .pyc Files Without Original Source Code

First, obfuscate your corpus to .py files in the normal way (see other use cases).

Then, you can use the file, which is included in the Python standard library, to compile your .py files to .pyc files.

Example command line for Python 2.4 and up:

          python -m compileall .


Example command line for Python 2.3 and up:

          python c:\python23\lib\ .


Finally, use a shell script, bat file, makefile, or any other appropriate method to copy and/or package up your .pyc files.

If you are using the .poi file setting message_for_module_docstring, when you byte-compile your Python code you won't want to specify special optimizations that might remove the docstring (aka "Documentation String") from each module.

Also see: makefix and revdict, in the Project Settings File -- Intermediate and Advanced Optional Settings section of this manual.

7.5. Packaging Obfuscated Code Into an Executable With py2exe, freeze, Etc.

Obfuscated code bundles up into an executable using py2exe, freeze, and other similar tools just like non-obfuscated code does.

Depending on your build process, you might need to use makefix to adapt a makefile, shell script, or batchfile for obfuscated module names. If so, please see the makefix setting in the Project Settings File -- Intermediate And Advanced Optional Settings section of this manual.

7.6. More Use Cases

For more use cases, please see the Intermediate and Advanced Use Cases section of this manual.

8. Project Settings File (.poi file)

8.1. What Is It?

The project settings file, or .poi file, is simple Python source code, supplying settings to the obfuscator so it can do the right thing for your obfuscation project.

Settings are supplied by setting variables, e.g.

        settingname = settingvalue

Each of your obfuscation projects has its own .poi file, which is used as described in Running the Obfuscator.

8.2. Mandatory Settings

They are:

For explanations of these, see the Beginning Use Cases -- Simplest Possible Case section of this manual.

8.3. Introductory Optional Settings

8.3.1. keyword_arguments_report = True

If set to True, causes obfuscator to tell you where function calls use keyword arguments.

8.3.2. keyword_arguments_shortcut

See this section.

8.3.3. message_for_module_docstring

Originally designed to help you make sure your copyright notice is present in each and every module of your obfuscated code.

Specifies a message to be emplaced into the obfuscated code, in each module's docstring (aka "Documentation String").


            message_for_module_docstring = \
                ( C ) Copyright 1900 by Y2K Scare Consulting Co.  
                All Rights Reserved. 
                Please see for more information.

8.3.4. namebases

Specifies a list of strings, which will begin the replacement identifier names generated by the obfuscator.


If your project settings file (.poi file) says:

              namebases = ['truth__', 'beauty__', 'what__']

You might get obfuscated code resembling this:

              def truth__2e3(what__2f8, beauty__2f7):
                what__2f8.beauty__2f7 = beauty__2f7

8.3.5. profane_names

What if the obfuscator refuses to obfuscate one or more identifiers in your code that you would prefer that it obfuscate?

You can use profane_names to override the obfuscator's default behavior.

Specifies a single string, containing one or more identifier names separated by whitespace. These identifier names should be obfuscated!


            # Standard library includes built-in functions, modules, etc.
            # named upper, zip, and path,
            # but I use my own hand-rolled functions instead,
            # and I want their names obfuscated!
            profane_names = '''
                upper zip

Logically, these names should not include names that you make 'sacred' (protect from obfuscation) by sacred_names nor by NOOB_IDS... in-code directives.

8.3.6. sacred_names

For use if the obfuscator, contrary to your wishes, obfuscates one or more identifiers in your code that you would prefer that it not obfuscate.

Specifies a single string, containing one or more identifier names separated by whitespace. These identifier names shall not be obfuscated!


            # I want a user of my library to be able to see 
            # these variables, functions, and classes 
            # under their proper names!
            sacred_names = '''
                star planet dwarf_planet asteroid planetoid comet 
                orbital_elements avg_albedo time_step

These names should not include names that you make 'profane' (mark for obfuscation) by profane_names nor by OB_IDS... in-code directives.

8.3.7. trfix

Write to this pathname a Python utility script that deobfuscates 'traceback' / "stack crawl" from the obfuscated Python code. Also deobfuscates the code itself.

To deobfuscate:

  1. run the utility script
  2. feed into the utility's stdin (for example by copying-and-pasting) the traceback, stack crawl, or obfuscated code that you want deobfuscated.
  3. the deobfuscation is fed to stdout.


            trfix = 'my_secret_troubleshooting_tools/'

8.4. Intermediate And Advanced Optional Settings

8.4.1. assert_enhancement

      assert_enhancement = True

Slightly enhances assert statements so that:

8.4.2. blockout_sacred_names

      blockout_sacred_names = True

The rationale behind this feature is discussed in Intermediate And Advanced Use Cases -- Copyright Registration.

If you wish to produce a version of your source code with the names of most standard library functions, modules, etc. provided by the Python interpreter 'blocked out', this setting can provide such a version of your source code. The resulting source code cannot be run, as the blocked out names have lost information!

One or more blocked-out spaces might be inserted before or after some names, before both the names and the inserted spaces are 'blocked out.' This increases the difficulty of guessing the blocked out strings.

(In previous versions of the obfuscator, this feature wrote output source code files with utf-16 encoding to accommodate Unicode characters that had the appearance of solid black blocks. However, in the present version, to block out the deleted information we use X or space characters that can be represented in either ASCII or Unicode, following a suggestion from the United States Copyright Office: see e.g.

Also see:

8.4.3. countstart_renaming

The numbers included in replacement identifier names shall include none less than this number.


            countstart_renaming = 0x10000  # I like xx__10001, not xx_1.

8.4.4. digits_renaming

When renaming things, instead of using commonplace counting systems such as octal or hexadecimal, use the specified strings as digits.


            # I like code with names like:
            #   ____wowsoverydoge = ___muchohsoshiba(___muchdogemuchso, 2)
            namebases = ['____', '___']
            digits_renaming = ['shiba', 'wow', 'so', 'very', 'much', 'oh', 'doge']

8.4.5. doublecase_renaming = True

If True, name generation shall try to use both lowercase and uppercase versions of the same number, if that number contains any letters. Fully compatible with e.g. similar_chars_renaming.

In theory, this setting could present problems if renamed source code files will be outputted to and/or used on an operating system that does a poor job of distinguishing between uppercase and lowercase letters in filenames.

8.4.6. makefix

Write to this pathname a Python utility script that obfuscates standard input by applying the name replacements used in obfuscating the names of modules (source code files) in the project source code.


            makefix = 'release_builder/'


Can be used for converting a makefile that acts upon your non-obfuscated code to a corresponding makefile to act upon your obfuscated code.

To obfuscate, run the utility script with the non-obfuscated input fed into stdin; obfuscated form is written to stdout.

8.4.7. manual_renames

A dictionary that maps profane names to specific replacement names specified by the obfuscator's user.


          manual_renames = {
            'my_module_1':   'this_is_not_a_module1',
            'my_function_1': 'list_',
            'MyClass1':      'getatr'

Now MyClass1 will be renamed getatr.

8.4.8. non1to1renaming = False

Tells the obfuscator that any specific identifier to be replaced, when replaced, should always be replaced with the same replacement.


If you aren't using this setting, and you input the code

              class class_2:
                def __init__(self, a):
                  self.a = a
                def get_a(self):
                  return self.a

You might get the output:

              class xx__20001S:
                def __init__(xx__200015, xx__20000O):
                  xx__200015.xx__20000O = xx__20000O
                def xx__20001O(xx__20001Z):
                  return xx__20001Z.xx__20000O

Notice that 'self' has been obfuscated to two different identifiers: xx__200015 and xx__20001Z.

If for some reason this behavior is unsatisfactory, you can set non1to1renaming=False, in which case you might get the output:

              class xx__200012:
                def __init__(xx__200005, xx__20000Z):
                  xx__200005.xx__20000Z = xx__20000Z
                def xx__20001l(xx__200005):
                  return xx__200005.xx__20000Z

Notice that 'self' has been obfuscated to only one identifier: xx__200005.

8.4.9. octal_renaming

Shall replacement identifier names include octal numbers (rather than the default, hexadecimal)?


            octal_renaming = True  # I like xx_174, not xx_7c.

8.4.10. original_linenum_comments = True

Each line of obfuscated code is given an end-of-line comment showing the corresponding line number in the original, non-obfuscated code. (Exceptions: the comment may be omitted if no corresponding line actually exists in the original code, and is certainly omitted whenever necessary to avoid syntax errors.)

Some people find this very useful with trfix.

8.4.11. permit_pkg_renaming = True

Permits (sub)packages/(sub)directories in your profane code directory to be renamed by the obfuscation process.

To rename specific (sub)packages/(sub)directories, their original names must also be mentioned in the profane_names and/or manual_renames .poi file settings. (Otherwise, they will not be renamed by the obfuscator.)

For more information, also see Obfuscating Packages and Subpackages as a Single Unit, in Which Even Inter-Package Interfaces Are Themselves Obfuscated.

8.4.12. redact_calls_to_callables_named

In your project file (.poi file), you can use the redact_calls_to_callables_named setting to instruct the obfuscator to remove calls to functions or methods that are called by using specific names.


            redact_calls_to_callables_named = [
               'debug_log', 'internal_debug',

If a call's return value is actually used by the calling code, the call cannot be removed as cleanly, so the call is not removed in this case.

8.4.13. redact_directives_added

In your project file (.poi file), you can use the redact_directives_added setting to define additional equivalents to the OB_REDACT directive.

            redact_directives_added = ['OB_REDACT_FOR_OSX']

These added equivalents to OB_REDACT work as OB_REDACT does in both if: 'OB_REDACT' directives and OB_REDACT docstrings.

You can use this feature to redact different code from different builds (from different obfuscated versions) of your software.

8.4.14. sacred_names_case_insensitive

Similar to sacred_names, except that it is case-insensitive.

8.4.15. sacred_names_wildcards

Accepts a list of one or more 'wildcard'-style name patterns, to make matching names sacred.

For example,

            sacred_names_wildcards = [
              'test_*', 'another_example*', '*_is_a_great_programmer'

will cause test_abc, another_exampleQwerty15_stuff, and Bala_is_a_great_programmer to be sacred names. The asterisk acts as a wildcard. One or more asterisks may be placed at any point within a pattern.

8.4.16. setting_replacements

This is a relatively advanced option, but potentially useful for some specialized and/or custom software builds.

In your project file (.poi file), you can use the setting_replacements setting to specify that you want assignments to variables or attributes having certain names to have their rvalues replaced with certain other rvalues.

The replacement values may be specified in the .poi file as constant values or as expressions. If an expression is used to specify the replacement rvalue, the ultimate rvalue used will be the expression's actual value in the .poi file.

            setting_replacements = [
                ('licensee_name', _licensee_name),
                ('default_user_language', 'Inuktitut'),

At a minimum, the types supported are integer, float, string, unicode, list, and tuple. Type bytestring is also supported, in Python 2.6 and up.

8.4.17. similar_chars_renaming

The numbers included in replacement identifier names shall use a set of digits in which some digits are letters chosen for their visual similarity to other digits, e.g. lowercase L looks like the numeral 1.


            similar_chars_renaming = True # I'd rather have xx_1l than xx_13.

8.4.18. transitive_attribute_sanctifiers

Experimental feature.

Tells the obfuscator that variables (and attributes) having certain names should not have their attributes' names obfuscated.

Uses a single-string format similar to sacred_names.

            value_replacements = (
               # These are xmlrpclib ServerProxy objects;
               # calls to them are usually Remote Procedure Calls
               # to remote code wherein we cannot obfuscate names.
               'server_yoyodyne ' 

Now we can make RPC calls to remote procedures, such as

            print server_yoyodyne.dimensionTracker.getForecast()
            print remotes.server_weyland_yutani.employeeDB.findReplicantsByFirstName('John')

and obfuscate those calls without worrying so about the fact that dimensionTracker and getForecast must match the names used in non-obfuscated remote code.

8.4.19. value_replacements

This is a relatively advanced option, but potentially useful for some specialized and/or custom software builds.

In your project file (.poi file), you can use the value_replacements setting to specify that you want certain simple constant values occurring in your code to be replaced by certain others.

            value_replacements = [
                ('LICENSEE_NAME', _licensee_name),
                ('DEFAULT_USER_LANGUAGE', 'Inuktitut'),

The types supported are integer, float, string, and unicode. Type bytestring is also supported, in Python 2.6 and up.

9. Directives In Your Program Code

Directives are categorized below according to the form in which you can emplace in your code.

9.1. Docstring Directives

The standard Python tutorial at or explains what a docstring (aka "Documentation String") is.

9.1.1. """EXPORT ..."""

If the docstring for a function, class, module, or method begins with the all-caps word EXPORT

9.1.2. """OB_REDACT ..."""

If the docstring for a function definition begins with the all-caps word OB_REDACT, the entire function definition will be redacted (removed) from the code during obfuscation.

Example code:

          # The obfuscator will remove 
          # the useful version of my_debugger_help_fn1(),
          # leaving only a useless version.
               This function is useful for debugging and testing, 
               but in a release it would provide help to reverse engineers.
            print "The true significance of a is:", repr(a.significance)
          # At this point in the original code,
          # the useful version of the function has overriden the useless version.

9.2. 'Orphan String' Directives

9.2.1. Using 'Orphan Strings'

An 'orphan string' is a string that sits in your source code but is not used in any way, not even e.g. placed in a data structure or assigned to a variable.

Example code:

        'FOO'         # <-- This is an 'orphan string'.
        def foo(a):
          """This is a docstring about function foo."""
          '''BAR'''   # <-- This is an 'orphan string'.

A docstring (aka "Documentation String") is usually incorporated into your compiled code, so it is used in a way, and it is not an 'orphan string'.

The obfuscator's default behavior is to at least change the contents of 'orphan strings' during obfuscation, in case the orphan string provides useful clues to your code's inner workings.

Orphan strings can also be used for certain directives to the obfuscator.

If you use an orphan string directive, please place it into your code before (above) the first mention in your code of any identifier(s) that the orphan string is intended to modify the obfuscation of!

9.2.2. List of 'Orphan String' Directives OB_IDS

Instructs the obfuscator to obfuscate the specified identifiers. Identifiers are within the orphan string and are separated by whitespace.

Example code:

            # obfuscate identifiers identifier1 and identifier2.
            '''OB_IDS identifier1 identifier2'''

As an alternative, you might use .poi file setting profane_names instead of this directive. NOOB_IDS

The opposite of OB_IDS: instructs the obfuscator to NOT obfuscate certain identifiers.

Example code:

            # DO NOT obfuscate identifiers identifier3 and identifier4.
            '''NOOB_IDS identifier3 identifier4'''

As an alternative, you might use .poi file setting sacred_names instead of this directive. OB_NO_LDN

Instructs the obfuscator that identifier renaming within the affected function/method should be identical to renaming outside the afffected function/method.

For example, the input code

            happy = 1
            def foo(dopey, happy):
              """just prints its arguments."""
              print dopey, happy

will definitely, because of "OB_NO_LDN", be obfuscated to have happy outside any functions/methods/classes and happy inside foo renamed to the same name.

If you want to apply this directive to all of your code, see non1to1renaming in Project Settings File -- Intermediate And Advanced Optional Settings.

9.3. Function Directives

These functions are defined in

9.3.1. OB_ASID()

This function takes a single argument: a string constant. This function returns its argument unchanged. This function's purpose is to cause the obfuscator to obfuscate the argument string constant as if the string constant were an identifier.

When the obfuscator obfuscates the code, it can replace the OB_ASID call with the (properly obfuscated) argument.

For more explanation and a code example, read all of Code that needs to know names of its own identifiers.

9.3.2. OB_KEYARG()

A directive which can cause some keyword arguments in function calls to be obfuscated.

Please see

9.4. Special Directives

9.4.1. if 'OB_REDACT':

          if 'OB_REDACT':
            ... do something ...

Suppose this code occurs in your corpus.

In your unobfuscated (original) code, 'OB_REDACT' evaluates to True.

When code is obfuscated, this entire block of code is removed (replaced by pass.)

So this directive is useful for test code that you want to run during development, but don't want to release.

This directive should not be used in connection with else, nor elif!

10. Intermediate And Advanced Use Cases

10.1. Choosing Renaming Options: Use Case Examples

Different renaming options are best for different situations.

For example...

10.1.1. Reducing Code Size

If you are determined to crunch down your code size, you might want to reduce the size of your symbol names for variables, functions, etc. You might recall that each variable name consists of a 'namebase' string with a number appended. So you want:

So appropriate settings might be:

          namebases = ['q', 'qa', 'qb', 'qc', 'qd', 'qe', 'qf', 'qg']
          octal_renaming = False   # use the default hexadecimal numbering

If you aren't concerned that your target environment's filesystem might not adequately distinguish between capital letters and lowercase letters in filenames, you might also want to set

          doublecase_renaming = True     

Of course, you can also take advantage of the obfuscator's normal removal of all or some docstrings or comments.

If some functionality is not required on some target systems or for some customers, you might also want to take advantage of the OB_REDACT and the redact_directives_added .poi file options. For example, you might remove some debugging code when building a release. Or you might use different redact_directives_added values when building releases for different target systems or customers.

10.1.2. Making Your Code Look Relatively Big and Complicated

Trying to impress people who don't like an appearance of simple, clean code? To make your variable names reflect an appearance of complexity, you might not want them to look too short. You might prefer settings such as

          # These two namebase strings are spelled slightly differently.
          namebases = ['some_long_compilcated_name',   
          octal_renaming = True           # numbers look 'longer' :-)
          countstart_renaming = 017720    # octal number
             # ^ chosen so high digit of number will flip 
             #   within the range of numbers used by names. 

10.1.3. Making Your Code Especially Annoying to Analyze With Pencil and Paper

To require that notetakers read carefully and use good handwriting, (or even tempt them to use abbreviation systems that might lead to errors) you might want settings such as

          octal_renaming = True
          countstart_renaming = 0377777750  
             # ^ write many digits please
          similar_chars_renaming = True 
             # ^ use ambiguously similar-looking characters 
             #   for some numeric digits.
          #              one z:        two z's:   
          namebases = ['yxxxzxxxy',  'yxxxzxxzxy', 
                       'yxxxxzxxxy', 'yxxxzxzxxy',
                       'yxxxxxzxxy', 'yxxxxzxzxy',
                       'yxxxzxxxxy', 'yxxxxzxxzy', ]

10.1.4. If Your Code Has Many Non-Obfuscated Names

You might prefer obfuscated names that look like non-obfuscated names, to make the real non-obfuscated names less obvious to a casual reader of your code.

Until automation specially for this option is added to the obfuscator, you might want to try using a rather long namebases list, including many words related to your software's purpose / problem domain.

If enough interest is shown by obfuscator users, automation to support this kind of renaming might be added to the obfuscator.

10.1.5. Copyright Registration: For Copyright Registration With Source Code Deposit While Blocking Out Some Portions of Your Code That Contain Proprietary Trade Secret Algorithms / Methods

In the United States, when the author(s) of a computer program register(s) the copyright on that program, one or more copies of (some or all of) the program code are typically submitted to the Library of Congress. Lawyers and the general public might have the opportunity to inspect the copies.

Fortunately, U.S. law does provide options to help protect trade secrets within a copyright registration deposit of computer code, as mentioned in, especially the section headed "Trade Secret/Confidential Material". As one option, some portions of the code (up to 49% of the deposited code in some cases) that contain trade secrets may be blacked out (or replaced by sequences of capital X's) by the submitter in the copies of the program that are submitted to the Library of Congress.

IF YOUR LAWYER APPROVES, you might choose, as part of your blocking out, to block out the names of some identifiers such as variables, function names, and class names. Preparing to Manually, On Paper, Block Out Some of Your Own Identifiers

You might decide to block out some of your code's own identifiers on paper, with a thick felt-tip pen or special black tape, before making a mediocre-quality photocopy.

To provide the least information about which of these blocked out identifiers are which, you might want all of your own identifiers to be the same length.

You might choose to obfuscate program code with settings such as

            # make identifiers 6 characters long.
            # namebase always 2 chars long. e.g.
            namebases = ['xx']      
            # first 0xefff names always 6 chars long (if namebase(s) are length 2).
            countstart_renaming = 0x1000 Automated Blockout of "Sacred" Identifiers From Outside Your Project

Alternatively, or additionally, you might choose to block out the names of most standard library functions and other names defined by the Python interpreter. For this, please see

Project Settings File -- Intermediate And Advanced Optional Settings -- blockout_sacred_names


10.2. Metaprogramming: The Code That Dares To Speak Its (Own) Name(s)

Metaprogramming is creating program code that itself creates or manipulates program code, quite possibly even manipulating itself. When obfuscating metaprogramming-related code, special issues can arise.

10.2.1. (Un-)Confusing the Obfuscator on Which Indentifiers Should Be Sacred and Which Profane

To manually correct any misjudgements by the obfuscator on this issue, please see the following sections of this manual:

10.2.2. Code that needs to know names of its own identifiers

A trivial example (that's barely even metaprogramming):

          # Not properly adapted for obfuscator.
          def do_something():
            if (my_debug.verbose):
              sys.stderr.write('do_something() was called.\n')

But suppose you run your code through an obfuscator that renames do_something? You could easily get

          def xxx_1001():
            if xxx_5.xxx_17:
              sys.stderr.write('do_something() was called.\n')

The name of do_something is unobfuscated in the string.

Fortunately, the obfuscator provides an option to obfuscate string constants as identifiers. Here's how to do it.

  1. In your source code, make sure function OB_ASID() is correctly defined.

    • either
               # (you must copy into your project to import it)
               from ob_directives import *   # defines fn OB_ASID()
      Note: a copy of is included in this manual.

    • or yourself define as a function that returns its one argument, e.g.
               def OB_ASID(a):
                 return a

  2. If a string's contents are an identifier to be obfuscated as such, directly wrap your string constant in a call to OB_ASID(). e.g.

             name_of_var__species = OB_ASID('species')

  3. You might recall that the obfuscator provides an option to do non-1:1 renaming of identifers. If this option is enabled (it's enabled by default) it is possible that the same identifier, if it occurs in a disconnected way in different scopes, might be obfuscated to different replacement names in different portions of the code.

    The obfuscator does use some rudimentary heuristics that look for some signs of metaprogramming, which might force 1:1 renaming in the vicinity of the suspected metaprogramming. However, these heuristics are neither complete nor infallible.

    Usually none of this will be a problem.

    However, if you need to force 1:1 renaming by hand, please see the detailed entries on
    • non1to1renaming (a .poi file setting)
    • OB_NO_LDN (a directive in the source code)

      You might need to place this OB_NO_LDN directive in your relevant scope(s) (e.g. function definitions) and into any non-global scopes enclosing them:
                   # In this function, do not use replacement names
                   # that differ from the default replacement names.

So your code might finally become

          from ob_directives import *
          def do_something():
            """Does something."""
            'OB_NO_LDN'  # don't use different renames within this function.
            if (my_debug.verbose):
              sys.stderr.write(OB_ASID('do_something') + '() was called.\n')

which might be obfuscated to something like

          from xxx_999 import *
          def xxx_1001():
            if xxx_5.xxx_17:
              sys.stderr.write(('xxx_1001' + '() was called.\n'))

10.3. Keyword Arguments: Intermediate And Advanced Techniques

10.3.1. Limits On Handling of Keyword Arguments

If you make a function (or method) call using keyword argument syntax, for example

print foo( a = 1, b = 3 )

the obfuscator's default behavior is to leave any keyword argument names unobfuscated in the function call.

This is often correct.*

However, this is the wrong behavior if all three (not one or two) of the following are true:

10.3.2. Fixes For Handling of Keyword Arguments

There are three fixes for this. Any of these three will work.

10.3.3. Finding Keyword Arguments In Your Code

To quickly find any function/method calls in your corpus that use keyword arguments, you can use the .poi file setting

      keyword_arguments_report = True    

which causes the obfuscator to output a report listing the locations of all function/method calls using keyword argument syntax.

10.3.4. Additional Information

Also see Why Keyword Arguments Are not Obfuscated By Default.

The various function directives are defined in the file A copy of is in an appendix of this manual.

I (the obfuscator's author) really do want to know if you need or strongly desire more/better automation for obfuscated keyword arguments in function calls.

10.4. Imports That Don't Work On the Platform Where You Are Running the Obfuscator

10.4.1. The Problem: "I'm a Mac...I'm a PC..." etc.

When the obfuscator analyzes your source code to be obfuscated, part of the obfuscator's analysis is to look at the (sacred) modules and packages that your corpus of code to be obfuscated imports (or might try to import) from outside your corpus.

Looking at these sacred modules and packages helps the obfuscator to infer which identifiers occurring in your code are sacred and which are profane (which are OK to change).

However, it's possible to legitimately write code that incorporates a set of import (or from) statements which can't all succeed on any single environment.

For example, your code might incorporate one import to help it do something on Windows, and a different import to help it do something on a different operating system.

        # I think this is terrible code, but it illustrates a point.
        # Try to make a beep on Windows or IRIX.
          # Try to make a Windows operating system beep.
          from winsound import * # works only on Windows (maybe not on 95/98)
          Beep(1000, 500)        # Make old-style PC speaker beep without sound card
          # I guess we're not on Windows.
            # Try to make an IRIX operating system beep.
            from al import *         # works only on SGI's IRIX
            from AL import *         #   ;
            print 'sorry, the IRIX beep isn't implemented yet.'
            print 'You should run this software on Windows or on SGI IRIX.'

Either winsound is missing (if you're not on Microsoft Windows) or al and AL are missing (if you're not on SGI IRIX).

Either way, the missing module(s) cause:

  1. A warning from the obfuscator that it couldn't find the missing module(s).
  2. Because the obfuscator couldn't find some module(s), the obfuscator probably doesn't know that some of the identifiers used by the missing module(s) are sacred and shouldn't be changed in your code.

10.4.2. Workarounds

If you hate to see the obfuscator output a warning message complaining about a missing module, you can put the import (or from) statement inside an exec statement so that the obfuscator will ignore it.

However, if the obfuscator ignores the import, you will have

to tell it manually about sacred identifers that you use in your code. To force the obfuscator to treat

identifiers as sacred, you can use the sacred_names setting in your .poi file, or the 'NOOB_IDS' directive within your source code.


        # Ugly code now incorporates ugly workaround!
        # Try to make a beep on Windows or IRIX.
          # Try to make a Windows operating system beep.
          'NOOB_IDS winsound Beep' # Preserve identifiers winsound or Beep.
          from winsound import * # works only on Windows (maybe not on 95/98)
          Beep(1000, 500)        # Make old-style PC speaker beep without sound card
          # I guess we're not on Windows.
            # Try to make an IRIX operating system beep.
                 al AL 
                 getqueuesize setqueuesize getwidth setwidth
                 getchannels setchannels getsampfmt setsampfmt 
                 getfloatmax setfloatmax
                 closeport getfd getfilled readsamps writesamps
                 getfillpoint setfillpoint getconfig setconfig 
            from al import *         # works only on SGI's IRIX
            from AL import *         #   ;
            print 'sorry, the IRIX beep isn't implemented yet.'
            print 'You should run this software on Windows or on SGI IRIX.'

10.5. Obfuscating Packages and Subpackages as a Single Unit, in Which Even Inter-Package Interfaces Are Themselves Obfuscated

As we saw in an above section, the project_dirs_in2out setting of the .poi file specifies an input directory of profane code to be obfuscated, and an output directory into which the obfuscated code is written.

If the input directory contains subdirectories that are packages imported by the profane code to be obfuscated, those subdirectory packages are also profane code that is outputted in obfuscated form into subdirectories of the output directory. Such package directories can be nested to form multiple levels of packages and subpackages. That is how multiple packages and subpackages can be obfuscated as a single unit, in which even inter-package interfaces are obfuscated.

In version 3.0 of the obfuscator, the default behavior is for these (sub)directories to retain their non-obfuscated names. Renaming these (sub)packages in the output code will require the .poi file setting permit_pkg_renaming.

10.6. Embedding Copyright Information Into Obfuscated Source Code Automatically

One way to embed a copyright notice into every module of source code outputted by the obfuscator is to use the message_for_module_docstring setting of your project's .poi file.

If you want more thorough and widespread embedding of copyright information, and don't mind somewhat enlarging your code's size, some additional methods include:

10.7. Special Django Techniques and Utilities

If you are obfuscating a Django project, please contact our technical support for additional tips and the latest version of our Django-related tools.

11. Troubleshooting and Workarounds Guide: Fixing Errors, Warnings, and Erroneous Output From the Obfuscator

Here is a list of problems, with workarounds for the problems.

11.1. An identifier (function name, variable name, attribute, etc.) was changed that should NOT have been changed.

For example, suppose you obfuscate

        # xml.minidom, xml.pulldom are modules in the Python standard library.
        from xml import minidom, pulldom 

and it becomes:

        from xml import xxx_51, xxx_52

but you've decided you don't want minidom, pulldom, and _nodeTypes_with_children to be changed to gibberishy names!

One solution is to use a NOOB_IDS 'orphan string directive' in your source code.

For example:

        'NOOB_IDS  minidom pulldom _nodeTypes_with_children'
        from xml import minidom, pulldom 
        # minidom, pulldom are modules in the Python standard library.

Your code will then be obfuscated more correctly to:

        from xml import minidom, pulldom 

For more information see:

Alternative solutions

11.2. A keyword argument in a function or method call was NOT renamed, but SHOULD have been renamed to match the rest of your code.

For example, suppose you obfuscate

        def myfunc(planets=None, moons=None, stars=None, comets=None):
          do_something(planets, moons, stars, comets, False)

and it obfuscates to:

        def xxY_25(xxx_22=None, xxx_23=None, xxx_24=None, xxx_25=None):
          xxx_26(xxx_22, xxx_23, xxx_24, xxx_25, False)

This is wrong! In the last line above, moons should be xxx_23 (to make the function call match the function definition.)

For solutions, see:

11.3. My 'orphan string' directive was IGNORED by the obfuscator.

12. Endnotes

12.1. Why Keyword Argument Names Should Sometimes Be Left UNobfuscated

See Why Keyword Arguments Are Not Obfuscated By Default.

12.2. Error Unlikely

In this specific example code, the error shown is not likely to happen in recent versions of the obfuscator.

12.3. Some Errors Shown Are Unlikely

In this specific example code showing a workaround for (an) error(s), the errors shown are difficult or impossible to replicate in recent versions of the obfuscator (because we fixed them. Boo hoo.)

13. Appendices

13.1. Apppendix A:

This is a working version of, suitable for most normal purposes.

Also see Function Directives.

13.1.1. Full Source Code of

     YOU *MAY* COPY 
       - you use this file and its contents as source code rather than as text
         nor as documentation for end users of your project.
       - you have a valid license to use Chris Niswander's / BitBoost's
         Python obfuscator.
     Defines do-nothing functions
     which a Python obfuscator may interpret as directives.
     - Instruction to Python obfuscator to obfuscate a string
       as if it were an identifier (variable name / fn name / etc.)
       in the Python code, IF THIS FEATURE IS ENABLED.
       e.g. OB_ASID('somevariable1')
     - Indications of whether a value is being handed to a keyword argument
       that should or should not be obfuscated.
       e.g. do_something(some_keywordarg1 = OB_KEYARG('some value'))
          def do_secret(secretname1, secretname2):
            print "no one knows what " + secretname1 + " and " \
                  + secretname2 + " are used in reference to."
          do_secret(secretname1=OB_KEYARG('edelweiss'), secretname2=OB_KEYARG('abba'))
       In the above example, because OB_KEYARG has been applied
       to the values of the function call's keyword arguments, 
       secretname1 and secretname2 in the function call 
       will be obfuscated
       to match the definition of function do_secret.
     Use in function calls e.g.
       def somefn(someargname1):
         'does something, which I'll fill in here later.'
       # someargname1 gets obfuscated to match the fn def.
    Copyright 2007 by Chris Niswander and BitBoost (of Arizona, USA)
    All Rights Reserved.
  def OB_ASID(a):
    return a
  def OB_KEYARG(a):
    return a

13.2. Appendix B: Example Project Settings / .poi File for blockout_sacred_names = True

This is a working version of a .poi / project settings file, suitable for demonstrating blockout_sacred_names.

Also see blockout_sacred_names.

13.2.1. Full Source Code of main_blockout.poi

  # main_blockout.poi
  # Demonstration of blockout_sacred_names (.poi file setting).
  # Suitable for 
  #   - test case
  #   - obfuscator manual example
  input_root_module = 'main'
  output_root_module = 'main'
  project_dirs_in2out = ['.', './out-blockout']
  if 1:
    message_for_module_docstring = \
      file produced by: blockout-example-1/main.poi
      ( C ) Copyright 2012 by Chris Niswander.  All Rights Reserved. 
      Please see for more information.
  # make 'profane' identifiers 18 characters long.
  # namebase always 12 chars long. e.g.
  namebases = ['identifier__']      
  # first 0xefff name numbers always 6 chars long (if namebase(s) are length 2).
  countstart_renaming = 0x1000
  # For secrecy's sake don't ship these files:
  # Prefer to use slash not backslash, this is a Python string!
  revdict = 'secret-blockout/'
  blockout_sacred_names = 1

13.2.2. Examples of Language Keywords (not blocked out) vs. General "Sacred Names" (blocked out): a Table

While this mode blocks out most names from the Python standard libraries (such as standard library function names) and many names defined by the Python interpreter, actual language keywords such as for and print are not blocked out.

not blocked out blocked out
import reload
from stdout
print write
+ extend
[] append
def __init__

13.3. Appendix C: Why Keyword Argument Names Are not Obfuscated By Default

13.3.1. Introduction to Possible Conflict

In function (and method) definitions in your code, parameter names are (generally) obfuscated. For example, self might be renamed xxy123.

However, creating a possible conflict...

If you make a function (or method) call using keyword argument syntax, for example

print foo( a = 1, b = 3 )

the obfuscator's default behavior is to leave any keyword argument names unobfuscated in the function call.

This behavior is sometimes correct, and sometimes incorrect.

13.3.2. Often, Keyword Argument Names In Function Calls Should NOT Be Obfuscated If Calling a Function Defined Outside Obfuscated Code

        string1 = raw_input("Please enter a paragraph of text.")
        periods_in_first_10_chars_of_string1 = string1.count('.', end=10)
        # We would not want 'end' to be obfuscated by itself, 
        # because built-in string method count would not understand.
        import datetime     # a module of the Python standard library.
        date1 = date(month=5, day=1, year=2008)
        # date and datetime would be confused if 'month', 'day', or 'year' were renamed. If the Keyword Argument and Value Go Into A Dictionary

Suppose the keyword arguments are going into a dictionary, as in this example code from Guido van Rossum's Python Tutorial (Release 2.4.2) wherein the dictionary is named "keywords" ...

          def cheeseshop(kind, *arguments, **keywords):
            print "-- Do you have any", kind, '?'
            print "-- I'm sorry, we're all out of", kind
            for arg in arguments: print arg
            print '-'*40
            keys = keywords.keys()
            for kw in keys: print kw, ':', keywords[kw]
          cheeseshop('Limburger', "It's very runny, sir.",
                     "It's really very, VERY runny, sir.",
                     client='John Cleese',         # keyword argument
                     shopkeeper='Michael Palin',   # keyword argument
                     sketch='Cheese Shop Sketch'   # keyword argument

which is intended to print:

          -- Do you have any Limburger ?
          -- I'm sorry, we're all out of Limburger
          It's very runny, sir.
          It's really very, VERY runny, sir.
          client : John Cleese
          shopkeeper : Michael Palin
          sketch : Cheese Shop Sketch  

The latter part of the output would be totally ruined if we obfuscate the keyword argument names, which would change the words 'client', 'shopkeeper' and 'sketch'. I didn't want to try to automatically distinguish every case where the keyword arguments' names should be obfuscated from every case where they should not be.

13.3.3. Sometimes It Is Very Difficult to Know What Function You're Really Calling, and Where It Came From

Complicated issues can arise if you write code which uses function references (which might refer to profane functions or might refer to sacred functions), or if you replace the functions or methods of a module, class, or individual object, or if you use some other not-all-that-tricky programming techniques.

In a "dynamic language" like Python, you can make it difficult or even provably impossible for automated pre-run code analysis to be certain of the origin of the function you are calling.

13.3.4. Conclusion

This shortcoming can become a nuisance, because if you're calling an obfuscated function that has many declared arguments, you might want your call to mention some declared arguments by name to improve your code's readability, and/or to help you select which of the function's default arguments you want used.

For solutions, please see the keyword arguments use case.

13.4. Appendix D: Python 2.6 Format Strings (As Backported From Python 3.0)

If a variable name is changed by code obfuscation, and that variable is referred to specifically by its old (pre-obfuscation) variable name within a replacement field of a format string, the format string would no longer be correct in the context of the post-obfuscation code.

If this is a problem for you, please complain to BitBoost or directly to Chris Niswander.

13.4.1. Stuff That "Just Works"

Some relatively simple and commonplace ways of using Python 2.6 format strings don't cause errors. For example:

      # ok
      """Hello {0}, you have {1} new message(s).""".format(
        user_name, new_msg_count)
      # ok
      """Hello {username}, you have {new_msg_count} new message(s).""".format(
        username=self.username, new_msg_count=self.get_new_msg_count())
      # ok
      """Hello {0}, you have {new_msg_count} new message(s).""".format(
        self.username, new_msg_count=self.get_new_msg_count())
      # ok
      """Hello {username}, you have {new_msg_count} new message(s).""".format(
        **{ 'username' : self.username,
          'new_msg_count' : self.get_new_msg_count()

The above examples would all result in correctly working output code from the obfuscator's present version, because the format strings do not depend on variable names as such.

But so you can know when problems can happen, a detailed discussion follows.

13.4.2. A Detailed Discussion: How To Understand What Does and Does Not "Just Work" And Why

(This section might be clearest to readers with some knowledge of the official Python language documentation about the format strings feature of Python 2.6 and 3.0.)

The obfuscator's present version does not change the contents of a format string during the obfuscation process. This means that if the format string's replacement fields try to refer to names outside the format string that were changed outside the format string by the obfuscator, the format string will become incorrect (through not having been changed to correspond to the new names.)

So, for example, if a format string's replacement fields try to refer by name to variables or class members that were renamed by the obfuscator, this would cause the format string to become incorrect.

Each replacement field within a format string can use:

str.format can be called with three different kinds of arguments:

An obfuscator user can make kludgy workarounds for the trouble cases I mention above. The workarounds most often involve using hinting functions such as OB_ASID, which are documented in the manual (even the manual for obfuscator version 1.2 covers them.) e.g.

      user_name = get_user_name()
        ("Hello, {" + OB_ASID("user_name") + "}, how are you?").

Some of these workarounds seem hardly worth the bother, although in some cases they might be worthwhile.

You can also read the obfuscator manual's sections on

13.5. Appendix E: Supported and Unsupported Python Language Features

This is a more in-depth look at a topic first mentioned in Versions of Python and Python Features Supported by the Obfuscator.

As that section mentioned, all features of Python 2.5 are supported.

For example, from __future__ import absolute_import is fully supported. Absolute imports and explicit relative imports are supported. Older-style imports are also supported.

Almost all features of Python 2.6 and/or 2.7 are also supported.

More specifically:

13.5.1. Obfuscating code that uses some Python 3 features while obfuscating with a Python 2 obfuscator

This is best done by using both of the following:

These feature imports should be explained in the documentation of the Python version and/or interpreter that you are using. For example, at press time, a table of these importable __future__ module features is in the most recent version of the Python 2 manuals, within the page at . The table includes links to descriptions of each Python 3 feature importable in this way.

The appropriate from __future__ import ... feature imports are acceptable in both the latest version(s) of Python 2 and in Python 3. It should be reassuring to know that "No feature description will ever be deleted from the module __future__," so your code using feature imports should be compatible with future versions of Python for a very long time.

For example, the Python 3 style "print as a function" syntax is supported by using from __future__ import print_function and running the obfuscator in Python 2.7. By using this import your Python 3 style print calls will work in Python 2.6, Python 2.7, and Python 3.x, when you run either the non-obfuscated or obfuscated versions of your code.

13.6. Appendix G: Some Material Left Out Of The Manual's Main Section

This documentation guaranteed to contain at least one tyop.

14. Legal Notices

14.1. Copyright

© Copyright 2017 by Chris Niswander and BitBoost (of Arizona, USA). All Rights Reserved.

The obfuscator documented by this manual (documentation) is © Copyright 2003-2017 by Chris Niswander and BitBoost.

14.2. Trademark Notice

Various product names and business names which might occur in this documentation, such as Microsoft, Microsoft Windows, Apple, Macintosh, OS X, Windows 2000, Windows XP, Windows Vista, Windows 7, Windows Server 2008, Windows 8, Windows 8.1, UNIX, Linux, Debian, Ubuntu, are trademarks and/or service marks claimed by various organizations.

BitBoost, BitBoost Systems, and bobs/BOBS/Bobs are trademarks and/or service marks of BitBoost aka BitBoost Systems, a business based in, or predominantly based in, the United States (specifically in Arizona as this is being written.)

14.3. This Manual (Documentation) Is Not Legal Advice