【引入】
- Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类
- 这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发
| __init__ :初始化类时触发 |
| __del__ :删除类时触发 |
| __new__ :构造类时触发 |
| __str__ :str函数或者print函数触发 |
| __repr__ :repr或者交互式解释器触发 |
| __doc__ :打印类内的注释内容 |
| __enter__ :打开文档触发 |
| __exit__ :关闭文档触发 |
| __getatrribute__:属性访问截断器,属性查找中优先级最高的 |
| __getattr__ : 访问不存在的属性时调用 |
| __setattr__ :设置实例对象的一个新的属性时调用 |
| __delattr__ :删除一个实例对象的属性时调用 |
| __setitem__ :列表添加值 |
| __getitem__ :将对象当作list使用 |
| __delitem__ :列表删除值 |
| __call__ :对象后面加括号,触发执行 |
| __iter__ :迭代器 |
【一】__init__()
和__new__()
__new__
方法是真正的类构造方法,用于产生实例化对象(空属性)。重写__new__
方法可以控制对象的产生过程。
__init__
方法是初始化方法,负责对实例化对象进行属性值初始化,此方法必须返回None,__new__
方法必须返回一个对象。重写__init__
方法可以控制对象的初始化过程。
| |
| |
| class Student: |
| __instance = None |
| |
| def __new__(cls, *args, **kwargs): |
| if not cls.__instance: |
| cls.__instance = object.__new__(cls) |
| return cls.__instance |
| |
| def sleep(self): |
| print('sleeping...') |
| |
| stu1 = Student() |
| stu2 = Student() |
| |
| print(id(stu1), id(stu2)) |
| print(stu1 is stu2) |
| |
个人感觉,__new__
一般很少用于普通的业务场景,更多的用于元类之中,因为可以更底层的处理对象的产生过程。而__init__
的使用场景更多。
既然知道了__new__()
方法,我们是不是可以考虑一下,如何应用它呢?最常见的就是单例模式了,下面给出实现实例。
| class Singleton(object): |
| _instance = None |
| |
| def __new__(cls, *args, **kwargs): |
| """ |
| 注意这实际上是一个类方法, cls 表示当前类 |
| :param args: |
| :param kwargs: |
| :return: |
| """ |
| if cls._instance is None: |
| cls._instance = super().__new__(cls, *args, **kwargs) |
| |
| return cls._instance |
| |
| |
| s1 = Singleton() |
| s2 = Singleton() |
| if s1 is s2: |
| print('yeah') |
【二】__str__
,__repr__
【1】__str__
__str__
方法会在对象被打印时自动触发
- print功能打印的就是它的返回值
- 我们通常基于方法来定制对象的打印信息
- 该方法必须返回字符串类型
| class People: |
| def __init__(self, name, age): |
| self.name = name |
| self.age = age |
| |
| def __str__(self): |
| |
| return f'<Name:{self.name} Age:{self.age}>' |
| |
| |
| person = People('xiao', 18) |
| print(person) |
| |
【2】__repr__
| class School: |
| def __init__(self, name, addr, type): |
| self.name = name |
| self.addr = addr |
| self.type = type |
| |
| def __repr__(self): |
| return 'School(%s,%s)' % (self.name, self.addr) |
| |
| def __str__(self): |
| return '(%s,%s)' % (self.name, self.addr) |
| |
| |
| s1 = School('City', '上海', '公办') |
| print('from repr: ', repr(s1)) |
| print('from str: ', str(s1)) |
| print(s1) |
【3】小结
- str函数或者print函数--->
obj.__str__()
- repr或者交互式解释器--->
obj.__repr__()
- 如果
__str__
没有被定义,那么就会使用__repr__
来代替输出
- 注意:这俩方法的返回值必须是字符串,否则抛出异常
【三】__del__
方法
__del__
用于当对象的引用计数为0时自动调用。
__del__
一般出现在两个地方:
1. 手工使用del减少对象引用计数至0,被垃圾回收处理时调用。
__del__
一般用于需要声明在对象被删除前需要处理的资源回收操作
| |
| |
| class Student: |
| |
| def __del__(self): |
| print('调用对象的del方法,此方法将会回收此对象内存地址') |
| |
| stu = Student() |
| |
| del stu |
| |
| print('下面还有程序其他代码') |
| class Student: |
| |
| def __del__(self): |
| print('调用对象的del方法,此方法将会回收此对象内存地址') |
| |
| stu = Student() |
【四】isinstance(obj,cls)
和issubclass(sub,super)
isinstance(obj,cls)
检查是否obj
是否是类cls
的对象
| class Bar(): |
| pass |
| |
| |
| class Foo(object): |
| pass |
| |
| |
| foo = Foo() |
| bar = Bar() |
| |
| |
| res_Foo = isinstance(foo, Foo) |
| res_Bar = isinstance(foo, Bar) |
| print(res_Foo) |
| print(res_Bar) |
issubclass(sub, super)
检查sub
类是否是 super
类的派生类
| class Foo(object): |
| pass |
| |
| |
| class Bar(Foo): |
| pass |
| |
| |
| res = issubclass(Bar, Foo) |
| print(res) |
【五】__doc__
| class Foo: |
| '我是描述信息' |
| pass |
| |
| |
| print(Foo.__doc__) |
| class Foo: |
| '我是描述信息' |
| pass |
| |
| class Bar(Foo): |
| pass |
| |
| |
| print(Bar.__doc__) |
【六】__enter__
和__exit__
| |
| |
| with open('a.txt') as f: |
| '代码块' |
- 上述叫做上下文管理协议,即with语句
- 为了让一个对象兼容with语句,必须在这个对象的类中声明
__enter__
和__exit__
方法
| class Open: |
| def __init__(self, name): |
| self.name = name |
| |
| def __enter__(self): |
| print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') |
| |
| |
| def __exit__(self, exc_type, exc_val, exc_tb): |
| print('with中代码块执行完毕时执行我啊') |
| |
| |
| with Open('a.txt') as f: |
| print('=====>执行代码块') |
| |
| class Open: |
| def __init__(self, name): |
| self.name = name |
| |
| def __enter__(self): |
| print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') |
| |
| def __exit__(self, exc_type, exc_val, exc_tb): |
| print('with中代码块执行完毕时执行我啊') |
| print(exc_type) |
| print(exc_val) |
| print(exc_tb) |
| |
| |
| with Open('a.txt') as f: |
| print('=====>执行代码块') |
| raise AttributeError('***着火啦,救火啊***') |
| print('0' * 100) |
- 如果
__exit__()
返回值为True,那么异常会被清空,就好像啥都没发生一样
| class Open: |
| def __init__(self, name): |
| self.name = name |
| |
| def __enter__(self): |
| print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') |
| |
| def __exit__(self, exc_type, exc_val, exc_tb): |
| print('with中代码块执行完毕时执行我啊') |
| print(exc_type) |
| print(exc_val) |
| print(exc_tb) |
| return True |
| |
| |
| with Open('a.txt') as f: |
| print('=====>执行代码块') |
| raise AttributeError('***着火啦,救火啊***') |
| print('0' * 100) |
| |
| |
| |
| |
| |
| |
| |
| |
【七】__setattr__
,__delattr__
,__getattr__
,__getatrribute__
【1】方法介绍
-
setattr
方法:当使用obj.x = y
的时候触发对象的setattr
方法;
-
delattr
方法:当del obj.x
的时候触发对象的delattr
方法;
-
getattr
方法:当尝试访问对象的一个不存在的属性时 obj.noexist
会触发getattr
方法;
getattr
方法是属性查找中优先级最低的。可以重写这3个方法来控制对象属性的访问、设置和删除。
特别注意:如果定义了getattr,而没有任何代码(即只有pass),则所有不存在的属性值都是None而不会报错,可以使用super().getattr()方法来处理
| class Student: |
| def __getattr__(self, item): |
| print('访问一个不存在的属性时候触发') |
| return '不存在' |
| |
| def __setattr__(self, key, value): |
| print('设置一个属性值的时候触发') |
| |
| self.__dict__[key] = value |
| |
| def __delattr__(self, item): |
| print('删除一个属性的时候触发') |
| if self.__dict__.get(item, None): |
| del self.__dict__[item] |
| |
| stu = Student() |
| stu.name = 'xiao' |
| print(stu.noexit) |
| del stu.name |
| |
__getatrribute__
这是一个属性访问截断器,即,在你访问属性时,这个方法会把你的访问行为截断,并优先执行此方法中的代码,此方法应该是属性查找顺序中优先级最高的。
属性查找顺序:
实例的getattribute–>实例对象字典–>实例所在类字典–>实例所在类的父类(MRO顺序)字典–>实例所在类的getattr–>报错
| class People: |
| a = 200 |
| |
| class Student(People): |
| a = 100 |
| |
| def __init__(self, a): |
| self.a = a |
| |
| def __getattr__(self, item): |
| print('没有找到:', item) |
| |
| def __getattribute__(self, item): |
| print('属性访问截断器') |
| if item == 'a': |
| return 1 |
| return super().__getattribute__(item) |
| |
| stu = Student(1) |
| print(stu.a) |
对象属性查找顺序
- 首先访问
__getattribute__()
魔法方法(隐含默认调用,无论何种情况,均会调用此方法
- 去实例对象t中查找是否具备该属性:
t.__dict__
中查找,每个类和实例对象都有一个__dict__
的属性
- 若在
t.__dict__
中找不到对应的属性, 则去该实例的类中寻找,即 t.__class__.__dict__
- 若在实例的类中也招不到该属性,则去父类中寻找,即
t.__class__.__bases__.__dict__
中寻找
- 若以上均无法找到,则会调用
__getattr__
方法,执行内部的命令(若未重载 __getattr__
方法,则直接报错:AttributeError)
【2】二次加工标准类型(包装)
(1)包装
- 包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
| |
| class List(list): |
| def append(self, p_object): |
| ' 派生自己的append:加上类型检查' |
| if not isinstance(p_object, int): |
| raise TypeError('must be int') |
| super().append(p_object) |
| |
| @property |
| def mid(self): |
| '新增自己的属性' |
| index = len(self) // 2 |
| return self[index] |
| |
| |
| l = List([1, 2, 3, 4]) |
| print(l) |
| l.append(5) |
| print(l) |
| |
| |
| print(l.mid) |
| |
| |
| l.insert(0, -123) |
| print(l) |
| |
| l.clear() |
| print(l) |
(2)授权
-
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。
-
授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
-
实现授权的关键点就是覆盖__getattr__
方法
| import time |
| |
| |
| class FileHandle: |
| def __init__(self, filename, mode='r', encoding='utf-8'): |
| |
| self.file = open(filename, mode, encoding=encoding) |
| |
| def write(self, line): |
| t = time.strftime('%Y-%m-%d %T') |
| self.file.write('%s %s' % (t, line)) |
| |
| def __getattr__(self, item): |
| |
| |
| return getattr(self.file, item) |
| |
| |
| |
| f1 = FileHandle('b.txt', 'w+') |
| |
| f1.write('你好啊') |
| |
| f1.seek(0) |
| |
| print(f1.read()) |
| |
| f1.close() |
| |
| class FileHandle: |
| def __init__(self, filename, mode='r', encoding='utf-8'): |
| if 'b' in mode: |
| self.file = open(filename, mode) |
| else: |
| self.file = open(filename, mode, encoding=encoding) |
| self.filename = filename |
| self.mode = mode |
| self.encoding = encoding |
| |
| def write(self, line): |
| if 'b' in self.mode: |
| if not isinstance(line, bytes): |
| raise TypeError('must be bytes') |
| self.file.write(line) |
| |
| def __getattr__(self, item): |
| return getattr(self.file, item) |
| |
| def __str__(self): |
| if 'b' in self.mode: |
| res = "<_io.BufferedReader name='%s'>" % self.filename |
| else: |
| res = "<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" % (self.filename, self.mode, self.encoding) |
| return res |
| |
| |
| f1 = FileHandle('b.txt', 'wb') |
| |
| |
| f1.write('你好啊'.encode('utf-8')) |
| print(f1) |
| f1.close() |
【八】__setitem__
,__getitem__
,__delitem__
重写此系列方法可以模拟对象成列表或者是字典,即可以使用key-value的类型。
| class StudentManager: |
| li = [] |
| dic = {} |
| |
| def add(self, obj): |
| self.li.append(obj) |
| self.dic[obj.name] = obj |
| |
| def __getitem__(self, item): |
| if isinstance(item, int): |
| |
| return self.li[item] |
| elif isinstance(item, slice): |
| |
| start = item.start |
| stop = item.stop |
| return [student for student in self.li[start:stop]] |
| elif isinstance(item, str): |
| |
| return self.dic.get(item, None) |
| else: |
| |
| raise TypeError('你输入的key类型错误!') |
| |
| class Student: |
| manager = StudentManager() |
| |
| def __init__(self, name): |
| self.name = name |
| |
| self.manager.add(self) |
| |
| def __str__(self): |
| return f'学生: {self.name}' |
| |
| __repr__ = __str__ |
| |
| |
| stu1 = Student('小明') |
| stu2 = Student('大白') |
| stu3 = Student('小红') |
| stu4 = Student('胖虎') |
| |
| |
| print(Student.manager[0]) |
| print(Student.manager[-1]) |
| print(Student.manager[1:3]) |
| |
| |
| print(Student.manager['胖虎']) |
| |
【九】__call__
| class Foo: |
| |
| def __init__(self): |
| pass |
| |
| def __call__(self, *args, **kwargs): |
| print('__call__') |
| |
| |
| obj = Foo() |
| obj() |
【十】__gt__()
,__lt__()
,__eq__()
,__ne__()
,__ge__()
- 定义对象比较方法,为关系运算符>,>=,<,==等提供接口
| class SavingsAccount(object): |
| def __init__(self, name, pin, balance=0.0): |
| self._name = name |
| self._pin = pin |
| self._balance = balance |
| |
| def __lt__(self, other): |
| return self._balance < other._balance |
| |
| |
| s1 = SavingsAccount("dream", "1000", 0) |
| s2 = SavingsAccount("hope", "1001", 30) |
| print(s1 < s2) |
- 其实,类似的数值运算的魔法方法非常非常之多,好吧,我真的用了两个,现在是三个非常来形容,以下列出部分
| class Point(object): |
| def __init__(self): |
| self.x = -1 |
| self.y = 5 |
| |
| |
| def __pos__(self): |
| pass |
| |
| def __neg__(self): |
| pass |
| |
| def __invert__(self): |
| pass |
| |
| def __abs__(self): |
| pass |
| |
| |
| def __add__(self, other): |
| pass |
| |
| def __sub__(self, other): |
| pass |
| |
| def __divmod__(self, other): |
| pass |
| |
| def __mul__(self, other): |
| pass |
| |
| def __mod__(self, other): |
| pass |
| |
| def __pow__(self, power, modulo=None): |
| pass |
| |
| |
| def __and__(self, other): |
| pass |
| |
| def __or__(self, other): |
| pass |
| |
| def __xor__(self, other): |
| pass |
| |
| |
| def __lshift__(self, other): |
| pass |
| |
| def __rshift__(self, other): |
| pass |
| |
| |
| def __iadd__(self, other): |
| pass |
| |
| def __imul__(self, other): |
| pass |
| |
| def __isub__(self, other): |
| pass |
| |
| def __idiv__(self, other): |
| pass |
| |
| def __imod__(self, other): |
| pass |
| |
| def __ipow__(self, other): |
| pass |
| |
| def __ilshift__(self, other): |
| pass |
| |
| def __irshift__(self, other): |
| pass |
【十一】__iter__()
和__next__
这2个方法用于将一个对象模拟成序列。内置类型如列表、元组都可以被迭代,文件对象也可以被迭代获取每一行内容。重写这两个方法就可以实现自定义的迭代对象。
| |
| |
| class Num: |
| def __init__(self, max_num): |
| self.max_num = max_num |
| self.count = 0 |
| |
| def __iter__(self): |
| return self |
| |
| def __next__(self): |
| if self.count < self.max_num: |
| self.count += 1 |
| return self.count |
| else: |
| raise StopIteration('已经到达临界') |
| |
| num = Num(10) |
| for i in num: |
| print(i) |
【十二】描述符(__get__()
,__set__()
,__delete__()
)
【1】什么是描述符
- 描述符本质就是一个新式类,在这个新式类中
- 至少实现了
__get__()
,__set__()
,__delete__()
中的一个
- 这也被称为描述符协议
__get__()
:调用一个属性时,触发
__set__()
:为一个属性赋值时,触发
__delete__()
:采用del删除属性时,触发
| class Foo: |
| def __get__(self, instance, owner): |
| pass |
| def __set__(self, instance, value): |
| pass |
| def __delete__(self, instance): |
| pass |
【2】描述符的作用
- 描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
【3】描述符分两种
- 数据描述符:至少实现了
__get__()
和__set__()
| class Foo: |
| def __set__(self, instance, value): |
| print('set') |
| |
| def __get__(self, instance, owner): |
| print('get') |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示