python面向对象高级

isinstance(obj,cls)和issubclass(sub,super)

isinstance(obj,cls)检查是否obj是否是类 cls 的对象

1
2
3
4
5
class Foo(object):
pass

obj = Foo()
isinstance(obj, Foo)

issubclass(sub, super)检查sub类是否是 super 类的派生类

1
2
3
4
5
6
7
class Foo(object):
pass

class Bar(Foo):
pass

issubclass(Bar, Foo)

反射

1 什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

2 python面向对象中的反射

通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

hasattr(object,name)

判断object中有没有一个name字符串对应的方法或属性

getattr(object, name, default=None)

1
2
3
4
5
6
7
class People:
def walk(self):
print("fuck u walk")

p = People()
x = getattr(p, 'walk')
x()

setattr(x, y, v)

1
2
3
4
5
6
7
class People:
def walk(self):
print("fuck u walk")

p = People()
setattr(p, 'age', 18) #设置age的值为18,没有这个属性则会创建
print(p.age)

delattr(x, y)

1
2
3
4
5
6
7
class People:
age = 18
def walk(self):
print("fuck u walk")

p = People()
delattr(p, 'age')

反射当前模块成员

1
2
3
4
5
6
7
8
9
10
11
12
import sys

def s1():
print('s1')

def s2():
print('s2')

this_module = sys.modules[__name__]

hasattr(this_module, 's1')
getattr(this_module, 's2')()

3 为什么用反射之反射的好处

好处一:实现可插拔机制

有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。

总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

1
2
3
4
5
class FtpClient:
'ftp客户端,但是还么有实现具体的功能'
def __init__(self,addr):
print('正在连接服务器[%s]' %addr)
self.addr=addr

不影响lili的代码编写

1
2
3
4
5
6
7
8
#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):
func_get=getattr(f1,'get')
func_get()
else:
print('---->不存在此方法')
print('处理其他的逻辑')

好处二:动态导入模块(基于反射当前模块成员)

__setattr__,__delattr__,__getattr__

三者的用法演示

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 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

二次加工标准类型(包装)

包装: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
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
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)
# l.append('1111111') #报错,必须为int类型

print(l.mid)

#其余的方法都继承list的
l.insert(0,-123)
print(l)
l.clear()
print(l)

练习(clear加权限限制)

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 List(list):
def __init__(self,item,tag=False):
super().__init__(item)
self.tag=tag
def append(self, p_object):
if not isinstance(p_object,str):
raise TypeError
super().append(p_object)
def clear(self):
if not self.tag:
raise PermissionError
super().clear()

l=List([1,2,3],False)
print(l)
print(l.tag)

l.append('saf')
print(l)

# l.clear() #异常

l.tag=True
l.clear()

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__方法

例:利用open()函数重新定制一个文件处理器,增加写内容添加时间的功能;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

class FileHandler:
def __init__(self,filename,mode='r',encoding="utf-8"):
self.file = open(filename,mode,encoding=encoding)
#self.file获取到一个文件句柄

def write(self,line):
t = time.strftime("%Y-%m-%d %X")
self.file.write("%s %s"%(t,line))

def __getattr__(self, item):
return getattr(self.file,item)
#当对象调用FileHandler类不存在的方法时,会返回open()函数的item字符串对应的方法;


f1 = FileHandler("a.txt","r+")
f1.write("你好吗\n")
f1.seek(0)
print(f1.tell())

__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)

f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

__str__

当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据

1
2
3
4
5
6
7
8
9
10
class People:
def __init__(self, name):
self.name = name

def __str__(self):
return 'your name is %s' % self.name


p = People('sb')
print(p)

结果为

1
your name is sb

__slots__

1
2
3
4
5
6
7
8
9
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。           更多的是用来作为一个内存优化工具。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Foo:
    __slots__='x'


f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #f1不再有__dict__

class Bar:
    __slots__=['x','y']
    
n=Bar()
n.x,n.y=1,2
n.z=3#报错

__next__和__iter__实现迭代器协议

模仿range的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class My_range:
def __init__(self, start, end):
self.start = start
self.end = end

def __iter__(self):
return self

def __next__(self):
if self.start == self.end:
raise StopIteration
n = self.start
self.start += 1
return n


s = My_range(1, 20)
for i in s:
print(i)

__doc__

1
2
3
4
5
6
7
8
9
class Foo:
'我是描述信息'
pass

class Bar(Foo):
pass
print(Bar.__doc__) #该属性无法继承给子类

该属性无法被继承

__module__和__class__

1
2
3
__module__ 表示当前操作的对象在那个模块

__class__ 表示当前操作的对象的类是什么

__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 f1
print('------->')

#输出结果
执行我啦
------->

典型的应用场景:

创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中

当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源

__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
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)

__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,那么异常会被清空,就好像啥都没发生一样,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
import time


class Open:
def __init__(self, file, m='r', en='utf-8'):
self.file = file
self.m = m
self.en = en
self.x = open(file, mode=m, encoding=en)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.x.close()

def __getattr__(self, item):
return getattr(self.x, item)

def write(self, content):
t = time.strftime("%Y-%m-%d-%X")
self.x.write('%s %s' % (t, content))


with Open('b.txt') as f:
print(f.read())

用途或者说好处:

1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在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__