魔法方法
一 isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
class Foo(object):
pass
obj = Foo()
isinstance(obj, Foo)
issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo(object):
pass
class Bar(Foo):
pass
issubclass(Bar, Foo)
二 反射
https://www.cnblogs.com/ZhZhang12138/p/14887275.html#/c/subject/p/14887275.html
三 setattr,delattr,getattr
#__setattr__添加/修改属性会触发它的执行
#__delattr__删除属性的时候会触发
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
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)
#__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
三者的用法演示
四 getattribute
# __getattr__只有在使用点调用属性且属性不存在的时候才会触发
# __getattribute__只有在使用点调用属性且属性不存在的时候才会触发
getattr与getattribute的区别
1.__getattribute__方法优先级比__getattr__高
2.只有在__getattribute__方法中找不到对应的属性时,才会调用__getattr__
3.如果是对不存在的属性做处理,尽量把逻辑写在__getattr__方法中
4.如果非得重写__getattribute__方法,需要注意两点:第一是避免.操作带来的死循环;第二是不要遗忘父类的__getattribute__方法在子类中起的作用
五 描述符
描述符是什么
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这个也被称之为描述符协议
__get__()调用一个属性时,触发
__set__()为一个属性赋值时,触发
__delete__()采用del删除属性时,触发
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
描述符是干什么的
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
class Foo: def __get__(self, instance, owner): print('触发get') def __set__(self, instance, value): print('触发set') def __delete__(self, instance): print('触发delete')#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法f1=Foo()f1.name='egon'f1.namedel f1.name#疑问:何时,何地,会触发这三个方法的执行
#描述符Strclass Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...')#描述符Intclass Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...')class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age#何地?:定义成另外一个类的类属性#何时?:且看下列演示p1=People('alex',18)#描述符Str的使用p1.namep1.name='egon'del p1.name#描述符Int的使用p1.agep1.age=18del p1.age#我们来瞅瞅到底发生了什么print(p1.__dict__)print(People.__dict__)#补充print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的print(type(p1).__dict__ == People.__dict__)
描述符分两种
一 数据描述符:至少实现了__get__()和__set__()
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
二 非数据描述符:没有实现__set__()
class Foo: def __get__(self, instance, owner): print('get')
注意事项
一 描述符本身应该定义成新式类,被代理的类也应该是新式类二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中三 要严格遵循该优先级,优先级由高到底分别是1.类属性2.数据描述符3.实例属性4.非数据描述符5.找不到的属性触发__getattr__()
六 getitem、setitem、delitem
class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): print('getitem执行', self.__dict__[item]) def __setitem__(self, key, value): print('setitem执行') self.__dict__[key] = value def __delitem__(self, key): print('del obj[key]时,delitem执行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key时,delattr执行') self.__dict__.pop(item)f1 = Foo('sb')
setitem
中括号赋值时触发f1['age'] = 18f1['age1'] = 19>>>:setitem执行setitem执行
getitem
中括号取值时触发f1['age']>>>:getitem执行 18
delitem与delattr
delitem:中括号删除时触发delattr:.删除时触发del f1.age1del f1['age']>>>:del obj.key时,delattr执行del obj[key]时,delitem执行
七 str、repr、format
str
改变对象的字符串显示,可以理解为print打印对象的显示
class Student: def __init__(self, name, age): self.name = name self.age = age # 定义对象的字符串表示 def __str__(self): return self.names1 = Student('张三', 24)print(s1) # 会调用s1的__str__方法>>> 张三
repr
str函数或者print函数调用的是obj.str()repr函数或者交互式解释器调用的是obj.repr()注意:如果__str__没有被定义,那么就会使用__repr__来代替输出。__str__和__repr__方法的返回值都必须是字符串。__repr__同__str__,优先级__str__高于__repr__
>>> class Student:... def __init__(self, name, age):... self.name = name... self.age = age... def __repr__(self):... return self.name... >>> s1 = Student('张三', 24)>>> s1张三
format
自定制格式化字符串
date_dic={ 'ymd':'{0.year}:{0.month}:{0.day}', 'dmy':'{0.day}/{0.month}/{0.year}', 'mdy':'{0.month}-{0.day}-{0.year}',}class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day def __format__(self, format_spec): if not format_spec or format_spec not in date_dic: format_spec='ymd' fmt=date_dic[format_spec] return fmt.format(self)d1=Date(2020,12,29)print(format(d1)) # 2020:12:29print('{:mdy}'.format(d1)) # 12-29-2020
八、del
del也称之为析构方法del会在对象被删除之前自动触发
class People: def __init__(self, name, age): self.name = name self.age = age self.f = open('test.txt', 'w', encoding='utf-8') def __del__(self): print('run======>') # 做回收系统资源相关的事情 self.f.close()obj = People('egon', 18)>>>:del obj # del obj会间接删除f的内存占用,但是还需要自定制__del__删除文件的系统占用print('主')
八、slots
什么是slots
slots是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)使用点来访问属性本质就是在访问类或者对象的dict属性字典(类的字典是共享的,而每个实例的是独立的)
为什么用slots
- 字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用slots取代实例的dict
- 当你定义slots后,slots就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在slots中列出的属性名在内部被映射到这个数组的指定小标上。使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在slots中定义的那些属性名。
class Foo: __slots__='x'f1=Foo()f1.x=1f1.y=2#报错print(f1.__slots__) #f1不再有__dict__class Bar: __slots__=['x','y'] n=Bar()n.x,n.y=1,2n.z=3#报错
- 注意:slots的很多特性都依赖于普通的基于字典的实现。另外,定义了slots后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上定义slots比如在程序中需要创建某个类的几百万个实例对象 。
- 关于slots的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用slots可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。
九、doc
- 返回类的注释信息
class Foo: '我是描述信息' passprint(Foo.__doc__)>>>:我是描述信息
- 该属性无法被继承
class Foo: '我是描述信息' passclass Bar(Foo): passprint(Bar.__doc__) #该属性无法继承给子类>>>:None
十、call
- 对象后面加括号时,触发执行。
- 注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): print('__init__触发了') def __call__(self, *args, **kwargs): print('__call__触发了')obj = Foo() # 执行 __init__>>>:__init__触发了obj() # 执行 __call____call__
十一、init__和__new
曾经我幼稚的以为认识了python的init()方法就相当于认识了类构造器,结果,new()方法突然出现在我眼前,让我突然认识到原来new才是老大。为什么这么说呢?我们首先得从new(cls[,…])的参数说说起,new方法的第一个参数是这个类,而其余的参数会在调用成功后全部传递给init方法初始化,这一下子就看出了谁是老子谁是小子的关系。所以,new方法(第一个执行)先于init方法执行:
class A: passclass B(A): def __new__(cls): print("__new__方法被执行") return super().__new__(cls) def __init__(self): print("__init__方法被执行")b = B()>>>:__new__方法被执行__init__方法被执行
我们比较两个方法的参数,可以发现new方法是传入类(cls),而init方法传入类的实例化对象(self),而有意思的是,new方法返回的值就是一个实例化对象(ps:如果new方法返回None,则init方法不会被执行,并且返回值只能调用父类中的new方法,而不能调用毫无关系的类的new方法)。我们可以这么理解它们之间的关系,new是开辟疆域的大将军,而init是在这片疆域上辛勤劳作的小老百姓,只有new执行完后,开辟好疆域后,init才能工作。
十二、next__和__iter
可迭代对象
1、内置有__iter__方法的对象都叫可迭代的对象调用可迭代对象的__iter__方法,返回的是它的迭代器
迭代器对象
1、内置有__next__方法2、内置有__iter__方法
注意
迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
# 内置的类型都是可迭代的对象'abc'.__iter__()[1,2,3].__iter__()(1,2,3).__iter__(){'k1':111}.__iter__(){1,2,3}.__iter__()f = open('db.txt','w') # 文件对象本身就是迭代器对象f.__iter__()f.__next__()
十三、module__和__class
-
module 表示当前操作的对象在那个模块
-
class表示当前操作的对象的类是什么
十四、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声明的变量') # return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊')with Open('a.txt') as f: print('=====>执行代码块') # print(f,f.name) >>>:出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量=====>执行代码块with中代码块执行完毕时执行我啊
-
exit()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
-
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
优点
使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在exit中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处