python包装与授权

所有便准数据类型都可以通过两种方式产生,一种是直接定义,一种是使用相应的函数,比如要产生字符串,可以s='hello'或s=str(hello)。像str这种函数都是工厂函数,实际上工厂函数都是类,我们可以通过继承这些类来定制自己的数据类型。(包装)

包装

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

二次加工标准类型(基于继承实现)

python 复制代码
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
        ' 派生自己的append:加上类型检查'
        if not isinstance(p_object,int): #if type(p_object) is int:
            raise TypeError('must be int')
        #self.append(p_object) #死循环
        #list.append(self, p_object) #调用父类的方法
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加权限限制

python 复制代码
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__方法 (包装是通过继承实现)。在代码里包含一个对getattr()内建函数的调用,调用getattr()得到默认对象的属性(数据属性或者方法)并返回它,以便于访问或者调用。 当引用一个属性时,解释器首先会在局部名称空间中查找那个名字,比如一个自定义的方法或局部实例属性。如果没有在局部字典中找到,则搜索类名称空间,以防一个类属性被访问。最后,如果两类搜索都失败了,搜索则对原对象开始授权请求,此时__getattr__()会被调用。

示例1

python 复制代码
import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        self.file=open(filename,mode,encoding=encoding)
    def write(self,line): #定制自己的write方法 -- 对写入的内容加上时间
        t=time.strftime('%Y-%m-%d %T')
        self.file.write('%s %s' %(t,line))

    def __getattr__(self, item):
        return getattr(self.file,item)# self.file是通过系统默认的open获取的,它包含了默认open的所有方法,item是一个字符串,getattr以字符串形式调用方法

f1=FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0)
print(f1.read()) 
f1.close()

在实例和类FileHandle中都没有找到该属性,所以触发__getattr__先找f1的属性字典,没有则找类FileHandle的属性字典,没有则触发__getattr__,__getattr__中调用的是系统open返回的文件描述符的方法,通过自己的类实例化,调用系统的方法。

示例2

python 复制代码
#_*_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()

示例3

python 复制代码
class List:
    def __init__(self,seq):
        self.seq=seq

    def append(self, p_object):
        ' 派生自己的append加上类型检查,覆盖原有的append'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        self.seq.append(p_object)

    @property
    def mid(self):
        '新增自己的方法'
        index=len(self.seq)//2
        return self.seq[index]

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

    def __str__(self):
        return str(self.seq)

l=List([1,2,3])
print(l)
l.append(4)
print(l)
# l.append('3333333') #报错,必须为int类型

print(l.mid)

#基于授权,获得insert方法
l.insert(0,-123)
print(l)

示例4

python 复制代码
class List:
    def __init__(self,seq,permission=False):
        self.seq=seq
        self.permission=permission
    def clear(self):
        if not self.permission:
            raise PermissionError('not allow the operation')
        self.seq.clear()

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

    def __str__(self):
        return str(self.seq)
l=List([1,2,3])
# l.clear() #此时没有权限,抛出异常


l.permission=True
print(l)
l.clear()
print(l)

#基于授权,获得insert方法
l.insert(0,-123)
print(l)

setattr,delattr,getattr

python 复制代码
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 #这就无限递归了,因为设置属性会触发__setattr__
        # 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

getattr(obj)相当于obj.getattr() setattr(obj)相当于obj.setattr() delattr(obj)相当于obj.delattr()

  • 类的内置attr属性,你不定义的话会用默认的,如果定义了则用自己定义的。
  • class.at 调用时触发:如果类class中属性at不存在,则触发__getattr__
  • del class.at删除时触发:删除类class的属性at时,触发__delattr__
  • class.at=1设置时触发:设置类class的属性at时,触发__setattr__

自己定义这三个函数,在相应操作时,就可以触发自己想要的逻辑。

python 复制代码
class MyClass:
def __init__(self, name)
    self.name = name #触发__setattr__
def __getattr__(self, item) #默认的__getattr__是属性不存在则报错,我们修改后不报错,仅打印提示
    print("attr [%s] not found" %item)
def __setattr__(self, k, v) #默认的setattr会把设置的属性加在属性字典,但是我们自定义的setattr什么也没做,属性字典不会增加属性
    print('set attr', k, v)
    if tyoe(v) is str: #限制value只能以字符串的形式设置
        print('set')
        #self.k = v #再次触发__setattr__,进入死循环
        self.__dict__[k] = v.upper() 
#真正的设置 -- 直接操作底层数据结构,在属性字典中添加
    else:
        print('not str')
def __delattr__(self, item)
    """ #自己控制-所有属性不可删除
    print('can not delete [%s]' %item)
    pass
    """
    print('delete attr[%s]' %item)
    #del self.item #触发__delattr__ 进入死循环
    self.__dict__.pop(item) #真正的删除

mc = MyClass('hello') #实例化的时候会触发__init__
print(mc.name) #hello
print(mc.hhh) #触发__getattr__  mcself  hhhitem(字符串的形式)
mc.age = 18
mc.age = '18' #触发__setattr__  mcself  agek  '18'v
del mc.name #触发__delattr__  mcself  name item(字符串的形式)

getattribute

python 复制代码
#__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__
python 复制代码
#__getattribute__ - 不管属性是否存在,都会触发
class Foo:
    def __init__(self,x):
        self.x=x

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

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

如果二者同时出现

python 复制代码
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

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('哈哈') #抛出AttributeError异常--跳转到__getattr__

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

当__getattribute__与__getattr__同时存在,只会执行__getattribute__,除非__getattribute__在执行过程中抛出异常AttributeError,系统默认的__getattribute__,如果查找的属性不存在,当抛出异常AttributeError的时候,会跳转到__getattr__,也就是说,只有抛出异常AttributeError的时候,才会执行__getattr__,其他情况下只会执行__getattribute__。系统提供的默认__getattribute__,当要查找的属性存在时,会在当前属性字典中返回属性,如果要找的属性不存在会抛出异常AttributeError,转到__getattr__去执行,__getattr__接收异常来避免程序崩溃,并执行定义好的逻辑。

相关推荐
zopple7 小时前
常见的 Spring 项目目录结构
java·后端·spring
cjy0001119 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本10 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji341610 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan10 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer11 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor35611 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor35611 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer12 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP13 小时前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