python编程05 -- 反射机制

问题场景

在程序开发中,常常会遇到这样的需求:在执行对象中的某个方法,或者在调用对象的某个变量,但是由于一些原因,我们无法确定或者并不知道该方法或者变量是否存在,这时我们需要一个特殊的方法或者机制来访问或操作该未知的方法或变量,这种机制就被称之为反射。

反射机制

反射机制:反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块中寻找指定函数,对其进行操作。也就是利用字符串的形式去对象(模块)中操作(查找or获取or删除or添加)成员,一种基于字符串的事件驱动。

  • 反射有四个方法:hasattr、getattr、setattr、delattr,比较常用的是前两种,一般会结合起来用。

  • 最最重要的一点:通过字符串去操作对象的属性和方法,是字符串形式!

  • 什么对象可以用反射?
    实例化对象、类、其他模块、本模块
    只有以上四个才能使用,因为他们都能通过 . 的方式获取或调用,这也算是一种前提

反射机制常用的四个方法

getattr() 函数用于返回一个对象的属性值

  • 获取对象object的属性或者方法,存在则返回其属性,不存在则返回默认值,默认值可选。

注意:如果获取的是方法,存在则返回对象中方法的内存地址,若想运行则需通过”()”方法.

  • getattr 语法:getattr(object,name[,default])

参数

· object – 对象

· name – 字符串,对象属性

· default– 默认返回值,如果不提供该参数,在没有对应属性时,将触发AttrbuteError.

返回值 :返回对象属性值

class test():
name="david"
def run(self):
return "Hello David"
t=test()        # t 为一个test对象
getattr(t, "name") #获取name属性
getattr(t, "run") #获取run方法,存在就打印出方法的内存地址。
<bound method test.run of <__main__.test instance at 0x0269C878>>

getattr(t, "run")() #获取run方法,后面加括号可以将这个方法运行。
'Hello David'

getattr(t, "david") #获取一个不存在的属性。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: test instance has no attribute 'david'

getattr(t, "david","18") #若属性不存在,返回一个默认值。

hasattr() 函数用于判断对象是够包含对应的属性

  • 判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False。

name是一个字符串字面值或字符串类型变量。

  • hasattr 语法 :hasattr(object,name)

参数

· object – 对象

· name – 字符串,属性名

返回值 :如果对象有该属性返回 True,否则返回 False。

t=test()           # test 还是上面的那个
hasattr(t, "name") #判断对象有name属性
True
hasattr(t, "run") #判断对象有run方法
True

setattr() 函数

  • 对应函数getattr(),用于设置属性值,若属性不存在,则先创建在赋值。
  • setattr 语法:setattr(object, name, value)

参数

· object – 对象
· name – 字符串,属性名
· value – 属性值。

返回值 :无

>>> t=test()
>>> hasattr(t, "hdw") #判断属性是否存在
False
>>> setattr(t, "hdw", "18") #为属相赋值,并没有返回值
>>> hasattr(t, "hdw") #属性存在了
True

delattr() 函数

  • 用来删除指定对象的指定名称的属性,和setattr函数作用相反,属性必须存在,否则抛出AttributeError。只有属性删除不是方法
  • delattr语法:delattr(object, name)

This is a relative of setattr().

The arguments are an object and a string. The string must be the name of one of the object’s attributes.

The function deletes the named attribute, provided the object allows it.

For example, delattr(x, ‘foobar’) is equivalent to del x.foobar.

#定义类A
class A:
def __init__(self,name):
self.name = name
def sayHello(self):
print('hello',self.name)
A a;    # a是一个A对象
#测试属性和方法
a.name
'小麦'
a.sayHello()
hello 小麦

#删除属性
>>> delattr(a,'name')
>>> a.name
Traceback (most recent call last):
File "<pyshell#47>", line 1, in <module>
a.name
AttributeError: 'A' object has no attribute 'name'

>>> a.name #属性name已经删掉,不存在
Traceback (most recent call last):
File "<pyshell#47>", line 1, in <module>
a.name
AttributeError: 'A' object has no attribute 'name'

>>> delattr(a,'name') #再删除会报错
Traceback (most recent call last):
File "<pyshell#48>", line 1, in <module>
delattr(a,'name')
AttributeError: name

