Hacker News new | ask | show | jobs
by pansa2 1711 days ago
> Attribute lookup in Python is [...] an enormous tar pit

Spot on. Python is widely described as a simple language, but the complexity of attribute lookup is one thing that shows that's not true at all.

Many things in Python are easy, such as adding `@property` above a method definition to turn it into a getter. But `@property` is far from simple - the way it actually works is very complex (for example, properties have to be data descriptors, because non-data descriptors cannot override object attributes of the same name).

2 comments

  > Python is widely described as a simple language, but the complexity
  > of attribute lookup is one thing that shows that's not true at all.
Python is a simple language to _learn_. My children learned the basics of Python before their seventh birthdays. But Python is not a simple language to _implement_.
It’s an easy language to learn the basics of, sure. But the complexity of things like attribute lookup doesn’t only affect language implementors.

The complexity is all exposed to Python programmers, which makes the language hard to master and, unlike truly simple languages, too large for anyone to understand completely.

There was a time (during Python 2 days) where one could master the language - completely. Today's 3.10 is indeed a very large language; fortunately, one doesn't have to exercise every new feature. And, it's pretty easy to find examples of all features that one might stumble across while reading other people's code.
https://blog.peterlamut.com/2018/11/04/python-attribute-look... is a great overview of how this works. Start with the summary at the end!

The really cool thing about this, how descriptors have their __get__ called, is that methods are implemented this way. So when you access instance.method(), it’s a normal lookup for the attribute named “method”, which is (normally) itself a descriptor, so the __get__ magic is called and this binds the method to the instance at the moment it’s needed! Then you can just call it like a normal function. It’s incredibly elegant but extremely obscure. And vital to understand if you want to dive into monkey patching, which is an incredible skill to have!

Good article, thanks. It's worth pointing out that the summary (class hierarchy data descriptor > instance `__dict__` > class hierarchy other) only applies when looking up a normal attribute on a normal object:

* Special-method lookup (e.g. of `__add__` when you do `a + b`) works differently because it doesn't look at the instance `__dict__`, only the class hierarchy.

* Lookup on a class works differently because as well as looking at `__dict__`, it has to consider superclasses.

Much of the complexity relates to the different ways of handling the instance `__dict__`. By contrast, Ruby is able to have much simpler lookup rules because it never considers the instance, only ever the class hierarchy.

> Ruby is able to have much simpler lookup rules because it never considers the instance, only ever the class hierarchy.

“But!” I can hear people say, “I heard you could attach methods to instances in Ruby! How is this possible if Ruby never considers the instance in resolving a method call?”

Well, that’s tricky. The thing is, Ruby instances have no public members, instance variables are private (for direct access, but not in any strict way, because there are public methods called instance_variable_get and instance_variable_get on Object which…do exactly what the names say.)

But every Ruby instance conceptually has (though it doesn’t concretely have one unless you add something to it) a unique class for the instance that is the first thing in its class heirarchy. And you can add methods to that class (the instance “metaclass”, which is different than a Python metaclass) for the effect of attaching them uniquely to the instance itself.

You might even say that without classes python would be a simpler language, while keeping almost all its power :)
It's a fun dream, but without the ability for a variable to carry along with it a notion of, say, how it implements slice notation, or the ability to avoid verbose names like foo_to_repr(foo) and bar_to_repr(bar) so that names don't collide... arguably we never would have seen the rise of scientific Python! Object oriented programming is an incredibly good abstraction for a lot of real-world scenarios.