6

This is my first post here so please let me know if I'm doing it wrong. I tried looking for an existing answer, but wasn't sure what to search for.

Consider the following simple example, a python module called mymath.py which uses only built-in python operations and modules. This custom module is portable, so anyone can execute the code without installing anything other than stock python.

# mymath.py
import sys

def minus(a, b):
    return a-b

def mult(a, b):
    return a*b

def div(a, b):
    return a/b

def plus(a, b):
    return a+b

def sum_series(int_list):
    sum = 0
    for i in int_list:
        sum = plus(sum, i)
    return sum

def main():
    my_list = [2, 4, 6]
    value = sum_series(my_list)
    sys.stdout.write("your list total = {}".format(value))

Notice, main() only calls sum_series() which in turn calls plus(). The other functions may be required elsewhere in this fictional code base, but we're only concerned with main().

Now, I would like to copy only the relevant source code to another object as a text string. In other words, gather main() and all it's dependencies (recursively), resulting in a string of executable code.

My current solution:

import inspect
import mymath
# copy the source code in to the callback
source = inspect.getsource(mymath)
# then append a call to the main function
source += "\nmain()"

This works, producing a local copy of the module as a string that can run main() without requiring an import of mymath. The problem is that knob is now bloated with all the extra unused functions, although it is able to pick up any changes I make to mymath.py by rerunning my current solution.

So, the question is - is there a way to do the equivalent of:

source = getFunctionSourcerRecursively(mymath.main)  
source += "\nmain()"

resulting in source =

# mymath.py 
import sys  

def plus(a, b):  
    return a+b  

def sum_series(int_list):  
    sum = 0  
    for i in int_list:  
        sum = plus(sum, i)  
    return sum  

def main():  
    my_list = [2, 4, 6]  
    sys.stdout.write("your list total = {}".format(sum_series(my_list)))  

main()

So, basically "source" now contains only the relevant code and is portable, no longer requiring people offsite to have mymath installed.

If you're curious, my real-world case involves using The Foundry Nuke (compositing application) which has an internal callback system that can run code when callback events are triggered on a knob (property). I want to be able to share these saved Nuke files (.nk or .nknc) with offsite clients, without requiring them to modify their system.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
rdutta
  • 61
  • 3
  • This sounds like an overly complicated and error-prone way to do packaging. It's a non-trivial problem to determine the runtime dependencies of Python code (consider, for example, a conditional branch based on the output of `datetime.now()` or `random.random()`). Why not just write the `setup.py` and distribute code the usual way, as an egg or wheel to be installed with `pip`? – wim Sep 17 '17 at 19:35

1 Answers1

0

You might try informal interfaces (a.k.a. protocols). While protocols work fine in many cases, there are situations where informal interfaces or duck typing in general can cause confusion. For example, an Addition and Multiplication both are mathFunc(). But they ain't the same thing even if they implement the same interfaces/protocols. Abstract Base Classes or ABCs can help solve this issue.

The concept behind ABCs is simple - user defines base classes which are abstract in nature. We define certain methods on the base classes as abstract methods. So any objects deriving from these bases classes are forced to implement those methods. And since we’re using base classes, if we see an object has our class as a base class, we can say that this object implements the interface. That is now we can use types to tell if an object implements a certain interface.

import mymath

class MathClass(mymath.ABC):
    @mymath.abstractmethod
    def mathFunc(self):
        pass
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220