简体   繁体   中英

Why does python dict behave this way?

I have some code written like so:

class Invite(models.Model):
    STATE_UNKNOWN = 0
    STATE_WILL_PLAY = 1
    STATE_WONT_PLAY = 2
    STATE_READY = 3
    STATE_CHOICES = ((STATE_UNKNOWN, _("Unknown")),
                     (STATE_WILL_PLAY, _("Yes, I'll play")), 
                     (STATE_WONT_PLAY, _("Sorry, can't play")),
                     (STATE_READY, _("I'm ready to play now")))
    ...


    def change_state(self, state):
        assert(state in dict(Invite.STATE_CHOICES))

This code works like I want it to, but I'm curious as to why it works this way. It is admittedly very convenient that it does work this way, but it seems like maybe I'm missing some underlying philosophy as to why that is.

If I try something like:

dict((1,2,3), (2,2,3), (3,2,3))
ValueError: dictionary update sequence element #0 has length 3; 2 is required

it doesn't create a dict that looks like

{1: (2,3), 2: (2,3), 3: (2,3)}

So the general pattern is not to take the first part of the tuple as the key and the rest as the value. Is there some fundamental underpinning that causes this behavior, or it is just, well, it would be convenient if it did....

The constructor of dict accepts (among other things) a sequence of (key, value) tuples. Your second examples passes a list of tuples of length 3 instead of 2, and hence fails.

dict([(1, (2, 3)), (2, (2, 3)), (3, (2, 3))])

however will create the dictionary

{1: (2, 3), 2: (2, 3), 3: (2, 3)}

I think it's somewhat obvious. In your example, (1,2,3) is a single object. So the idea behind a dictionary is to map a key to a value (ie object).

So consider the output:

>>> dict(((1,(2,3)), (2,(2,3)))).items()
[(1, (2, 3)), (2, (2, 3))]

But you can also do something like this:

>>> dict((((1,2),3), ((2,2),3)))
[((1, 2), 3), ((2, 2), 3)]

Where the key is actually an object too! In this case a tuple also.

So in your example:

dict((1,2,3), (2,2,3), (3,2,3))

how do you know which part of each tuple is the key and which is the value?

If you find this annoying, it's a simple fix to write your own constructor:

def special_dict(*args):
    return dict((arg[0], arg[1:]) for arg in args)

Also, to Rafe's comment, you should define the dictionary right away:

class Invite(models.Model):
    STATE_UNKNOWN = 0
    STATE_WILL_PLAY = 1
    STATE_WONT_PLAY = 2
    STATE_READY = 3
    STATE_CHOICES = dict(((STATE_UNKNOWN, _("Unknown")),
                     (STATE_WILL_PLAY, _("Yes, I'll play")), 
                     (STATE_WONT_PLAY, _("Sorry, can't play")),
                     (STATE_READY, _("I'm ready to play now"))))
    ...


    def change_state(self, state):
        assert(state in Invite.STATE_CHOICES)

If you ever want to iterate over the states, all you have to do is:

for state, description = Invite.STATE_CHOICES.iteritems():
    print "{0} == {1}".format(state, description)

The construction of the dictionary in your change_state function is unnecessarily costly.

When you define the Django field, just do:

models.IntegerField(sorted(choices=Invite.STATE_CHOICES.iteritems()))

The general pattern is just this: you can create a dict from a list (in general: iterable) of pairs , treated as (key, value). Anything longer would be arbitrary: why (1,2,3)->{1:(2,3)} and not (1,2,3)-> {(1,2):3}?

Moreover, the pairs<->dict conversion is obviously two-way. With triples it couldn't be (see the above example).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM