zoukankan      html  css  js  c++  java
  • python 的魔法方法

     

    这里只分析几个可能会常用到的魔法方法,像__new__这种不常用的,用来做元类初始化的或者是__init__这种初始化使用的 每个人都会用的就不介绍了。

    其实每个魔法方法都是在对内建方法的重写,和做像装饰器一样的行为。理解这个道理 再尝试去理解每个细节装饰器会比较方便。

    关于__str__和__repr__:

    直接上例子:

    复制代码
    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 str
    world is world_big repr

    复制代码

    其实__str__相当于是str()方法 而__repr__相当于repr()方法。str是针对于让人更好理解的字符串格式化,而repr是让机器更好理解的字符串格式化。

    其实获得返回值的方法也很好测试,在我们平时使用ipython的时候,在不使用print直接输出对象的时候,通常调用的就是repr方法,这个时候改写repr方法可以让他方便的输出我们想要知道的内容,而不是一个默认内容。

    关于__hash__和__dir__:

    其实在实际应用中写了这么久python,也没有用到需要这两个方法出现的地方,但是在有些库里面是有看到过。

    __hash__是hash()方法的装饰器版本,而__dir__是dir()的装饰器版本。

    上代码展示一下__hash__用法:

    复制代码
    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:

    False
    True
    <__main__.Test2 object at 0x101b0c590> 1241
    <__main__.Test2 object at 0x101b0c4d0> 1241

    复制代码

    可以看到这里的hash()方法总是会返回int型的数字。可以用于比较一个唯一的对象,比方说一个不同内存的object不会相当,而相同字符串hash之后就会相等。然后我们通过修改__hash__方法来修改hash函数的行为。让他总是返回1241,也是可以轻松做到的。

    另外一个方法是dir(),熟悉python的人都知道dir()可以让我们查看当前环境下有哪些方法和属性可以进行调用。如果我们使用dir(object)语法,可以获得一个对象拥有的方法和属性。同样的道理如果我们在类中定义了__dir__(),就可以指定哪些方法和属性能够被dir()方法所查看查找到。道理一样我这里不再贴出代码了,有兴趣的朋友可以自己去试试。

    关于控制参数访问的__getattr__, __setattr__, __delattr__, __getattribute__:

    __getattr__是一旦我们尝试访问一个并不存在的属性的时候就会调用,而如果这个属性存在则不会调用该方法。

    来看一个__getattr__的例子:

    复制代码
    class Test(object):
        def __init__(self, world):
            self.world = world
    
        def __getattr__(self, item):
            return item
    
    
    x = Test('world123')
    print x.world4

    output:
    world4
    复制代码

    这里我们并没有world4属性,在找不到属性的情况下,正常的继承object的对象都会抛出AtrributeError的错误。但是这里我通过__getattr__魔法方法改变了找不到属性时候的类的行为。输出了查找的属性的参数。

    __setattr__是设置参数的时候会调用到的魔法方法,相当于设置参数前的一个钩子。每个设置属性的方法都绕不开这个魔法方法,只有拥有这个魔法方法的对象才可以设置属性。在使用这个方法的时候要特别注意到不要被循环调用了。

    下面来看一个例子:

    复制代码
    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.value

    output:
    123
    100
    复制代码

    这里我们先初始化一个Test类的实例x,通过__init__方法我们可以注意到,会给初始化的world参数进行赋值。这里的self.world = world语句就是在做这个事情。

    注意,这里在进行world参数赋值的时候,就是会调用到__setattr__方法。这个例子来看world就是name,而后面的值的world就是value。我在__setattr__里面做了一个行为改写,我将判断name 值是'value'的进行特殊处理,把它的value值减少100. 所以输出了预期的结果。

    我为什么说__setattr__特别容易出现循环调用?因为任何赋值方法都会走这个魔法方法,如果你在你改写__setattr__方法里面使用了类似的赋值,又回循环调用回__setattr__方法。例如:

    复制代码
    class Test(object):
        def __init__(self, world):
            self.world = world
    
        def __setattr__(self, name, value):
            self.name = value
    
    
    x = Test(123)
    print x.world

    output:
    RuntimeError: maximum recursion depth exceeded
    复制代码

    这里我们想让__setattr__执行默认行为,也就是将value赋值给name,和object对象中的同样方法,做类似的操作。但是这里我们不调用父类__setattr__的方法来实现,做这样的尝试得到的结果就是,超过循环调用深度,报错。因为这里在执行初始化方法self.world = world的时候,就会调用__setattr__方法,而这里的__setattr__方法里面的self.name = value又会调用自身。所以造成了循环调用。所以使用该魔法方法的时候要特别注意。

    __delattr__的行为和__setattr__特别相似,同样需要注意的也是循环调用问题,其他都差不多,只是把属性赋值变成了 del self.name这样的表示。下面直接上个例子,不再多赘述。

    复制代码
    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.world

    output:

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

    复制代码

    可以看到我们将属性删除之后,就找不到那个属性了。但是在删除属性的时候调用了__delattr__,我在里面打印了一段话,在执行之前被打印出来了

    __getattribute__和__getattr__方法唯一不同的地方是,上面我们已经介绍了__getattr__方法只能在找不到属性的时候拦截调用,然后进行重载或者加入一些其他操作。但是__getattribute__更加强大,他可以拦截所有的属性获取。所以也容易出现我们上面提到的,循环调用的问题。下面上一个例子来说明这个问题:

    复制代码
    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.pp

    output:
    get_something: world
    world
    get_something: pp
    pp
    复制代码

    可以看到,区别于__getattr__只拦截不存在的属性,__getattribute__会拦截所有的属性。所以导致了已经被初始化的world值123,也被改写成了字符串world。而不存在的属性也被改写了成了pp。

    关于__dict__:

    先上个例子:

    class Test(object):
        fly = True
    
        def __init__(self, age):
            self.age = age

    __dict__魔法方法可以被称为系统,他是存储各分层属性的魔法方法。__dict__中,键为属性名,值为属性本身。可以这样理解,在平时我们给类和实例定义的那些属性,都会被存储到__dict__方法中用于读取。而我们平时使用的类似这样的语法Test.fly 其实就是调用了类属性,同样可以写成Test.__dict__['fly']。除了类属性,还有实例属性。当我们用类实例化一个实例,例如上文我们使用p = Test(2)实例化类Test,p也会具有__dict__属性。这里会输出:

    {'age': 2}

    由上可以发现,python中的属性是进行分层定义的。/object/Test/p这样一层一层下来的。当我们需要调用某个属性的时候,python会一层一层往上面遍历上去。先从实例,然后实例的__class__的__dict__,然后是该类的__base__。这样__dict__一路找上去。如果最后都没有找到,就抛出AttributeError错误。

    这里可以延伸一下,没记错的话,我前面有篇文章讲了一个方法__slot__。__slots__方法就是通过限制__dict__,只让类实例初始化__slots__里面定义的属性,而且让实例不再拥有__dict__方法,来达到节约内存的目的。我将会就上面的那个例子重写一下,来说明这个问题。

    复制代码
     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
    复制代码

    可以看到,__slots__方法并没有阻止由下至上的属性查找方法,只是不会再允许没有包含在__slots__数组中的属性再被赋值给实例了。但这并不妨碍,继续调用允许访问的属性,以及类属性。

    关于__get__, __set__, __del__:

    在前面的文章里面我也介绍过这三个魔法方法,虽然一般是用不到的,但是在写库的时候它们有特别的用途。他们是python另外一个协议descriptor的根基。

    同一个对象的不同属性之间可能存在依赖关系。当某个属性被修改时,我们希望依赖于该属性的其他属性也同时变化。在这种环境下面__dict__方法就无法办到。因为__dict__方法只能用来存储静态属性。python提供了多种即时生成属性的方法。其中一种就是property。property是特殊的属性。比如我们为上面的例子增加一个property特性,使得他能够动态变化。来看这个例子:

    复制代码
    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

    可以看到 我们可以使用这种手段,动态修改属性值。property有四个参数。前三个参数为函数,分别用于处理查询特性、修改特性、删除特性。最后一个参数为特性的文档,可以为一个字符串,起说明作用。这里我只是要到了第一个参数,查询的时候动态修改他的返回值,而第二个参数是在修改值的时候就会体现出来。

  • 相关阅读:
    LeetCode 461. Hamming Distance
    LeetCode 442. Find All Duplicates in an Array
    LeetCode 448. Find All Numbers Disappeared in an Array
    LeetCode Find the Difference
    LeetCode 415. Add Strings
    LeetCode 445. Add Two Numbers II
    LeetCode 438. Find All Anagrams in a String
    LeetCode 463. Island Perimeter
    LeetCode 362. Design Hit Counter
    LeetCode 359. Logger Rate Limiter
  • 原文地址:https://www.cnblogs.com/lincappu/p/13647082.html
Copyright © 2011-2022 走看看