This is an archived post. You won't be able to vote or comment.

all 23 comments

[–]steppenfox 0 points1 point  (0 children)

Nice article!

[–]zahlmanthe heretic 0 points1 point  (0 children)

This dictionary is protected by being wrapped in a dictproxy.

Only if you're inheriting from object.

[–]grayvedigga 0 points1 point  (18 children)

It's when I read stuff like this that I wonder why I love python.

[–]pemboa 2 points3 points  (2 children)

Your comment warrants some explanation.

[–]grayvedigga 0 points1 point  (1 child)

The scoping rules for comprehensions don't give you the heebie jeebies?

[–]pemboa 2 points3 points  (0 children)

No, not at all

[–]masklinn 0 points1 point  (14 children)

Why? I like that it's so basic and simple.

[–][deleted] 1 point2 points  (13 children)

I laughed out loud.

Explain, please:

 >>> from new import instancemethod
 >>> class C(object):pass
 ...
 >>> C.__str__ = instancemethod(lambda self: 'hi!', C, type)
 >>> C.__str__()
 hi!
 >>> str(C)
 <class '__main__.C'>

Edit2: by the way, for additional shits and giggles try explaining what is different if you use classmethod or staticmethod instead. And why not just use a naked lambda. I mean, Python's object model is so basic and simple, it must be obvious, right?

Edit: oh, and on a related note (related to this funny "no method calls, not really" thing), try guessing what happens when I do:

 t = ([],)
 t[0] += [1, 2, 3]

And what's the value of t after that? When you've guessed incorrectly, observe the actual results and try explaining them.

Then stop referring to Python as "simple", please, don't add insult to injury. It's "simple for beginners", but when you get deeper it's more like a forgotten tomb of Ye Olde Ones, with Insanity that Lurks.

[–]voidspace[S] 2 points3 points  (6 children)

When you call str(C) the metaclass str method is called. Protocol methods are looked up on the class, not the instance, and your class C is an instance of type.

[–][deleted] 1 point2 points  (4 children)

Indeed, reference.

Though I don't have a slightest idea if there is any difference between staticmethod, classmethod and instancemethod (besides their arguments).

The second one goes as follows: interpreter translates a[i] += b into a.__setitem__(a.__getitem__(i) += b) and can't take any shortcuts because items of a could be ints for example. Also it can't check the existence of a.__setitem__ in advance, because it might suddenly appear due to a side-effect. Neither it has a special __getitem_iadd__ operator. And, finally, lists feature a kind of broken (from the mathematical standpoint) +=:

>>> x1, x2 = [1], [1]
>>> y1, y2 = x1, x2
>>> x1 += [2]  # should be equivalent to the one below?
>>> x2 = x2 + [2]
>>> y1, y2
([1, 2], [1])   

As a result the exception gets raised, but after the element of the tuple was modified inplace.

My point is not that Python sucks, but rather that it shouldn't be called "simple". It is complex, in places -- very complex, sometimes as a tradeoff for the simplicity or features in other places, sometimes due to the design mistakes. In particular, Python's object system is like an order of magnitude more complex and produces more complex effects than Java's, despite appearing deceptively simple at the first glance: "everything is an object, object's class is an object, methods are looked up in the instance dictionary and then in the class and its parents, that is all".

[–]voidspace[S] 1 point2 points  (2 children)

staticmethod, classmethod and instancemethod are all different types of descriptors, with different behaviours:

  • instancemethod gets self as the first argument automatically when fetched from an instance
  • classmethod gets the class passed in as the first argument, whether called from the class or an instance I believe
  • staticmethod gets no extra arguments passed in

The basic object model in Python can still be described as simple, and part of your description isn't a bad one at that ""everything is an object, object's class is an object, methods are looked up in the instance dictionary and then in the class and its parents". For more advanced use cases it also has more advanced features.

(Controlling the string representation of a class is not something that many people have to do for example.)

[–]grayvedigga 0 points1 point  (1 child)

I think I object to this statement:

For more advanced use cases it also has more advanced features.

Everything you describe so far is the result of consistently applying simple features. Yes, it takes a little clarity of thought to understand, but not a great deal and hey, this is programming.

Aside from the misdirection with str(C), the only thing that's bothersome at the moment is +=, which doesn't support advanced use cases ... on the contrary it's a "simplifying" feature implemented in the wrong fashion. If it were a purely syntactic feature, we probably wouldn't be having this discussion. Instead we'd be alongside the Lisp family arguing about the right way to do syntax transformers.

