[Python-Dev] Patch to use dict subclasses in eval(), exec

Jeff Epler jepler@unpythonic.net
Tue, 29 Oct 2002 08:34:26 -0600


On Tue, Oct 29, 2002 at 07:09:22AM -0500, Guido van Rossum wrote:
> I don't see the extra DECREF here (and below) needed to compensate for
> the fact that PyObject_GetItem() returns the object with incremented
> refcount but PyDict_GetItem() doesn't.

oops.  I believe this can be fixed, if only by a DECREF in all paths and
an INCREF in the fast path.  However, this will worsen the "fast case"
performance figures at least a tiny bit.

[guido again]
> What are you going to do with all the *other* places where
> PyDict_GetItem() is used?

Which places do you have in mind?  The common feature request seems to
be for globals() only, and I can't immediately think of a way of
accessing globals() that my patch doesn't (attempt to) cover.

Unfortunately, I'm not particularly excited about this capability
myself.

What I think this does show, however, is that using a sequence like
(not quite C code)
    inline PyObject *PyObject_GetItem_FastForDicts(o, k) {
        if(PyDict_CheckExact(o)) {
            ret = PyDict_GetItem(o, k);
            Py_INCREF(ret);
            return ret;
        }
        return PyObject_GetItem(o, k);
    }
will impose in-the-noise penalties for 'dict' instances, modest
penalties for 'dict' subclass instances without __getitem__, and
provide the full power for general objects.


On Tue, Oct 29, 2002 at 08:57:01AM +0100, M.-A. Lemburg wrote:
> If all you want is a way to define a default value for a failing
> dictionary lookup, wouldn't it be better to simply add this feature
> to the standard dictionary implementation ?

It doesn't seem to be quite this simple, because you almost certainly
want to have items in the builtin namespace appear even if you want to
give a default on what would eventually be a NameError for a
LOAD_GLOBAL/LOAD_NAME.  You end up with something like
    class D(dict):
        def __getitem__(self, item):
            try:
                return dict.__getitem__(self, item)
            except KeyError:
                try:
                    return getattr(__builtins__, item)
                except AttributeError:
                    return "XX"
which is more complicated that a "simple" defaulting dictionary.

Unfortunately, this is another time when I am not a strong proponent of a 
feature, but merely wrote the patch to see if the "common wisdom" on which
the rejection was based is valid.  (common wisdom's been right in earlier
cases, but I'm tempted to call the speed penalties for this change
entirely tolerable if the purposes of people who do want this patch are
good ones)


On Tue, Oct 29, 2002 at 12:11:12AM -0500, Raymond Hettinger wrote
[in a private message]:
> Do you know why the following snippet doesn't work with your patch?
>
> class SpreadSheet(dict):
>     def __getitem__(self, key):
>         return eval(dict.__getitem__(self,key), self)
>
> ss = SpreadSheet()
> ss['a1'] = '5'
> ss['a2'] = 'a1*6'
> ss['a3'] = 'a2*a1'
> print ss['a3']

It took me awhile to figure this out...

LOAD_NAME first tries to load from the locals dict, where I did not
change the way that name resolution functions.  eval() uses
the same dict for locals and globals if the locals (third arg) are not
directly specified.

The following works:
    class SpreadSheet(dict):
        def __getitem__(self, key):
            return eval(dict.__getitem__(self,key), self, {})

Jeff