views:

272

answers:

3

I 'm trying to do :

award_dict = {
    "url" : "http://facebook.com",
    "imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png",
    "count" : 1,
}

def award(name, count, points, desc_string, my_size, parent) :
    if my_size > count :
        a = {
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }
        a.update(award_dict)
        return self.add_award(a, siteAlias, alias).award

But if felt really cumbersome in the function, and I would have rather done :

        return self.add_award({
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }.update(award_dict), siteAlias, alias).award

Why doesn't update return the object so you can chain?

JQuery does this to do chaining. Why isn't it acceptable in python?

+1  A: 

Its not that it isn't acceptable, but rather that dicts weren't implemented that way.

If you look at Django's ORM, it makes extensive use of chaining. Its not discouraged, you could even inherit from dict and only override update to do update and return self, if you really want it.

class myDict(dict):
    def update(self, *args):
     dict.update(self, *args)
     return self
voyager
Thank you, this could patch dict, I just wanted to know why dict() didn't allow this functionality itself (since it is as easy as you demonstrate). Does Django patch dict like this?
Paul Tarjan
+5  A: 

Python's API, by convention, distinguishes between procedures and functions. Functions compute new values out of their parameters (including any target object); procedures modify objects and don't return anything (i.e. they return None). So procedures have side effects, functions don't. update is a procedure, hence it doesn't return a value.

The motivation for doing it that way is that otherwise, you may get undesirable side effects. Consider

bar = foo.reverse()

If reverse (which reverts the list in-place) would also return the list, users may think that reverse returns a new list which gets assigned to bar, and never notice that foo also gets modified. By making reverse return None, they immediately recognize that bar is not the result of the reversal, and will look more close what the effect of reverse is.

Martin v. Löwis
Thank you. Why wouldn't reverse also give the option to not do it inplace? Performance? doing `reverse(foo)` feels weird.
Paul Tarjan
Adding an option would be inappropriate: it would change the nature of the method depending on a parameter. However, methods should really have fixed return types (there are, unfortunately, cases where this rule is broken). It's easy to create a reverted copy: just make a copy (using `bar=foo[:]`), then revert the copy.
Martin v. Löwis
I think the reason is explicitness. In `bar = foo.reverse()`, you could think that `foo` is not modified. To avoid confusion, you have both `foo.reverse()` and `bar = reversed(foo)`.
Roberto Bonvallet
+9  A: 

Python's mostly implementing a pragmatically tinged flavor of command-query separation: mutators return None (with pragmatically induced exceptions such as pop;-) so they can't possibly be confused with accessors (and in the same vein, assignment is not an expression, the statement-expression separation is there, and so forth).

That doesn't mean there aren't a lot of ways to merge things up when you really want, e.g., dict(a, **award_dict) makes a new dict much like the one you appear to wish .update returned -- so why not use THAT if you really feel it's important?

Edit: btw, no need, in your specific case, to create a along the way, either:

dict(name=name, description=desc % count, points=points, parent_award=parent,
     **award_dict)

creates a single dict with exactly the same semantics as your a.update(award_dict) (including, in case of conflicts, the fact that entries in award_dict override those you're giving explicitly; to get the other semantics, i.e., to have explicit entries "winning" such conflicts, pass award_dict as the sole positional arg, before the keyword ones, and bereft of the ** form -- dict(award_dict, name=name etc etc).

Alex Martelli
Well, that will create another dictionary after I had to make a. I wanted to create a dict, and then add a bunch of other values, and then give it to a function.
Paul Tarjan
@Paul, and that's exactly what you're doing -- with two statements (much more readable than the nested way you wanted) which to you "felt really cumbersome". Editing my answer to show how to avoid creating `a` altogether, btw,
Alex Martelli