Python – Abstract Function class

Python is great. It’s dynamic, it’s flexible, and best of all, for almost all major datatypes, there’s an associated abstract class from which you can over-ride and build your own object out of.

Of course, Python doesn’t treat functions in this same way. But as developers we cope and we move on when we have to.

Until recently I’ve been blissfully oblivious of this fact, of course I’ve never had the need to overload the default functionality of a function and thus have never had needs of an abstract class for functions as well. As far as I’m concerned, using the def operator is much more simplistic than having to toil to create a brand new class and in doing so cheating python out of its gracefulness.

But a few days ago I had to write a program in which native python functions are given and the program needs to find out whether the functions’ opcodes matches and return true if it does. Naturally I assumed that the == operator for functions covers this aspect. I was shocked to find out that it doesn’t. Now I’m stuck with one of two ways out. I could write a messy block of code to manually check for the func_code.co_code, or I could write my own abstract class for function types and then just use the == operator to keep everything elegant.

Well, I must have been bored because rather then going along with writing an intermediate function to do the checking, I actually wrote a whole base-class to replicate a function’s, well, functionalities.

The class started off as it would for any other class, I knew that I wanted an object that’s callable, and when attributes are being requested would return instead those from the function that it’s derived from. I want to keep things as simple as possible so the only instance member of this class would be the actual function that it’s derived from.

The skeleton code for thus far goes like

class function(object):
    def __init__(self, fn):
        self.fn = fn
    def __getattr__(self, name):
        try:
            return self.fn.__getattribute__(name)
        except:
            return self.fn
    def __repr__(self):
        return str(self.fn)
    def __call__(self):
        return self.fn()

Alright, now all I had to do was add a decorator @function and all of the functions automatically becomes instances of the abstract function.

@function
def f():
	print "a"

calling f() prints “a”, nothing fancy there and we still retain all of the elegance of Python.
Note how the __getattr__ method delegates all instance attributes to the self.fn object? This is to make the abstract function wrapper as transparent as possible.

And now adding the __eq__ method

    def __eq__(self, other):
        try:
            return self.func_code.co_code == other.func_code.co_code
        except:
            return False

Remember here that the __getattr__ already delegates all of these requests to self.fn so self.func_code actually returns self.fn.func_code, this retains compatibility with other instances of the function class as well as native python functions.

Everything works great up to this point. Let’s test the equality operator on two functions with identical opcodes

@function
def f():
	return 1
def x():
	return 1
@function
def y():
	return 1

print f == x, f == y, x == y

Which should print True True True

And here’s where we run into the problem, if we have the following function

@function
def f(a)
	print a

and we try to call f(1), it’ll return an error saying that we’ve entered too many arguments.

This is because we have

    def __call__(self):
        return self.fn()

This poses two problems: We can’t get the keys of the parameters, and we don’t know how many parameters there are going to be.

My initial attempt at solving this problem was to include an *args parameter in the call method and then call that back to self.fn as in

    def __call__(self, *args):
        return self.fn(args)

but then I realized that args was a list of parameters. In which case f(1) would have turned into f([1]), clearly not what we wanted.

The only alternative that we have is to use the built in eval function. Here’s the __doc__ for eval.


eval(source[, globals[, locals]]) -> value

Evaluate the source in the context of globals and locals.
The source may be a string representing a Python expression
or a code object as returned by compile().
The globals must be a dictionary and locals can be any mapping,
defaulting to the current globals and locals.
If only globals is given, locals defaults to it.

So with that in mind, we can do a little bit of hacking around by manipulating the accessible variables inside the eval environment

    def __call__(self, *args):
        return self.fn(args)
        params = ""
        for arg in args: params += "%s," % arg
        eval("self.fn(%s)"%params, {'self':self})

And viola, everything’s as it should be. However, in order to make this callable with

def f(a = None):
	print a
f(a = 23)

we have to add a bit more

    def __call__(self, *args, **kwargs):
        params = ""
        for arg in args: params += "%s," % arg
        for kwarg in kwargs.keys(): params += "%s=%s," % (kwarg, kwarg)
        kwargs['self'] = self
        return eval("self.fn(%s)"%params,kwargs)

The **kwargs parameter means that all key=value type parameters are automatically inserted into this dict. And in this sense, since the second parameter of eval HAS to be a dict, we might as well just save some typing and add ‘self’:self into the kwargs dict since everything in there must be accessible in the eval environment. So in this case we have a parameter list made up of “a = a” and where a = 1.

And finally, the complete function class:

class function(object):
    def __init__(self, fn):
        self.fn = fn

    def __getattr__(self, name):
        try:
            return self.fn.__getattribute__(name)
        except:
            return self.fn

    def __eq__(self, other):
        try:
            if type(other) == type(lambda:0) or isinstance(other, function):
                return self.func_code.co_code == other.func_code.co_code
        except:
            return False

    def __repr__(self):
        return str(self.fn)
    def __call__(self, *args, **kwargs):
        params = ""
        for arg in args: params += "%s," % arg
        for kwarg in kwargs.keys(): params += "%s=%s," % (kwarg, kwarg)
        kwargs['self'] = self
        return eval("self.fn(%s)"%params,kwargs)

And an example using this abstract class

>>> @function
... def f(a):
...     return a
...
>>> @function
... def x(a):
...     return a
...
>>> def z(a):
...     return a
...
>>> f == x
True
>>> f == z
True
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Lee\workspace\sandbox\function.py", line 26, in __call__

  File "<string>", line 1, in <module>
TypeError: f() takes exactly 1 argument (0 given)
>>> f(1)
1
>>> f(a = 1)
1
>>> f(b = 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Lee\workspace\sandbox\function.py", line 26, in __call__

  File "<string>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'b'
>>>

Cheers on your quest to becoming a better programmer.

This entry was posted in Programming, Python, Snippets and tagged , , , , , , , , , , , , . Bookmark the permalink.

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>