Module Operator invokes special object methods

Asked

Viewed 122 times

4

To biblioteca padrão do Python has the module operator, studying the same I noticed that some of its functions have say "aliases", for example:

operator.setitem(a, b, c)
operator.__setitem__(a, b, c)
Set the value of a at index b to c.

To better understand the module I created my own object to check the behavior with the module calls.

class Spam:

   def __add__(self, other):
       print('call __add__')


>>> operator.add(Spam(), 3)
"call __add__"
>>> operator.__add__(Spam(), 3) 
"call __add__"

How can they operator.add and operator.__add__ call the same special method. My doubts are:

  • There is a difference between the métodos(sem dunders) and the métodos dunders. Example:operator.setitem(a, b, c) and operator.__setitem__(a, b, c)?
  • As we saw some of the métodos do módulo operator make calls to métodos internos do objeto if it has been defined. But I honestly don’t know why, I’m led to believe that when we do an operation for example of sum, is somehow invoked operator.add(or operator.__add__), am I wrong?
  • For those interested, module source code operator: https://github.com/python/cpython/blob/master/Lib/operator.py#L420

3 answers

1


There is a difference between the methods (without dunders) and the dunders methods. Example:Operator.setitem(a, b, c) and operator.__setitem__(a, b, c)?

There’s probably no difference - (as in the other answer, they’re just aliases). The recommendation however is: let the language call internally the names with "Dunder", and, when making explicit calls, always use the name without Dunder - when there is.

As we have seen some of the Operator module methods make calls to internal object methods if it has been defined. But I honestly don’t know why, I’m led to believe that when we do an operation for example of sum, is somehow invoked Operator.add(or operator.__add__), am I wrong?

in fact, it is the opposite - in Python, each class defines how objects will behave with operators - that is, the language allows "Operator Overriding". The way this is done by language is described in the document called Data Model in language documentation. . In short - the Dunder methods in class is that contains the code that will be executed when instances of that class are involved with the use of operators, or other actions involving the "Dunder methods".

It is easy to see that the flow is this when trying to imagine the opposite: if the "specific code" for the add of each class was in the "power" of operator.add, and not in every class, where developers would put the code to the operator operator.add use? Or even thinking about the existing code - would it make sense to operator.add centralize both the code for the addition of sequences (which is concatenation) and numbers (addition)?

So the path is the opposite - the module operator is a "nice to have", but by no means essential in any Python program.

In practice, it is just a way of maintaining the rule - more of style than necessity - of "you don’t need to call the 'Dunder' methods directly". So you can write operator.neg(b) instead of b.__neg__(). (For binary operators, the functions in Operator do a little more - why they also implement the logic of calling the reverse sum - __radd__, in the second object of an operation, if the sum between the object types of the expression is not implemented in the first object).

So much so that it contains the mathematical operators and others with proper syntax in the language - whose most common use is in expressions that get fixed in the program (that is - it is more common for you to write a = b + c than a = add(b, c)). However, some Dunder methods that do not have special syntax have the call equivalent to those in the module operator direct as built-in language - for example, the functions len and hash which call respectively the methods __len__ and __hash__

One of the uses that module operator has is when, at the time you write a type of code, you don’t know yet what operation will be executed between two operands - for example, a calculator program can check whether the user has typed "-" or "+" to choose "Operator.add" or "Operator.sub" programmatically, more elegantly than a sequence of ifs where the expression is repeated every time:

Instead of:

if operacao == "+":
    a = b + c
elif operacao == "-":
    a = b - c
...

it is possible to write something like:

operacoes = {'+': operator.add, '-': operator.sub, ...}
a = operacores[operacao](b, c)

and that being said, some members of the "Operator" module still do something else - for example, the itemgetter, attrgetter and methodcaller return a reusable calling object (which can be called as if it were a function), which can be used with several different objects in creating rather elegant code.

  • Thanks again for the reply.

0

They’re the same thing. Look at the library code from line 248: https://github.com/python/cpython/blob/3.7/Lib/operator.py

# All of these "__func__ = func" assignments have to happen after importing
# from _operator to make sure they're set to the right function
...
__abs__ = abs
__add__ = add
__and__ = and_

You can do the same thing:

class Spam:
    def __add__(self, other):
        print('call __add__')
    add = __add__

s = Spam()
print(Spam.__add__ == Spam.add)
print(s.__add__ == s.add)

0

I will use the explanation of the book Fluent Python Ramalho:

The first thing to know about special methods is that they were created to be called by the Python interpreter, not by Oce. We don’t write my_object.__len__(). We wrote len(my_object) and, if my_object is an instance of a class defined by Voce, Python will call the instance method __len__ that Voce has implemented.

However, for built-in (built-in) types like list, str, bytearray and others, the interpreter will use a shortcut: the implementation of len() of Cpython, actually returns the field value ob_size of struct C PyVarobject which represents qq embedded object of variable size in memory. This is much faster than calling a method.

Most often the call to the special method will be implicit. For example the instruction for i in x: causes the call of iter(x), who in turn may call x.__iter__() if he’s available.

If you are not using enough metaprogramming, your code should not have calls to the so-called special methods.

The only special method called directly by the user frequently is the __init__ to invoke the superclass initializer when the user implements the proper __init__.

Last but not least, avoid creating attributes with syntax __foo__, because in the future these names may acquire special meaning, even if they are not currently in use.

Edited

About the call of __init__ in the book of Luciano:

inserir a descrição da imagem aqui

  • Where the __init__ is called "direct by the user"? : -) It is just a good example that Python calls the "Dunder" methods - you instantiate the class with the normal syntax of "calling an object" - with the parentheses with parameters after the object name, and Python calls the __init__. What is different in __init__ in relation to others it is rather that in most of the Python code that is written, it is the only duner method that you need to write - unless you are creating base classes with custom behavior.

  • @jsbueno, as I said at the beginning of the text, I took it directly from the book of Ramalho, thinking well after you observed I was in doubt, I would need to see if it was an error of the edition or he meant something that was not clear.

  • If that is the phrase, it is really wrong in that context. I have a copy of this book, I will check here - do you know if there is a forum/ address that Ramalho reserved for sending erratas? In the general text, this would be a small thing, but if it is accepting minor corrections for the upcoming issues it is worth pointing out that yes.

  • Already "talked" to him via email, including about an issue here at STO, he was very receptive, his email: [email protected]. I checked here in the Portuguese version on paper, the phrase is exactly this. I will scan and edit my SP.

  • Cool - I think that’s what I commented above: the difference to the __init__ is that the programmer has to worry about implementing it more often - but even so it is not explicitly called. You can tell him I told you.

  • 1

    You have to explicitly call the __init__ when you make your own __init__ in a subclass and wants to call the __init__ of the parent class... Usually like this: super().__init__(...) - That’s what the author of the book meant. Even so it’s a little strange because the same can be said for any method you want to overwrite in the daughter class, having Dunder or not.

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.