3
\$\begingroup\$

The first version of my solution was wrong (the usage of compile() function), for details see Stack Overflow.

The solution has been fixed and now it works correctly. Another approach is used: my_add.__code__. The questions stayed the same.

I am reading the book: Obi Ike-Nwosu. Inside The Python Virtual Machine and have reached the "Code Objects" chapter. This chapter has all code object fields printed out:

co_argcount = 1
co_cellvars = ()
co_code = b'|\x00d\x01\x16\x00d\x02k\x02r\x1e|\x00d\x03\x16\x00d\x02k\x02r\x1ed\\
x04S\x00n,|\x00d\x01\x16\x00d\x02k\x02r0d\x05S\x00n\x1a|\x00d\x03\x16\x00d\x02k\x02r\
Bd\x06S\x00n\x08t\x00|\x00\x83\x01S\x00d\x00S\x00'
co_consts = (None, 3, 0, 5, 'FizzBuzz', 'Fizz', 'Buzz')
co_filename = /Users/c4obi/projects/python_source/cpython/fizzbuzz.py
co_firstlineno = 6
co_flags = 67
etc...

but there is no explanation how this can be done programatically, so I wrote my own code:

# The explored function
def my_add(a,b):
    first = a
    second = b

    return first + second

def print_co_obj_fields(code_obj):
    # Iterating through all instance attributes
    # and calling all having the 'co_' prefix
    for name in dir(code_obj):
        if name.startswith('co_'):
            co_field = getattr(code_obj, name)
            print(f'{name:<20} = {co_field}')

# The function code object (__code__) usage
print_co_obj_fields(my_add.__code__)

Output

co_argcount          = 2
co_cellvars          = ()
co_code              = b'|\x00}\x02|\x01}\x03|\x02|\x03\x17\x00S\x00'
co_consts            = (None,)
co_filename          = ./source.py
co_firstlineno       = 17
co_flags             = 67
co_freevars          = ()
co_kwonlyargcount    = 0
co_lnotab            = b'\x00\x01\x04\x01\x04\x02'
co_name              = my_add
co_names             = ()
co_nlocals           = 4
co_stacksize         = 2
co_varnames          = ('a', 'b', 'first', 'second')

Have I done this the optimal way? How would you solve this problem?

\$\endgroup\$
2
\$\begingroup\$

main guard

Put the parts of your code that are the ones calling for execution behind a if __name__ == "__main__": guard

if __name__ == "__main__":
    def my_add(a, b):
        first = a
        second = b

        return first + second
    print_co_obj_fields(my_add.__code__)

This way you can import this python module from other places if you ever want to

Documentation

There is a python convention (docstring) to document the behaviour of a method: PEP-257

def print_co_obj_fields(code_obj):
    """prints all the instance variables of the code object that start with 'co_'"""

Indentation

To decrease the levels of indentation, you can reverse the test of startswith, and continue when it fails:

def print_co_obj_fields(code_obj):
    # Iterating through all instance attributes
    # and calling all having the 'co_' prefix
    for name in dir(code_obj):
        if not name.startswith('co_'):
            continue
        co_field = getattr(code_obj, name)
        print(f'{name:<20} = {co_field}')

An alternative if using the builtin filter:

def print_co_obj_fields(code_obj):
    # Iterating through all instance attributes
    # and calling all having the 'co_' prefix
    for name in filter(lambda field: field.startswith("co_"), dir(code_obj)):
        co_field = getattr(code_obj, name)
        print(f'{name:<20} = {co_field}')

Presentation

Separate the generation of the results from the presentation. The easiest way to do this is to have print_co_obj_fields yield the name and co_field instead of printing them

def co_obj_fields(code_obj):
    """generator that yields the fields in a code_object and their value"""
    for name in filter(lambda field: field.startswith("co_"), dir(code_obj)):
        co_field = getattr(code_obj, name)
        yield name, co_field

def print_co_obj_fields(fields):
    for name, co_field in fields:
        print(f"{name:<20} = {co_field}")

The main part then changes to:

if __name__ == "__main__":

    def my_add(a, b):
        first = a
        second = b

        return first + second

    fields = co_obj_fields(my_add.__code__)
    print_co_obj_fields(fields)
\$\endgroup\$
  • \$\begingroup\$ "Separate the generation of the results from the presentation." But it means, that I should write the presentation part every time I use co_obj_fields. It is good from one point of view - I can customize presentation as needed (it is the main purpose of separation, I think), but it is bad when I need the same formatting everywhere, so I will get the duplication in the code. Or in this case it would be better to wrap the presentation part into another function and call it? \$\endgroup\$ – MiniMax Apr 29 at 12:03
  • 1
    \$\begingroup\$ It would be better to wrap the presentation part in another function and call that. If you follow Brendan Rhodes' clean architecture you don't have that function call the co_obj_fields, but it just receives the results of that call. \$\endgroup\$ – Maarten Fabré Apr 29 at 13:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.