3

In python a function is a first class object. A class can be called. So you can replace a function with a class. But can you make a function behave like a class? Can you add and remove attributes or call inner functions( then called methods) in a function?

I found a way to do this via code inspection.

import inspect

class AddOne(object):
    """class definition"""
    def __init__(self, num):
        self.num = num

    def getResult(self):
        """
        class method
        """
        def addOneFunc(num):
            "inner function"
            return num + 1

        return addOneFunc(self.num);

if __name__ == '__main__':
    two = AddOne(1);
    two_src = '\n'.join([line[4:] for line in inspect.getsource(AddOne.getResult).split('\n')])
    one_src = '\n'.join([line[4:] for line in two_src.split('\n')
                    if line[:4] == '    ' and line[4:8] == '    ' or line[4:8] == 'def '])
    one_co = compile(one_src, '<string>', 'exec')
    exec one_co
    print addOneFunc(5)
    print addOneFunc.__doc__

But is there a way to access the local variables and functions defined in a class in a more direct way?

EDIT

The question is about how to access the inner structure of python to get a better understanding. Of course I wouldn't do this in normal programming. The question arose when we had a discussion about private variables in python. My opinion was this to be against the philosophy of the language. So someone came up with the example above. At the moment it seems he is right. You cannot access the function inside a function without the inspect module, rendering this function private. With co_varnames we are awfully close because we already have the name of the function. But where is the namespace dictionary to hold the name. If you try to use

getResult.__dict__

it is empty. What I like to have is an answer from python like

function addOneFunc at <0xXXXXXXXXX>
Johannes Maria Frank
  • 2,747
  • 1
  • 29
  • 38
  • With two.getResult.im_func.func_code.co_varnames[1] I can access the name of the function. But how do I find the function itself? The name is just a string. – Johannes Maria Frank Aug 06 '14 at 11:55
  • You can still use things `AddOne.getResult.__code__.co_consts[1].co_name` (which gives `addOneFunc`), but that's very similar to using the inspect module (I haven't checked, but it may very well be that the inspect module uses this). –  Aug 06 '14 at 13:00
  • Yes this is another way of getting the string. But I would like to call the function. – Johannes Maria Frank Aug 06 '14 at 13:03

2 Answers2

8

You can consider a function to be an instance of a class that only implements __call__, i.e.

def foo(bar):
    return bar

is roughly equivalent to

class Foo(object):

    def __call__(self, bar):
        return bar

foo = Foo()

Function instances have a __dict__ attribute, so you can freely add new attributes to them.


Adding an attribute to a function can be used, for example, to implement a memoization decorator, which caches previous calls to a function:

def memo(f):
    @functools.wraps(f)
    def func(*args):
        if args not in func.cache: # access attribute
            func.cache[args] = f(*args)
        return func.cache[args]
    func.cache = {} # add attribute
    return func

Note that this attribute can also be accessed inside the function, although it can't be defined until after the function.


You could therefore do something like:

>>> def foo(baz):
        def multiply(x, n):
                return x * n
        return multiply(foo.bar(baz), foo.n)

>>> def bar(baz):
    return baz

>>> foo.bar = bar
>>> foo.n = 2
>>> foo('baz')
'bazbaz'
>>> foo.bar = len
>>> foo('baz')
6

(although it's possible that nobody would thank you for it!)

Note, however, that multiply, which was not made an attribute of foo, is not accessible from outside the function:

>>> foo.multiply(1, 2)

Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    foo.multiply(1, 2)
AttributeError: 'function' object has no attribute 'multiply'

The other question addresses exactly what you're trying to do:

>>> import inspect
>>> import new
>>> class AddOne(object):
    """Class definition."""

    def __init__(self, num):
        self.num = num

    def getResult(self):
        """Class method."""
        def addOneFunc(num):
            "inner function"
            return num + 1    
        return addOneFunc(self.num)

>>> two = AddOne(1)
>>> for c in two.getResult.func_code.co_consts:
    if inspect.iscode(c):
        print new.function(c, globals())


<function addOneFunc at 0x0321E930>
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Can I make multiply an attribute of foo, so I can access it? – Johannes Maria Frank Aug 06 '14 at 11:27
  • @JohannesFrank yes, if you like - you can assign `foo.multiply = multiply` inside `foo`. – jonrsharpe Aug 06 '14 at 11:30
  • Ok, but from the outside I do not have a chance? I tried to find the name with dir() inside the function but couldn't find it. If I could find it I could add it, but first I have to find it. – Johannes Maria Frank Aug 06 '14 at 11:34
  • @JohannesFrank why would you want to do that? If you need access to the inner function from outside the function, just define it outside the function! If you *really* want to do it, see [here](http://stackoverflow.com/q/1234672/3001761), but how much of a pain that is reflects how much you shouldn't be doing it (if something's that hard in Python, it's probably considered a bad idea)! – jonrsharpe Aug 06 '14 at 12:24
  • See my EDIT of the question about the why. Thank you for the link, but it does not cover an inner function which addOneFunc is. Additionally I am back to inspect. – Johannes Maria Frank Aug 06 '14 at 12:52
  • @JohannesFrank Yes, you're back to `inspect`, because that's the only way you can do it. The inner function *doesn't exist* until the function is called, so you have to inspect the code to find it. And that question does exactly what you ask for - see the example at the end of my question for ``... – jonrsharpe Aug 06 '14 at 13:00
2

Not sure if the following is what you're thinking about, but you can do this:

>>> def f(x):
...   print(x)
... 
>>> f.a = 1
>>> f.a
1
>>> f(54)
54
>>> f.a = f
>>> f
<function f at 0x7fb03579b320>
>>> f.a
<function f at 0x7fb03579b320>
>>> f.a(2)
2

So you can assign attributes to a function, and those attributes can be variables or functions (note that f.a = f was chosen for simplicity; you can assign f.a to any function of course).

If you want to access the local variables inside the function, I think then it's more difficult, and you may indeed need to revert to introspection. The example below uses the func_code attribute:

>>> def f(x):
...   a = 1
...   return x * a
... 
>>> f.func_code.co_nlocals
2
>>> f.func_code.co_varnames
('x', 'a')
>>> f.func_code.co_consts
(None, 1)
  • You could also use `f.a` in `f`, e.g. `f = lambda x: x + f.a if hasattr(f, "a") else x`... but using a class is probably better, anyway. – tobias_k Aug 06 '14 at 09:26