自学内容网 自学内容网

[Python学习日记-69] 反射与其他的内置方法(面向对象进阶)

简介

        到此面向对象的大部分内容基本已经介绍完毕了,不过面向对象当中还有很多的内置方法可以供大家来深度定制自己的类,其中最常用的就要数反射了,在与用户交互时经常会使用到,下面我们除了介绍反射及其内置方法外,还会介绍其他的内置方法。

反射与其内置方法

一、什么是反射

        反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。在面向对象中的反射是指程序在运行时可以动态地获取类的信息并操作类的属性和方法。通过反射,可以在运行时动态创建对象、调用方法、访问属性等。在面向对象的编程中,反射是一种非常强大的工具,可以实现很多灵活和动态的操作。

二、面向对象中的反射

        反射可以通过字符串的形式操作对象相关的属性,而在 Python 中的一切事物都是对象,即都可以使用反射,下面我们介绍四个可以实现反射的方法:hasattr()(判断有无)、getattr()(获取值)、setattr()(修改、新增)、delattr()(删除),代码如下

需要进行反射的类:

class People:
    country = 'China'

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name)

obj = People('jove',18)

注意:由于与用户交互时所使用的 input 输入的全是字符串格式,但是在进行属性调用时并没有办法直接调用,所以才需要使用反射 

choice = input('>>: ')  # choice = 'name'
print(obj.choice)  # print(obj.'name')

使用 hasattr() 方法判断是否存在:

# 判断有无
print(hasattr(obj,'name'))  # 判断obj下有没有name这个属性,本质就是判断obj__dict__['name']有没有这个属性
print(hasattr(obj,'talk'))  # 一定是要字符串 obj.talk

代码输出如下:

使用 getattr() 方法获取相应属性的值:

# 获取值
print(getattr(obj,'name'))  # 拿到obj.name得值
print(getattr(obj,'name1',None))  # 如果没有的话就返回None
print(getattr(obj,'talk',None))
print(getattr(People,'country',None))  # People.country 类也可以用

代码输出如下:

        其实在不使用反射的情况下也能访问到相应的属性,如下

# 不使用反射访问对象属性
obj.__dict__[choice]

# 不使用反射访问类属性或方法
cls.__dict__[choice]
cls.__dict__[choice]()

使用 setattr() 方法修改、新增相应属性:

# 修改 新增
setattr(obj,'sex','male')  # obj.sex = 'male'
setattr(People,'country','US')  # People.country = 'US'
print(obj.sex)
print(People.country)

代码输出如下:

使用 delattr() 方法删除相应属性:

# 删除
delattr(obj,'age')  # del obj.sex
delattr(People,'country')
print(obj.__dict__)
print(People.__dict__)

代码输出如下:

{'name': 'jove'}
{'__module__': '__main__', '__init__': <function People.__init__ at 0x00000199C2168CC0>, 'talk': <function People.talk at 0x00000199C2168B80>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}

反射的应用:

        编写一个服务让用户上传和下载文件,语法格式如下:

# 下载

get a.txt

# 上传

put a.txt 

代码如下: 

class Service:
    def run(self):
        while True:
            cmd = input('>>: ').strip()  # cmd = 'get a.txt' 用户要下载a.txt
            cmds = cmd.split()  # cmds = ['get','a.txt']
            if hasattr(self,cmds[0]):  # 判断是否含有该属性或方法
                getattr(self,cmds[0])(cmds)  # 需要下载文件,所以把列表的cmds传入

    def get(self,cmds):
        print('get........',cmds)

    def put(self,cmds):
        print('put........',cmds)

obj = Service()
obj.run()
choice = input('>>: ')

代码输出如下:

三、反射的好处

1、实现可插拔机制

        在多人进行协作编程时,即使你需要调用的类你也可以先使用反射来进行调用,然后等另一个人完成时你的代码也无需变动,即可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种“后期绑定”,即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能,举个例子如下

# A 未写完的类
class FtpClient:
    # ftp客户端,但是还么有实现具体的功能
    def __init__(self,addr):
        print('正在连接服务器[%s]' %addr)
        self.addr=addr
# B 需要调用 A 未写完的类
from module import FtpClient
f1=FtpClient('172.16.16.16')
if hasattr(f1,'get'):
    func_get=getattr(f1,'get')
    func_get()
else:
    print('---->不存在此方法')
    print('处理其他的逻辑')

代码输出如下: 

2、动态导入模块(基于反射当前模块成员)