>>> a.sayHello
<bound method A.sayHello of <__main__.A object at 0x03F014B0>>
>>> delattr(a,'sayHello') #不能用于删除方法
Traceback (most recent call last):
File "<pyshell#50>", line 1, in <module>
delattr(a,'sayHello')
AttributeError: sayHello
>>>

eval() 函数

  • 用来执行一个字符串表达式,并返回表达式的值。

  • 语法:eval(expression[, globals[, locals]])

参数

expression – 表达式。

globals – 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。

locals – 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。

返回值:表达式计算结果。

>>>x = 7
>>> eval( '3 * x' )
21
>>> eval('pow(2,2)')
4

反射机制的用法

属性的反射操作

class A:

name = "海绵宝宝"

def func(self):
print(666)

content = input("请输入:").strip()
ret = getattr(A, content)
print(ret)
# 运行结果一:
请输入:name
海绵宝宝

# 运行结果二:
请输入:func
<function A.func at 0x7f4bdc6710d0>

# 运行结果三:
请输入:123
Traceback (most recent call last):
File "test01.py", line 9, in <module>
ret = getattr(A, content)
AttributeError: type object 'A' has no attribute '123'

# 原因解析:报错提示类 A 里面没有这个属性
# 也就是说,只有用户输入的是字符串形式的属于类 A 的属性时才不会报错

实例化对象的反射操作

class A:

country = "中国"
area = "深圳"

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

def func(self):
print(666)

a = A("Jane", 18)
# 对象的属性
print(a.name) # Jane

# 注意这个变量名也要用字符串形式!
print(hasattr(a, "name")) # True

# 不是这样用,而是字符串形式的属性名
print(hasattr(a, "Jane")) # False

# 一般 hasattr 与 getattr 结合起来使用
if hasattr(a, "name"):
print(getattr(a, "name")) # Jane

# 可以设置一个默认值,目的是防止程序报错
# 如果没有该属性,就返回默认值
print(getattr(a, "sex", None)) # None

print(a.country) # 中国
print(getattr(a, "country")) # 中国

ret = getattr(a, "func")
# 注意这里 ret() 相当于 func()
print(ret()) # 666

# 给对象添加一个属性
setattr(a, "sex", "男")
print(a.sex) # 男

# 删除对象的某个属性
delattr(a, "name")
print(a.name)

类名的反射操作

class A:

country = "中国"
area = "深圳"

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

def func(self):
print(666)

# 获取类 A 的静态属性 country
print(getattr(A, "country")) # 中国
# 获取类 A 的静态属性 area
print(getattr(A, "area")) # 深圳

# 获取类A 的动态方法并执行
getattr(A, "func")() # 666

其他 py 文件的反射操作

####################################
# test02.py
####################################
flag = True
def func(a):
return a + 3
class B:
name_list = ["aaa", "bbb", "ccc"]
def __init__(self, name, sex):
self.name = name
self.sex = sex
def func(self):
print(666)
####################################
# test.py不用反射的操作方法
####################################
import test02
print(test02.flag) # True

ret = test02.func
print(ret(10)) # 13

print(test02.B.name_list) # ['aaa', 'bbb', 'ccc']

obj = test02.B("barry", "男")
print(obj.name_list) # ['aaa', 'bbb', 'ccc']

####################################
# test.py用反射的操作方法
####################################
import test02

# 获取 test02 包中的 flag 变量对应的值
print(getattr(test02, "flag")) # True

# 执行 test02 包中的 func 方法
ret = getattr(test02, "func")(10)
print(ret) # 13

# 获取 test02 包中的类 B
print(getattr(test02, "B")) # <class 'test02.B'>

# 获取 test02 包中的类 B 的 name_list 属性的方式:
# 方式一:
print(getattr(test02, "B").name_list) # ['aaa', 'bbb', 'ccc']
# 方式二:
print(getattr(test02.B, "name_list")) # ['aaa', 'bbb', 'ccc']

# 执行 test02 包中的类 B 的 func 方法(同上两种方式)
getattr(test02, "B").func(111) # 666
getattr(test02.B, "func")(1) # 666

