Python学习教程_Python学习路线:python反射机制

Python学习教程(Python学习路线):python反射机制python

Python学习教程_Python学习路线:python反射机制

反射编程

反射机制就是在运行时,动态的肯定对象的类型,并能够经过字符串调用对象属性、方法、导入模块,是一种基于字符串的事件驱动。bash

解释型语言:程序不须要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次。所以效率比较低。相对于编译型语言存在的,源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。好比Python/JavaScript / Perl /Shell等都是解释型语言。app

python是一门解释型语言,所以对于反射机制支持很好。在python中支持反射机制的函数有getattr()、setattr()、delattr()、exec()、eval()、__import__,这些函数均可以执行字符串。框架

eval函数

计算指定表达式的值。它只能执行单个表达式,而不能是复杂的代码逻辑。并且不能是赋值表达式。学习

单个表达式:ui

a = "12 + 43"
b = eval(a)
print(b)
复制代码

复杂表达式:this

a = "print(12 + 43); print(1111)"
b = eval(a)
print(b)
# 输出:
Traceback (most recent call last):
 File "xxxx.py", line 10, in <module>
 b = eval(a)
 File "<string>", line 1
 print(12 + 43); print(1111)
 ^
SyntaxError: invalid syntax
复制代码

赋值:spa

a = 1
b = eval("a = 21")
print(b)
复制代码

一般咱们使用eval的时候,主要是使用它的返回值,获取表达式计算出的值

exec

执行复杂表达式,返回值永远都是None

b = exec("aa = 21")
print(b) # None,exec返回值为None
print(aa) # 21,exec执行了赋值语句,并定义了aa变量
复制代码

执行复杂语句:

a = '''ret = [] for i in range(10): ret.append(i)'''
exec(a)
print(ret) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
复制代码

导入模块:

# 导入模块
exec("import config")
print(config.KEYWORD)
# 动态建立类
class Base:
 def __init__(self):
 print("Base")
a = "Base"
exec(a+"()")
复制代码

导入模块这个功能就很是厉害了,这样咱们就能够动态的建立各类模块类。

eval()函数和exec()函数的区别:

eval()函数只能计算单个表达式的值,而exec()函数能够动态运行代码段。

eval()函数能够有返回值,而exec()函数返回值永远为None。

再看一下下面的例子:

class Base:
 def __init__(self):
 print("Base")
 def test(self):
 print("test")
 return "Base::test"
复制代码

若是咱们想经过字符串来调用a对象的test方法,应该怎么作呢,若是要获取返回值,那么可使用

b = eval("a.test()")
print(b)
复制代码

输出:

test

Base::test

若是不须要获取返回值,那么可使用exec,exec("a.test()"),输出:test

虽然咱们可使用eval和exec来执行以上代码,可是这种方式有一个缺陷,假如这个属性是不存在的,那么这种调用就会报错。那么作好的方式是什么呢?先判断属性是否存在,若是存在就调用,不存在就不调用,python为咱们提供了一套方法:hasattr、getattr、setattr、delattr

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
复制代码

经过源码注释咱们知道,它返回对象是否具备指定名称的属性。并且它是经过调用getattr并捕获AttributeError异常来判断的。就像上面的属性调用,咱们就可使用hasattr(a, "test")来判断,经过源码注释咱们也能够思考一下,eval这种是否是也能够实现这种方法呢?

def has_attr(obj, name):
 try:
 eval("obj.%s()" % name)
 return True
 except AttributeError as e:
 return False
a = Base()
if has_attr(a, "test"):
 eval("a.test()")
# 输出:
Base
test
test
复制代码

可是这种方式是有缺陷的,由于test输出了两次,由于咱们调用了两次test(),这跟咱们想要的效果不同。若是用hasattr呢,这个函数就不会在判断的时候调用一次了。

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
复制代码

从源码注释咱们就能知道获取object对象的名为name的属性,想到与object.name,若是提供了default参数,那么当属性不存在的时候,就会返回默认值。一样是上面的例子:

a = Base()
if hasattr(a, "test"):
 func = getattr(a, "test")
 func()
# 输出:
Base
test
复制代码

从例子中咱们能够看出,hasattr并无调用test函数,并且getattr获取到的是函数对象,也没有调用它,经过咱们主动执行func()才执行了a.test()函数,这样相比于exec和eval就灵活了许多。

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
复制代码

将一个特殊值设置给object对象的name属性,至关于x.y = v

class Base:
 def __init__(self):
 self.name = "name"
a = Base()
setattr(a, "name", "zhangsan")
print(a.name) # 改变原有属性的值
setattr(a, "age", 32)
print(getattr(a, "age")) # 新增不存在的属性,并设置值
复制代码

虽然setattr(a, "age", 32)等于a.age=32,可是咱们不要忘了,这是经过一个字符串来增长的属性。

判断、获取、增长都有了,固然还有删除delattr,这个咱们就不详述了,接下来咱们要看一个比较重要的方法。

import

在学习exec的时候,咱们有一个例子,导入配置文件exec("import config"),针对这种方式python也为咱们提供了更好的方法。

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
复制代码

在这里咱们最须要关注的是formlist参数,先看一个简单的例子:

a = __import__("config")
print(a.KEYWORD)
复制代码

config是一个py脚本-config.py,内部有一个变量KEYWORD,咱们要经过其余py模块来导入这个文件,使用__import__咱们就能够把它导入为一个对象,而后使用对象的方式去调用,而不是一直用exec字符串的形式去调用。上面咱们说了formlist这个参数须要关注,为何呢?咱们新增了一个模块:comm。模块内有一个脚本function.py

# function.py
def comm_function():
 print("test_module")
复制代码

咱们如今想经过动态引入的方式调用comm_function函数,那么按照上面的方式来

a = __import__("comm.function")
a.comm_function()
复制代码

结果输出:

Traceback (most recent call last):
 File "xxx.py", line 10, in <module>
 print(a.comm_function())
AttributeError: module 'comm' has no attribute 'comm_function'
复制代码

意思是comm模块没有comm_function这个属性,为何是comm模块而不是function呢?咱们能够打印一下模块的引入名称print(a.__name__),打印的结果是comm,就是说咱们经过上面的方式只是引入comm,而不是function。其实经过源码注释咱们就知道了,__import__(A.B),若是fromlist为空,返回的是A包,若是不为空,则返回其子包B。修改一下咱们的代码:

a = __import__("comm.function", fromlist=True)
print(a.__name__)
a.comm_function()
# 输出:
comm.function
test_module
复制代码

引入的模块和执行函数都正确了,符合了咱们的预期要求。

总结

经过以上的函数学习,其中有经常使用的,也有不经常使用的,可是这些函数在咱们进行框架设计时是必不可少的,尤为是__import__,接下来咱们还会继续看框架设计中最重要的一个概念--元编程。学完了这些概念就能够设计框架了。开玩笑的,哪有那么简单。阅读源码是一种增加知识的最快捷方式,可是前提是基础必定要打好。不然看源码是一头雾水。咱们整理完这些概念后,在找几个源码库看看,学习一下里面的设计理念。

更多的Python学习教程会继续为你们更新哦!或者说你们有想学的Python学习教程,也能够留言哇!

相关文章
相关标签/搜索