Builtin dict should be callable, since a dict defines a function

holger krekel pyth at devel.trillke.net
Fri Dec 20 18:06:43 EST 2002


[Bengt]
> [holger]
> >> >It's a simple change, sure, but furthermore it's trivial enough for you
> >> >to implement it yourself in a subclass of dict.
> >> Yes (as I mentioned less specifically ;-) But not without the performance
> >> hit of an extra layer.
> >
> >class fdict(dict):
> >    __call__ = dict.__getitem__
> >
> >no performance hit at all.
> I wondered, so I tested:

I didn't think that there could possibly be a difference
because __call__ and __getitem__ point to the same 
object and are in the same class dict.  So i thought
there shouldn't be any difference.  So on to your tests :-)

>  >>> from time import clock
>  >>> def test(f, n):
>  ...     t12=t23=0.0
>  ...     for i in xrange(n):
>  ...         t1=clock()
>  ...         y=f('x')
>  ...         t2=clock()
>  ...         t3=clock()
>  ...         t12+=t2-t1
>  ...         t23+=t3-t2
>  ...     return (t12-t23)/n
>  ...
>  >>> class DSC(dict):
>  ...     __call__ = dict.__getitem__
>  ...
>  >>> d={'x':1}
>  >>> dsc=DSC()
>  >>> dsc['x']=1
>  >>> d
>  {'x': 1}
>  >>> dsc
>  {'x': 1}
>  >>> dmw=d.__getitem__
>  >>> test(dsc,100000)
>  9.4262484684884387e-006
>  >>> test(dmw,100000)
>  3.5901983099864767e-006

1) getitem: you resolve d.__getitem__ outside the
   test (inside it just uses fast locals)

2) call: you resolve the __call__ method inside
   the test (it has to be looked up!)

I changed your test but still __getitem__ is a lot faster
than __call__ (although they are bound to the same object). 
This is probably because __getitem__ is specialized/optimized
which is also why it produces different bytecode.  
 
> It looks like the class version runs 2-3 times slower than
> the direct method wrapper call, AFAICS

yes.

> >I am not sure that it is a good idea to put this into the python dict
> >implementation because it would provide *two* obvious ways to use 
> >a dict-mapping.  
> >
> Well, that's one way to look at it, but not the only one ;-)
> 
> >Maybe it makes sense to implement a different __call__ method:
> If it does, you can always subclass and override. The good old
> __getitem__ interface will still be there ;-)

this argument can be and has been turned against you ;)

> These are interesting.
> >
> >    def __call__(self, *args):
> >        for key in args:
> >            yield self[key]
> >
> >so that you can get to multiple results with one call.  
> >There are certainly a lot of variations/additions possible:
> Ditto
> >
> >    def __call__(self, *args):
> >        for key in args:
> >            if isinstance(key, list):
> >                yield map(self.__getitem__, key)
> >            else:
> >                yield self[key]
> >
> >which would essentially allow 
> >
> >>>> a=fdict(zip(range(0,10),range(10,20)))
> >>>> a([1,2])
> ><generator object at 0x824e040>
> >>>> a([1,2]).next()
> >[11, 12]
> >>>> map(None, a([1,2], 3,4))
> >[[11, 12], 13, 14]
> >>>> v1,v2,v3 = a(1,2,3)
> >
> >Now the real work is to figure out which __call__ semantics 
> >would satisfy a real need or be really simplifying in everyday
> >work. 
> Well, I think these are all interesting variations, but I don't
> think any qualifies as a uniquely simple and unambiguous semantic
> for a dict, whereas I think the simple 1:1 key:value mapping function
> does. I think that the index:value mappings of lists and tuples also
> define simply understood functions, and I would like the unifying alias
> of a default __call__ for those analogously, while recognizing that
> I can use obj.__getitem__ now.

I guess that only different (needed/helpful) behaviour could possibly
convince people to make a change.  Duplicating a name on one
of the fundamental types in python goes against the grain of the
language design (IMHO). 

> It's not as if I'm proposing making  (x, y, z)() == x(y, z)
> ;-)

regards,

    holger




More information about the Python-list mailing list