Python高级第八天

魔法方法

在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高级第八天)