A functor, or function object is sometimes a convenient way to encapsulate helper functions and data needed to achieve a small task, and also to share data between calls to the same function. This snippet defines a base class functor which permits an easy functor creation for python 2.
# Functor.py (python 2)
""" This module define a base class 'functor' to help define function objects.
Author: Gribouillis, for the python forum at www.daniweb.com
License: This program has been placed in the public domain
Created and published June the 27th 2011
Tests: tests can be run on the command line with 'python -m doctest -v Functor.py'
"""
class metafunctor(type):
"""helper metaclass for functors"""
def __new__(meta, class_name, bases, new_attrs):
if "__init__" in new_attrs:
raise TypeError, "__init__() forbidden in functor class '%s'" % class_name
klass = type.__new__(meta, class_name, bases, new_attrs)
def wrapper(*args, **kwd):
return klass()(*args, **kwd)
from functools import update_wrapper
update_wrapper(wrapper, klass)
wrapper._functor_class = klass
return wrapper
class functor(object):
"""A base class to define functors, a special kind of function objects.
Usage:
Subclassing functor defines a function instead of a class, for example
class myfunction(functor):
def __call__(self, what):
print "called with argument", repr(what)
myfunction("hello world") # myfunction is now a function and not a class
Each time myfunction is called, a *new* instance of the hidden class is created and
the instance's __call__ method is called instead. The hidden class itself can be accessed
as myfunction._functor_class.
The benefit of defining functors instead of functions is to encapsulate helper data
and methods for the task executed by the function. Here is a more complete example
of a functor to reverse the letters in each word of a sentence
>>> class reverse_words(functor):
... 'A functor to reverse words in a sentence.'
... def __call__(self, sentence):
... return self.regex.sub(self.subf, sentence)
... import re
... regex = re.compile(r'[a-zA-Z_]\w*')
... def subf(self, match): # helper functions and data are encapsulated in the functor
... return match.group(0)[::-1]
>>> print reverse_words("please reverse words in this test sentence.")
esaelp esrever sdrow ni siht tset ecnetnes.
"""
__metaclass__ = metafunctor
def __call__(self, *args, **kwd):
raise NotImplementedError, ("__call__() not implemented for functor '%s'"
% self.__class__.__name__)
functor = functor._functor_class
The above functor base class can also be used to define methods, static methods and class methods, here is an example
class A(object):
def __init__(self):
self.value = None
class foo(functor):
def __call__(self, this, n):
this.value = n
class sbar(functor):
def __call__(self, stuff):
print "sbar called with", repr(stuff)
sbar = staticmethod(sbar)
class cbar(functor):
def __call__(self, cls, stuff):
print "cbar called with", cls, repr(stuff)
cbar = classmethod(cbar)
a = A()
a.foo("value set by the foo functor")
print a.value
a.sbar("static call with instance")
A.sbar("static call with class")
a.cbar("classmethod call")
""" my output --->
value set by the foo functor
sbar called with 'static call with instance'
sbar called with 'static call with class'
cbar called with <class '__main__.A'> 'classmethod call'
"""
Notice that the 'method functor' has 2 implicit arguments: the functor object and the A instance.
Wikipedia function object Python section is using simple object with __call__ and __init__ for start value as Accumulator. Could you give example of benefit of your approach disallowing the __init__?
There is a big difference with a traditional functor. A traditional functor is an object initialized once and called several times. It maintains a state between the calls. In my approach, an instance is automatically created every time the function is called. The benefit is that the functor has exactly the same interface as a function: it does not need to be instantiated explicitely, and it can be called recursively, or in different threads. The idea here is not particularly to keep a state between subsequent calls (such static data could be stored in the class' dict, for example self.__class__.n += x ). The key idea is encapsulation: from the client code, the fact that it is a functor is an invisible implementation detail.
Since the class is automatically instantiated for each call, there is no need for an __init__ function. Initialization code can be written in the __call__ method if it is needed. The data stored in self is meant to be shared with the helper methods during an execution, but not between calls.
It's a much more dynamic object than a traditional functor.
Perhaps you can begin by reading "Starting Python" , prominently stickied at the the top of the forum, rather than hijacking someone's code snippet with off-topic questions.
In a chain reaction, each atom explodes once and the pieces go collide other atoms which explode, etc. This example uses a functor chain_reaction(item, explode) which takes an initial item (atom) and an explosion function to recursively "collide" new atoms. Each atom must be generated only once in the chain reaction. This functor can be useful to traverse any graph in python
With a little work, the previous functor can be turned into a decorator wrapping the explosion function. Here is the new formulation with an application to traversing a hierarchy of directories
from functools import update_wrapper
class chain_reaction(functor):
"""Decorate an explode function to create a chain reaction.
Usage:
@chain_reaction
def explode(atom):
'function with a single atom argument, returning a sequence of atoms'
...
yield another_atom # for example
for atom in explode(an_atom):
# loop over atoms recursively exploded
An atom can be any hashable python object.
"""
def __call__(self, explode):
self.marked = set()
self.explode = explode
wrapper = lambda item: self.reaction(item)
update_wrapper(wrapper, explode)
return wrapper
def reaction(self, item):
if item in self.marked:
return
self.marked.add(item)
exp = self.explode(item)
yield item
for x in exp:
for y in self.reaction(x):
yield y
if __name__ == "__main__":
from itertools import islice
import os
@chain_reaction
def hierarchy(folder):
"""Recursively generate the descendant directories of a directory"""
p, dirnames, filenames = next(os.walk(folder))
return (os.path.join(folder, d) for d in dirnames)
root = os.path.expanduser("~")
for d in islice(hierarchy(root), 0, 100):
print d
Neat hack, at least if you do not consider that you can get same result as example use with oneliner:
import os
from itertools import islice
print('\n'.join(value[0] for value in islice(os.walk(os.path.expanduser("~")), 0, 100)))
Thanks for the os.path.expanduser('~'). It actually works also in Windows.
I agree that the example was somewhat artificial. Here is a variation which shows how easily you can use chain_reaction to display a tree. I include an improved version of chain_reaction() as an attached file
if __name__ == "__main__":
from easygui import codebox
from itertools import islice
import os
@chain_reaction(unique=False)
def levels((p, n, maxdepth)):
if os.path.isdir(p) and (maxdepth is None or n < maxdepth):
for name in sorted(os.listdir(p)):
yield (os.path.join(p, name), n + 1, maxdepth)
def dirlines(p, maxdepth=None):
for q, n, d in levels((p, 0, maxdepth)):
s = (" " * n) + os.path.basename(q)
yield s + "/" + ("..." if maxdepth is not None and n >= maxdepth else "") \
if os.path.isdir(q) else s
home = os.path.expanduser("~")
codebox(msg = home, text="\n".join(islice(dirlines(home, maxdepth=3), 0, 100)))
Yes this example is less trivial, you can save effort of doing and debugging yourself depth-first walk:
import os
import itertools as it
def process(name, depth, max_depth=None):
return (depth*' '+ (name + '/...' if max_depth and depth >= max_depth else
(name + '/' if max_depth else name)
)
)
def dir_to_depth(current, depth, max_depth, function=process ):
yield function(os.path.basename(current), depth, max_depth)
if depth < max_depth:
listing = sorted(os.listdir(current))
dirs = [dn for dn in listing if os.path.isdir(os.path.join(current,dn))]
for d in dirs:
for deeper in dir_to_depth(os.path.join(current, d), depth + 1, max_depth, function):
yield deeper
dirs = set(dirs)
for f in [fn for fn in listing if fn not in dirs]:
yield function(f, depth+1)
print('\n'.join(it.islice(dir_to_depth(os.path.expanduser('~'), 0, 3), 0, 100)))