Magic function

dg.google.groups at thesamovar.net dg.google.groups at thesamovar.net
Fri Jan 11 17:29:18 CET 2008


Hi all,

I'm part of a small team writing a Python package for a scientific
computing project. The idea is to make it easy to use for relatively
inexperienced programmers. As part of that aim, we're using what we're
calling 'magic functions', and I'm a little bit concerned that they
are dangerous code. I'm looking for advice on what the risks are (e.g.
possibility of introducing subtle bugs, code won't be compatible with
future versions of Python, etc.).

Quick background: Part of the way our package works is that you create
a lot of objects, and then you create a new object which collects
together these objects and operates on them. We originally were
writing things like:

obj1 = Obj(params1)
obj2 = Obj(params2)
...
bigobj = Bigobj(objects=[obj1,obj2])
bigobj.run()

This is fine, but we decided that for clarity of these programs, and
to make it easier for inexperienced programmers, we would like to be
able to write something like:

obj1 = Obj(params1)
obj2 = Obj(params2)
...
run()

The idea is that the run() function inspects the stack, and looks for
object which are instances of class Obj, creates a Bigobj with those
objects and calls its run() method.

So, any comments on that approach?

I'm including the code I've written to do this, and if you have time
to look through it, I'd also be very grateful for any more specific
comments about the way I've implemented it (in particular, can it be
made faster, is my program creating cycles that stop the garbage
collection from working, etc.). I hope the code will be formatted
correctly:

def
getInstances(instancetype,level=1,includeglobals=True,containersearchdepth=1,exclude={},predicate=lambda
x:True):
    """Find all instances of a given class at a given level in the
stack
    """
    vars = {}
    # Note: we use level+1 because level refers to the level relative
to the function calling this one
    if includeglobals: vars.update(stack()[level+1][0].f_globals)
    vars.update(stack()[level+1][0].f_locals)
    # Note that you can't extract the names from vars.itervalues() so
we provide via knownnames the names vars.iterkeys(),
    # containersearchdepth+1 is used because vars.itervalues() is the
initial container from the point of view of this
    # function, but not from the point of view of the person calling
getInstances
    objs, names =
extractInstances(instancetype,vars.itervalues(),containersearchdepth
+1,knownnames=vars.iterkeys(),exclude=exclude,predicate=predicate)
    return (objs,names)

def
extractInstances(instancetype,container,depth,containingname='vars()',knownnames=None,exclude={},predicate=lambda
x:True):
    if depth<=0: return ([],[])
    if isinstance(container,str): return ([],[]) # Assumption: no need
to search through strings
    # Ideally, this line wouldn't be here, but it seems to cause
programs to crash, probably because
    # some of the simulator objects are iterable but shouldn't be
iterated over normally
    # TODO: Investigate what is causing this to crash, and possibly
put in a global preference to turn this line off?
    if not isinstance(container,
(list,tuple,dict,type({}.itervalues()))): return ([],[])
    # Note that knownnames is only provided by the initial call of
extractInstances and the known
    # names are from the dictionary of variables. After the initial
call, names can only come from
    # the __name__ attribute of a variable if it has one, and that is
checked explicitly below
    if knownnames is None:
        knewnames = False
        knownnames = repeat(containingname)
    else:
        knewnames = True
    objs = []
    names = []
    try: # container may not be a container, if it isn't, we'll
encounter a TypeError
        for x,name in zip(container,knownnames):
            # Note that we always have a name variable defined, but if
knewnames=False then this is just
            # a copy of containingname, so the name we want to give it
in this instance is redefined in this
            # case. We have to use this nasty check because we want to
iterate over the pair (x,name) as
            # variables in the same position in the container have the
same name, and we can't necessarily
            # use __getitem__
            if hasattr(x,'__name__'): name = x.__name__
            elif not knewnames: name = 'Unnamed object, id =
'+str(id(x))+', contained in: '+containingname
            if isinstance(x,instancetype):
                if x not in exclude and predicate(x):
                    objs.append(x)
                    names.append(name)
            else: # Assumption: an object of the instancetype is not
also a container we want to search in.
                # Note that x may not be a container, but then
extractInstances will just return an empty list
                newobjs, newnames =
extractInstances(instancetype,x,depth-1,containingname=name,predicate=predicate)
                objs += newobjs
                names += newnames
        return (objs,names)
    except: # if we encounter a TypeError from the for loop, we just
return an empty pair, container wasn't a container
        return ([],[])

In case that doesn't work, here it is without the comments:

def
getInstances(instancetype,level=1,includeglobals=True,containersearchdepth=1,exclude={},predicate=lambda
x:True):
    vars = {}
    if includeglobals: vars.update(stack()[level+1][0].f_globals)
    vars.update(stack()[level+1][0].f_locals)
    objs, names =
extractInstances(instancetype,vars.itervalues(),containersearchdepth
+1,knownnames=vars.iterkeys(),exclude=exclude,predicate=predicate)
    return (objs,names)

def
extractInstances(instancetype,container,depth,containingname='vars()',knownnames=None,exclude={},predicate=lambda
x:True):
    if depth<=0: return ([],[])
    if isinstance(container,str): return ([],[])
    if not isinstance(container,
(list,tuple,dict,type({}.itervalues()))): return ([],[])
    if knownnames is None:
        knewnames = False
        knownnames = repeat(containingname)
    else:
        knewnames = True
    objs = []
    names = []
    try:
        for x,name in zip(container,knownnames):
            if hasattr(x,'__name__'): name = x.__name__
            elif not knewnames: name = 'Unnamed object, id =
'+str(id(x))+', contained in: '+containingname
            if isinstance(x,instancetype):
                if x not in exclude and predicate(x):
                    objs.append(x)
                    names.append(name)
            else:
                newobjs, newnames =
extractInstances(instancetype,x,depth-1,containingname=name,predicate=predicate)
                objs += newobjs
                names += newnames
        return (objs,names)
    except:
        return ([],[])



More information about the Python-list mailing list