Johannes Maria Frank
Johannes Maria Frank

Reputation: 2827

Can a function in python behave like a class?

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>

Upvotes: 3

Views: 2165

Answers (2)

jonrsharpe
jonrsharpe

Reputation: 122032

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>

Upvotes: 8

user707650
user707650

Reputation:

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)

Upvotes: 2

Related Questions