第四周:Python反射
反射(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("-------") """ 打印结果: ******* hhh ******* 一段话 ------- hhh 一段话 ------- """
-
有点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的私有方法其实就是
_类名__私有方法名
这样存储的。