Python是一门动态强类型语言,想要实现为类增添属性有好几种方式,本篇博客就此做一个简单的总结。
在总结之前,让我们先来看看python的反射
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义信息。
一个对象能够在运行时,像照镜子一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其type、class、attribute 或 method的能力,称为反射或者自省。
具有反射能力的函数有 type()、isinstance()、calladle()、getattr()等
需求:
有一个Point类,需要查看它实例的属性,并修改它。动态为实例添加属性
思考:
代码:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "Point({},{})".format(self.x, self.y)
def show(self):
print(self.x, self.y)
p1 = Point(3, 4)
print(p1)
print(p1.__dict__)
p1.__dict__['y'] = 10
print(p1.__dict__)
p1.z = 20
print(p1.__dict__)
print(dir(p1))
print(p1.__dir__())
总结:
基本功能是实现了,通过属性字典__dict__来访问对象的属性,本质上也是利用的反射的能力。但是我们发现这样的方式不够优雅,Python为我们提供了内置的函数。如下所示:
内建函数 | 意义 |
---|---|
getattr(object,name[,default]) | 通过name返回object的属性值。当属性不存在,将使用default返回,如果没有defaul,则抛出AttributeError。name必须为字符串 |
setattr(object,name,value) | object的属性存在,则覆盖,不存在,新增 |
hasattr(object,name) | 判断对象是否有这个名字的属性,name必须为字符串 |
用上面的方法来修改上例的代码,如下:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x},{self.y})"
def show(self):
return self
__repr__ = __str__
p1 = Point(4, 5)
p2 = Point(3, 2)
print(repr(p1), repr(p2), sep='\n')
print(p1.__dict__)
setattr(p1, 'x', 5)
setattr(p1, 'u', 5)
print(getattr(p1, '__dict__'))
if hasattr(p1, 'x'):
print('Yes, i have')
print(getattr(p1, 'xx', 2000))
if not hasattr(Point, 'add'):
setattr(Point, 'add', lambda self,other: Point(self.x + other.x, self.y + other.y))
print(Point.add)
print(p1.add)
print(p1.add(p2))
if not hasattr(p1, 'sub'):
setattr(p1, 'sub', lambda self,other: Point(self.x - other.x, self.y - other.y))
print(p1.sub)
print(p1.sub(p1, p2))
print(p1.__dict__)
print(Point.__dict__)
思考:
这种动态增加属性的方法和装饰器修饰一个类、Mixin方式的差异在哪里?
getattr()/setattr() | Mixin | 装饰器修饰类 | |
---|---|---|---|
优点 | 1.通过反射来动态为类增加属性,最为灵活 | 1.使用多继承的方式来实现对类的功能增加 2.符合OCP原则,多继承、少修改 3.从设计模式的角度来说,多组合,少继承 |
1.简单方便,在需要的地方动态增加,直接使用装饰器 2.可以为类灵活的增加功能 |
局限性 | 虽然动态为类增加了属性,但是同时也修改了类的内容 | 不会修改原有类属性,但是灵活性比前者低 | 可以通过先继承后修饰的方式,来避免对原有第三方库的修改 |
增加时机 | 在运行时改变类或实例 | 在类定义时,编译前 | 在类定义时,编译前 |
总结:
这种动态增删属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性。