魔法方法

本节最主要搞懂魔法方法什么时候被触发(被调用)

调用分为隐式调用和显式调用,而魔法方法都属于隐式调用。

1. 构造和初始化

  • new():在实例创建之前被调用的,它的任务就是创建实例然后返回该实例,是个静态方法
  • init():当实例对象创建完成后被调用的,然后设置对象属性的一些初始值
当自己创建了__new__()和__init__()同时存在时,会只调用__new__()
解决方法:在__new__()创建实例+返回:
def __new__(cls,...):
    instance = object.__new__(cls)
    return instance

self是当前实例本身
cls是type类的实例(类对象)
print(type(cls))
—— 

执行顺序:new() --> init()

【例】new() 和 init()的比较

class Person(object):
    def __new__(cls, *args, **kwargs):
        print('__new__')
        print(type(cls))
        instance = object.__new__(cls)
        print(id(instance))
        return instance

    def __init__(self, name):
        print(id(self))
        self.name = name
        self.age = 33
        print('init')

    def output(self):
        print('hello')

p = Person('xuebi')
# __new__
#     # print(type(cls))
# 10882896    # print(id(instance))
# 10882896    # print(id(self))
# init
print(p.name)    # self.name = name
# xuebi
print(p.age)    # self.age = 33
# 33
p.output()
# hello

2. 属性访问控制

  • getattr(self, name):访问不存在的属性时调用
  • getattribute(self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)
  • setattr(self, name, value):设置实例对象的一个新的属性时调用
  • delattr(self, name):删除一个实例对象的属性时调用
当自己创建了__getattribute__()时,实例名.__dict__得到的时None
解决方法:在__getattribute__()中返回调用父类的__getattribute__():
def __getattribute__(self, item):
    return super().__getattribute__(item)

执行顺序:实例化 --> setattr() --> getattr()或getattribute()

【例】setattr()、getattr()、getattribute()的用法

class Foo:
    def __init__(self, y):
        self.y = y
        pass

    def __getattribute__(self, item):
        print('__getattribute__')
        print(item)
        # self.key=value    # 这就无限递归了
        return super().__getattribute__(item)    # 正确做法

    def __getattr__(self, item):
        print('__getattr__')
        print(item)
        return '未知属性'

    def __setattr__(self, key, value):
        print('__setattr__')
        print(key)
        print(value)
        # self.key = value    # 这就无限递归了
        self.__dict__[key] = value    # 正确做法

    def __delattr__(self, item):
        print('__delattr__')
        # del self.item    # 无限递归了
        super().__delattr__(item)    # 正确做法

f = Foo(100)
# __setattr__
# y    # print(key)
# 100    # print(value)
# __getattribute__
# __dict__    # print(item)

print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100}
# --------------------------------------------------

print('-'*50)
f.z = 3    # 调用__setattr__()方法
print('-'*50)
# --------------------------------------------------
# __setattr__
# z    # print(key)
# 3    # print(value)
# __getattribute__
# __dict__    # print(item)
# --------------------------------------------------

print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3}
# --------------------------------------------------

print('-'*50)
f.x = 90    # f.x == f.__dict__['x']
print('-'*50)
# --------------------------------------------------
# __setattr__
# x
# 90
# __getattribute__
# __dict__
# --------------------------------------------------

print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3, 'x': 90}
# --------------------------------------------------

print('-'*50)
del f.x    # 调用__delattr__()方法
print('-'*50)
# --------------------------------------------------
# __delattr__
# --------------------------------------------------

print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3}
# --------------------------------------------------

print('-'*50)
print(f.xxxxxxxxx)    # 在__getattribute__()方法中尝试返回时失败,调用__getattr__()方法
print('-'*50)
# --------------------------------------------------
# __getattribute__
# xxxxxxxxx    # print(item)
# __getattr__
# xxxxxxxxx    # print(item)
# 未知属性
# --------------------------------------------------

# 下面与上面的f.xxxxxxxxx功能上一样
# print(f.abc)
# print(f.uuu)
# print(f.name)
# print(f.age)
f.name = 'xuebi'
# __setattr__
# name
# xuebi
# __getattribute__
# __dict__

3. 描述符

实现了以下三个方法之一,就是一个描述符

  • get():用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。
  • set():将在属性分配操作中调用。不会返回任何内容。
  • delete():控制删除操作。不会返回内容。

【例】成绩处理

class Score:
    def __init__(self, value=0):
        self.__score = value

    # 处理
    def __set__(self, instance, value):
        print('__set__')
        if not isinstance(value, int):
            raise TypeError('TypeError')
        if not 0 <= value <= 100:
            raise ValueError('ValueError')

        self.__score = value

    def __get__(self, instance, owner):
        print('__get__')
        return self.__score

    def __del__(self):
        del self.__score

class Student:
    math = Score()
    chinese = Score()
    english = Score()
    wuli = Score()

    def __init__(self, name, math, chinese, english,wuli):
        self.name = name
        self.math = math
        self.chinese = chinese
        self.english = english
        self.wuli = wuli

    def __str__(self):
        return "".format(
                self.name, self.math, self.chinese, self.english, self.wuli
            )

stu = Student('xuebi',100,78,98,32)
# __set__
# __set__
# __set__
# __set__
# __get__
print(type(stu.math))
# 
print(stu.__dict__)
# {'name': 'xuebi'}
print(stu)
# __get__
# __get__
# __get__
# __get__
# 

# 过程:
# stu = Student('xuebi',100,78,98,32),在__init__()中:
# self.name = name
# self.math = math --> __set__() --> print('__set__') --> self.__score = value
# self.chinese = chinese --> __set__() --> print('__set__') --> self.__score = value
# self.english = english --> __set__() --> print('__set__') --> self.__score = value
# self.wuli = wuli --> __set__() --> print('__set__') --> self.__score = value

# print(type(stu.math))
# 略

# print(stu.__dict__)
# 略

# print(stu) --> __str__() --> return ... --> 每碰到self.?时 --> __get__() --> print('__get__()') --> return self.__score

【例】

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)
# set方法
# set方法
# set方法
print(p1.__dict__)
# {'name': 'alex', 'age': 13, 'salary': 13.3}
print(p1.name)
# get方法
# alex
p1.name = 'egon'
# set方法
print(p1.__dict__)
# {'name': 'egon', 'age': 13, 'salary': 13.3}
del p1.name
# delete方法
print(p1.__dict__)
# {'age': 13, 'salary': 13.3}
# print(p1.name)  # 相应的键值对已在底层字典中删除了,报错
# KeyError: 'name'

4. 构造自定义容器(Container)

  • getitem(self,key):返回键对应的值。
  • setitem(self,key,value):设置给定键的值
  • delitem(self,key):删除给定键对应的元素。
  • len():返回元素的数量

【例】从无限数据中自定义生成一个a1为1,d为2的等差数列,输出前十个数

class ArithemeticSequence:
    def __init__(self, start, step):
        print('__init__')
        self.start = start
        self.step = step
        self.datas = {}

    def __getitem__(self, index):
        # if index in self.datas:
        #     return self.datas[index]
        # else:
        #     return self.start + index * self.step
        try:
            return self.datas[index]
        except KeyError:
            return self.start + index * self.step

    def __setitem__(self, key, value):
        self.datas[key] = value

    def __len__(self):
        return len(self.datas)

    def __delitem__(self, key):
        del self.datas[key]

am = ArithemeticSequence(1,3)
# print('__init__')

am[1] = 32


for i in range(10):
    print(am[i])
# 1
# 32
# 7
# 10
# 13
# 16
# 19
# 22
# 25
# 28

print(len(am))
# 1
del am[1]
print(len(am))
# 0

# del只删除字典里面的值,而对原本的无限序列没有任何影响
for i in range(10):
    print(am[i])
# 1
# 4
# 7
# 10
# 13
# 16
# 19
# 22
# 25
# 28

【例】从无限数据中自定义斐波那契数列

class feibo:
    def __init__(self, start=0, end=1, index=1):
        self.start = start
        self.end = end
        self.index = index
        # self.myData = {}

    # 递推法
    def __getitem__(self, index):
        for i in range(self.index):
            self.start, self.end = self.end, self.start + self.end
        return self.start


# 1 1 2 3 5 8
s = feibo()
print(s)
print(s[0])
print(s[1])
print(s[2])
print(s[3])
print(s[4])
print(s[5])

# <__main__.feibo object at 0x011EEDD0>
# 1
# 1
# 2
# 3
# 5
# 8

