【Python】反射

少女dtysky

世界Skill

时刻2015.02.22

以下例子皆基于这个工程:
https://github.com/dtysky/Gal2Renpy/tree/master/Gal2Renpy
反射,指的是程序可以对自身进行访问、修改等等的能力,一个具有反射特性的语言能够为我们提供许多便利-比如轻松地实现扩展,轻松地实现事件等添加等等,我们可以获得任何一个类、函数、实例、模块等自身的信息,通过这些信息进行一些动态的操作,使得某些功能的实现更为简单和清晰。


1.需求

在编写游戏剧本解析器的时候我遇到了某些问题,这源于一个需求:

如何能够采用某种方式,使得用户可以自己扩展一个标签,并且编写自己的py代码去实现这个标签的所有功能?

这意味着我需要去寻找这样的一种方法:

一开始搜索所有具有某种相同特性的“类”,之后自动创建他们的实例,在之后去调用这些实例中相同的方法

如果能够实现这样的方法,之后只需要提供给用户一个接口(扩展的规范),用户按照这个规范来编写自己的类,放到指定位置,程序就能够自动识别这个类作为自己的一部分,从而便完成了扩展。
在寻找了一段时间后,我终于找到了方法,这个灵感是在编写sublime的插件时出现的,也就是利用py的反射特性和“万物皆对象”的哲学,来完成这个需求,下面我就会用这个实例来说明。


2.实现

1.获取当前类名:
定义一个类后,我们可以使用以下方法获取类自身的信息:

self.__class__.xxx

对于类的名字,我们可以用以下方法获得:

self.__class__.__name__

这样一来我们就有了判断某个类是否属于某个预定好的类型的一个简单方法——使用正则去检查类的名字,属于某个规范的均为一类,比如我想要设定一个规范,这个规范下的类要执行的是一些要创建某些东西的方法,我可以这样命名:

class CreatA:
    pass
class CreatB:
    pass

之后使用CreatX去匹配,便可以判断当前类是否是某个规范下的类。
当然我并没有用这个方法,而是用了比较偷懒的方法(也是为了方便管理嘛)——将所有同样类型的类放在同一个文件夹下,比如这个文件夹内就是用于执行所有标签解析方法(规范Sp)的类。

2.继承:
光是获得了类名显然还是不够的,我们必须对类名进行某些处理以便于之后的操作,这里我设计了一个父类,在父类预先定义好一些方法,作为某个规范的标准,之后所有从属于这个规范的类均继承自这个父类。
所有子类使用了驼峰式命名法,并均会被继承自父类的GetFlag方法解析,比如我的Sp规范下的子类:

def GetFlag(self):
    s=self.__class__.__name__.replace('Sp','')
    tmp=''
    for _s_ in s:
        tmp+=_s_ if _s_.islower() else '_'+_s_.lower()
    return tmp[1:]

此方法会返回解析后的类名,比如SpAaBb会返回aa_bb

3.获取:
创建完所有子类后,我们便可以对他们进行实例化,在程序运行时(比如此处是在剧本解析时),只需要根据他们的名字(此处是标签)来选择对应的实例进行操作即可。
为了实现这个目的,我们首先要获取所有这个规范下的类,我采用的方法如下:
GetAllClass
这是一个函数,用于获取一个文件夹下所有的类,并且判断这个类是否属于某个标签。
py可以传递任何东西,包括模块、类、函数,这位我提供了很大的便利。
这个函数的两个参数中前一个是子类的文件夹路径,后一个是子类的父类。
首先遍历这个文件夹,将后缀为.py的文件作为模块动态导入,并存入Mds列表:

Mds.append(__import__(n))

__import()__

函数用于动态导入模块。

之后遍历拥有所有模块的队列,从模块中获取类的名字:

for d in dir(m):
    d=getattr(m,d)

先注意dir()函数,它是反射的一部分,用于列举参数中所有的属性、方法等等,比如我们可以执行:

>>> import os
>>> dir(os)
['F_OK', 'O_APPEND', 'O_BINARY', 'O_CREAT', 'O_EXCL', 'O_NOINHERIT', 'O_RANDOM', 'O_RDONLY', 'O_RDWR', 'O_SEQUENTIAL', 'O_SHORT_LIVED', 'O_TEMPORARY', 'O_TEXT', 'O_TRUNC', 'O_WRONLY', 'P_DETACH', 'P_NOWAIT', 'P_NOWAITO', 'P_OVERLAY', 'P_WAIT', 'R_OK', 'SEEK_CUR'
...............

再注意getattr()函数,它的作用是获取m.d属性:

>>> getattr(os,'walk')
<function walk at 0x00000000020AD9E8>

接下来便可以判断这个当前的对象是不是类、以及是不是我们需要的子类了:

def IsClass(d):
    return type(d)==type(ParentClass)
def IsSubOfTag(d):
    return issubclass(d,ParentClass)

第一个函数用于判定当前对象的类型,如果是类则返回真,否则返回假。这里使用了type()方法,这个方法用于判定对象的类型:

>>> type(getattr(os,'walk'))
<type 'function'>

第二个函数判定当前已经被判定为类的对象是否为父类ParentClass的子类,使用了issubclass方法。

以上都结束后便可以将所有符合要求的子类构成干的列表返回备用了。


4.实例化:
有了子类构成的列表我们便可以开始利用这些子类了,利用的先决条件是进行实例化,实例化之后可以选择立即使用或者存以待用,比如以下代码是存以待用的一个例子:

def SpCreat(SpSyntaxPath='SpSyntax'):
    Cls=GetAllClass(SpSyntaxPath,SpSyntax)
    Objs={}
    for c in Cls:
        obj=c()
        Objs[obj.GetFlag()]=obj
    return Objs

首先使用上面的GetAllClass方法获取所有Sp规范的子类,然后对每一个子类进行实例化,实例化之后利用每一个子类的GetFlag方法获取其名字,之后将名字和其本身作为键值对存入字典,之后便可以根据标签名来查找这个字典、去调用对应对象的方法了。
比如,我可以设定一个规范,所有Sp规范下的子类必须有一个Show方法,去返回这个子类职责下执行的结果,那么日后我便可以这样用:

SpC = SpCreat()
SpC[NowTag].Show()

结束。

如果不是自己的创作,少女是会标识出来的,所以要告诉别人是少女写的哦。