目录结构:

Foo/

| -- import_lib

|      | -- __init__.py

|      | -- metaclass.py

|

| -- test.py

metaclass.py

print("I'm Jove.")

test.py

import importlib

# __import__("import_lib.metaclass")    # 解释器内部使用
importlib.import_module("import_lib.metaclass")    # 与上面的是一样效果,官方推荐使用

代码输出如下:

 

其他的内置方法

一、isinstance 和 issubclass

  • isinstance(obj,cls):判断 obj 是不是 cls 类的对象
  • issubclass(sub,super):判断 sub 是不是 super 的子类

演示代码如下: 

class Foo(object):
    pass

class Bar(Foo):
    pass


obj = Foo()

print(isinstance(obj,Foo))  # 判断obj是不是Foo类的对象
print(issubclass(Bar,Foo))  # 判断Bar是不是Foo的子类

 代码输出如下:

二、__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    # 这样写就相当于不停的在调用__setattr__,就会无限递归了
        self.__dict__[key]=value    # 应该使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item    # 这样写就相当于不停的在调用__delattr__,就会无限递归了
        self.__dict__.pop(item)

# __setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)

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

# __getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx

代码输出如下:

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

  • 包装:Python 为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增和改写方法,这就用到了我们刚学的继承和派生的知识(其他的标准类型均可以通过基于继承的方式进行二次加工),如下
class List(list):    # 继承list所有的属性,也可以派生出自己新的功能,例如append、mid和clear
    def __init__(self,item,tag=False):
        super().__init__(item)
        self.tag=tag    # clear的权限控制

    def append(self, p_object):
        # 派生自己的append:加上类型检查
        if not isinstance(p_object,str):
            raise TypeError('must be int')
        super().append(p_object)

    @property
    def mid(self):
        # 新增自己的属性
        index=len(self)//2
        return self[index]

    def clear(self):
        if not self.tag:
            raise PermissionError
        super().clear()


l=List([1,2,3],False)
print(l)
l.append(5)
print(l)
print(l.tag)
# l.append('1111111') #异常

print(l.mid)

# l.clear() #异常
l.tag=True
l.clear()

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

代码输出如下:

l.append('1111111') 的报错:

l.tag 为 False 时 l.clear() 的报错:

正常运行时的输出:

  • 授权:授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能,其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性,实现授权的关键点就是覆盖 __getattr__ 方法。
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')
        t = time.strftime('%Y-%m-%d %T').encode(self.encoding)
        line = t + b' ' + line
        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('早上好') # 不转换为 byte 形式会报错
f1.write('Jove,你好啊'.encode('utf-8'))
print(f1)
f1.close() # 基于授权获得的close方法

四、__getattribute__

        在前面介绍的 __getattr__,当出现不存在的属性访问时会触发 __getattr__ 方法,而 __getattribute__ 则是无论存不存在都会执行,但当 __getattribute__ 与 __getattr__ 同时存在时,只会执行 __getattribute__,除非 __getattribute__ 在执行过程中抛出异常 AttributeError,如下

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

    def __getattr__(self, item):
        print('__getattribute__有AttributeError时执行的是我')

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

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

代码输出如下:

五、__get__、__set__、__delete__(描述符)

1、什么是描述符

        描述符本质就是一个新式类,在这个新式类中,至少实现了 __get__()、__set__()、__delete__() 中的一个,这也被称为描述符协议,这三个方法的具体作用如下:

  • __get__():调用一个属性时触发
  • __set__():为一个属性赋值时触发
  • __delete__():采用 del 删除属性时触发
class Foo:     # 在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass

2、描述符的作用

        描述符的作用是用来代理另外一个类的属性,这就必须把描述符定义成这个类的类属性,而不能定义到构造方法中才行。那什么情况下才会用到 __get__()、__set__()、__delete__() 这三个方法呢?由 1、 中的类实例化产生的对象进行属性的调用、赋值、删除,并不会触发这三个方法,使用这三个方法需要如下代码所示

# 描述符Str
class Str:
    def __get__(self, instance, owner):
        print('Str调用')
    def __set__(self, instance, value):
        print('Str设置...')
    def __delete__(self, instance):
        print('Str删除...')

# 描述符Int
class Int:
    def __get__(self, instance, owner):
        print('Int调用')
    def __set__(self, instance, value):
        print('Int设置...')
    def __delete__(self, instance):
        print('Int删除...')

