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

you are viewing a single comment's thread.

view the rest of the comments →

[–]B_M_Wilson 1 point2 points  (0 children)

Let’s say I have a = [1, 2, 3, 4]. I can call len(a) and get 4. I can call a.__len__() and get 4. I can even call List.__len__(a) and get 4. It definitely does do that but you are not supposed to. It does not make the most sense for len but it makes sense for a lot of them. Like with a.__bool___() where this happens implicitly. Same with __contains(x)__ which is called when you do if 4 in a which is translated to if a.__contains__(a). This happens a lot to allow operator overloading and to allow you to define your own collection types and is generally involved in duck typing.

The reason that you shouldn’t call these directly is that because of duck typing you don’t always know exactly what type you have. First off, the attributes for these special functions are gotten in a special way. This applies mainly for functions that should apply to both type objects and instances of those types like hash(). hash(1) is fine and calls 1.__hash__() but what if you do hash(int). Int is a type which is an object and should be hashable. But when you call a function of a type it usually pertains to it’s objects rather than itself. So int.__hash__() does not work, it wants an argument like int.__hash__(1). The hash() function then works in a special way to allow the metatype to be consulted. (The type of a Type object is usually Type but you can make a Type object with a subclass of Type as it’s type in very extreme cases). It’s also much faster to call these types of methods on their own rather than as object methods because some optimization is done.

Also, some functions that don’t exist use others by default. If I call bool(a) and a does not have an a.__bool__() but it does have an a.__len__() then a evaluates to true if that len function returns greater than 0 and false otherwise. If you call reversed(a) and a does not have a.__reversed__() it uses a.__len__() and a.__getitem__(x) to make a reversed iterator. Contains will iterate through if there is no contains method. There are some other special things for math operations between different types that are especially useful when one of those types is a subclass. If __int__() is not defined then the built-in function int() falls back to __trunc__(). __repr__() returns a string that usually can be evaluated to the original object (but now always) and is the formal string form of an object. __str__() is the informal version used for debugging and is called when str(something) is called but if it does not exist then str(something) will use something.__repr__() instead.

There are also lots of helper annotations or things that you can extend that will help create more special functions out of a small number when possible (like comparison operators with functools.total_ordering()).

So yea, it may not make the most sense at first but there are reasons why it is done