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

Bengt Richter bokr at oz.net
Fri Dec 20 22:44:00 EST 2002


On 21 Dec 2002 00:25:25 +0100, martin at v.loewis.de (Martin v. =?iso-8859-15?q?L=F6wis?=) wrote:

>bokr at oz.net (Bengt Richter) writes:
>
>> >Your misconception is that you want to make one functional access to an 
>> >object the preferred one. This is confusing:
>> Not to be confrontational, but what gives you the idea that I want
>> to make it preferred? 
>
>Your message <atttql$ouf$0 at 216.39.172.122> indicated so. You wrote
>
># __call__(self, *args): return self[args]
>
>So you want d.__call__ to be the same as d.__getitem__. I was asking
(BTW actually the above is not the same due to single arg not getting
tuplified, see below.)

>why it shouldn't be 
>
> def __call__(self, *args):
>   return self.get(args)
>
Because get takes an extra argument and effectively extends the function
dynamically also if the default is not used. I am taking d[k] as the
defining function, being the simpler (considering exceptions to be exceptional ;-)
(In any case they're the same).

>I.e. d.__call__ = d.__get__
>
>Both are meaningful calling interfaces for the function defined by the
>dictionary, yet you prefer one of these two options over the other.
>
True.

>> Even so, I still don't see the objection to the nicer spelling,
>> since that syntax is not doing anything by default.
>
>My objection is that such a spelling should have an obvious meaning,
>i.e. that one should refuse the temptation to guess. It is not obvious
>to me why __getitem__ is better than get.
The point is for __call__ to do as default exactly what __getitem__ does.
What's to guess? (overriding is a different ball game).

>
>> One defines a (non-exception-raising) function mapping finite sets 1:1,
>> the other maps the rest of the universe to any selected element of the
>> universe as well. ISTM the simpler is the more natural default
>
>I question that the exception-raising one is the simpler one. From a
>functional point, exception raising is a difficult issue, functions
>are conceptually always total.
>
>> But I still would ask the question, "Why should an object which has an
>> unambiguous, simple, sensible, intuitively obvious functional
>> interpretation refuse to be called as a function (i.e., provide the
>> __call__ method wrapper) if this is not already defined ?"
>
>Can't answer this question in general. In the specific case, there is
>an easy answer: There is no unambiguous functional interpretation for
>dictionaries.

Well, there is in my mind ;-) Here's a go at a concrete representation
of an unambigous translation of d[x] to f(x), where f(x) does not depend
on d.__getitem__. I.e., it's really a function, just to show a concrete
function rendering of the same abstraction. And there is an unambiguous
(ISTM) 1:1 relationship.

Hence what makedfun returns when passed a dict is what I would say is an
unambiguous functional interpretation for the given dictionary.
It defines the *behavior* I was proposing (albeit without len(args)==1 test)
for default dict.__call__ behaviour (though I'm certainly not proposing
this implementation ;-))

Now pretend this _function_ is bound to __call__. Then optimize. Will
you not get d.__call__ == d.__getitem__ ?

====< makedefun.py >=========================================
def makedfun(dct):
    keys, values = map(list,zip(*dct.items()))
    def dictfun(*key):
        if len(key)==1: key=key[0]
        if key not in keys: raise KeyError, 'dictfun KeyError: %s' % key
        i = keys.index(key)
        return values[i]
    return dictfun

def test(d):
    dfoo = makedfun(d)
    for k,v in d.items():
        print 'k=%s, d[%s]=%s, dfoo[%s]=%s' % (k,k,d[k],k,dfoo(k))

if __name__=='__main__':
    test(dict([(x,chr(x)) for x in range(ord('a'),ord('e')+1)]))
    test({1:'one',2:'two',(1,2):'tup12',(3,4):'tup34',(1,):'tup1'})
    print 'Should be 1:',makedfun({'x':1})('x'),"Next should fail with 'y' key"
    try:
        print makedfun({'x':1})('y')
    except KeyError, e:
        print e
    else:
        print "Failed to fail on key 'y'"
=============================================================
Output:

k=97, d[97]=a, dfoo[97]=a
k=98, d[98]=b, dfoo[98]=b
k=99, d[99]=c, dfoo[99]=c
k=100, d[100]=d, dfoo[100]=d
k=101, d[101]=e, dfoo[101]=e
k=(1,), d[(1,)]=tup1, dfoo[(1,)]=tup1
k=1, d[1]=one, dfoo[1]=one
k=2, d[2]=two, dfoo[2]=two
k=(1, 2), d[(1, 2)]=tup12, dfoo[(1, 2)]=tup12
k=(3, 4), d[(3, 4)]=tup34, dfoo[(3, 4)]=tup34
Should be 1: 1 Next should fail with 'y' key
dictfun KeyError: y

If you want to, you can pick a nit with the equivalent results
of dfun() and dfun(()) ;-)

 >>> import makedfun
 >>> dfun = makedfun.makedfun({1:'one',():'tempty tuple'})
 >>> d = {1:'one',():'tempty tuple'}
 >>> dfun()
 'tempty tuple'
 >>> dfun(())
 'tempty tuple'
 >>> d[()]
 'tempty tuple'
 >>> d[]
   File "<stdin>", line 1
     d[]
       ^
 SyntaxError: invalid syntax

Regards,
Bengt Richter



More information about the Python-list mailing list