Python魔术方法
isinstance和issubclass
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
|
1
2
3
4
|
class Foo(object): passobj = Foo()print(isinstance(obj, Foo)) # True |
issubclass(sub, super)检查sub类是否是 super 类的派生类
|
1
2
3
4
5
6
|
class Foo(object): passclass Bar(Foo): passprint(issubclass(Bar,Foo)) # True |
二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,
新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class List(list):#继承list所有的属性,也可以派生出自己新的,比如append和mid def append(self,p_object): if 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)# l.append('1111111') #报错,必须为int类型print(l.mid)#其余的方法都继承list的l.insert(0,-123)print(l)l.clear()print(l) |
__getattr__与__getattribute__
object.__getattr__(self, name)
当一般位置找不到attribute的时候,会调用getattr,返回一个值或AttributeError异常。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import timeclass 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): print('执行__getattr__item %s' %item) return getattr(self.file,item)f1=FileHandle('b.txt','w+')f1.write('你好啊') f1.seek(0) # 执行__getattr__item seekprint(f1.read()) # 执行__getattr__item read 2018-06-11 21:04:32 你好啊f1.close() # 执行__getattr__item close<br>f1.xxxxxx #不存在的属性访问,触发__getattr__ |
object.__getattribute__(self, name)
无条件被调用,通过实例访问属性。如果class中定义了__getattr__(),则__getattr__()不会被调用(除非显示调用或引发AttributeError异常)
|
1
2
3
4
5
6
7
8
9
10
|
class Foo: def __init__(self,x): self.x=x def __getattribute__(self, item): print('不管是否存在,我都会执行')f1=Foo(10)f1.x # 不管是否存在,我都会执行f1.xxxxxx # 不管是否存在,我都会执行 |
二者同时出现
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print('执行的是我') # return self.__dict__[item] def __getattribute__(self, item): print('不管是否存在,我都会执行') raise AttributeError('哈哈')f1=Foo(10)f1.xf1.xxxxxx#------输出结果-----------不管是否存在,我都会执行执行的是我不管是否存在,我都会执行执行的是我#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError |
描述符(__get__,__set__,__delete__)
1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议 __get__():
调用一个属性时,触发 __set__():为一个属性赋值时,触发 __delete__():采用del删除属性时,触发
定义一个描述符
|
1
2
3
4
5
6
7
8
9
|
#_*_coding:utf-8_*___author__ = 'Linhaifeng'class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass |
2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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#疑问:何时,何地,会触发这三个方法的执行 |
描述符应用
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#描述符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__)# 输出结果Str设置...Int设置...Str调用Str设置...Str删除...Int调用Int设置...Int删除...{}{'__module__': '__main__', 'name': <__main__.Str object at 0x0000000014B0A470>, 'age': <__main__.Int object at 0x0000000014B0A7B8>, '__init__': <function People.__init__ at 0x0000000014B2E1E0>, <br>'__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}TrueTrue |
描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name)class People: name=Typed('name',str) age=Typed('name',int) salary=Typed('name',float) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary# p1=People(123,18,3333.3)# p1=People('egon','18',3333.3)p1=People('egon',18,3333) |
类的装饰器:无参
|
1
2
3
4
5
6
7
8
9
10
11
12
|
def decorate(cls): print('类的装饰器开始运行啦------>') return cls@decorate #无参:People=decorate(People)class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salaryp1=People('egon',18,3333.3) |
类的装饰器:有参
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>',kwargs) return cls return decorate@typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salaryp1=People('egon',18,3333.3) |
通过反射和装饰器实现
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name)def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>',kwargs) for name,expected_type in kwargs.items(): setattr(cls,name,Typed(name,expected_type)) return cls return decorate@typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salaryprint(People.__dict__)p1=People('egon',18,3333.3) |
__setitem__,__getitem__,__delitem__
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Foo: def __init__(self,name): self.name=name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key]=value def __delitem__(self, key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key时,我执行') self.__dict__.pop(item)<br>#通过字典方式访问f1=Foo('sb')f1['age']=18f1['age1']=19del f1.age1del f1['age']f1['name']='alex'print(f1.__dict__) |
__str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
format_dict={ 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名}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) def __format__(self, format_spec): # if format_spec if not format_spec or format_spec not in format_dict: format_spec='nat' fmt=format_dict[format_spec] return fmt.format(obj=self)s1=School('oldboy1','北京','私立')print('from repr: ',repr(s1))print('from str: ',str(s1))print(s1) # 调用__str__# -----输出结果-----------# from repr: School(oldboy1,北京)# from str: (oldboy1,北京)# (oldboy1,北京)'''str函数或者print函数--->obj.__str__()repr或者交互式解释器--->obj.__repr__()如果__str__没有被定义,那么就会使用__repr__来代替输出注意:这俩方法的返回值必须是字符串,否则抛出异常'''print(format(s1,'nat'))print(format(s1,'tna'))print(format(s1,'tan'))# -----输出结果-----------oldboy1-北京-私立私立:oldboy1:北京私立/北京/oldboy1 |
slots
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
'''1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。'''f1=Student()f1.name = 'al' # 绑定属性'name'f1.age = 2 # 绑定属性'age'print(f1.__slots__) # f1不再有__dict__print(f1.__dict__) # 报错f1.score = 99 # 报错class Student: __slots__=['name','age']f1=Student()f1.name='alex'f1.age=18print(f1.__slots__) # ['name', 'age']f2=Student()f2.name='egon'f2.age=19print(f2.__slots__) # ['name', 'age']print(Student.__dict__)#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存 |
__next__和__iter__实现迭代器协议
简单模拟range,加上步长
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Range: def __init__(self,n,stop,step): self.n=n self.stop=stop self.step=step def __next__(self): if self.n >= self.stop: raise StopIteration x=self.n self.n+=self.step return x def __iter__(self): return selffor i in Range(1,7,2): # print(i) |
斐波那契数列
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class Fib: def __init__(self,x): self._a=0 self._b=1 self.x = x self.n = 0 def __iter__(self): return self def __next__(self): if self.n >= self.x: raise StopIteration self._a,self._b=self._b,self._a + self._b self.n += 1 return self._af1=Fib(10)print(f1.__next__())print(next(f1))while True: try: print(next(f1)) except StopIteration: pass |
__doc__
类的描述信息,该属性无法被继承
|
1
2
3
4
5
6
7
8
9
|
class Foo: '我是描述信息' passclass Bar(Foo): passprint(Foo.__doc__) # 我是描述信息print(Bar.__doc__) # None 该属性无法继承给子类 |
__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
|
1
2
3
4
5
6
7
8
|
class C: def __init__(self): self.name = 'alex'obj = C()print(obj.__module__) # 输出 __main__,即:输出模块print(obj.__class__) # 输出 <class '__main__.C'>,即:输出类 |
__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,
即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
class Foo: def __del__(self): print('执行我啦')f1=Foo()del f1print('------->')#输出结果执行我啦-------> |
__enter__和__exit__
我们知道在操作文件对象的时候可以这么写
|
1
2
|
with open('a.txt') as f: '代码块' |
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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后的代码都无法执行
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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) #------------------------------->不会执行 # print(f,f.name)#------------输出结果-------# 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量# =====>执行代码块# with中代码块执行完毕时执行我啊# <class 'AttributeError'># ***着火啦,救火啊***# <traceback object at 0x00000000126EAB48> |
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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 Truewith Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***')print('0'*10) #------------------------------->会执行# # -------------输出结果------------------# 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量# =====>执行代码块# with中代码块执行完毕时执行我啊# <class 'AttributeError'># ***着火啦,救火啊***# <traceback object at 0x00000000126EAB88><br># 0000000000 |
模拟Open
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Open: def __init__(self,file_name,mode,encoding='utf-8'): self.file_name=file_name self.mode = mode self.encoding = encoding def __enter__(self): print('enter') self.f = open(self.file_name,self.mode,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): print("exit") self.f.close() return True def __getattr__(self, item): print('执行的是我') return getattr(self.f, item)with Open('test.txt','w') as f: f.write('aadddda') |
-
使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
-
在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
|
1
2
3
4
5
6
7
8
9
10
11
12
|
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__')obj = Foo() # 执行 __init__obj() # 执行 __call__ |
__new__
在基础类object中,__new__被定义成了一个静态方法,并且需要传递一个参数cls。cls表示需要实例化的类,此参数在实例化时由Python解析器自动提供。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Foo(object): def __init__(self): print('init') def __new__(cls, *args, **kwargs): print('call __new__() is running. cls id is %s' %id(cls)) r = super(Foo, cls).__new__(cls,*args, **kwargs) print('r_id is % s' % id(r)) return rf = Foo()----------输出结果----------call __new__() is running. cls id is 309410296r_id is 60819440init |
实例化对象f的时候,调用__init__()初始化之前,先调用了__new__()方法
产生的新对象 = object.__new__(继承object类的子类)
__new__()必须要有返回值,返回实例化出来的实例,需要注意的是,可以return父类__new__()出来的实例,也可以直接将object的__new__()出来的实例返回。
__init__()有一个参数self,该self参数就是__new__()返回的实例,__init__()在__new__()的基础上可以完成一些其它初始化的动作,__init__()不需要返回值。
若__new__()没有正确返回当前类cls的实例,那__init__()将不会被调用,即使是父类的实例也不行。
我们可以将类比作制造商,__new__()方法就是前期的原材料购买环节,__init__()方法就是在有原材料的基础上,加工,初始化商品环节。
__new__ 的作用
依照Python官方文档的说法,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。
首先我们来看一下第一个功能,具体我们可以用int来作为一个例子:
假如我们需要一个永远都是正数的整数类型,通过集成int,我们可能会写出这样的代码
|
1
2
3
4
5
6
7
|
class PositiveInteger(int): def __new__(cls, value): return super(PositiveInteger, cls).__new__(cls,abs(value))p = PositiveInteger(-3)print(p) # 3 |
__new__ 实现单例
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Singleton(object): def __new__(cls): # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象 if not hasattr(cls, 'instance'): cls.instance = super(Singleton, cls).__new__(cls) return cls.instanceobj1 = Singleton()obj2 = Singleton()obj1.attr1 = 'value1'print(obj1.attr1, obj2.attr1)print(obj1 is obj2) |

浙公网安备 33010602011771号