class People:
    # 在定义成另外一个类的类属性的时候就开始使用描述符了
    name1=Str()
    age1=Int()
    def __init__(self,name,age): # name被Str类代理,age被Int类代理,
        self.name1=name
        self.age1=age


# 被代理属性的类实例化对象的时候就开始调用执行描述符中的代码了
print('------People实例化对象p1------')
p1=People('jove',18)

#描述符Str的使用
print('------p1对被代理属性name------')
p1.name1 # 获取name的时候会执行Str中的__get__方法,Str.__get__(p1.name1,p1,People)
p1.name1='jove'  # Str.__set__(p1.name1,p1,'jove')
del p1.name1    # Str.__delete__(p1.name1,p1)

# #描述符Int的使用
print('------p1对被代理属性age------')
p1.age1  # Int.__get__(p1.age1,p1,People)
p1.age1=18  # Int.__set__(p1.age1,p1,18)
del p1.age1    # Int.__delete__(p1.age1,p1)

# 我们来瞅瞅到底发生了什么
print('------查看对象和类的命名空间------')
print(p1.__dict__)  # name1 和 age1 并不是对象独有的属性,而是类中的属性
print(People.__dict__)

#补充
print('------补充------')
print(type(p1) == People)   # type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)

代码输出如下:

------查看对象和类的命名空间------
{}
{'__module__': '__main__', 'name1': <__main__.Str object at 0x000001E7EBE396D0>, 'age1': <__main__.Int object at 0x000001E7EBE39700>, '__init__': <function People.__init__ at 0x000001E7EA220CC0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}

3、描述符的分类

        描述符分为两类:数据描述符和非数据描述符

  • 数据描述符:至少实现了 __get__() 和 __set__()
  • 非数据描述符:只实现了 __get__(),没有实现 __set__()

4、注意事项

  • 描述符本身应该定义成新式类,被代理的类也应该是新式类
  • 必须把描述符定义成这个类的类属性,不能为定义到构造方法中
  • 要严格遵循该优先级,优先级由高到底分别是:类属性>数据描述符(有 __set__)>实例属性>非数据描述符(无 __set__)>找不到的属性触发(__getattr__()方法)

5、描述符的使用

        接触了那么久的 Python,Python 是弱类型语言这是大家都知道的了,即参数的赋值没有类型限制,下面我们可以通过描述符机制来实现类型限制功能,如下

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)  # 类型限制str
    age=Typed('age',int)   # 类型限制int
    salary=Typed('salary',float)  # 类型限制float

    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary


p1=People(123,18,3333.3)    # name报错
p1=People('egon','18',3333.3) # age报错
p1=People('egon',18,3333)   # salary报错
p1=People('egon',18,3333.3) # 正常运行

代码输出如下:

name 报错:

age 报错:

salary报错:

正常运行:

        上面的代码基本已经实现了功能,但是如果类有很多的属性,那你就需要定义一堆类属性的方式去实现,这样效率太低了,这个时候我们就要用到装饰器了,有两种装饰器,分别是有参(@typeassert)和无参(@decorate)的

有参:

def typeassert(**kwargs):
    def decorate(cls):
        print('类的装饰器开始运行啦------>',kwargs)
        return cls
    return decorate

'''
有参:
1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs
2.People=decorate(People)
'''
@typeassert(name=str,age=int,salary=float)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary


p1=People('jove',18,3333.3)

代码输出如下:

无参:

def decorate(cls):
    print('类的装饰器开始运行啦------>')
    return cls

# 无参:People=decorate(People)
@decorate
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary


p1=People('jove',18,3333.3)

代码输出如下:

        最后我们把修饰器运用到之前写的代码当中,如下

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

'''
1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs
2.People=decorate(People)
'''
@typeassert(name=str,age=int,salary=float)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary


print(People.__dict__)
# p1=People(123,18,3333.3)    # name报错
# p1=People('egon','18',3333.3) # age报错
# p1=People('egon',18,3333)   # salary报错
p1=People('egon',18,3333.3) # 正常运行

代码输出如下:

{'__module__': '__main__', '__init__': <function People.__init__ at 0x000002415A3F0CC0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x000002415C069BB0>, 'age': <__main__.Typed object at 0x000002415C069D30>, 'salary': <__main__.Typed object at 0x000002415C069D90>}

        总的来说,描述符是可以实现大部分 Python 类特性中的底层魔法,包括 @classmethod、@staticmethd、@property,甚至是 __slots__ 属性。描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件。

