前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 反射

Python 反射

作者头像
keinYe
发布2020-04-09 17:15:33
7050
发布2020-04-09 17:15:33
举报
文章被收录于专栏:keinYekeinYe

在计算机学中,反射(英语:reflection)是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。[1]用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

以上是维基百科中对反射的解释。我的理解反射是在运行过程中,获取和修改未知对象的属性和方法的一种解决方案。

反射在一定程度上提供了灵活性和通用性,是程序在运行过程中根据实际需要执行额外的操作。同时反射降低了代码的可读性,为团队间的协作制造了一定的障碍。

反射广泛应用于软件测试中,在运行中创建和实例化对象,并操作属性和方法。

Python 中的反射

Python 中反射的方法:

  • hasattr(obj, name): 判断对象中是否有以 name 命名的属性或方法。
  • getattr(obj, name): 获取对象中以 name 命名的属性或方法,如果是属性获取到将是属性的值,如果是方法获取到的是方法的实例。
  • setattr(obj, name, value): 为对象设置一个以 value 为值以 name 为名的属性或方法。
  • delattr(obj, name): 删除对象中以 name 为名称的属性或方法。
  • dir([obj]): 该方法返回对象大多数属性名的列表。

反射中方法的使用示例:

代码语言:javascript
复制
class Test():
    def __init__(self, name):
        self.name = name

    def say(self):
        print('Hello ' + self.name)

test = Test("keinYe")
if hasattr(test, 'name'):
    print('name = %s' % getattr(test, 'name'))
    setattr(test, 'name', 'python')
    print('name = %s' % getattr(test, 'name'))
else:
    print("The attribute 'name' not exist in Test")

delattr(test, 'name')
if not hasattr(test, 'name'):
    print('name is deleted')
    setattr(test, 'name', 'python')
    print('name = %s' % getattr(test, 'name'))

attr = dir(test)
print(attr)

运行以上代码,你将得到以下结果:

代码语言:javascript
复制
name = keinYe
name = python
name is deleted
name = python
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say']

在使用 dir 函数获取对象的属性和方法时,我们得到很多以 __ 开头和结尾的属性,我们本身并没有定义这些属性,这些属性是 python 自动添加的,它们保存了对象的元数据,我们可以去读取这些属性,但是尽量不要修改他们,以免引起不必要的错误。

判断获取到的对象是否是方法/函数

通过反射获取到的对象有属性也有方法或函数,可以通过以下方法判断获取到的对象是否是方法或函数。

1、判断是否具有 __call__ 属性

代码语言:javascript
复制
add = lambda a, b:a+b
if hasattr(add, '__call__'):
    print(add(1, 2))

2、利用 callable 判断

代码语言:javascript
复制
if callable(add):
    print(add(1, 2))

3、利用 inspect 模块中的 isfunction 函数判断

代码语言:javascript
复制
from inspect import isfunction
if isfunction(add):
    print(add(1, 2))

注意:isfunction 函数无法判断类中的方法。

4、使用 types.MethodType 或 types.FunctionType 来判断。

代码语言:javascript
复制
import types

class Test():
    def __init__(self, name):
        self.name = name

    def say(self):
        print('Hello ' + self.name)

test = Test("keinYe")
print(type(test.say))
print(type(isfunction))
print(type(print))
if isinstance(isfunction, types.FunctionType):
    print(True)
if isinstance(test.say, types.MethodType):
    print(True)
if isinstance(print, types.BuiltinMethodType):
    print(True)   

以上代码输出结果

代码语言:javascript
复制
<class 'method'>
<class 'function'>
<class 'builtin_function_or_method'>
True
True
True

注意:print 等 python 内置函数/方法,使用 type 函数获取到的类型将是 <class 'builtin_function_or_method'>

方法/函数的使用

当使用反射获取到方法或函数的对象后,可以直接在后加上括号进行访问「前提是该函数没有参数」。

代码语言:javascript
复制
func = getattr(test, 'say')
if hasattr(func, '__call__'):
    func()

如果方法/函数有参数,那么以上调用方法将会报 TypeError。那么我们如果来获取函数的参数呢,使用 inspect 模块的 getfullargspec 函数来获取参数列表。

代码语言:javascript
复制
if hasattr(func, '__call__'):
    args = inspect.getfullargspec(func)
    print(args.args)

执行以上代码,将得到以下结果:

代码语言:javascript
复制
['self']

由于 say 是类方法,其第一个参数总是 self,此时如果我们在 say 方法上新增一个参数,我们可以看到获取到的参数列表的变化。

代码语言:javascript
复制
class Test():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self, time):
        print('Hello ' + self.name)

test = Test('keinYe', 20)

func = getattr(test, 'say')
if hasattr(func, '__call__'):
    args = inspect.getfullargspec(func)
    print(args.args)

此时我们将得到 say 方法的两个参数 self 和 time。

反射的应用

在使用 python 进行网络通信时,不可避免的会使用到 json 格式,那么将一个类转换为 json 或将一个 json 转换为类对象,如果每次都手动将类属性转换为 json 数据,那么将是一个非常费力不讨好的工作,我们可以使用反射来实现,让每个数据类继承这些方法,即可实现一次编程到处使用。

以下是示例代码:

代码语言:javascript
复制
import json

class Base(object):
    def to_json(self):
        attrs = dir(self)
        json_dict = {}
        for attr in attrs:
            if attr.startswith('_') or attr.endswith('_'):
                continue
            attr_obj = getattr(self, attr)
            if hasattr(attr_obj, '__call__'):
                continue
            json_dict[attr] = attr_obj
        return json.dumps(json_dict)

    def to_object(self, json_str):
        json_dict = json.loads(json_str)
        attrs = dir(self)
        for attr in attrs:
            if attr.startswith('_') or attr.endswith('_'):
                continue
            if hasattr(attr, '__call__'):
                continue
            if attr in json_dict:
                setattr(self, attr, json_dict.get(attr))

class Person(Base):
    def __init__(self, name=None, age=None):
        self.name = name
        self.age = age
    
    def say(self):
        print('name = %s, age = %s' % (self.name, self.age))

person1 = Person('keinYe', 25)
print(person1.to_json())
person2 = Person()
json_str = '{"name":"test", "age": 27}'
person2.to_object(json_str)
person2.say()

执行以上代码将得到以下结果:

代码语言:javascript
复制
{"age": 25, "name": "keinYe"}
name = None, age = None
name = test, age = 27
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 keinYe 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Python 中的反射
    • 判断获取到的对象是否是方法/函数
      • 方法/函数的使用
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档