面向对象进阶2:二次加工标准类型(包装)及一些内置函数

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

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工

# 方法一:基于继承加派生来实现二次加工标准类型
# 派生原有的list类
class List(list):
    def append(self, p_object):      #自定义一个父类中本来就有的append
        if type(p_object) is str:    #规定只能附加一个字符串
            # self.append(p_object)   #self自己调用自己的方法,陷入递归死循环当中
            super().append(p_object)  #用super()更好,等价于list.append(p_object)
            #调用父类中的append()只要不调用自己的就不会递归死循环
        else:
            print('只能添加字符串类型')

    def show_midlle(self):           #取列表中的中间的那个值
        mid_index=int(len(self)/2)
        return self[mid_index]


l2=list('helloworld')   #原来系统内置的list
print(l2,type(l2))       #['h','e','l','l','o','w','o','r','l','d'] <class 'list'>

l1=List('helloworld')   #用从list继承过来的自己的List
print(l1,type(l1))        #['h','e','l','l','o','w','o','r','l','d'] <class '_main_.List'>
# l1的类型为当前运行文件下的一个list类
print(l1.show_midlle())     #w
l1.append(1111111111111111111111)   #非字符串本函数功能不允许,加不成功  #参数会传给p_object
l1.append('SB')   #是字符串,能成功加入
print(l1)

授权:授权是包装的一个特性, 不通过继承来做。还是想要定制一个原有的标准类型。

包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。

授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

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

#方法二:组合的方式完成授权(权限管理)
#定制,可增加,可修改一部分,并不要把全部的都写全
#如果不定制的话,就会把所有的方法的使用权限都放开了
import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        # self.filename=filename
        self.file=open(filename,mode,encoding=encoding)   #打开一个文件句柄
        self.mode=mode
        self.encoding=encoding
    
    #需求2:要求在写内容时全加上时间(字符串拼接)
    def write(self,line):
        print('------------>',line)
        t=time.strftime('%Y-%m-%d %X')
        self.file.write('%s %s' %(t,line))   #调用self.file中的write方法来写

    #需求1:如果没有某函数就会报错,即会调用此内置函数
    #利用此函数,相当于做了一步中转
    def __getattr__(self, item):      
        # print(item,type(item))    #read  <class 'str'>
        # self.file中有read方法
        return getattr(self.file,item)   #对象self.file通过字符串来获取自己的属性,在file中找有没有你想要的功能

#需求1:实现能够调用file中的函数属性
f1=FileHandle('a.txt','w+')   #w模式下,如果没有a.txt文件,系统会帮你创建。
print(f1.file)   #
print(f1.__dict__)   #查看f1的属性字典
# {'encoding':'utf-8','file':<_io.TextIOWrapper name='a.txt' mode='w+' encoding='utf-8'>,'mode':'w+'}
# f1的属性字典中没有函数read属性,f1所属的类中也没有
print('==>',f1.read)  #由于f1的所属类中没有read函数,则f.read会触发__getattr__
# 而现在你在_getattr_中写入了将self.file中含有的函数方法返回,这将得到此方法的内存地址
# <build-in method read of _io.TextIOWrapper object at 0x000000000059EA68>
# 此结果将和如下代码(调用系统自己内置的read函数)的一样:
sys_f=open('b.txt','w+')
print(getattr(sys_f,'read'))
# <build-in method read of _io.TextIOWrapper object at 0x000000000059EA68>