5. 上下文管理

操作文本对象的时候,我们要用 with open ,这就是一个上下文管理的例子。

with open('test.txt') as f:
    print f.readlines()

注:
(1)上下文表达式:with open('test.txt') as f:
(2)上下文管理器:open('test.txt')
(3) f 不是上下文管理器,应该是资源对象。

上下文管理协议:
在一个类里,如果实现了enterexit方法,这个类的实例就是一个上下文管理器。

方法一:
定义一个类,在类中定义enter() 和 exit()方法

  • enter():
  • exit():

方法二:
不定义类,直接定义生成器函数,在函数名上面添加@contextlib.contextmanager装饰器,在函数中加入yield关键字(作用跟方法一一样)

系统默认调用exit(),如果retrun True,则不会抛出异常

【例】上下文管理器(但不支持处理异常)

class Resource:
    def __enter__(self):
        print('__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')

    def output(self):
        print('output')

with Resource() as r:
    r.output()

# __enter__
# output
# __exit__

【例】上下文管理器(允许异常处理)

class Resource:
    def __enter__(self):
        print('__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True    # 不写return True时同样会报错

    def output(self):
        # 1 / 0
        print('output')


def main2():
    r = Resource()
    # r.output()    # 直接调用output方法时会报错,但经过with后就不会报错了
    with Resource() as r:
        r.output()

    print('finish')

main2()

# 输出结果:
# (1)当出现异常时(1 / 0):
# __enter__
# __exit__
#     # exc_type的值
# division by zero    # exc_val的值
#     # exc_tb的值
# finish

# (2)当没出现异常时(正常输出):
# __enter__
# output
# __exit__
# None
# None
# None
# finish

# 过程:
# main2() --> 进入main2()函数中,创建Resource类的实例对象r
# --> 执行with,调用output方法
# --> 执行1/0时捕获异常
# --> 回到class Resource类,进入__enter__()方法,输出__enter__,返回实例对象r
# --> 进入__exit__()方法,输出__exit__,分别输出exc_type、exc_val、exc_tb的值,返回True
# --> output方法中的output不会输出
# --> with执行完毕后,输出finish

【例】按照 contextlib 的协议实现一个文件读取的上下文管理器(但不支持处理异常)

import contextlib

@contextlib.contextmanager
def open_func(filename):
    f = open(filename)

    yield f

    f.close()

with open_func('mytest.txt') as file_in:
    for line in file_in:
        print(line, end='')

# hello world
# hello kitty

【例】按照 contextlib 的协议实现一个文件读取的上下文管理器(允许异常处理)

import contextlib

@contextlib.contextmanager
def open_func(filename):
    print('open file' + filename)
    f = open(filename)

    # 异常处理
    try:
        yield f
    except Exception as e:
        print(e)
    finally:
        f.close()
        print('file closed')


with open_func('mytest.txt') as file_in:
    for line in file_in:
        # 1 / 0
        print(line)

# 输出结果:
# (1)当出现异常时(1 / 0):
# open filemytest.txt
# division by zero
# file closed

# (2)当没出现异常时(正常输出):
# open filemytest.txt
# hello world
#
# hello kitty
# file closed

6. 比较运算符

  • lg():判断
  • lt():判断
  • eq():判断等于
  • le():判断
  • ge():判断
  • ne():判断不等于

【例】比较狗年龄

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __lt__(self, other):
        print('lt')
        return self.name < other.name

    def __le__(self, other):
        print('lt')
        return self.name < other.name

d1 = Dog('aa',1)
d2 = Dog('lisi',2)
print(d1 < d2)    # d1 < d2 == d2.__lt__(d1)
print(d1 > d2)    # d1 > d2 == d2.__gt__(d1)
print(d1 == d2)    # d1 == d2 == d2.__eq__(d1)
print(d1 != d2)    # d1 != d2 == d2.__ne__(d1)
print(d1 <= d2)    # d1 <= d2 == d2.__le__(d1)
print(d1 >= d2)    # d1 >= d2 == d2.__ge__(d1)

【例】创建房间和屋子类,计算屋子面积和比较不同屋子面积的大小

class Room:
    def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width
        self.area = length*width

class House:
    def __init__(self, name):
        self.name = name
        self.rooms = []

    def add_room(self, room):
        self.rooms.append(room)

    @property    # 保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性
    def house_area(self):
        lst = [room.area for room in self.rooms]
        return sum(lst)

    # 定义__eq__()方法后才能进行等值比较
    # def __eq__(self, other):
    #     return self.house_area == other.house_area

    # def __lt__(self,other):
    #     return self.house_area < other.house_area
    # def __gt__(self,other):
    #     return self.house_area > other.house_area

    # 定义__lt__()方法后才能进行比较
    __lt__ = lambda self, other: self.house_area < other.house_area
    # __le__ = lambda self,other : self < other or self == other
    # __gt__ = lambda self,other : not (self<=other)

    def __repr__(self):
        return self.name

h1 = House('1号')
h2 = House('2号')
h3 = House('3号')

print(h2)
# 2号

h1.add_room(Room('厕所', 3, 3))
h1.add_room(Room('南屋', 6, 3))
print(f'{h1}屋子的面积为{h1.house_area}')
# 1号屋子的面积为27

h2.add_room(Room('厕所', 2, 4))
h2.add_room(Room('南屋', 7, 3))
print(f'{h2}屋子的面积为{h2.house_area}')
# 2号屋子的面积为29

h3.add_room(Room('厕所', 2, 3))
h3.add_room(Room('南屋', 1, 5))
print(f'{h3}屋子的面积为{h3.house_area}')
# 3号屋子的面积为11

lst = [h1, h2, h3]
print(f'排序前:{lst}')
# 排序前:[1号, 2号, 3号]

# 一般的实例不能进行比较,但由于上面定义了__lt__()方法,这里就可以进行比较和排序
lst.sort()
print(f'排序前:{lst}')
# 排序前:[3号, 1号, 2号]

print(h1 != h2)
# True
if h1 > h2:
    print('1号小于2号')
else:
    print('1号大于等于2号')
# 1号大于等于2号

【拓展】lambda表达式

# def __eq__(self, other):
#     pass
# 上面这个__eq__()方法用lambda表达式可以写成:
__eq__ = lambda self, other: self == other

# 同理:
__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

7. _srt() 和 repr()方法

最好每个类都有一个repr()方法

参考: https://blog.csdn.net/z_feng12489/article/details/89708907

调用顺序:
先在当前类型中找str() 和 repr(),如果都没有定义的话,会到父类object中找str() 和 repr()

【例】对比str() 和 repr()方法的不同

class Car(object):
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage

    def __repr__(self):
        print('__repr__')
        return f'{self.color} {self.mileage}'

    def __str__(self):
        print('__str__')
        return f'{self.color} {self.mileage}'


car = Car('red', 333)
# 直接打印实例时,会显示内存地址,要想print()打印出有用的内容,就得定义__str__() 或 __repr__()方法
print(car)
# __str__
# red 333

str(car)
# __str__
repr(car)
# __repr__

print(str(car))
# __str__
# red 333

print(repr(car))
# __repr__
# red 333

print(f'this is a {repr(car)}')
# __repr__
# this is a red 333

lst = [car]
print(lst)
# __repr__
# [red 333]

【例】打印日期

import datetime
today = datetime.date(2019, 12, 5)    # 实例化
print(today)
# 2019-12-05
print(str(today))
# 2019-12-05
print(repr(today))
# datetime.date(2019, 12, 5)

# 结论:object中带有__str__() 和 __repr__()方法

8. call()

实现call()方法,把一个类实例变成一个可调用对象

【例】call()方法的使用:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend, best_friend):
        print('My name is %s...' % self.name)
        print('My friend is %s...' % friend)
        print('My best_friend is %s...' % best_friend)

p = Person('xuebi', 'male')
p('zhipeng', 'xiaoru')    # 直接调用Person实例,括号中这两个值分别传给了__call__()中的friend和best_friend
# My name is xuebi...
# My friend is zhipeng...
# My best_friend is xiaoru...

# 【例】把实例对象用类似函数的形式表示
class Fib(object):
    def __init__(self):
        pass

    def __call__(self, num):
        a, b = 0, 1
        self.lst = []

        for i in range(num):
            self.lst.append(a)
            a, b = b, a+b
        return self.lst

    def __str__(self):
        return str(self.lst)

f = Fib()
print(f(10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

你可能感兴趣的:(魔法方法)