The magic method of python

Here are only a few magic methods that may be commonly used, such as __new__ which is not commonly used, which is used for metaclass initialization or __init__ which is used by everyone and will not be introduced.

In fact, every magic method is rewriting the built-in method and behaves like a decorator. It will be more convenient to understand this truth and try to understand each detail decorator.

About __str__ and __repr__:

Direct example:

class Test(object):
    def __init__(self, world):
        self.world = world

    def __str__(self):
        return 'world is %s str' % self.world

    def __repr__(self):
        return 'world is %s repr' % self.world

t = Test('world_big')
print str(t)
print repr(t)output:
world is world_big strworld is world_big repr

In fact, __str__ is equivalent to the str() method and __repr__ is equivalent to the repr() method. str is for string formatting for better understanding, and repr is for string formatting for better understanding of machines.

In fact, the method of obtaining the return value is also very easy to test. When we usually use ipython, when we do not use print to directly output the object, we usually call the repr method. At this time, rewriting the repr method can make it convenient to output what we want Known content, not a default content.

About __hash__ and __dir__:

In fact, I have written python for so long in practical applications, and I have not used the places where these two methods are needed, but I have seen them in some libraries.

__hash__ is the decorator version of the hash() method, and __dir__ is the decorator version of dir().

The above code shows the usage of __hash__:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class Test(object):
    def __init__(self, world):
        self.world = world


x = Test('world')
p = Test('world')
print hash(x) == hash(p)
print hash(x.world) == hash(p.world)


class Test2(object):
    def __init__(self, song):
        self.song = song

    def __hash__(self):
        return 1241

x = Test2('popo')
p = Test2('janan')

print x, hash(x)
print p, hash(p)output:
FalseTrue<__main__.Test2 object at 0x101b0c590> 1241<__main__.Test2 object at 0x101b0c4d0> 1241

Copy code

You can see that the hash() method here always returns an int type number. It can be used to compare a unique object, for example, an object with different memory will not be equal, but the same string will be equal after being hashed. Then we modify the behavior of the hash function by modifying the __hash__ method. It is easy to make him always return to 1241.

Another method is dir(). Anyone familiar with python knows that dir() allows us to see which methods and attributes can be called in the current environment. If we use the dir(object) syntax, we can get the methods and properties of an object.

In the same way, if we define __dir__() in the class, we can specify which methods and attributes can be found by the dir() method. For the same reason, I no longer post the code here, and friends who are interested can try it by themselves.

About __getattr__, setattr , delattr , getattribute that control parameter access :

__getattr__ is called once we try to access an attribute that does not exist, and if the attribute exists, the method will not be called.

Look at an example of __getattr__:

class Test(object):
    def __init__(self, world):
        self.world = world

    def __getattr__(self, item):
        return item


x = Test('world123')
print x.world4output:world4

Here we don't have the world4 attribute. If the attribute is not found, the normal object inheriting object will throw AtrributeError. But here I changed the behavior of the class when the attribute was not found through the magic method of __getattr__. The parameters of the searched attributes are output.

__setattr__ is a magic method that will be called when setting parameters, which is equivalent to a hook before setting parameters. Every method of setting attributes cannot bypass this magic method, and only objects with this magic method can set attributes. When using this method, pay special attention not to be called in a loop.

Let's look at an example:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class Test(object):
    def __init__(self, world):
        self.world = world

    def __setattr__(self, name, value):
        if name == 'value':
            object.__setattr__(self, name, value - 100)
        else:
            object.__setattr__(self, name, value)

x = Test(123)
print x.world
x.value = 200
print x.valueoutput:123100

Here we first initialize an instance x of the Test class. Through the __init__ method, we can notice that the initialized world parameter will be assigned. The self.world = world statement here is doing this.

Note that when the world parameter is assigned here, the __setattr__ method will be called. In this example, world is the name, and the world of the following value is the value. I made a behavior rewrite in __setattr__, I will judge the name value to be'value' for special treatment, and reduce its value by 100. So the expected result is output.

Why do I say that __setattr__ is particularly prone to loop calls? Because any assignment method will follow this magic method, if you use a similar assignment in your rewrite __setattr__ method, call back to the __setattr__ method in a loop. E.g:

class Test(object):
    def __init__(self, world):
        self.world = world

    def __setattr__(self, name, value):
        self.name = value


x = Test(123)
print x.worldoutput:RuntimeError: maximum recursion depth exceeded

Here we want to make __setattr__ perform the default behavior, that is, assign value to name, and do similar operations with the same method in the object object. But here we do not call the method of the parent class __setattr__ to achieve, the result of such an attempt is that the depth of the loop call is exceeded, and an error is reported. Because here when the initialization method self.world = world is executed, the __setattr__ method will be called, and the self.name = value in the __setattr__ method here will call itself. So it caused a loop call. So pay special attention when using this magic method.

