首页 > 编程语言 >python(27)反射机制

python(27)反射机制

时间:2022-11-09 10:15:07浏览次数:50  
标签:__ 反射 27 python py commons module func def

1. 什么是反射?

它的核心本质其实就是基于字符串的事件驱动,通过字符串的形式去操作对象的属性或者方法
 

2. 反射的优点

一个概念被提出来,就是要明白它的优点有哪些,这样我们才能知道为什么要使用反射。

2.1 场景构造

开发1个网站,由两个文件组成,一个是具体执行操作的文件commons.py,一个是入口文件visit.py
需求:需要在入口文件中设置让用户输入url, 根据用户输入的url去执行相应的操作

# commons.py
def login():
    print("这是一个登陆页面!")


def logout():
    print("这是一个退出页面!")


def home():
    print("这是网站主页面!")
# visit.py
import commons


def run():
    inp = input("请输入您想访问页面的url:  ").strip()
    if inp == 'login':
        commons.login()
    elif inp == 'logout':
        commons.logout()
    elif inp == 'index':
        commons.home()
    else:
        print('404')


if __name__ == '__main__':
    run()

运行run方法后,结果如下:

请输入您想访问页面的url:  login
这是一个登陆页面!

提问:上面使用if判断,根据每一个url请求去执行指定的函数,若commons.py中有100个操作,再使用if判断就不合适了
回答:使用python反射机制,commons.py文件内容不变,修改visit.py文件,内容如下

import commons


def run():
    inp = input("请输入您想访问页面的url:  ").strip()
    if hasattr(commons, inp):
        getattr(commons, inp)()
    else:
        print("404")


if __name__ == '__main__':
    run()

使用这几行代码,可以应对​​commons.py​​文件中任意多个页面函数的调用!
 

反射中的内置函数

 

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

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()​​来获取该方法。
 

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​
 

delattr

def delattr(x, y): # real signature unknown; restored from __doc__
    """
    Deletes the named attribute from the given object.
    
    delattr(x, 'y') is equivalent to ``del x.y''
    """
    pass

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

__import__模块反射

接着上面网站的例子,现在一个后台文件已经不能满足我的需求,这个时候需要根据职能划分后台文件,现在我又新增了一个​​user.py这个用户类的文件,也需要导入到首页以备调用。
 

但是,上面网站的例子,我已经写死了只能指定commons模块的方法任意调用,现在新增了user模块,那此时我又要使用if判断?
答:不用,使用Python自带的函数__import__
 

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

# user.py
def add_user():
    print('添加用户')


def del_user():
    print('删除用户')
# commons.py
def login():
    print("这是一个登陆页面!")


def logout():
    print("这是一个退出页面!")


def home():
    print("这是网站主页面!")
# visit.py
def run():
    inp = input("请输入您想访问页面的url:  ").strip()
    # modules代表导入的模块,func代表导入模块里面的方法
    modules, func = inp.split('/')
    obj_module = __import__(modules)
    if hasattr(obj_module, func):
        getattr(obj_module, func)()
    else:
        print("404")


if __name__ == '__main__':
    run()

最后执行run函数,结果如下:

请输入您想访问页面的url:  user/add_user
添加用户

请输入您想访问页面的url:  user/del_user
删除用户

现在我们就能体会到__import__的作用了,就是把字符串当做模块去导入。
 

但是如果我的网站结构变成下面的

|- visit.py
|- commons.py
|- user.py
|- lib
    |- __init__.py
    |- connectdb.py

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

# connectdb.py
def conn():
    print("已连接mysql")
# visit.py
def run():
    inp = input("请输入您想访问页面的url:  ").strip()
    # modules代表导入的模块,func代表导入模块里面的方法
    modules, func = inp.split('/')
    obj_module = __import__('lib.' + modules)
    if hasattr(obj_module, func):
        getattr(obj_module, func)()
    else:
        print("404")


if __name__ == '__main__':
    run()

运行run命令,结果如下:

请输入您想访问页面的url:  connectdb/conn
404

结果显示找不到,为了测试调用lib下的模块,我抛弃了对所有同级目录模块的支持,所以我们需要查看__import__源码

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.  The level argument 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这个包对象,如果这个参数不为空,则返回包下面指定的模块对象,所以我们上面是返回了包对象,所以会返回404的结果,现在修改如下:

