系统学习Python——装饰器:“私有“和“公有“属性案例-[隐式运行的运算符重载方法无法在Python3.X下委托]
分类目录:《系统学习Python》总目录
就像使用__getattr__
的所有基于委托的类,仅当使用常规命名的或显式调用的属性时,这个装饰器才能够跨版本工作。当通过内置操作隐式地运行时,像__str__
和__add__
这样的运算符重载方法在新式类中表现不同。因为这段代码仅在Python3.X中被解释为新式类,所以当像目前编写的这样在这一Python系列下运行时,这样的操作不能够访问一个定义了它们的内嵌对象。
正如我们在之前的文章所了解到的,内置操作在经典类的实例中查找运算符重载名称,但在新式类的实例中不会这么做一一对于后者,它们完整地跳过实例并在类中搜索这样的方法。技术上讲,是在实例树中全部类的命名空间字典内搜索。因此,内置操作隐式运行的__X__
运算符重载方法不会在新式类中触发__getattr__
或__getattribute__
;因为这样的属性获取一并略过我们onlnstance
类的__getattr__
,因此它们无法被验证或委托。
我们的装饰器类没有显式地编写为新式类(通过派生自object
编写),因此如果在Python2.X下作为默认经典类运行,它将会捕获运算符重载方法。然而在Python3.X中,因为所有的类都自动(并强制)是新式类,所以如果它们由内嵌对象来实现,这样的方法将会失效一一一因为它们没有被代理捕获,所以不会被继续传递。
Python3.X中最直接的解决方案是,在onlnstance
中重新定义所有那些可能在被包装的对象中用到的运算符重载方法。这样的额外方法可以手动添加,可以使用一定程度上自动化该任务的工具来添加(例如,使用类装饰器或者元类),或者通过在可重用的父类中的定义来添加。尽管十分冗余,并且代码过于密集以致这里省略了大部分一一一不久我们将会探索满足这一Python3.X要求的方法。
然而首先,要亲自看到不同,可尝试在Python2.X下对使用运算符重载方法的类运用该装饰器;属性验证与前面一样有效,但是打印所使用的__str__
方法和为+
而运行的__add__
方法二者都会调用装饰器的__getattr__
,并由此最终结束验证并正确地委托给主体Person
对象。然而下面显示的是在Python3.X中运行的结果,虽然其在Python2.X中不汇报出任何错误,但同样的代码在Python3.X下运行的时候,隐式调用的__str__
和__add__
将会忽略装饰器的__getattr__
,并且在装饰器类之中或其上查找定义。print
最终查找到从类类型继承的默认显示(从技术上讲,是从Python3.X中隐含的object
父类继承),并且+
产生一个错误,因为没有默认值被继承:
traceMe = False
def trace(*args):
if traceMe:
print('[' + ' '.join(map(str, args)) + ']')
def accessControl(failIf):
def onDecorator(aClass):
class onInstance:
def __init__(self, *args, **kwags):
self.__wrapped = aClass(*args, **kwags)
def __getattr__(self, attr):
trace('get:', attr)
if failIf(attr):
raise TypeError('Private attribute fetch:' + attr)
else:
return getattr(self.__wrapped, attr)
def __setattr__(self, attr, value):
trace('set:', attr, value)
if attr == '_onInstance__wrapped':
self.__dict__[attr] = value
elif failIf(attr):
raise TypeError('Private attribute change:' + attr)
else:
setattr(self.__wrapped, attr, value)
return onInstance
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
@Private('age')
class Person:
def __init__(self):
self.age = 42
def __str__(self):
return 'Person:' + str(self.age)
def __add__(self, years):
self.age += years
输入:
X = Person()
X.age
输出:
TypeError Traceback (most recent call last)
Input In [55], in <cell line: 2>()
1 X = Person()
----> 2 X.age
Input In [53], in accessControl.<locals>.onDecorator.<locals>.onInstance.__getattr__(self, attr)
13 trace('get:', attr)
14 if failIf(attr):
---> 15 raise TypeError('Private attribute fetch:' + attr)
16 else:
17 return getattr(self.__wrapped, attr)
TypeError: Private attribute fetch:age
输入:
print(X)
输出:
<__main__.accessControl.<locals>.onDecorator.<locals>.onInstance object at 0x00000267F51E0F40>
输入:
X + 10
输出:
TypeError Traceback (most recent call last)
Input In [57], in <cell line: 1>()
----> 1 X + 10
TypeError: unsupported operand type(s) for +: 'onInstance' and 'int'
输入:
print(X)
输出:
<__main__.accessControl.<locals>.onDecorator.<locals>.onInstance object at 0x00000267F51E0F40>
奇怪的是,只有分发来自内置操作时这才发生,其对重载方法的显式直接调用被指向__getattr__
,尽管不能期望使用运算符重载的客户端做同样的事:
输入:
X.__add__(10)
X._onInstance__wrapped.age
输出:
52
换言之,这是内置操作与显式调用的问题。它基本上与所包含方法的实际名称无关,仅仅由于内置的操作,Python对3.X的新式类略过了这一步。
使用替代的__getattribute__
方法在这里帮不上忙一一尽管它定义为捕获每次属性引用(而不只是未定义的名称),但是它也不会由内置操作运行。我们在前面的文章介绍的Python的property
功能,在这里无法直接帮上忙。回忆一下,property
是自动运行的代码,与在编写类时定义的特定属性(attribute)相关联,并且意图不在于处理被包装对象中的任意属性(attribute)
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.