The behavior of __delattr__ is very similar to that of __setattr__. The same thing that needs to be paid attention to is the problem of loop calls. The others are similar, except that the attribute assignment is changed to a representation like del self.name. Let's go directly to the previous example, so I won't repeat it.

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class Test(object):
    def __init__(self, world):
        self.world = world

    def __delattr__(self, item):
        print 'hahaha del something'
        object.__delattr__(self, item)


x = Test(123)
del x.world
print x.worldoutput:
hahaha del somethingTraceback (most recent call last):File "/Users/piperck/Desktop/py_pra/laplace_pra/practie_01_23/c2.py", line 12, in <module>print x.worldAttributeError: 'Test' object has no attribute 'world'

You can see that after we delete the attribute, we can't find that attribute. But __delattr__ is called when deleting the attribute, I printed a paragraph in it, which was printed out before execution

The only difference between the __getattribute__ and __getattr__ methods is that we have introduced above that the __getattr__ method can only intercept the call when the attribute is not found, and then reload or add some other operations. But __getattribute__ is more powerful, it can intercept all attribute acquisition. So it is also prone to the problem of loop calls we mentioned above. The following example illustrates this problem:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class Test(object):
    def __init__(self, world):
        self.world = world

    def __getattribute__(self, item):
        print 'get_something: %s' % item
        return item


x = Test(123)
print x.world
print x.ppoutput:get_something: worldworldget_something: pppp

As you can see, unlike __getattr__, which only intercepts non-existent attributes, __getattribute__ will intercept all attributes. As a result, the world value 123, which has been initialized, has also been rewritten into the string world. The non-existent attributes have also been rewritten into pp.

About __dict__:

First example:

class Test(object):
    fly = True

    def __init__(self, age):
        self.age = age

The __dict__ magic method can be called a system, it is a magic method to store each layered attribute. In __dict__, the key is the attribute name and the value is the attribute itself. It can be understood that the attributes that we define for classes and instances in normal times will be stored in the __dict__ method for reading. And the syntax Test.fly that we usually use like this is actually calling the class attribute, which can also be written as Test. dict ['fly']. In addition to class attributes, there are also instance attributes. When we instantiate an instance with a class, for example, we used p = Test(2) to instantiate the class Test, p will also have the __dict__ attribute. This will output:

{
    
    'age': 2}

It can be found from the above that the attributes in python are defined hierarchically. /object/Test/p is so layered. When we need to call a certain attribute, python will traverse it layer by layer. Start with the instance, then the __dict__ of the __class__ of the instance, and then the __base__ of the class. In this way, __dict__ all the way up. If none is found at the end, an AttributeError is thrown.

Here can be extended, if I remember correctly, I mentioned a method __slot__ in the previous article. The __slots__ method is to limit __dict__, only let the class instance initialize the attributes defined in __slots__, and make the instance no longer have the __dict__ method to achieve the purpose of saving memory. I will rewrite the above example to illustrate this problem.

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class Test(object):
     __slots__ = ['age']

     fly = True

     def __init__(self, age):
         self.age = age

output:

In [25]: Test.__dict__
Out[25]:
dict_proxy({
    
    '__doc__': None,
            '__init__': <function __main__.__init__>,
            '__module__': '__main__',
            '__slots__': ['age'],
            'age': <member 'age' of 'Test' objects>,
            'fly': True})


In [36]: p.__dict__
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-36-3a1cec47d020> in <module>()
----> 1 p.__dict__

AttributeError: 'Test' object has no attribute '__dict__'

In [37]: p.age
Out[37]: 3

In [38]: p.fly
Out[38]: True

As you can see, the __slots__ method does not prevent the bottom-up attribute search method, but it will no longer allow attributes not included in the __slots__ array to be assigned to the instance. But this does not prevent you from continuing to call the allowed attributes and class attributes.

About __get__, set , del :

In the previous article, I also introduced these three magic methods. Although they are generally not used, they have special uses when writing libraries. They are the foundation of another Python protocol descriptor.

There may be dependencies between different attributes of the same object. When a property is modified, we hope that other properties that depend on that property will also change at the same time. The __dict__ method cannot do it in this environment. Because the __dict__ method can only be used to store static attributes. Python provides a variety of methods to generate attributes on the fly. One of them is property. property is a special property. For example, we add a property feature to the above example so that it can change dynamically. Look at this example:

class Test(object):
    fly = True

    def __init__(self, age):
        self.age = age

    def whether_fly(self):
        if self.age <= 30:
            return True
        else:
            return False

    def just_try_try(self, other):
        pass

    whether_fly = property(whether_fly)

p = Test(20)
print p.age
print p.whether_fly
p.age = 40
print p.age
print p.whether_fly

output:

20
True
40
False

You can see that we can use this method to dynamically modify attribute values. Property has four parameters. The first three parameters are functions, which are used to process query features, modify features, and delete features. The last parameter is the feature document, which can be a string for explanatory purposes. Here I just want to get to the first parameter, dynamically modify its return value when querying, and the second parameter will be reflected when the value is modified.

Guess you like

Origin blog.csdn.net/sinat_38682860/article/details/108681281