什么是反射?
反省、自省。
反射指的是一个对象具有检测、访问、修改自身属性的能力
反射本质上就是在使用四个内置函数,然后对属性进行增啥改查。
class A:
x = 1
def f(self):
print('我是函数f')
a = A()
hasattr(object, '属性名'): 查看对象object中有没有’属性名‘这个属性。因为类也是一个对象,所以第一个参数也可以直接是类名。一切皆对象:所有对象都可以反射。返回值:bool
bool_value = hasattr(a, 'x')
print(bool_value) #True
bool_value = hasattr(A, 'x')
print(bool_value) #True
getattr(objcet, '属性名', '没有这个属性时的默认返回值'):
相当于:obj.属性
参数:第三个参数,指的是object中没有指定的字符串属性时,会默认将第三个参数返回。
返回值:返回对象里面的一个属性
x = getattr(a, 'x')
print(x) # 1
f = getattr(a, 'f')
print(f) # <bound method A.f of <__main__.A object at 0x10da19b10>>
f() # 我是函数f
setattr(object, '属性名', '属性值'):
相当于:obj.属性 = 值
第一个参数:反射的对象
第二个参数:反射的对象的属性名
第三个参数:要修改的反射的对象的值
返回值:None
setattr(A, 'x', '设置后的值')
print(A.x) # 设置后的值
print(a.x) #设置后的值
delattr(object, '要删除的属性'):
返回值None
delattr(A, 'x')
print(A.x)
# print(A.x)
# AttributeError: type object 'A' has no attribute 'x'
反射的使用场景
1、对属性进行增删改查,使用对象 __ dict _ 也可以对属性进行增删改查,但是语法繁琐,不好理解。
2、如果这个对象不是自己写的,就必须判断这个对象是否满足,也就是是否是我需要的属性。
if hasattr(a, 'f'):
func = getattr(a, 'f')
func()
else:
print('f这个函数不存在时要处理的逻辑')
# 确认A类里面的有没有f这个函数名,, 这时候调用
from module import A
# 在你的代码中,需要用到别人的代码,但是别人出去玩了代码没写完。这个时候就可以利用反射只定义接口,后期再完善。
a = A()
if hasattr(a, 'test'):
#得到a的test方法
test = getattr(a, 'test')
else:
print('不存在此方法,执行其他的逻辑')
#反射当前模块
import sys
def s1():
print('s1')
x
def s2():
print('s2')
this_module = sys.modules[__name__]
hasattr(this_module, 's1')
s = getattr(this_module, 's2')
s()
#动态导入
import importlib
# 拿到模块,根据模块的路径
plugin = importlib.import_module('lib.plugins')
print(plugin)
# 从模块中取出类
cls = getattr(plugin, 'WinCMD')
# obj = cls()
# obj.cd()
s = setattr(cls, 'x', 'cdd')
print(s)
print(cls.x)
反射机制
#写一个函数
def func():
print('我是一个函数')
#写一个字符串
s = 'func'
我们可以通过 func+()的方式调用func这个函数,但是我们却不能用字符串 'func'()的形式调用func函数。说白了就是:不能通过字符串来调用名字看起来相同的函数!
反射机制要做的事就是:让字符串可以直接调用名字相同的函数!
func = getattr(object, s)是关键,通过getattr函数,从object里面查找名字跟s相同的函数名(这里s = 'func'),然后将函数返回赋值给func变量,这时候func+()就能直接调用object里面的函数。
getattr做的事,其实就是把一个字符串转为一个函数的过程。这里就是把字符串’func‘ 转为了函数 func
实现网站url路由功能
#网站的视图 views.py 文件
def login():
print('我是登录页面')
def logout():
print('我是退出')
def index():
print('我是网站首页')
#运行文件 start.py
import views
def run():
while True:
'''
url = 'www.baidu.com/index'
这个url指向的视图为index
'''
url = input('输入要跳转的路径:').strip()
view = url.split('/')[-1]
if view == 'index':
views.index()
if view == 'login':
views.login()
if view == 'logout':
views.logout()
上面模拟了一个简单的路由系统,可以看到,url的最后一个 / 后面的内容,是views.py 视图文件里面的一个方法。上面有三个视图,所以写了三个路由的if语句。试想一下,如果一个网站有几百上千的url,是不是要写上千的 if语句? 显然:这个不科学的。
由于 url 的最后一个 / 后面的字符串,跟视图文件里面的函数名相同,我们可不可以直接把字符串当做函数来用?这个时候,反射机制就可以大显身手了。
对执行文件稍作修改:
#运行文件 start.py
import views
def run():
while True:
'''
url = 'www.baidu.com/index'
这个url指向的视图为index
'''
url = input('输入要跳转的路径:').strip()
view = url.split('/')[-1]
if hasattr(views, view):
func = getattr(views, view) # getattr如果对象里面没有与字符串相同属性就会报错,所以需要先hasattr一下,查看字符串对应的属性存不存在。
func()
else:
print('url 404 ')
上面的这种情况只针对,运行文件start.py和视图文件views.py在同一个文件夹下的情况。在实际项目中,视图文件都是分散在各个不同目录的不同模块中的。为了解决这个问题,可以使用 __ import __ 解决。
__ import __ (字符串参数), __ import __ 会根据字符串参数,动态导入同名的模块。
项目路径示意:
-- my_app
--|—user.py
--|—|—index()
--|—|—check_info()
—|—core.py
--|—|— home() #跳转到 home
在此修改 start.py 文件的代码:
#start.py
def run():
while True:
'''
url = 'www.baidu.com/index'
这个url指向的视图为index
'''
url = input('输入要跳转的路径:').strip()
modules, view = url.split('/') #user/index core/home
views = __import__(modules) #动态导入同名模块,不用再手动导入了。
if hasattr(views, view):
func = getattr(views, view)
func()
else:
print('url 404 ')
如果 user.py 和 core.py 在一个 lib 包下面,跟启动文件start.py不在同一个目录下,如下所示:
my_app
—lib
—|—user.py
—|—|—index() #跳转到用户首页
—|—|—check_info()
—|—core.py
—|—|— home() #跳转到 home
—|—start.py
def run():
while True:
'''
url = 'www.baidu.com/index'
这个url指向的视图为index
'''
url = input('输入要跳转的路径:').strip()
modules, view = url.split('/') #user/index core/home
#自然的会想到在modules前面加个lib
#看起来似乎没什么问题,和import lib.core的传统方法类似,但实际上运行的时候会有错误。__import__需要加个参数 fromlist=True
views = __import__('lib.' + modules, fromlist=True)
if hasattr(views, view):
func = getattr(views, view)
func()
else:
print('url 404 ')