# 实例化对象
obj = getattr(test02, "B")("小明", "男")

# 获取实例化对象的属性 name
print(obj.name) # 小明

# 通过实例化对象获取到类 B 中的共享数据之静态属性: name_list
print(obj.name_list) # ['aaa', 'bbb', 'ccc']

# 通过实例化对象执行类 B 中的共享数据之动态方法: func()
obj.func() # 666

本py文件的反射操作:反射的主体是本文件

import sys

def func():
print(666)

ret = input("请输入: ").strip()
obj = sys.modules[__name__]
getattr(obj, ret)()
# 运行结果:只有输入 func 才不会报错
请输入: func
666


# 在本文件调用所有的函数
def func1():
print("in func1")

def func2():
print("in func2")

def func3():
print("in func3")

l1 = [func1, func2, func3]
for i in l1:
i()
# 运行结果:
in func1
in func2
in func3
# 要是有100个就不能这样了


import sys

l1 = ["func%s" % i for i in range(1, 4)]
print(l1) # ['func1', 'func2', 'func3']
obj = sys.modules[__name__]
for i in l1:
getattr(obj, i)()
# 运行结果:
in func1
in func2
in func3

特殊双下划线方法

  • 项目中几乎不会自定义一个特殊双下划线方法,因为一般是给 Python 开发者在源码中使用

__len__ 方法

class A:

def __init__(self, name, age):
self.name = name
self.age = age
self.sex = "男"

def __len__(self):
return len(self.__dict__)

a1 = A("蓬蓬", 18)
# 这里假如要计算实例化对象中 a1 的属性个性
# 我们知道,直接使用 len(a1) 是不行的,因为类没有 len 方法
# 这时候,可以在 A 里面添加一个 __len__ 方法,伪装它有 len()
print(len(a1)) # 3
# 通过这个例子可以得知,那些能使用 len()的数据类型内部肯定有 __len__方法


print(hash(a1)) # -9223371957293561519
# 这里的结果是实例化对象(object) 中有 __hash__ ,而不是A的
# 因为 A 里并没有添加一个 __hash__ 方法

__str__方法

class A:

def __init__(self, name, age):
self.name = name
self.age = age
self.sex = "男"

def __str__(self):
print(555)
return "abc"

a1 = A("蓬蓬", 18)
# 对一个对象打印时,自动执行 __str__ 方法
print(a1)
# 运行结果:
555
abc

__call__方法

class Foo:

def __init__(self):
print("实例化一个对象时自动执行 __init__ 方法")

def __call__(self, *args, **kwargs):
print('调用实例化对象时自动触发 __call__ 方法')


obj = Foo() # 实例化一个对象时自动执行 __init__ 方法
obj() # 调用实例化对象时自动触发 __call__ 方法

__new__构造方法

class A:

def __init__(self, name):
self.name = name
print("in A __init__")

a = A("小明")
# 实例化一个对象时,发生了三件事
# 1. 在内存中开辟了一个对象空间,注意是 obj (即 a )中 __new__ 开辟的
# 2. 自动执行 __init__ 方法,将空间传给 self
# 3. 在 __init__ 给对象封装属性,并返回给对象
# 也就是说,实例化一个对象的时候,首先执行了 __new__ 方法
# 然后执行了 __init__ 方法
class A:

def __init__(self, name):
self.name = name
print("in A __init__")

def __new__(cls, *args, **kwargs):
print(111)

a = A("小明")
print(a.name)
# 运行结果:
111
Traceback (most recent call last):
File "test01.py", line 11, in <module>
print(a.name)
AttributeError: 'NoneType' object has no attribute 'name'

# 原因分析:注意本来实例化一个对象 a 后
# a 的 __new__ 方法会在内存中创建一个空间
# 但是,这里类中有 __new__,此时只会执行 A 中的 __new__,即会打印 111
# 也就是说,内存中并没有 a1 的空间,因此也就没有 a1.name
# 最终,print(a1.name) 会报错
# 为了执行 obj(a1) 的 __new__,可以这样:
class A:

def __init__(self, name):
self.name = name
print("in A __init__")

def __new__(cls, *args, **kwargs):
print("in A __new__")
return object.__new__(cls)