6、自定制 @property(实现延迟计算)

        可以利用描述符原理完成一个自定制 @property,并实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数的 key 和 value 为描述符类产生的对象),在做自定制 @property 之前我们先回顾一下之前学的 @property,自定制 @property 代码如下

class selfproperty:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:
            return self
        return self.func(instance)  # 此时你应该明白,到底是谁在为你做自动传递self的事情

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @selfproperty   # area=selfproperty(area) 相当于定义了一个类属性,即描述符
    def area(self):
        return self.width * self.length


r1=Room('jove',1,1)
print(r1.area)  # selfproperty.__get__(Room.area,r1,Room)

代码输出如下:

实现延迟计算功能:

class selfproperty:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:
            return self
        else:
            print('---> ',end='')
            value=self.func(instance)
            setattr(instance,self.func.__name__,value)  # 计算一次就缓存到实例的属性字典中
            return value

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @selfproperty   # area=selfproperty(area) 相当于'定义了一个类属性,即描述符'
    def area(self):
        return self.width * self.length


r1=Room('jove',1,1)
print(r1.__dict__)
print(r1.area)  # 先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
print(r1.area)  # 先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
print(r1.__dict__)

代码输出如下:

        当完成延迟计算功能之后我们想能不能把修改的也定制一下?结果是失败的,原因是在增加 __set__ 方法后从非数据描述符变成了数据描述符,然后根据优先级,数据描述符比实例属性有更高的优先级,因此所有的属性操作都去找描述符了,具体代码如下

class selfproperty:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:
            return self
        else:
            value=self.func(instance)
            instance.__dict__[self.func.__name__]=value
            return value

    def __set__(self, instance, value):
        print('这是新增的自定义修改功能,r1.area实际是要执行r1.area()')

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @selfproperty   # area=selfproperty(area) 相当于定义了一个类属性,即描述符
    def area(self):
        return self.width * self.length


print(Room.__dict__)
r1=Room('jove',1,1)
print(r1.__dict__)
print(r1.area)
print(r1.area)
print(r1.area)
print(r1.area)  # 缓存功能失效,每次都去找描述符了,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
print(r1.__dict__)
print(Room.__dict__)

代码输出如下:

调用前的类命名空间:{'__module__': '__main__', '__init__': <function Room.__init__ at 0x000002656666ADE0>, 'area': <__main__.selfproperty object at 0x00000265683F9370>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}

调用后的类命名空间:{'__module__': '__main__', '__init__': <function Room.__init__ at 0x000002656666ADE0>, 'area': <__main__.selfproperty object at 0x00000265683F9370>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}

7、自定制 @classmethod

无参:

class SelfClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): # 类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback():
            print('这是我们自己定制的静态属性,可以在这里加功能')
            return self.func(owner)
        return feedback

class People:
    name='jove'

    @SelfClassMethod    # say_hi=SelfClassMethod(say_hi)
    def say_hi(cls):
        print('早上好,吃饭了没有呀,%s' %cls.name)


People.say_hi()
p1=People()
p1.say_hi()

代码输出如下:

有参:

class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): # 类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args,**kwargs):
            print('这是我们自己定制的静态属性,可以在这里加功能')
            return self.func(owner,*args,**kwargs)
        return feedback

class People:
    name='jove'

    @ClassMethod    # say_hi=ClassMethod(say_hi)
    def say_hi(cls,msg):
        print('早上好,吃饭了没有呀,%s,%s' %(cls.name,msg))


People.say_hi('行街啊?')
print('------下面是对象调用------')
p1=People()
p1.say_hi('行街啊?')

代码输出如下:

8、自定制的 @staticmethod

class SelfStaticMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): # 类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args,**kwargs):
            print('这是我们自己定制的静态属性,可以在这里加功能')
            return self.func(*args,**kwargs)
        return feedback

class People:
    @SelfStaticMethod   # say_hi=SelfStaticMethod(say_hi)
    def say_hi(x,y,z):
        print('------> ',x,y,z)


People.say_hi(1,2,3)
print('------下面是对象调用------')
p1=People()
p1.say_hi(4,5,6)

代码输出如下:

六、__setitem__、__getitem__、__delitem__

        __setitem__、__getitem__、__delitem__ 是用于处理索引赋值、索引取值和索引删除操作的特殊方法。

  • __setitem__ 方法:用于在对象上执行索引赋值操作,如下
class Foo:
    def __init__(self,name):
        self.name=name

    def __setitem__(self, key, value):
        print('f1[key]=value时,我执行')
        self.__dict__[key]=value


