[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

Magic function

dg.google.groups

1/11/2008 4:29:00 PM

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 ([],[])
15 Answers

Mike Meyer

1/11/2008 4:51:00 PM

0

On Fri, 11 Jan 2008 08:29:18 -0800 (PST) dg.google.groups@thesamovar.net wrote:

> 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?

The basic idea is ok, but looking at the stack makes me a bit
nervous. That makes the code complicated, and probably fragile in the
face of changing python versions.

The unittest module does much the same thing - you run unittest.main,
and it runs all the tests in any TestCase subclass in your module
(assuming you didn't do something to limit it). However, it does it by
examining the module, not the stack. The real difference is that your
"magic" classes have to be global to your module. On the other hand,
it provides some nice tools to let you partition things, so you can
easily run subsets of the classes from the command line.

It's probably worth a look.

<mike
--
Mike Meyer <mwm@mired.org> http://www.mired.org/consu...
Independent Network/Unix/Perforce consultant, email for more information.

oj

1/11/2008 5:09:00 PM

0

On Jan 11, 4:29 pm, dg.google.gro...@thesamovar.net wrote:
> 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 ([],[])

If you are the author of class Obj, then why not just make the class
maintain a record of any objects that have been instantiated?

That way, run could simply call a class method to obtain a list of all
the objects it needs.

Ruediger

1/11/2008 8:21:00 PM

0

dg.google.groups@thesamovar.net wrote:

> 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.


Well i would do it this way:
no fancy stuff, all standard and fast.

from weakref import ref

class bigobject(set):
def __iter__(self):
for obj in set.__iter__(self):
yield obj()
def run(self):
for obj in self:
print obj.value

class foo(object):
""" weakref doesn't prevent garbage collection if last instance
is destroyed """
__instances__ = bigobject()
def __init__(self, value):
foo.__instances__.add(ref(self,foo.__instances__.remove))
self.value = value

if __name__ == "__main__":
obj1 = foo("obj1")
obj2 = foo("obj2")
obj3 = foo("obj3")
obj4 = foo("obj4")
foo.__instances__.run()
print "test garbage collection."
del obj1, obj2, obj3, obj4
foo.__instances__.run()






Paul Rubin

1/11/2008 8:54:00 PM

0

dg.google.groups@thesamovar.net writes:

> 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?

Bleeearrrrrggggh!!!! Just make the object initializer remember where
the instances are. Or, write something like:

newobj = Bigobj()
# give Bigobj a __call__ method to create and record an object

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

Steven D'Aprano

1/12/2008 12:16:00 AM

0

On Fri, 11 Jan 2008 08:29:18 -0800, dg.google.groups wrote:

> 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.

....

> 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?

Your users are *scientists*, and you don't trust their intellectual
ability to learn a programming language as simple as Python?

Instead of spending time and effort writing, debugging and maintaining
such a fragile approach, why not invest in a couple of introductory books
on Python programming and require your scientists to go through the first
few chapters? Or write out a one-page "cheat sheet" showing them simple
examples. Or, and probably most effectively, make sure all your classes
have doc strings with lots of examples, and teach them how to use help().

Some people problems are best dealt with by a technical solution, and
some are not.



--
Steven

mtobis

1/12/2008 1:36:00 AM

0

On Jan 11, 6:15 pm, Steven D'Aprano <st...@REMOVE-THIS-
cybersource.com.au> wrote:
> Your users are *scientists*, and you don't trust their intellectual
> ability to learn a programming language as simple as Python?
>
> Instead of spending time and effort writing, debugging and maintaining
> such a fragile approach, why not invest in a couple of introductory books
> on Python programming and require your scientists to go through the first
> few chapters? Or write out a one-page "cheat sheet" showing them simple
> examples. Or, and probably most effectively, make sure all your classes
> have doc strings with lots of examples, and teach them how to use help().
>
> Some people problems are best dealt with by a technical solution, and
> some are not.
>
> --
> Steven

I am currently talking very similar trash on my blog, See
http://initforthegold.blogspot.com/2008/01/staying-... and
http://initforthegold.blogspot.com/2007/12/why-is-climate-modeling-...

You seem to think that learning the simple language is equivalent to
grasping the expressive power that the language provides.

Yes, users are scientists. Therefore they do not have the time or
interest to gain the depth of skill to identify the right abstractions
to do their work.

There are many abstractions that could be useful in science that are
currently provided with awkward libraries or messy one-off codes.

The idea that a scientist should be expected to be able to write
correct and useful Python is reasonable. I and the OP are relying on
it.

The idea that a scientist should be expected to identify and build
clever and elegant abstractions is not. If you think every scientist
can be a really good programmer you underestimate at least one of what
good scientists do or what good programmers do or what existing high
performance scientific codes are called upon to do.

mt

Steven D'Aprano

1/12/2008 2:41:00 AM

0

On Fri, 11 Jan 2008 17:36:10 -0800, Michael Tobis wrote:

> On Jan 11, 6:15 pm, Steven D'Aprano <st...@REMOVE-THIS-
> cybersource.com.au> wrote:
>> Your users are *scientists*, and you don't trust their intellectual
>> ability to learn a programming language as simple as Python?
>>
>> Instead of spending time and effort writing, debugging and maintaining
>> such a fragile approach, why not invest in a couple of introductory
>> books on Python programming and require your scientists to go through
>> the first few chapters? Or write out a one-page "cheat sheet" showing
>> them simple examples. Or, and probably most effectively, make sure all
>> your classes have doc strings with lots of examples, and teach them how
>> to use help().
>>
>> Some people problems are best dealt with by a technical solution, and
>> some are not.
>>
>> --
>> Steven
>
> I am currently talking very similar trash on my blog, See
> http://initforthegold.blogspot.com/2008/01/staying-... and
> http://initforthegold.blogspot.com/2007/12/why-is-climate...
stuck.html
>
> You seem to think that learning the simple language is equivalent to
> grasping the expressive power that the language provides.


I do? What did I say that led you to that conclusion?


> Yes, users are scientists. Therefore they do not have the time or
> interest to gain the depth of skill to identify the right abstractions
> to do their work.

I don't follow you. If they aren't learning the skills they need to do
their work, what are they doing? Hammering screws in with a hacksaw?
(Metaphorically speaking.)



> There are many abstractions that could be useful in science that are
> currently provided with awkward libraries or messy one-off codes.

I'm sure you're right. Attempts to make elegant libraries and re-usable
code should be encouraged. The OP's attempt to dumb-down his library
strikes me as a step in the wrong direction.


> The idea that a scientist should be expected to be able to write correct
> and useful Python is reasonable. I and the OP are relying on it.

Please go back and look at the example the OP gave. According to the
example given, his users would find this too difficult to deal with:

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


That's not terribly complex code, thanks to Python's easy-to-use object
model. Dropping the explicit construction of the Bigobj in favour of a
mysterious, implicit auto-magic run() is a serious step in the wrong
direction. Any scientist working with this can see exactly what is being
run(), and not have to rely on hunting through the entire source code
looking for Obj() calls he might have missed.

As simple as the above is, it could be made simpler. Judging from the
example given, the Bigobj constructor doesn't need a keyword argument, it
could just as easily take an arbitrary number of arguments:

bigobj = Bigobj(obj1, obj2, obj3, obj4...)


> The idea that a scientist should be expected to identify and build
> clever and elegant abstractions is not.

But that's their job. That's what scientists do: identify and build
clever and elegant abstractions, such as Newton's Laws of Motion, Special
Relativity, Evolution by Natural Selection, the Ideal Gas Laws, and so on.

Even climate change models are abstractions, and we would hope they are
clever and elegant rather than stupid and ugly.



> If you think every scientist can
> be a really good programmer you underestimate at least one of what good
> scientists do or what good programmers do or what existing high
> performance scientific codes are called upon to do.

Read the OP's post again. His (her?) users aren't expected to create the
toolkit, merely to use it. To create good toolkits you need both a master
programmer and an expert in the field. It is an advantage if they are the
same person. But to use such a good toolkit, you shouldn't need to be a
master programmer.



--
Steven

mtobis

1/12/2008 3:36:00 AM

0

On Jan 11, 8:40 pm, Steven D'Aprano <st...@REMOVE-THIS-
cybersource.com.au> wrote:

> Read the OP's post again. His (her?) users aren't expected to create the
> toolkit, merely to use it. To create good toolkits you need both a master
> programmer and an expert in the field. It is an advantage if they are the
> same person. But to use such a good toolkit, you shouldn't need to be a
> master programmer.

It appears we are in agreement, then.

But that leaves me in a position where I can't understand your
complaint. There's no reason I can see for the sort of compromise you
ask for.

Clean abstractions benefit from their cleanliness.

Of course the users will have a lot to learn regardless, but that's
the point. A user has to decide whether to take on a new tool.

If that learning is about meaningless incantations (the way beginning
programmers are currently taught to say "abracadabra public static
void main") users will be less impressed with the advantage of the
abstractions and be less likely to engage the new methods on offer. If
the learning exposes new potential, that makes your tool more
attractive.

What's more, the next higher layer of abstraction will be easier to
compose if the composer of that abstraction doesn't have to make the
sort of compromise you suggest. Abstractions that stay out of the way
until you need to expand on them is a big part of what Python is all
about.

It's not clear that this is the sort of application where cutting
corners makes sense, so I don't see how your advice is justified.

mt

Steven D'Aprano

1/12/2008 5:18:00 AM

0

On Fri, 11 Jan 2008 19:36:24 -0800, Michael Tobis wrote:

> On Jan 11, 8:40 pm, Steven D'Aprano <st...@REMOVE-THIS-
> cybersource.com.au> wrote:
>
>> Read the OP's post again. His (her?) users aren't expected to create
>> the toolkit, merely to use it. To create good toolkits you need both a
>> master programmer and an expert in the field. It is an advantage if
>> they are the same person. But to use such a good toolkit, you shouldn't
>> need to be a master programmer.
>
> It appears we are in agreement, then.
>
> But that leaves me in a position where I can't understand your
> complaint. There's no reason I can see for the sort of compromise you
> ask for.

What compromise do you think I'm asking for?

I'm suggesting that the scientists be given a brief, introductory
education in *how to use their tool*, namely, Python.

Instead of creating some sort of magic function that "just works" (except
when it doesn't) by doing some sort of implicit "grab every object of
type Obj() you can find and do processing on that", stick to the more
reliable and safer technique of having the programmer explicitly provide
the objects she wants to work with.


> Clean abstractions benefit from their cleanliness.

An automatic "run()" that uses a bunch of stuff you can't see as input is
not a clean abstraction. "Do What I Mean" functions have a long and
inglorious history of not doing what the user meant.

There's a fundamental difference between (say) Python's automatic garbage
collection and what the OP is suggesting. Explicitly deleting variables
is almost always the sort of trivial incantation you rightly decry. The
computer can tell when a variable is no longer reachable, and therefore
is safe to delete. But the computer can't safely tell when the user wants
to use a variable as input to a function. The user needs to explicitly
tell the computer what is input and what isn't.

The OP is suggesting taking that decision out of the hands of the user,
and making every variable of type Obj automatically input. If you think
that's a good idea, consider a programming tool kit with a function sum()
which inspects every variable you have and adds up every one that is a
number.



> It's not clear that this is the sort of application where cutting
> corners makes sense, so I don't see how your advice is justified.

Sorry, are you suggesting that training the scientists to use their tools
is cutting corners? Because I'd call the OP's suggestion to use magic
functions a dangerous, ill-conceived cut corner.


--
Steven

Carl Banks

1/12/2008 6:33:00 AM

0

On Fri, 11 Jan 2008 08:29:18 -0800, dg.google.groups wrote:

> 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?


1. Even if you implement magic functions, don't get rid of the
straightforward "hard way".

Magic functions should be for convenience only. The user should be free
to choose to do it the straightforward, explicit "hard way", and not rely
on the magic. In your example, Bigobj should still be available to
users, and should be documented at least as well as the magic run()
function.

The main reason for this (aside from the philosophical question) is that
users often have different needs that you can anticipate, and your magic
might not meet those unanticipated needs, forcing the user to resort to
hacks and workarounds.


2. If your intention is to perform this operation on all Objs, then it
might be a good idea to arrange your code so that Objs are already
registered by the time the user gets them.

One way to do this has already been mentioned: by having the Obj class
track all its instances.

Another way that might be preferable is to have Bigobj create Objs on
behalf of the user. Here's a stripped down example:


class Bigobj(object):
def __init__(self):
self.tracked_objs = set()
def create_object(self,*args):
obj = Obj(*args)
self.tracked_objs.add(obj)
return obj
def run(self):
for obj in self.tracked_objs:
# do something with obj

bigobj = Bigobj()

obj1 = bigobj.create_object(params1)
obj2 = bigobj.create_object(params2)

# maybe do something with obj1 and obj2 here

bigobj.run()


Carl Banks