"the implicit use of special methods always depends on the binding of the class to a special method",

I find it difficult to understand the last part (in bold) from Python in a nutshell

Instance Methods

An instance can have instance bindings for all attributes, including called attributes (methods). For a method, as well as for any other attribute (except those related to redefinition of descriptors), binding to an instance hides the binding at the class level: Attribute lookup does not take into account the class when it finds the bindings directly in the instance. Linking to a specific instance for the called attribute does not perform any transformations, it is described in detail in the section “Related and unrelated methods” on page 110: the link attribute returns exactly the same called object that was previously associated directly with the instance attribute.

However, this does not work, as you might expect for bindings for each instance of special methods that Python invokes implicitly as a result of various operations, as described in “Special Methods” on page 123. Such implicit use of special methods always rely on class binding to special method, if any. . Example:

def fake_get_item(idx): return idx class MyClass(object): pass n = MyClass() n.__getitem__ = fake_get_item print(n[23]) # results in: # Traceback (most recent call last): # File "<stdin>", line 1, in ? # TypeError: unindexable object 

What does this mean specifically?

Why is the example error?

Thanks.

+5
source share
3 answers

Neglecting all the exact details, it is basically said that special methods (as defined in the Pythons data model - usually methods starting with two underscores and ending with two underscores and rarely, if ever, called directly) will never be used implicitly from an instance even if it is defined there:

 n[whatever] # will always call type(n).__getitem__(n, whatever) 

This is different from an attribute lookup that first validates an instance:

 def fake_get_item(idx): return idx class MyClass(object): pass n = MyClass() n.__getitem__ = fake_get_item print(n.__getitem__(23)) # works because attribute lookup checks the instance first 

The documentation has a whole section (including justification): "Search for special methods" :

3.3.9. Search for special methods

For custom classes, it is guaranteed that implicit calls to special methods will work correctly if they are defined in the type of objects, and not in the dictionary of object instances. This behavior causes the following code to throw an exception:

 >>> class C: ... pass ... >>> c = C() >>> c.__len__ = lambda: 5 >>> len(c) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: object of type 'C' has no len() 

The rationale for this behavior lies in a number of special methods, such as __hash__() and __repr__() , which are implemented by all objects, including objects of type. If the implicit search for these methods used the usual search process, they would not work when calling an object of the type itself:

 >>> 1 .__hash__() == hash(1) True >>> int.__hash__() == hash(int) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: descriptor '__hash__' of 'int' object needs an argument 

[...]

Bypassing the __getattribute__() technique in this way provides a significant opportunity for optimizing speed inside the interpreter due to some flexibility in processing special methods (a special method must be installed on the class object itself in order to be sequentially called by the interpreter).

+6
source

To make this even more clear, this means that you cannot override dunder methods on the fly. As a result, == , + , and the remaining operators always mean the same thing for all objects of type T.

+2
source

I will try to summarize what is said in the excerpt, and, in particular, the part in bold. Generally speaking, when Python tries to find the value of the attribute (including the method), it first checks the instance (i.e., the actual object you created), and then the class. The code below illustrates the general behavior.

 class MyClass(object): def a(self): print("howdy from the class") n = MyClass() #here the class method is called na() #'howdy from the class' def new_a(): print("hello from new a") na = new_a #the new instance binding hides the class binding na() #'hello from new a' 

What part of fatty conditions is that this behavior does not apply to “special methods” such as __getitem__ . In other words, overriding __getitem__ at the instance level ( n.__getitem__ = fake_get_item in your example) does nothing: when a method is called via n[] syntax, an error occurs because the class does not implement this method. (If the general behavior was also carried out in this case, the result of print(n[23]) would be to print 23, that is, Execute the fake_get_item method).

Another example of the same behavior:

 class MyClass(object): def __getitem__(self, idx): return idx n = MyClass() fake_get_item = lambda x: "fake" print(fake_get_item(23)) #'fake' n.__getitem__ = fake_get_item print(n[23]) #'23' 

In this example, instead of the instance binding method, the class method for __getitem__ (which returns the index number) (which returns 'fake' ) is called.

+2
source

Source: https://habr.com/ru/post/1271532/


All Articles