古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。

从Python对象的内建属性和方法谈Python的自省(Introspection)和反射机制(Reflection)

Python admin 129℃ 0评论

最近在疯狂复习Java的语法,警觉现在的高级语言互相借鉴学习的地方真是越来越多了,玩Python的时候会对其强大的灵活性非常迷恋,但是今天才算理解Java也可以通过反射实现同样的机制,真是’天下文章一大抄’,当然万法归宗,各种语言越来越同质化归一化对我们学习使用也是一种利好。

以下是我搬运来的文章内容,仅供学习参考借鉴。

1. 从dir()函数说起

对于dir()这个Python的内置函数,Python进阶群里的小伙伴们一定不陌生。我不止一次地介绍过这个函数。每当想要了解一个类或类实例包含了什么属性和方法时,我都会求助于这个函数。

对于模块、内置函数,以及自定义的类,dir()一视同仁,照样可用。

读到这里,一定会有很多小伙伴会说,我的PyCharm(也可能是VSCode或者其他什么)也会告诉我,当前的对象有什么属性和方法,还是自动显示的,不需要我动手。没错,IDE的确为我们提供了很多便利,但是,你有没有想过IDE是如何实现这些功能的呢?假如你的任务就是设计一款类似的IDE,你真的不要深入理解Python内在的机制吗?

2. 内建属性和方法

下面的代码中,类Player定义了两个属性和一个方法,p是Player的一个实例。调用dir()显示实例p的属性和方法,就会发现,除了代码中定义name,rating和say_hello()外,其他都是以双下划线开头、以双下划线结尾,这些就是传说中的Python对象的内建属性和方法。

这些内建属性和方法中,似乎只有__init__和__new__看起来有点面熟,其他那些都有什么用途呢?下面,我选其中的几个演示一下。

2.1 _ doc _

__doc__是最常用的内建属性,有很多小伙伴并没有意识到这一点。一个规范的代码文件,除了代码本身,还会提供很多必要信息,比如类、函数的说明,这些说明,我们称其为文档字符串(DocString)。__doc__就是对象的文档字符串。

这里显示的文档字符串,就是我在定义Player时写在特定位置的注释(没有注意到这一点的小伙伴,请返回查看前面的Player类定义代码)。

2.2 _ module _

很容易猜到,内建属性__mudule__表示对象所属的模块。这里,Player类及其实例,都是当前__main__模块。如我们引入一个模块,更容易说明__module__的含义。

2.3 _ dict _

内建属性__dict__,是一个由对象的属性键值对构成的字典。类的__dict__和类实例的__dict__有不同的表现。

2.4 _ class _

通过类的实例化,可以得到一个类实例。那么如何从一个类实例,逆向得到类呢?实际上,类实例的内建属性__class__就是类。我们完全可以用一个实例的__class__去初始化另一个实例。

2.5 _ dir _

dir()函数是Python的内置函数,内建方法__dir__类似于dir()函数。。

2.6 _ getattribute _

顾名思义,__getattribute__返回对象的属性——实际上是属性或方法。这是一个内建方法,使用的时候其后必须有圆括号,参数是指定的属性或方法的名字。

3. 动态加载及调用

学习任何一门编程语言的初级阶段,我们几乎都会遇到一个共同的问题:动态创建一个变量或对象。在这里,“动态”只是强调变量或对象名称不是由程序员决定,而是由另外的参与方(比如交互程序中的操作者,C/S或B/S程序中的客户端)决定。也许不是一个准确的说法,但我想不出一个更好的词汇来表述此种应用需求。

以Python为例:从键盘上读入一个字符串,以该字符串为名创建一个整型对象,令其值等于3。通常,这样的问题我们使用exec()函数就可以解决。为什么不是eval()函数呢?eval()函数仅是计算一个字符串形式的表达式,无法完成赋值操作。

理解了“动态”的概念,我们来看看如何动态加载模块、如何动态调用对象等

3.1 动态加载模块

按照Python编码规范,脚本文件一般会在编码格式声明和文档说明之后统一导入模块。有些情况下,代码需要根据程序运行时的具体情况,临时导入相应的模块——通常,这种情况下,导入的模块命是由一个字符串指定的。下面的代码给出了动态加载模块的实例。

3.2 通过对象名取得对象

这个需求听起来有点奇怪,但也有很多人会遇到。Player类实例p为例,如果我们只有字符串’p’,怎样才能得到p实例呢?我们知道内置函数globals()返回全局的对象字典,locals()返回所处层次的对象字典,这两个字典的键就是对象名的字符串。有了这个思路,就很容易通过对象名取得对象了。

3.3 动态调用对象

动态调用对象最典型的应用是服务接口的实现。假如客户端通过发送服务的名字字符串来调用服务端的一个服务,名字字符串和服务有者一一对应的关系。如果没有动态调用,代码恐怕就得写成下面这个样子。

下面的代码,演示了服务端如何根据接收到的命令动态调用对应的服务。

4. 自省和反射机制

是时候说说自省和反射了。但是,截止到这里,我已经把自省和反射全部讲完了,只是没有使用自省和反射这两个词罢了。仅从这一点,就可以说明,自省和反射是完全多余的概念。如果有小伙伴搞不清楚这两个概念,那也完全没有关系,一点儿都不会影响你对编程的理解。

所谓的自省,就是对象自身提供可以查看自身属性、方法、类型的手段。内建方法__dir__不正是对象的自省吗?另外,内置函数dir()、type()、isinstance()都可以提供类似自省的部分或全部功能。

反射机制是Java和PHP等语言提供的一个特性,准确描述起来有些费劲,简而言之,就是在运行态可以获取对象的属性和方法,并随时调用他们,最典型的应用就是通过字符串形式的对象名获取对象。这不就是我说的“动态加载和调用”吗?

写道这里,不由地再次致敬龟叔当年的远见卓识:早在Java诞生前好多年,龟叔就已经全面地规划了Python对象的内建机制,其前瞻性远远超过了自省和反射机制。

参考文献

转载请注明:北凉柿子 » 从Python对象的内建属性和方法谈Python的自省(Introspection)和反射机制(Reflection)

喜欢 (0)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址