a = A("小明")
print(a.name)
# 运行结果:
in A __new__
in A __init__
小明

# 根据上面所写实例化对象时的三个步骤,可以推导出以上打印结果

单例模式:一个类只能实例化一个对象

  • 一般情况下,一个类可以有很多的实例化对象,但是每个的内存地址不一样
class A:
pass

ret1 = A()
ret2 = A()

print(ret1)
print(ret2)

# 运行结果:
<__main__.A object at 0x7f33e2ccab70>
<__main__.A object at 0x7f33e12b1400>
  • 单例模式示例
class A:

__instance = None

def __new__(cls, *args, **kwargs):
# cls 表示类本身, 这一句表示如果是第一次实例化对象
if cls.__instance is None:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance

ret1 = A()
ret2 = A()

print(ret1)
print(ret2)

# 运行结果:
<__main__.A object at 0x7f2fc69493c8>
<__main__.A object at 0x7f2fc69493c8>

# 可以看出是在同一个内存地址,也就是说单例模式可以节省内存
# 比如有时很多地方要调用这个类,又必须调用实例对象,就用单例模式
  • 通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案
class A:

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

__instance = None

def __new__(cls, *args, **kwargs):
if cls.__instance is None:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance

ret1 = A("小明", 20)
ret2 = A("小花", 28)

print(ret1)
print(ret2)

# 运行结果:
<__main__.A object at 0x7f0b8309f4a8>
<__main__.A object at 0x7f0b8309f4a8>

item 系列

class Foo:

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

def __getitem__(self, item):
print(self.__dict__[item])

def __setitem__(self, key, value):
# 这里不能使用 self.key = value
# 因为那样相当于 self."age" = 18
self.__dict__[key]=value

def __delitem__(self, key):
print('del obj[key] 时, 我执行')
self.__dict__.pop(key)

def __delattr__(self, item):
print('del obj.key 时, 我执行')
self.__dict__.pop(item)

f = Foo('abc')
f['age'] = 18
f['age1'] = 19
del f.age1 # del obj.key时,我执行
del f['age'] # del obj[key]时,我执行
f['name'] = '小明'
print(f.__dict__) # {'name': '小明'}

动态导入模块名

反射实现

imp = input(“请输入你想导入的模块名:”)
CC = __import__(imp) 這种方式就是通过输入字符串导入你所想导入的模块
CC.f1() # 执行模块中的f1方法
  • 实现了动态输入模块名,从而可以执行里面的函数,但是有一个缺点, 执行的函数被固定了。那如何动态输入函数名并且来执行呢?这就的通过反射机制。
#dynamic.py
imp = input("请输入模块:")
dd = __import__(imp)    
# 等价于import imp
inp_func = input("请输入要执行的函数:")

f = getattr(dd,inp_func,None)    
#作用:从导入模块中找到你需要调用的函数inp_func,
然后返回一个该函数的引用.没有找到就烦会None

f() # 执行该函数
  • 上面实现了,动态导入一个模块,并且动态输入函数名然后执行相应功能。
    当然,上面还存在一点点小问题:那就是我的模块名有可能不是在本级目录中存放着。有可能是如下图存放方式:
dd = __import__("lib.text.commons")  
#这样仅仅导入了lib模块
dd = __import__("lib.text.commons",fromlist = True)
#改用这种方式就能导入成功
# 等价于import config
inp_func = input("请输入要执行的函数:")
f = getattr(dd,inp_func)
f()
  • 平常可能需要用到上面的四个函数。

    • r = hasattr(commons,xxx)

      判断某个函数或者变量是否存在

    • setattr(commons,’age’,18)

      给commons模块增加一个全局变量age = 18,创建成功返回none

    • setattr(config,’age’,lambda a:a+1)

      给模块添加一个函数

    • delattr(commons,’age’)

      删除模块中某个变量或者函数

  • 注释:getattr,hasattr,setattr,delattr对模块的修改都在内存中进行,并不会影响文件中真实内容。