f1=Foo('handsome')
f1['age']=18
f1['age1']=19
f1['name']='jove'

代码输出如下:

  • __getitem__ 方法:用于在对象上执行索引取值操作,如下
class Foo:
    def __init__(self,name):
        self.name=name

    def __setitem__(self, key, value):
        print('f1[key]=value时,我执行')
        self.__dict__[key]=value

    def __getitem__(self, item):
        print('f1[key]时,我执行')
        return self.__dict__[item]


f1=Foo('handsome')
print(f1['name'])

代码输出如下:

  • __delitem__ 方法:用于在对象上执行索引删除操作,如下
class Foo:
    def __init__(self,name):
        self.name=name

    def __setitem__(self, key, value):
        print('f1[key]=value时,我执行')
        self.__dict__[key]=value

    def __getitem__(self, item):
        print('f1[key]时,我执行')
        return self.__dict__[item]

    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('handsome')
f1['age']=18
f1['age1']=19
print(f1.__dict__)
del f1.age1
del f1['age']
print(f1.__dict__)

代码输出如下: 

七、__str__、__repr__、__format__

format_dict={
    'nat':'{obj.name}-{obj.addr}-{obj.type}',   # 学校名-学校地址-学校类型
    'tna':'{obj.type}:{obj.name}:{obj.addr}',   # 学校类型:学校名:学校地址
    'tan':'{obj.type}/{obj.addr}/{obj.name}',   # 学校类型/学校地址/学校名
}
  • __str__ 方法:用于返回对象的可读性较好的字符串表示形式,当使用 print() 方法或者 str() 方法时就会调用该方法,如下
class School:
    def __init__(self,name,addr,type):
        self.name=name
        self.addr=addr
        self.type=type

    def __str__(self):
        return '(%s,%s)' % (self.name,self.addr)


s1=School('清华大学','北京','公立')
print('------str------')
# str 方法或者 print 方法 ---> obj.__str__()
print('from str: ',str(s1))
print(s1)   # 如果__str__有被定义,默认str

代码输出如下:

  • __repr__ 方法:用于返回对象的官方字符串表示形式,通常包含足够的信息以重新创建该对象,当在交互式环境中直接输入对象名时会调用该方法,如下
class School:
    def __init__(self,name,addr,type):
        self.name=name
        self.addr=addr
        self.type=type

    def __str__(self):
        return '(%s,%s)' % (self.name,self.addr)

    def __repr__(self):
        return 'School(%s,%s)' % (self.name,self.addr)


s1=School('清华大学','北京','公立')
print('------repr------')
# repr 或者交互式解释器 ---> obj.__repr__()
print('from repr: ',repr(s1))

代码输出如下:

  • __format__ 方法:用于自定义对象的格式化输出,当使用 format() 进行字符串格式化时会调用该方法,如下
