isinstance(obj, cls) : 判断对象obj是否为一个类cls实例化出来的对象,能反映继承关系,即B继承A, B实例化出来的对象b, isinstance(b, A) 为 True
type() : 判断实例化出来的对象obj所属的类
issubclass(cls1, cls2) : 判断类cls1是否为类cls2的子类
raise XXX : 如 用来发出异常信号给屏幕, 比如 raise AttributeError('异常')
__getattribute__ : __getattr__的大哥,不管通过实例对象是否调用到某个方法或属性,都会先触发__getattribute__
其实内置的__getattribute__大体就做了这么两件事:
1、如果实例对象能调用到某个方法或属性,则使得实例获取到这个方法或属性
2、如果调用不到则raise AttributeError ,然后这个操作会呼唤小弟__getattr__执行
(注意这个AttributeError被小弟接住了,不会程序中断;但是默认的__getattr__小弟自己的逻辑还会raise一个AttributeError)
如下我们自己定义一个__getattribute__,模拟这个过程
class A: def __init__(self, x): self.x = x def __getattr__(self, item): print('执行getattr') def __getattribute__(self, item): print('执行getattribute') raise AttributeError('交给我小弟getattr去做') a = A(1) a.xx
#__getitem__、__setitem__、__delitem__
类似前面的__getattr__ ...... ,不同的是这只当通过字典的方式(不是操作__dict__字典那种方式)操作才会触发
class A: def __setitem__(self, key, value): print('__setitem__被执行') self.__dict__[key] = value def __getitem__(self, item): print('__getitem__被执行') return self.__dict__[item] def __delitem__(self, key): print('__delitem__被执行') self.__dict__.pop(key) a = A() print(a.__dict__) a['name'] = 'alex' #触发__setitem__ a['age'] = 18 #触发__setitem__ print(a.__dict__) del a['name'] #触发__delitem__ print(a.__dict__) print(a['age']) #触发__getitem__
#__str__
__str__本身为内置的一个显示实例化对象的方法,也可自己定制.有以下两个特点:
当使用print输出对象的时候,会自动触发这个对象下的__str__(self)方法,并打印从这个方法中return的值
__str__方法需要返回一个字符串,当做这个对象的描写
class A: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return '自定的实例显示方法%s %s' %(self.name, self.age) a = A('alex', 18) print(a) # print实际会触发 str(a) --> 也就是a.__str__,打印出a.__str__的返回值 #通过以下例子就可以看出来 class B(list): def __str__(self): return '我自定的显示' print(B('abcdef')) #如果再怀疑print()是否真的触发了__str__ 可以去终端试试(终端定义类的或者函数的时候会进入 ... 为开头的模式,定义完成后连续两下Enter就可退出) #事实证明,自己定义的__str__在print()后才触发, 并且str(a) 其实就是在调用a.__str__
__repr__
类似__str__, 只是__str__是在print时候触发, 而__repr__在解释器里显示的时候触发的
并且如果这个类没定义__str__,则print()就用__repr__的返回值,有的话就使用__str__的返回值,以下为终端演示:
__format__
当使用format(a)时候, 其实就是在使用a.__format__,例如:
class Date: def __init__(self,year, mon, day): self.year = year self.mon = mon self.day = day date = Date(2019, 10, 9) print("{0.mon}-{0.day}-{0.year}".format(date)) #调用实例对象属性的format格式化
但是我们为何不自定一个format()的规则呢?如下:
class Date: format_dic = { 'm-d-y': '{0.mon}-{0.day}-{0.year}', 'y:m:d': '{0.year}:{0.mon}:{0.day}' } def __init__(self,year, mon, day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): if not format_spec or (format_spec not in self.format_dic): print('使用默认格式') fm = '{0.year} {0.mon} {0.day}' return fm.format(self)
print('使用指定格式') fm = self.format_dic[format_spec] return fm.format(self) date = Date(2019, 10, 9) print(format(date)) print(format(date, 'm-d-y'))
__slots__
之前的小补充:使用 . 的方式访问属性本质上就是在方位对象的__dict__字典,类的字典是共享的,而每个对象又是独立的(这也就是为什么实例化的对象的__dici__字典里只有__init__里定义的属性)
为什么要使用__slots__?: 字典会占用大量内存,而每个类实例化出来的对象都有着独立的属性字典,如果实例化的对象太多,占用内存就太大;所以可以使用__slots__替代属性字典__dict__来节省内存。下面为使用的例子:
class Foo: #创建单个属性 # __slots__ = 'name' #相当于属性字典里的 {'name':None} #若创建多个属性 __slots__ = ['name', 'age'] #相当于属性字典里的 {'name':None, 'age':None} f1 = Foo() f1.name = 'alex' # print(f1.__dict__) #会报错,用了__slots__生成的实例对象,已经不再有__dict__属性 #f1.age = 18 #会报错,已经无法增加新的属性,因为本质上是在调用__setattr__,而这需要操作__dict__属性字典 print(f1.__slots__, Foo.__slots__) #这时候已经没有实例的属性字典了,他俩一样 print(f1.name) print(f1.__slots__)
但是注意:慎用,因为通过__slots__定义对象属性,相当于把对象的属性给写死了,没法再添加;同时所有有关操作__dict__的方法都失效了
__doc__
查看类的描述信息,但注意:这个描述信息没法被子类继承
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Foo: " 这是描述信息 " pass class Foo1(Foo): pass f = Foo() f1 = Foo1 print(f.__doc__) print(f1.__doc__)
__module__: 查看当前操作的对象在哪个模块(调用的外部模块或者当前模块__main__)
__class__: 查看当前操作的对象的类是什么
__del__: 析构函数,当对象在内存中被释放时,自动触发执行
注:此方法一般无需定义,因为Python是一门高级语言,程序猿使用时无需关心内存的分配和释放,都是交给Python解释器来执行;
所以析构函数的调用是在解释器进行垃圾回收时自动触发执行的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Foo: def __init__(self, name): self.name = name def __del__(self): print('对象被释放') f1 = Foo('alex') del f1 #此时对象f1被删除后,内存中被释放 print('------------') #注意当程序执行完毕后,后自动释放内存,此时会触发__del__ class Foo1: def __init__(self, name): self.name = name def __del__(self): print('对象被释放') f2 = Foo1('alex') del f1.name print('------------')
__call__: 对象后面加括号时,触发执行
注:对象()的形式本质上是在调用对象的__call__:__call__会返回一个对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Foo: pass f = Foo() #print(f.__call__) #对象f没有__call__方法 #f() #所以无法使用对象()的形式 class Foo1: def __call__(self, *args, **kwargs): print('使用了对象(),触发了__call__') f1 = Foo1() f1() #触发__call__
__next__、__iter__
复习一下迭代器协议: 是指对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,否则引起一个stop Iteration异常,以终止迭代,并且不可逆。
如何利用__next__、__iter__做一个自己的迭代器?
class Foo: def __init__(self, n): self.n = n def __iter__(self): return self def __next__(self): if self.n >= 5: raise StopIteration('迭代完毕') else: self.n += 1 return self.n f = Foo(0) #迭代完毕后继续迭代会raise StopIteration('迭代完毕') 错误 # f.__next__() # f.__next__() # f.__next__() # f.__next__() # f.__next__() # f.__next__() #用 for 则会自动停止,处理好你这个错误: 用了 try: except StopIteration : ;详情看迭代器那章 for i in f: #相当于iter(f) == f.__iter__(), 然后 f.__iter__().__next__() = i print(i)
创建一个斐波那契迭代器
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Fibonacci: def __init__(self): self.n1 = 1 self.n2 = 1 self.count = True #为了返回第一个数设置的开关 def __iter__(self): return self def __next__(self): if self.n1 > 100: raise StopIteration('迭代终止') elif self.n2 == 1 and self.count: self.count = False return 1 self.n1, self.n2 = self.n2, self.n1 + self.n2 return self.n1 f = Fibonacci() for i in f: print(i)
#上下文管理协议:__enter__、__exit__
当我们使用with open() as f : 的时候,其实就是open类实例化出一个对象,然后把这个对象赋值给变量f
那么是不是其他类也能通过这种方式实例化对象呢?答案是肯定的
我们只要在这个类中定义__enter__与__exit__方法就可以实现这种方式,具体如下
class Foo: def __enter__(self): print('enter触发') return self #如果不返回对象,这个对象就不会赋值给下面的f def __exit__(self, exc_type, exc_val, exc_tb): print('exit触发') with Foo() as f: #在这里触发__enter__, 并把Foo()实例化出来的对象传给__enter__;也就是f=Foo().__enter__();而不是认为做了f=Foo()--》特别注意!! print(f) #在下面的代码块执行完毕后触发__exit__ print('这时已经触发完__exit__')
__exit__触发意味着with这个结构结束了
这里问题又来了,__exit__中的exc_type, exc_val, exc_tb三个参数又是什么呢?
如果with 下面的代码块正确执行,那么他们都的值都是None,一旦代码块中间报错时,会先触发__exit__,然后抛出异常
exc_type:发生的异常类型
exc_val:发生异常的值
exc_tb:异常的追踪信息
小补充:任何异常都分为三部分,而上面三个参数就是分别获取这三个部分
class Foo: 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) with Foo() as f: print(不存在的变量) #这里触发__exit__ print(f) print('这时已经触发完__exit__')
若__exit__ return 一个True,那么这个异常会被吃了,代码继续正常执行;但在with这个结构中,触发了__exit__之后就会退出这个结构的代码块
class Foo: 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 with Foo() as f: print(不存在的变量) #这里触发__exit__ print(f) print("你看,异常被吃了,这里没有继续执行") print('这时已经触发完__exit__')
总结: with open obj as f:
代码块
1. with obj ----> 触发obj.__enter__() --> f = obj.__enter__()
2.执行代码块
一:没有异常的情况下,整个代码块执行完后触发__exit__,它的三个参数都为None
二:有异常情况下,从异常出现的位置直接触发__exit__() ;
a:如果__exit__的返回值为True,代表吞掉异常,with语句退出
b:如果__exit__的返回值为不为True,raise异常
c:__exit__的运行代表with语句的结束
三:以上的例子都没做释放内存的操作,需要在__exit__实现,后面再讲
优点:
#描述符: __get__()
、__set__()
、__delete__()注意别和__del__混了,__del__是析构方法 重要!!!
一个实现了 描述符协议
的类就是一个描述符。
描述符协议:实现了 __get__()
、__set__()
、__delete__()
其中至少一个方法的类,就是一个描述符。
-
__get__
: 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。 -
__set__
:将在属性分配操作中调用。不会返回任何内容。 -
__delete__
:控制删除操作。不会返回内容。
描述符分两种:
1、数据描述符:至少实现了__get__()和__set__()
2、非数据描述符:没有实现__set__()
注意事项:
-
描述符本身应该定义为新式类,被代理的类也应该是新式类
-
必须把描述符定义成这个类的类属性,不能定义到构造函数__init__中
-
严格遵循该优先级:由高到低
1、类属性
2、数据描述符
3、实例属性
4、非数据描述符
5、找不到属性触发__getattr__()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get') def __delete__(self, instance): print('delete') class Bar: x = Foo() def __init__(self): self.x = 1 b = Bar() Bar.x #类调用时,也会触发__get__ print(b.x) #数据描述符 > 实例属性, 于是得到的不是__init__里定义的 1,而是__get__的返回值 b.x = 1 #触发__set__, 因为实例属性 < 数据描述符 print(b.x) #b.x的值还是那个代理的,而不是改成1 print(Bar.x) Bar.x = 1 #不会触发__set__,类属性优先级最高 print(Bar.x) #看到Bar.x的值被覆盖 #不信还可这样试试 #del Bar.x #b.x --> 报错,x被删了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
使用方法:
class Desc: def __init__(self, name): #为了记录被代理的变量的名字, 如这里的 'x' self.name = name def __get__(self, instance, owner): #这里的instance 就是实例t ;owner 为拥有这个实例的类TestDesc print("get!!!!") print("self=", self) print("instance=", instance) print("owner=", owner) print('*'*50, "\n") #return '返回值:{}'.format(instance.__dict__[self.name]) def __set__(self, instance, value): #value 为你设置的值 print("set!!!!") print("self=", self) print("instance=", instance) print("value=", value) print('*'*50, "\n") #下面使用可以看出,实例t的字典里并没有设置的值,所以这里可以手动把值添加到字典中 # instance.__dict__[self.name] = value def __delete__(self, instance): print("delete!!!!") print("self=", self) print("instance=", instance) print('*' * 50, "\n") class TestDesc: x = Desc('x') #通俗的说就是在TestDesc这个类中,用Desc去描述x属性;相当于x代理给了Desc y = 1 def __init__(self, n): self.x = n #不会存于实例的字典中,但是会存于类的字典中 t = TestDesc('__set__ 里面的value就是这个') #触发__set__: print(t.__dict__) #实例的字典里没x print(t.x) #触发__get__:当实例化对象属性(等于描述符的那个属性)被调用时 print(TestDesc.x) #通过类调用也会触发__get__,只是instance值为None t.x = '这个值传入__set__中' #触发__set__:当实例化对象设置描述符属性时候触发,如上面的x=Desc() del t.x #触发__delete__ print(TestDesc.__dict__) #类的字典里有x
特别注意:通过类的方式调用被代理的属性,也会触发__get__,只是此时instance的值为None!!!
为什么要使用描述符? 参考:https://www.cnblogs.com/wongbingming/p/10781688.html
这里附上最后的代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Score: """ 这是关于限制分数格式的描述符 """ def __init__(self, default=0): self._score = default def __set__(self, instance, value): if not isinstance(value, int): raise TypeError('Score must be integer') if not 0 <= value <= 100: raise ValueError('Valid value must be in [0, 100]') self._score =value def __get__(self, instance, owner): return self._score def __delete__(self, instance): del self._score class Student: math = Score() chinese = Score() english = Score() def __init__(self, name, math, chinese, english): self.name = name self.math = math self.chinese = chinese self.english = english def __repr__(self): return "<Student: {}, math: {}, chinese: {}, english: {}>".format( self.name, self.math, self.chinese, self.english ) stu1 = Student('alex', 76, 90, 95) print(stu1, '\n', stu1.math, stu1.chinese, stu1.english) #如果输入成绩不满足分数描述符里的限制 # stu2 = Student('alex', 110, 90, 70) # stu3 = Student('alex', '90', 90, 70)
但是以上不仅把输入的类型写死了,而且也没有把值添加到实例的字典中,下面这样会更好
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Score: """ 这是关于限制分数格式的描述符 """ def __init__(self, name, type): self._name = name self._type = type def __set__(self, instance, value): if not isinstance(value, self._type): raise TypeError('%s Score must be %s' %(self._name, self._type)) if isinstance(value, int): if not 0 <= value <= 100: raise ValueError('Valid value must be in [0, 100]') instance.__dict__[self._name] = value def __get__(self, instance, owner): return instance.__dict__[self._name] def __delete__(self, instance): instance.__dict__.pop(self._name) class Student: name = Score('name', str) math = Score('math', int) chinese = Score('chinese', int) english = Score('english', int) def __init__(self, name, math, chinese, english): self.name = name self.math = math self.chinese = chinese self.english = english def __repr__(self): return "<Student: {}, math: {}, chinese: {}, english: {}>".format( self.name, self.math, self.chinese, self.english ) stu1 = Student('alex', 76, 90, 95) print(stu1) #如果输入不满足分数描述符里的限制 # stu2 = Student(666, 100, 100, 100) # stu3 = Student('alex', 110, 90, 70) # stu4 = Student('alex', '90', 90, 70)
补充1: 软件开发规范
--项目名称 -- bin 一个项目的启动文件装在这个文件夹下 -- strat.py -- conf config配置,配置文件 -- settings.py 可能会发生改变的配置信息 -- core 核心代码 -- 相关文件 实际的python代码所在的文件
例如: -- user.py -- auth.py -- main.py -- lib 第三方库 -- db database 数据库 一堆数据文件,不一定是py文件 -- log 日志
此外,还可以有readme文件:
补充2:
pycharm 创建项目时,会自动把项目的主文件夹添加到环境变量中
方便你调用库,但是用open读取文件的时候无效(因为是在当前工作路径下读取)
小补充:
1、形如__XX__为内置方法,即系统自己给你定义的方法,并且如果你也定义了名字一样的方法则用你定义的。
2、getattr()...别看很__getattr__()...他们很像,但是功能完全不同,也没联系
3、python是弱类型语言:定义变量时无需定义它的类型