#假设还没有需求2的时候,利用_getattr_来调用file执行写操作
print(f1.write)       
f1.write('1111111111111111
')    #能成功写入

# 需求2:要求在写内容时全加上时间(字符串拼接)
# 在类中加入自定义的write函数
f1.write('cpu负载过高
')
f1.write('内存剩余不足
')
f1.write('硬盘剩余不足
')

# 查看a.txt文件,可以看到成功写入了:
# 2016-12-23 17:40:05 1111111111111111
# 2016-12-23 17:40:05 cpu负载过高
# 2016-12-23 17:40:05 内存剩余不足
# 2016-12-23 17:40:05 硬盘剩余不足

# f1.seek(0)
# print('--->',f1.read())
复制代码
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#我们来加上b模式支持
import time
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('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write('你好啊'.encode('utf-8'))
print(f1)
f1.close()
复制代码
 

二、函数isinstance(obj,cls)和issubclass(sub,super)

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

1 class Foo(object):     
2     pass
3  
4 obj = Foo()
5  
6 isinstance(obj, Foo)

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

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

三、 函数__getattribute__

# 回顾_getattr_
class Foo:
    def __init__(self,x):
        self.x=x

    def __getattr__(self, item):
        print('执行的是我')
        # return self.__dict__[item]

f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__
#执行的是我
#__getattribute__
class Foo:
    def __init__(self,x):
        self.x=x

    def __getattribute__(self, item):    #不管是否存在,都会执行
        print('执行_getattribute_')

f1=Foo(10)
f1.x                #对象中含有x,打印结果:
#'执行_getattribute_'
f1.xxxxxx       #对象中没有xxxxxx,打印结果:
#'执行_getattribute_'
# 不论有没有,都执行了_getattribute_
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

class Foo:
    def __init__(self,x):
        self.x=x

    def __getattr__(self, item):
        print('执行的是getattr')
        # return self.__dict__[item]
    def __getattribute__(self, item):
        print('执行的是getattribute')
        raise AttributeError('抛出异常了')      #raise模拟python抛出异常

f1=Foo(10)
f1.x
f1.xxxxxx

#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError时,会转去执行getattr

四 函数__setitem__,__getitem,__delitem__

#item系列和attr系列效果是一样的
class Foo:
    def __init__(self,name):
        self.name=name
        
    def __getitem__(self, item):
        print('getitem',item)
        return self.__dict__[item]         #查询

    def __setitem__(self, key, value):
        print('setitem')
        self.__dict__[key]=value    #调用属性字典来赋值

    def __delitem__(self, key):
        print('delitem')
        self.__dict__.pop(key)

f1=Foo()
print(f1.__dict__)   #查询发现里面什么都没有
# f1.name='egon'  #字符串的赋值方法,触发setattr
#--->setattr--->f1.__dict__['name']='egon'
f1['name']='egon'   #相当于字典形式的赋值,触发setitem
#--->setitem--->f1.__dict__['name']='egon'
f1['age']=18

print('===>',f1.__dict__)   #已经成功添加

#用点来访问,触发的是attr系列方法
del f1.name
print(f1.__dict__)
print(f1.age)

#用字典形式访问才会触发调用item方法
del f1['name']           #delitem
print(f1.__dict__)       #{'age':18}    #已经成功删掉name对应属性

print(f1['age'])      #getitem   #18
raise S

五 函数__str__,__repr__,__format__

# 引出问题:按理说一个类实例化出来的对象后
# 打印出来的结果应该是一个对象的形式,但l,file打印出的不是f1的形式
class foo:
    pass
f1=foo()
print(f1)    #<_main_.Foo object at 0x00000000007D0DA0>

l=list('hello')
print(l)    #['h','e','l','l','o']

file=open('test.txt','w')
print(file)     #<_io.TextIOWrapper name='test.txt' mode='w' encoding='cp936'>
# 在Pycharm中,控制上述不同打印结果的内置函数为_str_
# str函数或者print函数实质调用的是str(obj)-->obj.__str__()
class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __str__(self):
        return '名字是%s 年龄是%s' %(self.name,self.age)

f1=Foo('egon',18)
print(f1)     #名字是egon,年龄是18
#str(f1)--->f1.__str__()类中有就用自己的,没有就用系统内置的

x=str(f1)
print(x)

y=f1.__str__()
print(y)

#在解释器中直接查看对象f1时,触发对象所在类的__repr__函数
#在Pycharm中,如果找不到str,找repr来代替
class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    # def __str__(self):
    #     return '这是str'
    def __repr__(self):
        return '名字是%s 年龄是%s' %(self.name,self.age)

f1=Foo('egon',19)
#repr(f1)---->f1.__repr__()
print(f1) #str(f1)---》f1.__str__()找不到的话才去找-->f1.__repr__()
注意:repr和str函数的返回值只能是字符串类型,否则会报错
# 自定制格式化字符串__format__
x='{0}{0}{0}'.format('dog')
print(x)        #dogdogdog

class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
d1=Date(2016,12,26)

#方法一:笨办法,手动设置格式
x='{0.year}{0.mon}{0.day}'.format(d1)
y='{0.year}:{0.mon}:{0.day}'.format(d1)
z='{0.mon}-{0.day}-{0.year}'.format(d1)
print(x)    #20161226
print(y)    #2016:12:26
print(z)    #12-26-2016

#方法二:利用字典给用户选择不同的格式
format_dic={
    'ymd':'{0.year}{0.mon}{0.day}',
    'm-d-y':'{0.mon}-{0.day}-{0.year}',
    'y:m:d':'{0.year}:{0.mon}:{0.day}'
}
class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
    def __format__(self, format_spec):    #第二个参数是用于用户自行选择定制格式化 
        print('我执行啦')
        print('--->',format_spec)
        #判断format_spec是否为空,或者不在默认字典中时,设定默认输出格式
        if not format_spec or format_spec not in format_dic:
            format_spec='ymd'
        fm=format_dic[format_spec]
        return fm.format(self)        #format函数一定要有一个返回值
        
d1=Date(2016,12,26)
# 不传参数直接调用
# format(d1) #d1.__format__()
# print(format(d1))

#定制好后传入:
print(format(d1,'ymd'))
print(format(d1,'y:m:d'))
print(format(d1,'m-d-y'))
print(format(d1,'m-d:y'))  #字典中没用按照默认格式打印
print('===========>',format(d1,'asdfasdfsadfasdfasdfasdfasdfasdfasdfasdfasdfasdfasd'))

六 类变量__slots__

1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)

2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典。

类的字典是共享的,为了省内存,每一个实例都能调用。而每个实例的是独立的,不共享。

3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__

当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。

在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。

这样相当于C语言中的结构体的作用,将类当成一个函数作用域来使用。

使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。因此,slots函数应慎用。

4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。

另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 

class Foo:
    __slots__='name'    #{'name':None}
    #只写一个字符串,相当于只定义了一个key值
    # 也可定义多个属性,利用列表来存入
    # __slots__=['name','age']    #列表中只写key值  {'name':None,'age':None}
    #在定义了slots之后产生的类,不在具有_dict_属性了

f1=Foo()
f1.name='egon'
print(f1.name)    #能够成功赋值并顺利访问到

f1.age=18  #由于没有事先加入age这个key值,所以此句会报错
#触发-->setattr去设置属性字典--->f1.__dict__['age']=18
print(f1.__dict__)   #报错,此时已经没有这个字典了,被f1.__slots__取代了
print(f1.__slots__)   #name    #结果只会直接输出key值的名字
print(Foo.__slots__)  #name    #f1自己没有,查找的就是Foo类当中的_slots__
# 因此,__slots__限制了每个实例只能创建name的属性了,控制属性

七 __doc__

#类的描述信息
class Foo:
    '我是描述信息'    #在Foo的属性字典中
    pass

print(Foo.__doc__)

#该属性无法继承给子类,因为每个类默认就会有一个自己的doc的属性
class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass
print(Bar.__doc__)   #无法查询到父类中的doc

八、 __module__和__class__

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

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

#!/usr/bin/env python
# -*- coding:utf-8 -*-

class C:

    def __init__(self):
        self.name = ‘SB'
from lib.aa import C
c1=C()
print(c1.name)

print(c1.__module__)   #lib.aa
print(c1.__class__)        #<class 'lib.aa.C'>

九、  __del__

析构方法,当对象在内存中被释放时,自动触发执行。

注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__

class Foo:
    def __init__(self,name):
        self.name=name
    def __del__(self):
        print('我执行啦')

f1=Foo('alex')

#删除实例会触发__del__ 
del f1   
print('--------------------->')   
#输出结果为:
#执行我啦
# ------->

#删除实例的属性不会触发__del__
#但程序运行完毕之后会触发析构函数回收程序
del f1.name 
print('--------------------->')
#输出结果为:
# ------->
#执行我啦
#每次程序运行完毕会自动回收内存,触发__del__

典型的应用场景:

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

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

这与文件处理是一个道理:

f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件
del f #只回收用户空间的f,操作系统的文件还处于打开状态

#所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是
f=open('a.txt')
读写...
f.close()
很多情况下大家都容易忽略f.close,这就用到了with上下文管理

十、 __call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:   #类也是对象
    def __call__(self, *args, **kwargs):
        print('实例执行啦 obj()')

f1=Foo()    #Foo()实质在调用产生Foo的类的call方法

f1()  #f1的类Foo 下的__call__
原文地址:https://www.cnblogs.com/Josie-chen/p/8875557.html