Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>
like image 898
Johannes Maria Frank Avatar asked Sep 05 '26 12:09

Johannes Maria Frank


1 Answers

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>
like image 58
jonrsharpe Avatar answered Sep 07 '26 03:09

jonrsharpe