class School:
    def __init__(self,name,addr,type):
        self.name=name
        self.addr=addr
        self.type=type

    def __str__(self):
        return '(%s,%s)' % (self.name,self.addr)

    def __repr__(self):
        return 'School(%s,%s)' % (self.name,self.addr)

    def __format__(self, 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('清华大学','北京','公立')
print(format(s1,'nat'))
print(format(s1,'tna'))
print(format(s1,'tan'))
print(format(s1,'asfdasdffd'))

代码输出如下:

八、__slots__

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

        在访问类或者对象的属性时本质上就是在访问类或者对象的 __dict__ 属性字典(类的字典是共享的,而每个实例的是独立的),但是字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用 __slots__,但是有一个不好的地方就是我们不能再给实例添加新的属性了只能使用在 __slots__ 中定义的那些属性名,具体怎么使用还是要具体情况具体分析,使用方法如下

class Foo:
    __slots__ = 'x'


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


class Bar:
    __slots__ = ['x', 'y']


n = Bar()
n.x, n.y = 1, 2
# n.z = 3  # 报错
print(n.__slots__)

代码输出如下:

添加新的属性会报错(f1.y = 2 和 n.z = 3)

实例化对象 f1 不再有 __dict__(f1.__dict__)

注意事项:

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

九、__next__ 和 __iter__ 实现迭代器协议

  • __next__:方法用于返回迭代器的下一个元素。如果没有更多的元素可迭代,__next__ 方法应该抛出 StopIteration 异常
  •  __iter__:方法用于返回迭代器对象本身(self)

        下面我们简单模拟 range(包含步长功能的),如下

class Range:
    def __init__(self,start,stop,step):
        self.num=start
        self.stop=stop
        self.step=step

    def __iter__(self):
        return self

    def __next__(self):
        if self.num >= self.stop:
            raise StopIteration
        n=self.num
        self.num+=self.step
        return n


r=Range(1,5,3)    # 1,4
from typing import Iterator
print(isinstance(r,Iterator))
print(isinstance(r.__iter__(),Iterator))

for i in Range(1,5,3):    # 调用实例对象时就会触发__iter__,例如2 in Range(1,5)
    print(i)    # 每输出一次执行一次__next__

print('------next()------')
print(next(r))
print(next(r))
# print(next(r)) # 抛出 StopIteration

代码输出如下:

十、__doc__

        用于获取对象的文档字符串(docstring)。文档字符串是对象的第一个非空字符串,通常用于描述该对象的功能、用法和示例等信息,如下

class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass

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

代码输出如下:

十一、__module__ 和 __class__

  • __module__:表示当前操作的对象在那个模块
  • __class__:表示当前操作的对象的类是什么
# ./joveProject/lib/cc.py

class C:

    def __init__(self):
        self.name = 'jove'
from lib.cc import C

obj = C()
print(obj.__module__)  # 输出 lib.cc,即:输出模块
print(obj.__class__)      # 输出 lib.cc.C,即:输出类

代码输出如下:

十二、__del__

        用于定义对象的析构方法,当对象在内存中释放时自动被调用,用于执行一些清理操作。

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

演示代码如下:

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

    def __del__(self):
        print('执行我啦,我是: ',self.name)

f1=Foo('f1')
f2=Foo('f2')
del f1
print('------->')

代码输出如下:

        输出结果中 f2 是自动执行 __del__ 的,这是因为在程序结束后 Python 的垃圾回收机制会自动释放掉对象所占用的内存,在释放内存前会先执行 __del__,所以我们会看到输出当中最后执行的是 f2。

        数据库应用就是 __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 上下文管理,如下

with open('a.txt', 'r') as f:
    contents = f.read()
    # 在这里处理文件内容

# 在离开with块之后,文件会自动被关闭,不需要手动调用f.close()

十三、__enter__ 和 __exit__

        从上面的 __del__ 我们知道了文件操作时可以使用 with,如下

with open('a.txt', 'r') as f:
    contents = f.read()
    # 代码块...

         上述叫做上下文管理协议(with 语句),为了让一个对象兼容 with 语句,就必须在这个对象的类中声明 __enter__ 和 __exit__ 方法,如下

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 语句呢?其实在开发当中使用 with 是有一定好处的,首先使用 with 语句的目的就是把代码块放入 with 中执行,with 结束后,会自动完成清理工作,并且无须手动干预;其次在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在 __exit__ 中定制自动释放资源的机制,你无须再去关心这个问题,这将大有用处。

  •  __exit__():中的三个参数分别代表异常类型、异常值和追溯信息,with 语句中代码块出现异常,则 with 后的代码都无法执行
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 True    # 如果__exit__()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行


with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('******我报错啦!!!!******')

# ------不会执行------
print('0'*100)    # 如果__exit__()返回值为True,则会执行

代码输出如下:

__exit__() 返回 True 输出如下:

        下面来做一个定制化的 Open 方法,如下

class Open:
    def __init__(self,filepath,mode='r',encoding='utf-8'):
        self.filepath=filepath
        self.mode=mode
        self.encoding=encoding

    def __enter__(self):
        print('------enter------')
        self.f=open(self.filepath,mode=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):
        return getattr(self.f,item)

with Open('a.txt','w') as f:
    print(f)
    f.write('aaaaaa')
    f.wasdf    # 抛出异常,交给__exit__处理

代码输出如下:

 

十四、__call__

        __call__ 的执行只需要在对象后面加括号就能触发执行了,需要注意的是构造方法的执行是由创建对象触发的(对象 = 类名() ),而对于 __call__ 方法的执行是由对象后加括号触发的(对象() 或者 类()()),如下

class Foo:
    def __init__(self):
        print('------__init__------')
    
    def __call__(self, *args, **kwargs):
        print('------__call__------')


obj = Foo()    # 执行 __init__
obj()    # 执行 __call__

代码输出如下:

十五、metaclass

        后面元类会介绍到


原文地址:https://blog.csdn.net/zjw529507929/article/details/143643907

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!