python ::__setattr__,__delattr__,__getattr__

通常情况下,我们在访问类或者实例对象的时候,会牵扯到一些属性访问的魔法方法,主要包括:

__getattr__(self, name): 访问不存在的属性时调用

__getattribute__(self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)

__setattr__(self, name, value):设置实例对象的一个新的属性时调用

__delattr__(self, name):删除一个实例对象的属性时调用

为了验证以上,现列出代码如下:

class Test:
    def __getattr__(self, name):
        print('__getattr__')

    def __getattribute__(self, name):
        print('__getattribute__')

    def __setattr__(self, name, value):
        print('__setattr__')

    def __delattr__(self, name):
        print('__delattr__')

>>> t=Test()
>>> t.x
__getattribute__

如上述代码所示,x并不是Test类实例t的一个属性,首先去调用__getattribute__() 方法,得知该属性并不属于该实例对象;但是,按照常理,t.x应该打印__getattribute____getattr__,但实际情况并非如此,为什么呢?难道以上Python的规定无效吗?

不要着急,听我慢慢道来!

实例对象属性寻找的顺序如下:

① 首先访问__getattribute__()魔法方法(隐含默认调用,无论何种情况,均会调用此方法)

② 去实例对象t中查找是否具备该属性:t.__dict__中查找,每个类和实例对象都有一个 __dict__的属性

③ 若在t.__dict__中找不到对应的属性, 则去该实例的类中寻找,即 t.__class__.__dict__

④ 若在实例的类中也招不到该属性,则去父类中寻找,即 t.__class__.__bases__.__dict__中寻找

⑤ 若以上均无法找到,则会调用 __getattr__方法,执行内部的命令(若未重载 getattr 方法,则直接报错:AttributeError)

以上几个流程,即完成了属性的寻找。

但是,以上的说法,并不能解释为什么执行 t.x 时,不打印 __getattr__ 啊?

问题就出在了步骤的第④步,因为,一旦重载了__getattribute__()方法,如果找不到属性,则必须要手动加入第④步,否则无法进入到 第⑤步 (__getattr__)的。
验证一下以上说法是否正确:

方法一:采用object(所有类的基类)

class Test:
    def __getattr__(self, name):
        print('__getattr__')

    def __getattribute__(self, name):
        print('__getattribute__')
        object.__getattribute__(self, name)

    def __setattr__(self, name, value):
        print('__setattr__')

    def __delattr__(self, name):
        print('__delattr__')

        
>>> t=Test()
>>> t.x
__getattribute__
__getattr__

方法二:采用 super() 方法

class Test:
    def __getattr__(self, name):
        print('__getattr__')

    def __getattribute__(self, name):
        print('__getattribute__')
        super().__getattribute__(name)

    def __setattr__(self, name, value):
        print('__setattr__')

    def __delattr__(self, name):
        print('__delattr__')

        
>>> t=Test()
>>> t.x
__getattribute__
__getattr__
  • 动态导入模块

__setattr__,__delattr__,__getattr__

  • _ _setattr_ _设置属性时触发
  • _ _delattr_ _删除属性时触发
  • _ _getattr_ _只有在属性不存在时 触发
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)
#输出:----> from setattr

print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
#输出:{}

f1.z=3
# 输出:----> from setattr

print(f1.__dict__)
#输出:{}

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作

del f1.a   #输出:----> from delattr
del f1.x   #输出:----> from delattr

print(f1.__dict__)    #输出:{}

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx
#输出:----> from getattr:你找的属性不存在

Case 1 通过__setattr__设置属性的类型 __delattr__控制属性的删除

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

    def __getattr__(self, item):
        print('你找的属性【%s】不存在' % item)

    def __setattr__(self, k, v):
        print('执行setattr', k, v)
        if type(v) is str:
            print('开始设置')
            # self.k=v #触发__setattr__
            self.__dict__[k] = v.upper()
        else:
            print('必须是字符串类型')

    def __delattr__(self, item):
        print('不允许删除属性【%s】' % item)
        # print('执行delattr',item)
                # self.__dict__.pop(item)


f1 = Foo('alex')
f1.age = 18  # 触发__setattr__
print(f1.__dict__)

#输出
执行setattr name alex
开始设置
执行setattr age 18
必须是字符串类型
{'name': 'ALEX'}

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

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

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)

二次加工标准类型(基于继承实现)
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()

练习(clear加权限限制)

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

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

import time
class 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):
        return getattr(self.file,item)

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

 授权示范一
#_*_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()

授权示范二
#练习一
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)





#练习二
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)

练习题(授权)

你可能感兴趣的:(python面向对象编程)