json数据中定义的字符串模块导入

  • 一般在数据库中我们需要对某一个字段的逻辑进行处理,这时候有一些逻辑性关联表就可以在后端对其某个字段或者内容处理再将该抽象画字段存入逻辑表中,下次直接就可以导入相关的方法,数据量大的时候是最方便的,耦合性更好
  • 下面将以一个自创的json格式的字段,该字段里存放内容可变,分别为它的类别即模块和相关函数的位置用字符串表示,以及执行该函数需要的值value,就可以将每一个json中的函数进行调用和处理,并且该json内容可以保存在一个数据库表中,随时取用
##########################
#types.__init__.py
##########################
import ipaddress
import importlib
def get_instance(type:str):
m, c = type.rsplit('.', maxsplit=1)
print(m,c)
# 将类所在包进行导入并使用类
mod = importlib.import_module(m)
cls = getattr(mod, c)
obj = cls()
if isinstance(obj, BaseType):
return obj
raise TypeError('Wrong Type : {} is not sub of
class BaseType'.format(cls))

class BaseType:
def stringfy(selfs, value):
raise NotImplementedError()

def destringfy(self, value):
raise NotImplementedError()

class Int(BaseType):
def stringfy(selfs, value):
return str(int(value))

def destringfy(self, value):
return value

# IP判断方法,直接用内置,IP转化
class IP(BaseType):
def stringfy(selfs, value):
return str(ipaddress.ip_address(value))

def destringfy(self, value):
return value

###################################
# app.py
###################################
import json
from types import *
# 建立字典映射--x
# 从放在json中的type动态加载类,使用反射,
import importlib
jsonstr = """
{
"type":"cmdb.types.IP",
"value":"192.168.0.1"
}
"""

# 加载json
obj = json.loads(jsonstr)
print(obj)

# 使用反射实现动态加载类
print(get_instance(obj['type']).stringfy(obj['value']))

数据解析中的反射

  • 在做数据解析的时候,发现不同类别的数据解析的流程是一样的,只不过每次去查询获取数据的时候表不相同,为了代码能够简洁一些,想到了先创建一个父类A,每个数据类别对应一个A的子类,在子类中实现数据查询的方法,然后通过获取A的所有子类对象,用反射机制实例化对象,再调用数据查询的方法来完成所有类别的数据解析工作,这样如果再新增一个类别,只需要再添加一个A的子类对象即可。

  • model/m.py :创建父类A和它的两个子类B和C

class A(object):
def print_name(self):
print('this is A')

class B(A):
def print_name(self):
print('this is B')

class C(A):
def print_name(self):
print('this is C')
  • reflect.py

(1)首先获取A的所有子类对象

(2)利用反射机制实例化子类对象

(3)调用子类对象的print_name方法

from model.m import A

if __name__ == '__main__':
# 获取A的所有子类
sub_class_list = A.__subclasses__()
for i in range(len(sub_class_list)):
# 获取子类的类名
class_name=sub_class_list[i].__name__
print(class_name)
# 导入model模块
model_module = __import__('model')
"""
如果模块导入成功,该模块下的所有py文件会作为模块的属性,
因此使用getattr(模块,文件名)获取即可
文件名不需要加.py后缀
"""
m_py = getattr(model_module, 'm')
# 根据子类名称从m.py中获取该类
obj_class_name = getattr(m_py, class_name)
# 实例化对象
obj = obj_class_name()
# 调用print_name方法
getattr(obj, 'print_name')()

web框架上的反射机制

  • 反射机制常常都是使用在web框架上,比如你浏览某个网页,你点网页上的文字或则图片,则会跳转或者说生成新的页面,这是怎么实现的呢?就是采用反射机制实现的,当你点击某个东西是不是就对应不同的url,而url是字符串的形式,穿进去,就可以通过那几个函数找到对应的实现方法。

一个基于反射机制模拟的web框架路由

# 动态导入模块,并执行其中函数
url = input("url: ")

target_module, target_func = url.split('/')
m = __import__('lib.'+target_module, fromlist=True)

inp = url.split("/")[-1] # 分割url,并取出url最后一个字符串
if hasattr(m,target_func): # 判断在commons模块中是否存在inp这个字符串
target_func = getattr(m,target_func) # 获取inp的引用
target_func() # 执行
else:
print("404")
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值