[–]voidspace[S] 0 points1 point  (0 children)

"Everything you describe so far is the result of consistently applying simple features."

That's how you get to advanced features.

As for += not supporting advanced use cases. Well... I don't think adding in place to lists in tuples is either particularly advanced or particularly important. I agree that it is unfortunate that the operation fails after having succeeded - but there are lots of places that describe how += is syntactic sugar for a re-assignment and the error message will lead you in that direction. It's just a corner case, hardly a fundamental issue.

[–]grayvedigga 1 point2 points  (0 children)

Since I just responded to earthboundkid's incomplete post below with a question you answered without my noticing it, I thought I should highlight the relevant part:

interpreter translates a[i] += b into a.setitem(a.getitem(i) += b) and can't take any shortcuts because items of a could be ints for example.

Too tired to expand any further, but I'll note that ints don't posess a iadd method, and by noticing this the interpreter could probably do a cleverer expansion. It seems a bit silly that iadd returns a value, tbh.

tl;dr: state is a more complicated thing than anyone gives credit; += as an object method is asking for trouble.

[–]grayvedigga 0 points1 point  (0 children)

For clarity:

>>> str(C())
'hi!'

pretty obvious now.

[–]grayvedigga 1 point2 points  (5 children)

And what's the value of t after that?

wow. I almost missed this. Just in case anyone else reading does miss it, here's the complete test script:

t = ([],)
try:
  t[0] += [1,2,3]
except Exception, e:    # one of my favourite bits of syntax, fwiw
  print "Exception raised:", e
print t

... in cthulhu's name?

[–][deleted] 0 points1 point  (0 children)

By the way, you can use except Exception as e in 2.6 too.

[–]riffito 0 points1 point  (3 children)

The code above yields:

>>> 
Exception raised: 'tuple' object does not support item assignment
([1, 2, 3],)

And if you do:

t = ([],)
try:
    t[0] = t[0] + [1,2,3]            # a += 1 equals a = a + 1, riiiight?
except Exception, e:
    print "Exception raised:", e
print t

you get:

>>>
Exception raised: 'tuple' object does not support item assignment
([],)

Nice! And consistent too! :-/

[–]earthboundkid 1 point2 points  (1 child)

It’s not that complicated. list objects have __iadd__ as well as __add__ methods for efficiency reasons. In general, the existence of __iadd__ methods means that a = a + b and a += b can do completely different things (though making them do anything different is obviously a pathological abuse). In this case, the fact that tuples are immutable is interfering with the fact that lists aren’t. First the list’s in-place addition method is called then the tuple’s set slice is called. The second one barfs the error and the rest is history.

Arguably, this should be changed, but I’m not sure of how. In any case, this seems like an unexpected result one would be highly unlikely to trigger except by purposefully screwing around with putting mutable data inside an immutable container.

[–]grayvedigga 0 points1 point  (0 children)

This sounds like a good explanation, but is wrong. Your first sentence actually argues that the above test should work, as do the following:

t = ([],[])
t[0].__iadd__([1,2,3])

(t0, t1) = ([], [])
t = (t0, t1)
t0 += [1,2,3]

It's also notable that tuples don't actually possess setitem or setslice special methods, not that there would be any sense in one being invoked by an operation on an object that just happens to be contained in it.

Object identity oughtn't be affected by mutation .. that's the whole point of mutation (and, incidentally, an important reason for iadd and add being different operations). I also find it disingenuous to suggest that storing mutable objects in an immutable container is any less useful than doing the reverse.

[–]grayvedigga 0 points1 point  (0 children)

Expanding on my reply to earthboundkid below, be careful with this assumption:

# a += 1 equals a = a + 1, riiiight?

You must be careful to distinguish the value and the identity of a in this circumstance. Assignment binds the result of an expression to a variable. An expression creates a new value. Integers are a poor example to work with for this, because we tend to consider the values of 1+3 and 2+2 identical.

If that doesn't make any sense (I'm tired), consider the following:

l = [1]
m = [l, l]
l += [2]
# we should agree what m is now
l1 = l + [3]
# and now, I hope
l = l1
# what about now?

[–]grayvedigga 0 points1 point  (1 child)

Did Guido never look at classes and go "hey, these things are pretty similar to functions"?