获取指定成员
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的数据类型
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。