Python面试题之Python反射详解(python反射的作用和方法)

网友投稿 325 2022-09-02


Python面试题之Python反射详解(python反射的作用和方法)

0x00 前言

反射,可以理解为利用字符串的形式去对象中操作成员属性和方法反射的这点特性让我联想到了exec函数,也是把利用字符串的形式去让Python解释器去执行命令Python Version: 3.5+

解释Python的反射,先提一个简单的需求,现在我有一个简易的网站,由两个文件组成,一个是具体执行操作的​​commons.py​​​文件,一个是入口文件​​index.py​​,现在我需要在入口文件中设置,让用户输入url,根据用户输入的url去后端执行相应的操作,内容如下:

# commons.pydef login(): print('登录页面!')def logout(): print('退出页面!')def index(): print('主页面')

# index.pyimport commonsinp = input('url > ')if inp == 'login': commons.login()elif inp == 'logout': commons.logout()elif inp == 'index': commons.index()else: print('404')------------url > login登录页面!

上面我使用了if判断,根据每一个url请求去后端执行指定的函数。那现在我的网站内容变多了,在​​commons.py​​​中有100个页面操作,那么相对应的我在​​index.py​​中也要使用if else 对这100个页面函数进行手动指定。

有了Python????反射的特性,这个需求就变得异常简单了,先不多解释,先看代码(commons.py保持文件不变,还是拿三个页面的操作举例????):

# index.pyimport commonsdef run(): inp = input('url > ') if hasattr(commons, inp): func = getattr(commons, inp) func() else: print('404')if __name__ == "__main__": run() ------------url > logout退出页面!

Look! Python的反射立了大功,使用这几行代码,可以应对​​commons.py​​文件中任意多个页面函数的调用!接下来我们来详细介绍Python反射中用到的内建函数

0x01 getattr()

源码:

def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. """ pass

​​getattr()​​函数执行成功后会将参数中对象中的方法赋值给新的变量(会返回参数中指定的对象中的方法)相当于参数中的方法又多了一个栈区的变量去引用

​​getattr()​​​函数的第一个参数需要是个对象,上面的例子中,我导入了自定义的commons模块,commons就是个对象;第二个参数是指定前面对象中的一个方法名称。​​getattr(x, 'y')​​​ 等价于执行了 ​​x.y​​​。假如第二个参数输入了前面对象中不存在的方法,该函数会抛出异常并退出。所以这个时候,为了程序的健壮性,我们需要先判断一下该对象中有没有这个方法,于是​​hasattr()​​函数登场了~~

0x02 hasattr()

源码:

def hasattr(*args, **kwargs): # real signature unknown """ Return whether the object has an attribute with the given name. This is done by calling getattr(obj, name) and catching AttributeError. """ pass

​​hasattr()​​​函数返回对象是否拥有指定名称的属性,简单的说就是检查在第一个参数的对象中,能否找到与第二参数名相同的方法。源码的解释还说,该函数的实现其实就是调用了​​getattr()​​​函数,只不过它捕获了异常而已。所以通过这个函数,我们可以先去判断对象中有没有这个方法,有则使用​​getattr()​​来获取该方法。

0x03 delattr()

源码:

import commonsprint(dir(commons))delattr(commons, 'index')print(dir(commons))------------['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'index', 'login', 'logout']['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'login', 'logout']

删除指定对象中的指定方法,特别提示:只是在本次运行程序的内存中将该方法删除,并没有影响到文件的内容。

0x04 setattr()

源码:

def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v'' """ pass

​​setattr()​​​函数用来给指定对象中的方法重新赋值(将新的函数体/方法体赋值给指定的对象名)仅在本次程序运行的内存中生效。​​setattr(x, 'y', v)​​​ 等价于 ​​x.y = v​​

import commonscommons.index()def newindex(): print('new 主页面!')setattr(commons, 'index', newindex)commons.index()------------主页面new 主页面!

0x05 __import__模块反射

好的,网站发展至今,功能有了很多的扩展,现在一个后台文件已经不能满足我的需求,这个时候需要根据职能划分后台文件,现在我又新增了一个​​account.py​​这个用户管理类的文件,也需要导入到首页以备调用。

这个时候,我的首页通过反射,只能指定​​commons​​​模块的方法任意调用,现在新增了​​account​​​模块,是不是我又要加入if去判断啦?不用!Python已经帮我们想到这一点了!最后搬出​​__import__​​这个大救星。

