Python 数据描述符与透传 __set__ 命令
我在解决一个问题时遇到了一些麻烦。我有一组特殊的函数,这些函数会在整个程序中使用,基本上是动态可调用的,可以替代其他函数和方法。为了让它们能够正常工作,模仿方法的功能,这些函数重写了 __get__
方法,以提供一个包装版本,能够访问到获取对象。
不幸的是,如果函数直接设置在一个实例上,__get__
就无法正常工作。这是因为只有“数据描述符”在实例的 __dict__
中找到键时才会调用 __get__
方法。想到的唯一解决办法是:让 Python 误以为这是一个数据描述符。这需要在描述符上创建一个 __set__
方法。理想情况下,我希望这个 __set__
方法能够像个通道一样(返回控制权给调用者,并继续评估,就好像它不存在一样)。
有没有办法让 Python 误以为一个描述符是数据描述符,同时让包含的类/实例仍然能够正常使用它的 setattr 命令呢?
另外,我知道可以通过重写调用者的 __getattribute__
来实现这个功能。然而,这不是个好办法,因为我必须对内置的 'object' 以及任何重写它的东西都这样做。这并不是一个很好的解决方案。
如果有其他替代方案,我也很乐意听听。
这是一个问题示例:
class Descriptor(object):
def __get__(self, obj, objtype = None):
return None
class Caller(object):
a = Descriptor()
print a
>>> None
x = Caller()
print a
>>> None
x.a = Descriptor()
print x.a
>>> <__main__.Descriptor object at 0x011D7F10>
最后的情况应该打印 'None' 以保持一致性。
如果你在描述符中添加一个 __set__
,这将打印 'None'(如所期望的那样)。然而,这会导致 x.a = (某个值) 的命令无法像以前那样工作。因为我不想搞乱这个功能,所以这并没有帮助。任何解决方案都将非常有用。
更正:我之前的想法仍然行不通,因为我对描述符的处理有些误解。显然,如果一个描述符根本不在类上,它将永远不会被调用——无论 set 如何。我的条件只有在有一个字典值和一个同名的类访问器时才有帮助。实际上,我在寻找的解决方案更像是:http://blog.brianbeck.com/post/74086029/instance-descriptors,但不涉及让所有东西都继承一个特殊的接口。
不幸的是,考虑到对描述符接口的新理解,这可能是不可能的?为什么 Python 会让装饰器基本上变得非动态呢?
2 个回答
我觉得我可能找到了一个答案,虽然这个方法看起来不太好,但确实可以绕过问题。我现在的计划是手动绑定函数,就像Python那样。我之前已经在用我未绑定的函数的get命令来生成绑定类型的函数。一个可能的解决办法是强制任何想要设置新函数的人手动绑定它。这虽然有点麻烦,但也不是不合理。实际上,Python就是这样要求的(如果你只是把一个函数作为属性设置到实例上,它不会自动绑定)。
当然,如果能自动完成这个过程就更好了,但强制设置新函数的人使用x.a = Descriptor().get(x)来实现想要的效果也不是太糟糕。这样做在这个特定的例子中是有效的,虽然它不是一个通用的解决方案,但在这个方法绑定被模拟的情况下,它是可以工作的。话说回来,如果有人有更好的解决办法,我还是很乐意听听的。
我觉得最简单的办法是把__set__
保持原样,然后在类上设置描述符——如果需要的话,可以把原来的类包裹起来。也就是说,不要用x.a = Descriptor()
,而是用setdesc(x, 'a', Descriptor())
,其中:
class Wrapper(object): pass
def setdesc(x, name, desc):
t = type(x)
if not issubclass(t, wrapper):
class awrap(Wrapper, t): pass
x.__class__ = awrap
setattr(x.__class__, name, desc)
这是我建议的一种通用方法,适合那些想要在实例上设置任何东西(比如特殊方法或描述符),但又不想影响实例原始类的人。
当然,这一切只有在你使用新式类的时候才有效,不过其实描述符在旧式类中也不太好用;-)。