# visit.py
def run():
    inp = input("请输入您想访问页面的url:  ").strip()
    # modules代表导入的模块,func代表导入模块里面的方法
    modules, func = inp.split('/')
    # 只新增了fromlist=True
    obj_module = __import__('lib.' + modules, fromlist=True)
    if hasattr(obj_module, func):
        getattr(obj_module, func)()
    else:
        print("404")


if __name__ == '__main__':
    run()

运行run方法,结果如下:

请输入您想访问页面的url:  connectdb/conn
已连接mysql

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

def getf(module, func):
    """
    抽出公共部分
    """
    if hasattr(module, func):
        func = getattr(module, func)
        func()
    else:
        print('404')


def run():
    inp = input("请输入您想访问页面的url:  ").strip()
    if len(inp.split('/')) == 2:
        # modules代表导入的模块,func代表导入模块里面的方法
        modules, func = inp.split('/')
        obj_module = __import__(modules)
        getf(obj_module, func)
    elif len(inp.split("/")) == 3:
        p, module, func = inp.split('/')
        obj_module = __import__(p + '.' + module, fromlist=True)
        getf(obj_module, func)


if __name__ == '__main__':
    run()

运行run函数,结果如下:

请输入您想访问页面的url:  lib/connectdb/conn
已连接mysql

请输入您想访问页面的url:  user/add_user
添加用户

当然你也可以继续优化代码,现在只判断了有1个斜杠和2个斜杠的,如果目录层级更多呢,这个暂时不考虑,本次是为了了解python的反射机制

标签:__,反射,27,python,py,commons,module,func,def
From: https://www.cnblogs.com/jiakecong/p/16869432.html

相关文章

  • Python 判断字符串是否为数字
    最近的一个小软件,遇到了一个问题就是需要把字符串转成数字,可字符串中有时候会出来特殊字符。所以只需要做一个转换函数才可以的。下面这个函数比较凑效。这里做一个笔记本......
  • python3执行pip list出现警告
    python3执行piplist出现警告WARNING:Youareusingpipversion21.2.3;however,version22.3.1isavailable.Youshouldconsiderupgradingviathe'F:\Python39......
  • 利用python数据分析
    利用python进行数据分析本书由Pythonpandas项目创始人WesMcKinney亲笔撰写,详细介绍利用Python进行操作、处理、清洗和规整数据等方面的具体细节和基本要点。第2版针对P......
  • 盘点一个Python处理Excel两列单元格中有类似字符串就返回1,没有就返回0的操作
    大家好,我是Python进阶者。一、前言前几天在才哥的Python交流群遇到了一个粉丝提问,提问截图如下:觉得还挺有意思的,都是Pandas基础操作,这里拿出来给大家一起分享下。二、......
  • python get请求传array数组
    前言使用传统的http发get请求时,如果传参为array数组,参数名称为a时,可以这样传值a=1&a=2&a=3,但是当只有一个时,这种方式就不合理了。get请求还有另外一种方式传array数组,在......
  • 代码随想录day45 | 70. 爬楼梯 322. 零钱兑换 279. 完全平方数
    70.爬楼梯题目|文章思路这道题目要求有序,因此是全背包的排列做法。1.数组下标以及含义dp[i]:爬到n台阶一共有dp[i]种方法。2.递推关系dp[i]+=dp[i-j];3.初始......
  • 【Python】Python环境安装与简单代码运行
    Python环境安装与简单代码运行一、配置Python环境1.下载Python安装包建议使用Python3.8版本,Win7、Win8、Win10、Win11都能用。(1)Python官网下载:网站:www.python.orgPyt......
  • python中的字典和集合
    #1.字典存储数据存储一个数据的属性可以快速进行查找#dict_data={key:value,key:value,...}#key是键#value是值#以大括号表示#数据是以键值对出现的键值......
  • python课本学习-第二章
    chapter2编程基础一、基本语法1、注释python注释以\(“#”\)开始多行注释使用三个单引号/双引号作为开始和结束标志2、标识符与关键字python命名时避免使用下划......
  • Servlet的生命周期; Java IO流处理 ; python有点意思; matlab; 为什么要使用构造方法?JavaB
    Servlet的生命周期Servlet的生命周期主要有三个方法:init()初始化阶段service()处理客户端请求阶段destroy()终止阶段pagehelper怎么使用的​​https://pagehelper.github.......