Python 反射总结

Posted songofhawk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 反射总结相关的知识,希望对你有一定的参考价值。

获取指定成员

Python自身有getattr和setattr方法,作为基本的反射能力,可以用名字访问类成员和实例成员。例如:

class A:
    class_mem_x: int = 8

    def __init__(self, p):
        self.instance_mem_p = p
        # print(\'self.y in __init__: {}\'.format(self.instance_mem_p))


a = A(3)

print(\'getattr instance_mem_p: {}\'.format(getattr(a, \'instance_mem_p\')))
print(\'getattr class_mem_x: {}\'.format(getattr(a, \'class_mem_x\')))


setattr(a, \'instance_mem_p\', 5)
print(\'a.instance_mem_p after setattr: {}\'.format(a.instance_mem_p))

输出:

getattr instance_mem_p: 3
getattr class_mem_x: 8
a.instance_mem_p after setattr: 5

它们既可以访问类成员,也可以访问实例成员,就像直接用\'.\'访问的效果一样。但这两个方法都必须指定成员名称,如果要写的代码不知道传入的对象有哪些成员,就无法获取了。

遍历实例成员

所以python还为每一个对象提供了__dict__内部变量(类型就是dict),用于获取所有实例成员:

print(\'a.__dict__:{}\'.format(a.__dict__))

输出

a.__dict__:{\'instance_mem_p\': 5}

可以看出来,其实实例成员就是用一个dict存储的,这一点跟js很像。不过这个方法可取不到类成员,class A的类成员class_mem_x,就没在里面。

遍历类成员

python提供了一个叫dir的方法,可以获取对象的所有属性,返回结果是属性名称的数组:

print(\'dir(a):{}\'.format(dir(a)))

输出

dir(a):[\'__annotations__\', \'__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__\', \'class_mem_x\', \'instance_mem_p\']

可以看出类属性和实例属性都在里面了,可讨厌的是, 所有内置属性也都列出来了。我们要想剔除它们,就得用双划线这种字符串前后缀来区分。即使剔除了内置属性,也没法区分类成员和实例成员,当然,结合上面__dict__内置属性的内容,再剔除实例成员,也可以得到类成员。

下面是一个小工具方法:

def get_class_attr_names(obj):
    return [attr_name for attr_name in dir(obj) if (not attr_name.startswith(\'__\') and attr_name not in obj.__dict__)]

print(\'get_class_attr_names: {}\'.format(get_class_attr_names(a)))

输出

get_class_attr_names: [\'class_mem_x\']

上面的方法有个缺陷:无法排除实例方法,可以下一节获取类型再处理。

获取成员变量类型

貌似没有直接获取类型定义的方法,毕竟python不是强类型,而是所谓duck-typing语言,只能先获取成员属性的值,再用type函数判断类型了。type函数的基本用法如下:

type(a.class_mem_x)

输出

<class \'int\'>

返回值是一个Types类型

那么可以用下面的小函数,来获取每个属性的类型:

def get_obj_attr_types(obj):
    return [(attr_name, type(getattr(obj, attr_name))) for attr_name in dir(obj) if not attr_name.startswith(\'__\')]

输出

get_obj_attr_types: [(\'class_mem_x\', <class \'int\'>), (\'instance_mem_p\', <class \'int\'>), (\'test\', <class \'method\'>)]

返回值是一个tuple元素组成的数据,tuple由名称,类型对组成。

获取类成员改进

有了type函数判断类型,还可以对之前的get_class_attr_names做一点小改进——因为dir函数其实还会返回类方法,而__dict__内置属性里可没有类方法,要想把方法也剔除掉,就得借助类型判断,于是精确的类成员如下:

import sys
from types import MethodType


class A:
    class_mem_x: int = 8

    def __init__(self, p):
        self.instance_mem_p = p
        # print(\'self.y in __init__: {}\'.format(self.instance_mem_p))

    def test(self):
        pass


a = A(3)

def get_class_attr_names(obj):
    return [
            attr_name
            for attr_name in dir(obj)
            if (
                    not attr_name.startswith(\'__\')
                    and
                    attr_name not in obj.__dict__
                    and
                    type(getattr(obj, attr_name)) != MethodType
            )
    ]


print(\'get_class_attr_names: {}\'.format(get_class_attr_names(a)))

输出

get_class_attr_names: [\'class_mem_x\']

注意: MethodType不是字符串,而是一个class,需要引入types模块。

get_type_hints获取类信息

从python 3.5以后,typing模块提供了更好的方法get_type_hints,来获取类信息。它的好处在于,可以直接获取类成员定义的类型——如果给定义为str,而赋值为None,也能正确获取类型。下面看一下和自定义实现的对比结果

import sys
from types import MethodType
from typing import get_type_hints


class A:
    class_mem_x: int = 8
    class_mem_y: str = None

    def __init__(self, p):
        self.instance_mem_p = p
        # print(\'self.y in __init__: {}\'.format(self.instance_mem_p))

    def test(self):
        pass


a = A(3)


def get_class_attr_names(obj):
    return [
        attr_name
        for attr_name in dir(obj)
        if (
                not attr_name.startswith(\'__\')
                and
                attr_name not in obj.__dict__
                and
                type(getattr(obj, attr_name)) != MethodType
        )
    ]


class_attr_names = get_class_attr_names(a)


def get_all_types(obj, names):
    return [(name, type(getattr(obj, name))) for name in names]


print(\'get_all_types: {}\'.format(get_all_types(a, class_attr_names)))


print(\'get_type_hints: {}\'.format(get_type_hints(a)))

sys.exit()

输出

get_all_types: [(\'class_mem_x\', <class \'int\'>), (\'class_mem_y\', <class \'NoneType\'>)]
get_type_hints: {\'class_mem_x\': <class \'int\'>, \'class_mem_y\': <class \'str\'>}

从结果可以清晰看出差异:get_all_types 返回的y属性类型是NoneType,而get_type_hints返回的却是str

集合对象的泛型类型

对于集合对象, 比如list和dict,他们类型比较复杂,首先typing模块做了包装,分别为List和Dict,如果有下面的成员定义:

import sys
from typing import get_type_hints, List, Dict


class A:
    class_mem_x: List[int] = [3, 5, 7]
    class_mem_y: Dict[str, int] = {\'key1\': 9, \'key2\': 26}


a = A()
print(\'get_type_hints: {}\'.format(get_type_hints(a)))

sys.exit()    

输出为:

get_type_hints: {\'class_mem_x\': typing.List[int], \'class_mem_y\': typing.Dict[str, int]}

获取集合对象元素的类型

集合对象的type_hint,本身是一个type对象,要想在代码中确切地了解其原始类型,以及其中的元素类型,需要用到两个内部变量:__origin__ 和 __args__。紧接上例的代码:

x_types = get_type_hints(a)[\'class_mem_x\']
print(\'a.__origin__:{},  a.__args__:{}\'.format(x_types.__origin__, x_types.__args__))

输出为:

a.__origin__:<class \'list\'>,  a.__args__:(<class \'int\'>,)

其中__origin__ 是泛型对应的原始类型,比如list或是dict;__args__ 是泛型参数数组,对于list来说,它只有一个元素,表示了list中保存的是什么类型的数据,如果是dict,它应该有两个参数,分别表示key和value的数据类型

以上是关于Python 反射总结的主要内容,如果未能解决你的问题,请参考以下文章

Python学习总结

Python 反射总结

为啥我的 Ray March 片段着色器反射纹理查找会减慢我的帧速率?

OpenGL片段着色器不照亮场景

将 OpenGL 片段着色器设置为仅通过漫反射减少 vec4 色点的 RGB 值,而不是 alpha

反射机制