Python高级第八天

作者: code与有荣焉 | 来源:发表于2019-12-29 21:00 被阅读0次

    魔法方法

    在Python中,所有以双下划线包起来的方法,都统称为"魔术方法"。比如我们接触最多的__init__。 魔法方法帮助我们定义更加符合 Python 风格的对象。

    一、构造和初始化

    __new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法。

    __init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。

    故而“ 本质上 ”来说,__new__()方法负责创建实例,而__init__()仅仅是负责实例属性相关的初始化而已,执行顺序是,先new后init。

    二、属性访问控制

    通常情况下,我们在访问类或者实例对象的时候,会牵扯到一些属性访问的魔法方法,主要包括:

    __getattr__ (self, name): 访问不存在的属性时调用

    __getattribute__ (self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)

    __setattr__ (self, name, value):设置实例对象的一个新的属性时调用

    __delattr__ (self, name):删除一个实例对象的属性时调用 一个例子

    class Foo:
        x=1
        def __init__(self,y):
            self.y=y
    
        def __getattr__(self, item):
            print('----> from getattr:你找的属性不存在')
    
    
        def __setattr__(self, key, value):
            print('----> from setattr')
            # self.key=value #这就无限递归了,你好好想想
            self.__dict__[key]=value #应该使用它
    
        def __delattr__(self, item):
            print('----> from delattr')
            # del self.item #无限递归了
            self.__dict__.pop(item)
    
        def __getattribute__(self, item):
            print('----> __getattribute__')
            return super().__getattribute__(item)
    
    #__setattr__添加/修改属性会触发它的执行
    f1=Foo(10)
    print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
    f1.z=3
    print(f1.__dict__)
    
    #__delattr__删除属性的时候会触发
    f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
    del f1.a
    print(f1.__dict__)
    
    #__getattr__只有在使用点调用属性且属性不存在的时候才会触发
    f1.xxxxxx
    

    输出:

    ----> from setattr
    ----> __getattribute__
    ----> __getattribute__
    {'y': 10}
    ----> from setattr
    ----> __getattribute__
    ----> __getattribute__
    {'y': 10, 'z': 3}
    ----> __getattribute__
    ----> from delattr
    ----> __getattribute__
    ----> __getattribute__
    {'y': 10, 'z': 3}
    ----> __getattribute__
    ----> from getattr:你找的属性不存在
    

    注意,调用

    self.__dict__[key]=value
    

    会触发

        def __getattribute__(self, item):
            print('----> __getattribute__')
            return super().__getattribute__(item)
    

    因为 虽然是要给字典self.__dict__添加键值对,其中隐含着首先获得self.__dict__。 另外__getattribute__需要返回super().__getattribute__(item),否则函数默认返回None,报错。

    三、描述符

    一个实现了 描述符协议 的类就是一个描述符
    什么是描述符协议:实现了 __get__()__set__()__delete__() 其中至少一个方法的类,就是一个描述符。

    • __get__: 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。
    • __set__:将在属性分配操作中调用。不会返回任何内容。
    • __delete__:控制删除操作。不会返回内容。
    描述器应用---验证参数类型
    class Typed:
        def __init__(self, key, expected_type):  # 构造函数接收所传入的参数和参数类型
            self.key = key
            self.expected_type = expected_type
    
        def __get__(self, instance, owner):
            print('get方法')
            return instance.__dict__[self.key]  # 从底层字典获取值
    
        def __set__(self, instance, value):
            print('set方法')
            if not isinstance(value, self.expected_type):  # 类型判断
                raise TypeError('%s 传入的类型不是%s' % (self.key, self.expected_type)) # 格式化抛出异常
            instance.__dict__[self.key] = value # 修改底层字典
    
        def __delete__(self, instance):
            print('delete方法')
            instance.__dict__.pop(self.key)
    
    
    class People:
        name = Typed('name', str)  # p1.__set__()  self.__set__(),触发描述符__set__方法,设置参数类型传给构造函数
        age = Typed('age', int)  # p1.__set__()  self.__set__()
        salary = Typed('salary', float)  # p1.__set__()  self.__set__()
        def __init__(self, name, age, salary):
            self.name = name
            self.age = age
            self.salary = salary
    
    # p1=People('alex','13',13.3)#类型有误,报错
    p1 = People('alex', 13, 13.3)
    print(p1.__dict__)
    print(p1.name)
    p1.name = 'egon'
    print(p1.__dict__)
    del p1.name
    print(p1.__dict__)
    # print(p1.name)  # 相应的键值对已在底层字典中删除了,报错
    

    四、构造自定义容器(Container)

    在Python中,如果我们想实现创建类似于序列和映射的类(可以迭代以及通过[下标]返回元素),可以通过重写魔法方法__getitem____setitem____delitem____len__方法去模拟。

    魔术方法的作用:

    __getitem__(self,key):返回键对应的值。

    __setitem__(self,key,value):设置给定键的值

    __delitem__(self,key):删除给定键对应的元素。

    __len__():返回元素的数量

    '''
        desc:尝试定义一种新的数据类型
              等差数列
    '''
    class ArithemeticSequence:
        def __init__(self, start=0, step=1):
            print('Call function __init__')
    
            self.start = start
            self.step = step
            self.myData = {}
    
        # 定义获取值的方法
        def __getitem__(self, key):
            print('Call function __getitem__')
    
            try:
                return self.myData[key]
            except KeyError:
                return self.start + key * self.step
    
        # 定义赋值方法
        def __setitem__(self, key, value):
            print('Call function __setitem__')
            self.myData[key] = value
    
        # 定义获取长度的方法
        def __len__(self):
            print('Call function __len__')
    
            return len(self.myData)
    
        # 定义删除元素的方法
        def __delitem__(self, key):
            print('Call function __delitem__')
            del self.myData[key]
    
    
    s = ArithemeticSequence(1, 2)
    print(s[0])
    print(s[1])
    print(s[2])
    print(s[3])# 这里应该执行self.start+key*self.step,因为没有3这个key
    s[3] = 100  # 进行赋值
    print(s[3]) # 前面进行了赋值,那么直接输出赋的值100
    print(len(s))
    del s[3]  # 删除3这个key
    

    这些魔术方法的原理就是:当我们对类的属性item进行下标的操作时,首先会被__getitem__()__setitem__()__delitem__()拦截,从而执行我们在方法中设定的操作,如赋值,修改内容,删除内容等等。

    五、上下文管理

    使用上下文管理器有三个好处:

    1. 提高代码的复用率;
    2. 提高代码的优雅度;
    3. 提高代码的可读性;

    参考 https://juejin.im/post/5c87b165f265da2dac4589cc

    六、比较运算符

    你想让某个类的实例支持标准的比较运算(比如>=,!=,<=,<等),但是又不想去实现那一大丢的特殊方法。

    Python类对每个比较操作都需要实现一个特殊方法来支持。 例如为了支持>=操作符,你需要定义一个 __ge__()方法。 尽管定义一个方法没什么问题,但如果要你实现所有可能的比较方法那就有点烦人了。

    装饰器 functools.total_ordering 就是用来简化这个处理的。 使用它来装饰一个来,你只需定义一个 __eq__()方法, 外加其他方法(__lt__, __le__, __gt__, or __ge__)中的一个即可。 然后装饰器会自动为你填充其它比较方法。

    from functools import total_ordering
    
    class Room:
        def __init__(self, name, length, width):
            self.name = name
            self.length = length
            self.width = width
            self.square_feet = self.length * self.width
    
    @total_ordering
    class House:
        def __init__(self, name, style):
            self.name = name
            self.style = style
            self.rooms = list()
    
        @property
        def living_space_footage(self):
            return sum(r.square_feet for r in self.rooms)
    
        def add_room(self, room):
            self.rooms.append(room)
    
        def __str__(self):
            return '{}: {} square foot {}'.format(self.name,
                    self.living_space_footage,
                    self.style)
    
        def __eq__(self, other):
            return self.living_space_footage == other.living_space_footage
    
        def __lt__(self, other):
            return self.living_space_footage < other.living_space_footage
    

    装饰器functools.total_ordering原理:

    class House:
        def __eq__(self, other):
            pass
        def __lt__(self, other):
            pass
        # Methods created by @total_ordering
        __le__ = lambda self, other: self < other or self == other
        __gt__ = lambda self, other: not (self < other or self == other)
        __ge__ = lambda self, other: not (self < other)
        __ne__ = lambda self, other: not self == other
    

    七、__str____repr__ 方法

    可以实现类到字符串的转化

    __str____repr__ 的差别:

    __str__的返回结果可读性强。__str__的意义是得到便于人们阅读的信息

    __repr__的返回结果应更准确。__repr__存在的目的在于调试,便于开发者使用。若将__repr__返回的方式直接复制到命令行上,是可以直接执行的。

    注:每个类都最好有一个 repr 方法
    小结:
    1. 我们可以使用 __str____repr__方法定义类到字符串的转化方式,而不需要手动打印某些属性或是添加额外的方法。
    2. 一般来说,__str__的返回结果在于强可读性,而 __repr__的返回结果在于准确性。
    3. 我们至少需要添加一个__repr__方法来保证类到字符串的自定义转化的有效性,__str__是可选的。因为默认情况下,在需要却找不到__str__方法的时候,会自动调用 __repr__方法。

    八、魔法方法之__call__

    在Python中,函数其实是一个对象:

    >>> f = abs
    >>> f.__name__
    'abs'
    >>> f(-123)
    123
    

    由于 f 可以被调用,所以,f 被称为可调用对象。

    所有的函数都是可调用对象。

    一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法_call_()

    我们把 Person 类变成一个可调用对象:

    class Person(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
    
        def __call__(self, friend):
            print 'My name is %s...' % self.name
            print 'My friend is %s...' % friend
    

    现在可以对 Person 实例直接调用:

    >>> p = Person('Bob', 'male')
    >>> p('Tim')
    My name is Bob...
    My friend is Tim...
    

    相关文章

      网友评论

        本文标题:Python高级第八天

        本文链接:https://www.haomeiwen.com/subject/tcxuoctx.html