由于模块的导入也需要使用Python反射的特性,所以模块名也要加入到url中,所以现在url请求变成了类似于​​commons/index​​的形式

# account.pydef add_user(): print('添加用户')def del_user(): print('删除用户')

# commons.pydef login(): print('登录页面!')def logout(): print('退出页面!')def index(): print('主页面')

# index.pydef run(): inp = input('url > ') m, f = inp.split('/') obj_module = __import__(m) if hasattr(obj_module, f): func = getattr(obj_module, f) func() else: print('404')if __name__ == "__main__": run()

# 执行# python3 index.pyurl > account/add_user添加用户# python3 index.pyurl > commons/login登录页面!

能体会到​​__import__​​​的作用了吗,就是把字符串当做模块去导入。​​import 'sys'​​​ 和 ​​import sys​​​ 是不一样的,不信你执行一下~~要想导入字符串​​'sys'​​​只能通过​​__import__('sys')​​的方式导入

等等,还没完,我的网站进一步细化分工,现在又多了一层目录结构,如下所示:

|- index.py|- commons.py|- account.py|- lib |- __init__.py |- connectdb.py

现在我想在​​index​​​页面中调用​​lib​​​包下​​connectdb​​模块中的方法,还是用之前的方式调用可以吗?我们试一下

def run(): inp = input('url > ') m, f = inp.split('/') obj_module = __import__('lib.' + m) if hasattr(obj_module, f): func = getattr(obj_module, f) func() else: print('404')if __name__ == "__main__": run() ------------404

哎呦,不行啊。上面我为了测试调用lib下的模块,抛弃了对所有同级目录模块的支持,可还是不行,居然找不到这个这个模块的这个方法。还是来看下源码是怎么说的。

def __import__(name, globals=None, locals=None, fromlist=(), level=0): # real signature unknown; restored from __doc__ """ __import__(name, globals=None, locals=None, fromlist=(), level=0) -> module Import a module. Because this function is meant for use by the Python interpreter and not for general use it is better to use importlib.import_module() to programmatically import a module. The globals argument is only used to determine the context; they are not modified. The locals argument is unused. The fromlist should be a list of names to emulate ``from name import ...'', or an empty list to emulate ``import name''. When importing a module from a package, note that __import__('A.B', ...) returns package A when fromlist is empty, but its submodule B when fromlist is not empty. Level is used to determine whether to perform absolute or relative imports. 0 is absolute while a positive number is the number of parent directories to search relative to the current module. """ pass

​​__import__​​​函数中有一个​​fromlist​​参数,源码解释说,如果在一个包中导入一个模块,这个参数如果为空,则return这个包对象,如果这个参数不为空,则返回包下面指定的模块对象,于是做出如下修改

def run(): inp = input('url > ') m, f = inp.split('/') obj_module = __import__('lib.' + m, fromlist=True) if hasattr(obj_module, f): func = getattr(obj_module, f) func() else: print('404')if __name__ == "__main__": run()------------url > connectdb/mysql已连接mysql

成功了~~ 但是为了这次成功,我写死了lib前缀,相当于抛弃了commons和account两个导入的功能,所以以上代码并不完善,需求复杂后,还是需要对请求的url做一下判断

def run(): inp = input('url > ') if len(inp.split('/')) == 2: m, f = inp.split('/') obj_module = __import__(m) if hasattr(obj_module, f): func = getattr(obj_module, f) func() else: print('404') elif len(inp.split('/')) == 3: p, m, f = inp.split('/') obj_module = __import__(p + '.' + m, fromlist=True) if hasattr(obj_module, f): func = getattr(obj_module, f) func() else: print('404')if __name__ == "__main__": run()

# 执行# python3 index.pyurl > lib/connectdb/mysql已连接mysql# python3 index.pyurl > account/del_user删除用户

虽然重复代码量不高,但我们仍要有一颗消除重复代码的❤️

def run(): if len(inp.split('/')) == 2: m, f = inp.split('/') obj_module = __import__(m) getf(obj_module, f) elif len(inp.split('/')) == 3: p, m, f = inp.split('/') obj_module = __import__(p + '.' + m, fromlist=True) getf(obj_module, f)def getf(m, f): if hasattr(m, f): func = getattr(m, f) func() else: print('404')if __name__ == "__main__": inp = input('url > ') run()

​​参考​​


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python面试题之Python正则表达式re模块(菜鸟教程python正则表达式)
下一篇:Python面试题之Python迭代器
相关文章

 发表评论

暂时没有评论,来抢沙发吧~