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 反射总结的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 Ray March 片段着色器反射纹理查找会减慢我的帧速率?