Hello everyone , I am a Jiejie, Today I will introduce you to a very mysterious magic method .
This method is very humble , Narrow use , I hardly ever noticed it , However , When it is found that it may be the above “ The laws of ” The only exception to , I think it's worth writing another article to examine it in detail .
The main concerns of this paper are :
(1) __missing__() Who is the Holy ?
(2) __missing__() What's special ? Good at “ Make a living ” Magic ?
(3) __missing__() Is it really an exception to the above findings ? If so , Why is there such an exception ?
When taking a value from a common dictionary , There may be key A situation that does not exist :
dd = {'name':'PythonCat'} dd.get('age') # result :None dd.get('age', 18) # result :18 dd['age'] # Report errors KeyError dd.__getitem__('age') # Equate to dd['age']
about get() Method , It has a return value , And you can pass in a second parameter , As key Nonexistent return content , So it's also acceptable that . however , The other two ways of writing are wrong .
In order to solve the problem of the latter two ways of writing , You can use __missing__() Magic methods .
Now? , Suppose we have a claim like this : Take something from a dictionary key Corresponding value, If there is a value, return the value , If there is no value, insert key, And give it a default value ( For example, an empty list ).
If you use native dict, It's not very easy to implement , however ,Python Provides a very easy to use extension class collections.defaultdict:
As shown in the figure , When we take the nonexistent key when , There is no more report KeyError, It's stored in the dictionary by default .
Why? defaultdict This can be done ?
as a result of defaultdict Inherited the built-in type dict after , I've also defined one __missing__() Method , When __getitem__ When you take a value that doesn't exist , It will call the factory function passed in the input parameter ( The above example is to call list(), Create an empty list ).
As a typical example ,defaultdict Write... In the document comment :
In short ,__missing__() The main function of is by __getitem__ In the absence of key Called when the , So as to avoid KeyError.
Another typical use example is collections.Counter, So is it dict Subclasses of , In taking the statistics of key when , Return count 0:
It can be seen from the above that ,__missing__() stay __getitem__() Called when the value cannot be retrieved , however , I accidentally found a detail :__getitem__() When you can't get a value , You don't have to call __missing__().
This is because it is not a required property of the built-in type , It is not pre-defined in the dictionary base class .
If you go straight from dict Take the value of the attribute in the type , Reporting attribute does not exist :AttributeError: type object 'object' has no attribute '__missing__'.
Use dir() see , The attribute should not exist :
If from dict The parent class of is object View in , You'll find the same result .
What's going on here ? Why is it dict and object None of them __missing__ Attribute? ?
However , Check out the latest official documents ,object This attribute is clearly included in :
Source :https://docs.python.org/3/reference/datamodel.html?highlight=__missing__#object.__missing__
in other words , Theoretically object Class will be predefined __missing__, Its documentation proves that , But it's not really defined ! There is a deviation between the document and the reality !
In this way , When dict Subclasses of ( for example defaultdict and Counter) In defining __missing__ when , This magic method actually only belongs to this subclass , in other words , It's a magic method born of subclasses !
Accordingly , I have an immature guess :__getitem__() Will determine whether the current object is dict Subclasses of , And whether you have __missing__(), And then it's called ( If the parent class also has this method , You don't make a judgment first , It's called directly ).
I said this conjecture in the communication group , Some students will be there soon CPython Source code to find the validation :
And that's interesting , Magic methods that only exist on subclasses of built-in types , Throughout the Python The world , I'm afraid it's hard to find a second example .
I have a sudden Association : It's haunting __missing__(), It's like a guy who's good at playing “ Make a living ” The magician of , Let the audience see him through the glass first ( Official documents ), But when you open the door , He's not in it ( The built-in type ), Change the props again , He appeared intact again ( namely dict Subclasses of ).
__missing__() The magic of , Except that it changes itself “ Magic ” outside , It also needs a strong “ magic ” To drive .
In the last article , I found that the original magic methods were independent of each other , They are C The language interface may have the same core logic , But in Python Language interface , But there is no calling relationship :
This magic method “ Never see each other again ” The performance of the , It violates the general principles of code reuse , It's also the reason for some strange behavior of subclasses of built-in types .
official Python Would rather offer new UserString、UserList、UserDict Subclass , I don't want to reuse magic methods , The only reasonable explanation seems to be that the cost of making magic methods call each other is too high .
however , For special cases __missing__(),Python But they had to compromise , Have to pay this price !
__missing__() It's magic “ Two wait for a citizen ”, It doesn't have a separate call entry , Only passively by __getitem__() call , namely __missing__() Depend on __getitem__().
Different from those “ First class citizen ”, for example __init__()、__enter__()、__len__()、__eq__() wait , They are either triggered at a node in the object's life cycle or execution process , It's either triggered by a built-in function or operator , These are relatively independent events , Nothing to depend on .
__missing__() Depend on __getitem__(), In order to implement the method call ; and __getitem__() We have to rely on it __missing__(), In order to achieve full functionality .
To achieve this ,__getitem__() There's a back door in the interpreter code , from C Language interface back to Python Interface , To call the name “__missing__” The specific method of .
And that's what it really is “ magic ” 了 , So far, ,__missing__() It seems to be the only magic way to enjoy such treatment !
Python The dictionary provides two built-in methods for taking values , namely __getitem__() and get(), When the value does not exist , Their processing strategies are different : The former will report a mistake KeyError, The latter will return None.
Why? Python There are two different ways ? Or you should ask , Why? Python To make these two methods do different things ?
There may be a very complicated one ( It can also be very simple ) The explanation of , This paper will not go into it for the time being .
But one thing is certain : Native dict The type is simple and rough KeyError There's something wrong with this .
In order to make dictionary types more powerful ( Or let it be __getitem__() make get() That kind of performance ),Python Let the subclass of the dictionary define __missing__(), for __getitem__() Find call .
This paper has sorted out __missing__() Implementation principle of , Thus, it is revealed that it is not an insignificant existence , On the contrary , It's the only one that breaks the barriers between magic methods , Support special cases called by other magic methods !
Python In order to maintain the independence of magic methods , He took great pains to introduce UserString、UserList、UserDict These derived classes , But for __missing__(), It chose to compromise .
This article reveals the mystery of this magic method , I don't know how you feel after reading it ? Feel free to leave a comment .