nowox
nowox

Reputation: 29116

The reason of foo.__x__ and x(foo) in Python (i.e. len and __len__)

Intuitively a List class should implement an attribute or a method to retrieve the length of the instance. Fortunately Python'lists have a hidden method called __len__. Unfortunately this method is not meant to be used directly. I should instead use an external function that will read the hidden method for me.

It is like I need to ask someone else to open the fridge to grab a beer for me. The beer is in the fridge, I have ma both hands and I should be able to do it myself.

Conceptually this approach seems curious. Why not having an attribute, (rather than a method) for getting the length of a list.

In other words, I would prefer using foo.len instead of foo.len() or foo.__len__. len(foo) appears more bizarre to me.

Is there an explanation for this implementation?

This answer answers partially my question, but my frustration remains.

Upvotes: 3

Views: 450

Answers (1)

301_Moved_Permanently
301_Moved_Permanently

Reputation: 4186

You can find a deep rationale here and Guido's thoughts here.

For a summary, it’s because they might not be as closely related as you might think. Just talking about the len vs. __len__ of your post but you can find other examples in the first link.

Let's start by focusing on __len__:

class Test1:
    pass

class Test2:
    def __bool__(self):
        return False

class Test3:
    def __len__(self):
        return 0

t1 = Test1()
t2 = Test2()
t3 = Test3()

Now what is the evaluation of t1, t2¹, and t3 in a boolean context?

  • bool(t1) is True. Standard python behaviour, anything that is not explicitly False is considered True.
  • bool(t2) is False. Explicitly setting an object to False behaves accordingly.
  • bool(t3) is False. Since t3 implements __len__ is considered to be a container and since its length is 0 then it’s an empty one. By definition, an empty container is considered False in a boolean context.

__len__ is not bound to be called only by len.

len, on the other hand, offers you guaranties:

  • it will return a positive integer;
  • it will work on any container, not only lists;
  • it will count the number of elements in that container. Whatever it means is dependent of the container though: compare

    s = "A string with 𐐐"
    d = s.encode("utf-8")
    print(len(s)) # outputs 15
    print(len(d)) # outputs 18
    

    because s is a container of characters and d is a container of bytes.


¹ Note that __bool__ was __nonzero__ in python2.

Upvotes: 5

Related Questions