反射(reflection):指的是在运行时获取类型的定义信息。
本质:利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,是一种基于字符串的事件驱动。
简单来说,Python可以通过字符串来操作对象(类/方法/模块的)属性和方法(也可操作类),这就是Python的反射。
在Python中实例化对象、类、当前模块、其他模块可以使用反射机制。
Python反射的四个方法
getattr(obj, name,default):获取指定对象的属性
从对象和类中获取属性/方法
class A:
id = 1
def eat(self):
print("吃")
a = A()
# 分别从对象和类中获取属性/方法
print(getattr(A, "id")) # 1
print(getattr(a, "id")) # 1
print(getattr(A, "eat")) # <function A.eat at 0x0000021D287F5620>
print(getattr(a, "eat")) # <bound method A.eat of <__main__.A object at 0x00000130DEC926A0>>
e1 = getattr(A, "eat")
print(e1, type(e1)) # <function A.eat at 0x000001D1391E6620> <class 'function'> 类型是方法
e1(a) # 放一个对象进去就可以运行该方法了,其实就是A().eat()
e2 = getattr(a, "eat")
print(e2, type(e2)) # <bound method A.eat of <__main__.A object at 0x0000021D28802748>> <class 'method'> 类型是函数
e2() # 从对象中获取方法就可以直接运行该方法了
从其他模块获取属性和方法和类
# test.py
class B:
name = "这是类B"
def run(self):
print("跑")
num = 10
def count():
print("加加加")
import test
# 获取其他模块中的属性和方法
print(getattr(test, "num")) # 10
print(getattr(test, "count")) # <function count at 0x000001EE59ED6620>
print(getattr(test, "B")) # <class 'test.B'>
# print(getattr(test, "B.name")) # 这样就会报错
# print(getattr(test, "B().name")) # 这样也会报错
b = getattr(test, "B")() # 实例化这个获取的类
# 这样就可以获取到其中的方法和属性了
print(getattr(b, "name")) # 这是类B
print(getattr(b, "run")) # <bound method B.run of <test.B object at 0x000001F4BBA42940>>
从当前模块获取属性和方法和类
# 当前模块的反射本体就是当前模块的文件
import sys
str = "abc"
def say(s="cba")->str:
print(s)
class C:
pass
obj = sys.modules[__name__] # 这样就获得到了当前模块
print(getattr(obj, "str")) # abc
print(getattr(obj, "say")) # <function say at 0x000001F455622E18>
print(getattr(obj, "C")) # <class '__main__.C'>
hasattr(obj, name):判断对象是否有对应的属性
class A:
id = 1
def eat(self):
print("吃")
a = A()
print(hasattr(A, "id")) # True
print(hasattr(A, "eat")) # True
print(hasattr(a, "eat")) # True
print(hasattr(A, "name")) # False
print(hasattr(A, "say")) # False
print(hasattr(a, "A")) # False
其他模块和当前模块判断有没有指定属性和方法安照上面来就行,很简单。
setattr(obj, name, value):设定指定类/对象的属性和方法
class A:
id = 1
def eat(self):
print("吃")
# 向类中设置属性
setattr(A, "name", "哈哈哈")
a = A()
print(A.name) # 哈哈哈
print(a.name) # 哈哈哈
# 注意这里!!!!!!!!!!!!
# 要想把函数放进类中,一定要给一个参数!用于表示调用类中方法的实例对象
def drink(self):
print("喝", self, type(self))
# 向类中设置方法 这里取名drink0只为了区别drink函数本身,其实去掉0也没事
setattr(A, "drink0", drink)
print(getattr(A, "drink0")) # <function drink at 0x0000018830F767B8>
print(a.drink0) # <bound method drink of <__main__.A object at 0x0000018830F82D68>>
a.drink0()
# 喝 <__main__.A object at 0x0000018830F82D68> <class '__main__.A'>
def func():
text = "文本"
print("hhh")
# 向方法中添加
setattr(func, "word", "一段话")
# print(func.text) # 这样会抛异常,没有该属性!
print(func.word) # 一段话
# 那么就可以进一步这样写
def func():
text = "文本"
print("hhh")
if(hasattr(func, "word")): # 打印后续传进来的变量
print(getattr(func, "word"))
print("*******")
func()
print("*******")
# 设置新的
setattr(func, "word", "一段话")
# print(func.text) # 这样会抛异常,说没有该属性!
print(func.word) # 一段话
print("-------")
func()
print("-------")
有点Java反射的感觉了。可以在运行时添加和获取指定类或指定方法等
import test
class A:
id = 1
def eat(self):
print("吃")
# 给别的模块添加一个新的类
setattr(test, "A", A)
# 这时可以实例化这个新添加的类了
a = test.A()
a.eat() # 吃
# 甚至可以添加初始化方法
def __init__(self, name="默认名"):
print("这是初始化方法")
self.name = name
setattr(A, "__init__", __init__)
a = A("哈哈哈")
print(a.name)
"""
这是初始化方法
哈哈哈
"""
delattr(object, name):删除对象的指定属性/方法/类
# 删除上面刚给test模块添加的类A
delattr(test, "A")
a2 = test.A() # 抛异常
删除操作也好理解。
类中私有属性的获取和设置
class C:
def __init__(self):
self.__name = "CCC"
def get_id(self):
print(self.__id) # 假装可以设置私有方法,其实不行
def print_page(self):
if(hasattr(C.print_page, "page")): # 有这个属性时打印出来
print("page:", getattr(C.print_page, "page"))
c = C()
给__init__方法添加的属性,算作局部变量而不是类的属性
page = 10
setattr(C.__init__, "self.page", page)
setattr(C.__init__, "page", page)
print(hasattr(c, "self.page")) # False
print(hasattr(c, "page")) # False
print(hasattr(C, "self.page")) # False
print(hasattr(C, "page")) # False
# print(c.page) # 抛异常
# print(C.page)
print(hasattr(c.__init__, "page")) # True 因此page这时是局部变量
print(hasattr(C.__init__, "page")) # True
# 可以给类中普通方法添加方法
setattr(C.print_page, "page", page)
c.print_page() # 10
但是可以获取到私有方法
print(hasattr(c, "__name")) # False
print(hasattr(c, "_C__name")) # True 这有这样才能
print(c._C_name) # CCC
那么引申出
class D:
def __init__(self):
print("初始化D类")
self.page = 10
self.__ID = 2
def print_id(self):
print(self.__id)
def print_id2(self):
print(self._D_id)
def print_word(self):
print("打印word:" + self.__word)
def __init__(self, name="DDD"):
print("D类初始化")
self.name = name
self.__id = 1
self._D_word = "word" # 这样可以曲线添加给__init__添加私有属性
_D_text = "text" # 这不算类的属性,只能算局部变量
d = D() # 初始化D类
print(d.page) # 10
# 设置初始化方法,会覆盖掉原有的初始化方法
setattr(D, "__init__", __init__)
d2 = D() # D类初始化
print(d.page) # 10
# print(d2.page) # 新实例化对象没有page属性
# 查看私有属性
# print(d.__ID)
print(d._D__ID) # 2
# 看看新替换的__init__方法的私有属性可起作用
print(d2.__id) # 但这时__id就不是私有方法了,当作普通方法来了
# d2.print_id() # 抛异常
# d2.print_id2() # 抛异常
# 所以想这样设置私有属性,这能_D__word这样类设置
print(d2._D__word) # word
d2.print_word() # 打印word:word
# print(d2._D_text) # 抛异常,找不到这个属性
可以通过类的__dict__来查看对象有哪些属性
print(d.__dict__) # {'page': 10, '_D__ID': 2}
print(d2.__dict__) # {'name': 'DDD', '__id': 1, '_D__word': 'word'}
可以发现修改__init__初始化方法后,对象的属性变了,也可以动态添加私有属性了。
另外,也可以通过__init__来查看类有哪些属性
print(D.__dict__)
{'__module__': '__main__',
'__init__': <function __init__ at 0x0000022CD9B166A8>,
'print_id': <function D.print_id at 0x0000022CD9B16950>,
'print_id2': <function D.print_id2 at 0x0000022CD9B169D8>,
'print_word': <function D.print_word at 0x0000022CD9B16A60>,
'__dict__': <attribute '__dict__' of 'D' objects>,
'__weakref__': <attribute '__weakref__' of 'D' objects>,
'__doc__': None}
总结:
反射的四个方法更常用在程序运行状态下,而不是在编程阶段。
给类中setattr方法时,一定要给方法加上self形参并放在形参列表首位。
添加前创建的对象可以调用新添加的方法和属性
不能直接给类添加私有属性,但是可以单独写一个__init__(self,...)方法,里面按私有方法的存在方式_类名__属性名定义私有属性,再把该方法替换原有的类中的方法。
Python的私有方法其实就是_类名__私有方法名这样存储的。