• 面向对象之反射


    目录

    反射

    优点

    实战案例

    案例

    使用内置函数改造

    反射内建函数注意事项:实例方法绑定和非绑定的区别

    动态增加属性方法的区别


    反射

    其实它的核心本质其实就是利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!>>>>>通过字符串来操作对象的数据或方法.

    python的四个重要内置函数getattrhasattrdelattrsetattr较为全面的实现了基于字符串的反射机制。他们都是对内存内的模块进行操作,并不会对源文件进行修改。

        hasattr():判断对象是否含有某个字符串对应的属性
        getattr():获取对象字符串对应的属性
        setattr():根据字符串给对象设置属性
        delattr():根据字符串给对象删除属性
            

    1. import sys
    2. class Commons:
    3. @staticmethod
    4. def login():
    5. print("登录页面")
    6. @staticmethod
    7. def logout():
    8. print("退出页面")
    9. @staticmethod
    10. def home():
    11. print("这是网站主页")
    12. this_moudle = sys.modules[__name__]
    13. def run():
    14. inp = input("请输入想访问页面的URl:").strip()
    15. if hasattr(Commons,inp):
    16. func = getattr(Commons,inp,'没有这个页面')
    17. func()
    18. else:
    19. print("404!")
    20. run()
    1. class FtpServer:
    2. def serve_forever(self):
    3. while True:
    4. inp = input('input your cmd>>: ').strip()
    5. cmd, file = inp.split()
    6. if hasattr(self, cmd): # 根据用户输入的cmd,判断对象self有无对应的方法属性
    7. func = getattr(self, cmd) # 根据字符串cmd,获取对象self对应的方法属性
    8. func(file)
    9. def get(self, file):
    10. print('Downloading %s...' % file)
    11. def put(self, file):
    12. print('Uploading %s...' % file)
    13. obj = FtpServer()
    14. obj.serve_forever()

     

    从上面的例子可以看到hasattr及getattr的用法,另外还有delattr和setattr方法类似

    优点

            1、实现可插拔机制(对于代码来说),可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

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

    1. def run():
    2. inp = input("请输入您想访问页面的url: ").strip()
    3. modules, func = inp.split("/")
    4. obj = __import__(modules)
    5. if hasattr(obj, func):
    6. func = getattr(obj, func)
    7. func()
    8. else:
    9. print("404")
    10. run()

    请输入您想访问页面的url: commons/home

    执行结果为:这是网站主页

    实战案例

    1.加载配置文件纯大写的配置

    1. # 配置文件加载:获取配置文件中所有大写的配置 小写的直接忽略 组织成字典
    2. import settings
    3. new_dict = {}
    4. # print(dir(settings)) # dir获取括号中对象可以调用的名字
    5. # ['AGE', 'INFO', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'desc', 'name']
    6. for i in dir(settings):
    7. if i.isupper(): # 如果名字是纯大写 那么获取该大写名字对应的值 'AGE' 'INFO'
    8. v = getattr(settings, i)
    9. new_dict[i] = v
    10. print(new_dict)

    2.模拟操作系统cmd终端执行用户命令

    1. class WinCmd(object):
    2. def dir(self):
    3. print('dir获取当前目录下所有的文件名称')
    4. def ls(self):
    5. print('ls获取当前路径下所有的文件名称')
    6. def ipconfig(self):
    7. print('ipconfig获取当前计算机的网卡信息')
    8. obj = WinCmd()
    9. while True:
    10. cmd = input('请输入您的命令>>>:')
    11. if hasattr(obj, cmd):
    12. cmd_name = getattr(obj, cmd)
    13. cmd_name()
    14. else:
    15. print('%s 不是内部或外部命令,也不是可运行的程序或批处理文件' % cmd)

    接下来我们看看其他几个python内置的函数__getattr__、__setattr__、__delattr__

    1. class Foo:
    2. def __init__(self,name):
    3. self.name = name
    4. def __setattr__(self, key, value):
    5. #添加/修改属性会触发它的执行
    6. if isinstance(value,str):
    7. self.__dict__[key] = value
    8. print("__setattr__")
    9. else:
    10. raise TypeError("必须为字符串")
    11. def __getattr__(self, item):
    12. #只有在使用点调用属性且属性不存在的时候才会触发
    13. print("getattr--->%s %s"%(item,type(item)))
    14. def __delattr__(self, item):
    15. #删除属性的时候会触发
    16. self.__dict__.pop(item)
    17. print("__delattr__")
    18. f = Foo('yietong')
    19. print(f.name)
    20. f.age = '22'
    21. print(f.age)
    22. del f.age
    23. print(f.__dict__)
    24. print(f.xxxxx)

    案例

    需求:有一个 Point 类,查看它实例的属性,并修改它。动态为实例增加属性 

     

    1. class Point:
    2. def __init__(self, x, y):
    3. self.x = x
    4. self.y = y
    5. def __str__(self):
    6. return "Point:({},{})".format(self.x, self.y)
    7. def show(self):
    8. print(self, self.x, self.y)
    9. p = Point(4, 5)
    10. print(p) # Point:(4,5)
    11. print(p.__dict__) # {'x': 4, 'y': 5}
    12. p.__dict__['y'] = 16
    13. print(p.__dict__) # {'x': 4, 'y': 16}
    14. p.z = 10
    15. print(p.__dict__) # {'x': 4, 'y': 16, 'z': 10}
    16. print(dir(p))
    17. print(sorted(p.__dir__()))
    1. Point:(4,5)
    2. {'x': 4, 'y': 5}
    3. {'x': 4, 'y': 16}
    4. {'x': 4, 'y': 16, 'z': 10}
    5. ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z']
    6. ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z']

     

    • 上例通过属性字典 __dict__ 来访问对象的属性,本质上也是利用的反射的能力

    • 但是,上述的例子中,访问的方式不优雅, Python 提供了内置函数

     


    使用内置函数改造

    1. class Point:
    2. def __init__(self, x, y):
    3. self.x = x
    4. self.y = y
    5. def __str__(self):
    6. return "Point:({},{})".format(self.x, self.y)
    7. def show(self):
    8. print(self, self.x, self.y)
    9. p = Point(4, 5)
    10. print(p) # Point:(4,5)
    11. print(p.__dict__) # {'x': 4, 'y': 5}
    12. # p.__dict__['y'] = 16
    13. setattr(p, 'y', 16)
    14. print(p.__dict__) # {'x': 4, 'y': 16}
    15. # p.z = 10
    16. setattr(p, 'z', 10)
    17. # print(p.__dict__) # {'x': 4, 'y': 16, 'z': 10}
    18. print(getattr(p, '__dict__')) # {'x': 4, 'y': 16, 'z': 10}
    19. # 动态调用方法
    20. if hasattr(p, 'show'):
    21. print(getattr(p, 'show')) # >
    22. getattr(p, 'show')() # 调用此方法 Point:(4,16) 4 16
    23. # 为类动态增加方法
    24. if not hasattr(Point, 'add'):
    25. setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))
    26. print(Point.__dict__['add']) # 动态注入成功 at ...>
    27. print(Point.add) # 动态注入成功 at ...>
    28. p1 = Point(2, 3)
    29. p2 = Point(5, 6)
    30. print(p1.add) # 绑定方法 of <__main__.Point object...>>
    31. print(p1.add(p2)) # Point:(7,9)
    32. # 为实例增加方法,未绑定,调用方法时,不能自动注入self
    33. if not hasattr(p1, 'sub'):
    34. setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
    35. print(p1.sub) # at ...>
    36. print(p1.sub(p1, p2)) # 未绑定方法 Point:(-3,-3)
    37. print(p1.__dict__)
    38. print(Point.__dict__)
    1. Point:(4,5)
    2. {'x': 4, 'y': 5}
    3. {'x': 4, 'y': 16}
    4. {'x': 4, 'y': 16, 'z': 10}
    5. object at 0x0000000002417490>>
    6. Point:(4,16) 4 16
    7. lambda> at 0x00000000024CA160>
    8. lambda> at 0x00000000024CA160>
    9. lambda> of <__main__.Point object at 0x000000000244AF70>>
    10. Point:(7,9)
    11. lambda> at 0x00000000024CA430>
    12. Point:(-3,-3)
    13. {'x': 2, 'y': 3, 'sub': lambda> at 0x00000000024CA430>}
    14. {'__module__': '__main__', '__init__': 0x00000000024CA280>, '__str__': 0x00000000024CA310>, 'show': 0x00000000024CA3A0>, '__dict__': '__dict__' of 'Point' objects>, '__weakref__': '__weakref__' of 'Point' objects>, '__doc__': None, 'add': lambda> at 0x00000000024CA160>}

    反射内建函数注意事项:实例方法绑定和非绑定的区别

    1. class Point:
    2. def __init__(self, x, y):
    3. self.x = x
    4. self.y = y
    5. def show(self):
    6. return 'Point: <{}, {}>'.format(self.x, self.y)
    7. p1 = Point(2, 3)
    8. print(1, p1.show())
    9. print(2, getattr(p1, 'show'))
    10. # 动态给类增加方法
    11. print(3, setattr(Point, 'showy', lambda self: 'y is {}.'.format(self.y)))
    12. print(4, Point.showy)
    13. print(5, p1.showy())
    14. # 动态给实例增加方法
    15. print(6, setattr(p1, 'showx', lambda self: 'x is {}.'.format(self.x)))
    16. # print(7, p1.showx()) # () missing 1 required positional argument: 'self'
    17. print(8, p1.showx(p1))
    18. # print(9, Point.showx(p1)) # AttributeError: type object 'Point' has no attribute 'showx'
    19. # 注意绑定和不绑定的区别,绑定的方法会自动注入self
    20. # 实例动态添加的方法是没有绑定效果的,所以不会自动注入self
    21. print(10, p1.showy)
    22. print(11, p1.showx)
    23. print(12, getattr(p1, 'showy'))
    24. print(13, getattr(p1, 'showx'))
    25. # 方法一般都式定义在类上,一般不需要定义在实例上
    1. 1 Point: <2, 3>
    2. 2 object at 0x00000282E3516700>>
    3. 3 None
    4. 4 lambda> at 0x00000282E25E1CA0>
    5. 5 y is 3.
    6. 6 None
    7. 8 x is 2.
    8. 10 lambda> of <__main__.Point object at 0x00000282E3516700>>
    9. 11 lambda> at 0x00000282E23531F0>
    10. 12 lambda> of <__main__.Point object at 0x00000282E3516700>>
    11. 13 lambda> at 0x00000282E23531F0>

    动态增加属性方法的区别

    通过 setattr 动态增加方法和装饰器装饰一个类、Mixin方式的差异:setattr 是在运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性。

  • 相关阅读:
    【笔记】文献阅读[YOLOV3]-An Incremental Improvement
    2022-08-22 C++并发编程(十四)
    进程地址空间详解
    SQL Server数据库创建远程服务器备份计划(小白详细图文教程)
    11后端开发就是CRUD?没那么简单!
    云原生gitlab在k8s上配置ingress ssh端口访问仓库
    【广州华锐视点】VR塔吊模拟实操考核系统
    如何设置从小程序跳转到其它小程序
    Mac MF打印机驱动程序安装(亲测MacOS11.6和Mac12.4都可以)
    生产环境元空间内存溢出(OOM)的问题排查
  • 原文地址:https://blog.csdn.net/weixin_67531112/article/details/126816156