Python syntax in Lisp and Scheme

Alex Martelli aleax at aleax.it
Wed Oct 8 06:23:04 EDT 2003


Daniel P. M. Silva wrote:
   ...
> You still can't add new binding constructs or safe parameterizations like
> a with_directory form:
> 
> with_directory("/tmp", do_something())
> 
> Where do_something() would be evaluated with the current directory set to
> " tmp" and the old pwd would be restored afterward (even in the event of
> an exception).

Right: you need to code this very differently, namely:
    with_directory("/tmp", do_something)
*deferring* the call to do_something to within the with_directory
function.  Python uses strict evaluation order, so if and when you
choose to explicitly CALL do_something() it gets called,

So, I would code:

def with_directory(thedir, thefunc, *args, **kwds):
    pwd = os.getcwd()
    try: return thefunc(*args, **kwds)
    finally: os.chdir(pwd)

this is of course a widespread idiom in Python, e.g. see
unittest.TestCase.assertRaises for example.

The only annoyance here is that there is no good 'literal' form for
a code block (Python's lambda is too puny to count as such), so you
do have to *name* the 'thefunc' argument (with a 'def' statement --
Python firmly separates statements from expressions).

> Last year -- I think at LL2 -- someone showed how they added some sort of
> 'using "filename":' form to Python... by hacking the interpreter.

A "using" statement (which would take a specialized object, surely not
a string, and call the object's entry/normal-exit/abnormal-exit methods)
might often be a good alternative to try/finally (which makes no provision
for 'entry', i.e. setting up, and draws no distinction between normal
and abnormal 'exits' -- often one doesn't care, but sometimes yes).  On
this, I've seen some consensus on python-dev; but not (yet?) enough on
the details.  Consensus is culturally important, even though in the end
Guido decides: we are keen to ensure we all keep using the same language,
rather than ever fragmenting it into incompatible dialects.


> Some people use Python's hooks to create little languages inside Python
> (eg. to change the meaning of instantiation), which are not free of
> problems:
> 
> class Object(object):
>   def __init__(this, *args, **kwargs):

[invariably spelt as 'self', not 'this', but that's another issue]

>     this.rest = args
>     this.keys = kwargs
> 
> def new_obj_id(count=[0]):
>    count[0] = count[0] + 1
>    return count[0]
> 
> def tag_obj(obj, id):
>    obj.object_id = id
>    return obj
>    
> def obj_id(obj): return obj.object_id
>    
> type.__setattr__(Object, "__new__", staticmethod(lambda type, *args:
> tag_obj(object.__new__(type), new_obj_id())))
   ...
> # forgot to check for this case...
> print Object(foo="bar")

It's not an issue of "checking": you have written (in very obscure
and unreadable fashion) a callable which you want to accept (and
ignore) keyword arguments, but have coded it in such a way that it
in fact refuses keyword arguments.  Just add the **kwds after the
*args.  This bug is not really related to "little languages" at all:
you might forget to specify arguments which you do want your callable
to accept and ignore in a wide variety of other contexts, too.

A small but important help to avoid such mistakes is to express
your intentions more readably.  Let's suppose the 'Object' class is
externally defined (so we don't want change its metaclass, which would
be a more usual approach in Python), that we know it does not
implement a significant __new__ nor __slots__, and that we want to add 
to it the "tag all objects on creation" feechur, which must use/support
the also-existing and specified functions new_obj_id (for generating
new ids), tag_obj (to set them) and obj_id (to access them) -- each
of these specs is significant (we'd probably code differently if any
or all of them were changed).

Given all this, the normal way to code this functionality in Python
would be something like:

def tagging_new(cls, *args, **kwds):
    new_object = object.__new__(cls)
    new_id = new_obj_id()
    return tag_obj(new_object, new_id)
Object.__new__ = staticmethod(tagging_new)

The use of "type.__setattr__(Object, "__new__", staticmethod(lambda ..."
in lieu of the elementarily simple "Object.__new__ = staticmethod(..."
would be quite peculiar.  The use of a complicated lambda instead of
a simple def also decreases readability (and thus makes mistakes
such as forgetting that the new __new__ must also accept and ignore
**kwds more likely).  Another helpful idiom is to name the first arg
of staticmethod's as 'cls', NOT 'type' (which would cause confusion
with the builtin 'type', often needed in similar contexts).

Moreover, it is possible that the auxiliary functions new_obj_id and
tag_obj were not externally specified, but, rather, coded ad hoc just
to enable the new __new__ to be written as a lambda (i.e., within the
strictures of Python's lambda -- just one expression, no statements).

If that is the case, then they might also be easily refactored out, e.g.:

class Tagger(object):
  def __init__(self):
    self.next_id = 0
  def tag_new(self, cls, *args, **kwds):
    new_object = object.__new__(cls)
    self.next_id += 1
    new_object.object_id = self.next_id
    return new_object
tagging_new = Tagger().tag_new
Object.__new__ = staticmethod(tagging_new)

How best to subdivide the task "tag a new object" is debatable (since
a new tag should never be generated except during tagging, I do prefer
to have the generation of the tag and the affixion of the tag to an
object as inseparable -- but in some cases one might surely prefer to
have them separate from the __new__ so as to be able to tag already
existing objects -- that would, of course, be easy to achieve).  But
the point is that the preferred way in Python to package up some state
(particularly mutable state) and some behavior is within a class; the
use of a mutable default argument in new_obj_id is non-idiomatic -- it
seems better to make a class for the purpose.  This has the advantage
of grouping related state and behavior in ways that any Pythonista
will immediately recognize (the general advantage of using any given
language's idiomatic approach: by doing what readers expect you to do,
you make your code more readable than by being original and creative).

I have extracted the tag_new method of an instance of Tagger and
named it tagging_new in the assumption that we may want to then use
the SAME tagsequence for other classes as well (and may prefer to
avoid doing so by e.g. "OtherClass.__new__  = Object.__new__").  If
that is not the case, then merging the last two lines into

Object.__new__ = staticmethod(Tagger().tag_new)

would probably be preferable.


Alex





More information about the Python-list mailing list