Python 反射机制解析

一、从模块导入讲起

通过import模块,我们可以引用其他文件中定义的方法,变量.

test.py

def f1():
    print('f1')

def f2():
    print('f2')

def f3():
    print('f3')

def f4():
    print('f4')
a = 1

import test as ss

ss.f1()
ss.f2()
print(ss.a)

那么是否可以通过模块名动态导入,python提供了__import__来实现这一功能,可以随时访问到导入模块中的方法或者变量。

imp = input(“请输入你想导入的模块名:”)
CC = __import__(imp) 這种方式就是通过输入字符串导入你所想导入的模块 
CC.f1()  # 执行模块中的f1方法

上面还存在一点问题:那就是我的模块名有可能不是在本级目录中存放着。有可能是如下图存放方式:

  

  那么这种方式我们该如何搞定呢?看下面代码:

dd = __import__("lib.text.commons")  #这样仅仅导入了lib模块
dd = __import__("lib.text.commons",fromlist = True)  #改用这种方式就能导入成功

但是官方不推荐这种方法,主要是面对解释器的,对于动态导入模块,官方推荐通过 importlib来实现

Python 反射机制解析_第1张图片

import importlib

#__import__('import_lib.metaclass') #这是解释器自己内部用的

importlib.import_module('import_lib.metaclass') #与上面这句效果一样,官方建议用这个

 

二、反射

通过模块名动态导入模块,从而使我们能够执行该模块中的函数。那么是否可以直接通过函数名称来执行函数呢?

#dynamic.py
imp = input("请输入模块:")
dd = __import__(imp) # 等价于import imp
inp_func = input("请输入要执行的函数名称:")

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

f() # 执行该函数

通过字符串映射或修改程序运行时的状态、属性、方法, 有以下4个方法  

class Foo(object):
 
    def __init__(self):
        self.name = 'wupeiqi'
 
    def func(self):
        return 'func'
 
obj = Foo()
 
# #### 检查是否含有成员 ####
hasattr(obj, 'name')
hasattr(obj, 'func')
#判断对象obj是否包含名为name的属性,及func的方法(hasattr是通过调用getattr(ojbect, name)是否抛出异常来实现的) 

# #### 获取成员 ####
print(getattr(obj, 'name'))#如果Instance 对象中有属性name则打印self.name的值,否则打印'None'
getattr(obj, 'func','default')#如果有方法method,返回其地址,否则返回default 
print (getattr(obj, 'func', 'default')()) #如果有方法method,运行函数并打印None否则打印default 
 
# #### 设置成员 ####
setattr(obj, 'age', 18)
setattr(obj, 'show', lambda num: num + 1)
 
# #### 删除成员 ####
delattr(obj, 'name')
delattr(obj, 'func')

反射代码示例

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

 

三、实例

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

  需求:比如我们输入:www.xxx.com/commons/f1,返回f1的结果。

# 动态导入模块,并执行其中函数
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")

 首先,我们并没有定义任何一行import语句;

  其次,用户的输入url被要求为类似“commons/home”这种格式,其实也就是模拟web框架里的url地址,斜杠左边指向模块名,右边指向模块中的成员名。

  然后,modules,func = url.split("/")处理了用户输入,使我们获得的2个字符串,并分别保存在modules和func变量里。

  接下来,最关键的是m = __import__(modules)这一行,它让程序去导入了modules这个变量保存的字符串同名的模块,并将它赋值给m变量。

  最后的调用中,getattr去modules模块中调用func成员的含义和以前是一样的。

  总结:通过__import__函数,我们实现了基于字符串的动态的模块导入。

  同样的,这里也有个小瑕疵!

  如果我们的目录结构是这样的:

   那么在模块调用语句中,必须进行修改,我们想当然地会这么做:

1

2

3

4

5

6

7

8

9

10

11

12

def run():

    inp = input("请输入您想访问页面的url:  ").strip()

    modules, func = inp.split("/")

    obj = __import__("lib." + modules)      #注意字符串的拼接

    if hasattr(obj, func):

        func = getattr(obj, func)

        func()

    else:

        print("404")

 

if __name__ == '__main__':

    run(

  改了这么一个地方:obj = __import__("lib." + modules),看起来似乎没什么问题,和import lib.commons的传统方法类似,但实际上运行的时候会有错误。

1

2

3

4

5

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

404

 

请输入您想访问页面的url:  account/find

404

  为什么呢?因为对于lib.xxx.xxx.xxx这一类的模块导入路径,__import__默认只会导入最开头的圆点左边的目录,也就是“lib”。我们可以做个测试,在同级目录内新建一个文件,代码如下:

1

2

obj = __import__("lib.commons")

print(obj)

  执行结果:

1

'lib' (namespace)>

  这个问题怎么解决呢?加上fromlist = True参数即可!

1

2

3

4

5

6

7

8

9

10

11

12

def run():

    inp = input("请输入您想访问页面的url:  ").strip()

    modules, func = inp.split("/")

    obj = __import__("lib." + modules, fromlist=True)  # 注意fromlist参数

    if hasattr(obj, func):

        func = getattr(obj, func)

        func()

    else:

        print("404")

 

if __name__ == '__main__':

    run()

  至此,动态导入模块的问题基本都解决了,只剩下最后一个,那就是万一用户输入错误的模块名呢?比如用户输入了somemodules/find,由于实际上不存在somemodules这个模块,必然会报错!那有没有类似上面hasattr内置函数这么个功能呢?答案是没有!碰到这种,你只能通过异常处理来解决。


四、单例模式

 单例模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

 用装饰器方式实现

def wapper(cls):
    instances = {}
    def inner():
        if cls not in instances:
            instances[cls] = cls()
        return cls
    return inner

@wapper
def Foo():
    pass

f1 =Foo()
f2 =Foo()

print(f1 is f2)

静态方法实现:
 

class ConnectPool:
    __instatnce=None
    @staticmethod
    def get_instance():
        if ConnectPool.__instatnce:
            return ConnectPool.__instatnce
        else:
            ConnectPool.__instatnce = ConnectPool()
            return ConnectPool.__instatnce

obj =ConnectPool.get_instance()
print(obj)
obj1 =ConnectPool.get_instance()
print(obj1)
 

你可能感兴趣的:(Python)