为什么Python没有类似于staticmethod
和classmethod
的instancemethod
函数?你知道吗
我就是这样想的。假设我有一个对象,我知道它经常被散列,并且它的散列计算代价很高。在这种假设下,计算一次哈希值并将其缓存是合理的,如下例所示:
class A:
def __init__(self, x):
self.x = x
self._hash_cache = hash(self.x)
def __hash__(self):
return self._hash_cache
这个类中的__hash__
函数做的很少,只是一个属性查找和一个返回。天真地说,它应该等同于写:
class B:
def __init__(self, x):
self.x = x
self._hash_cache = hash(self.x)
__hash__ = operator.attrgetter('_hash_cache')
根据the documentation,operator.attrgetter
返回一个可调用对象,该对象从其操作数中获取给定的属性。如果它的操作数是self
,那么它将返回self._hash_cache
,这是所需的结果。不幸的是,这不起作用:
>>> hash(A(1))
1
>>> hash(B(1))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: attrgetter expected 1 arguments, got 0
原因如下。如果读取the descriptor HOWTO,就会发现类字典将方法存储为函数;函数是非数据描述符,其__get__
方法返回绑定方法。但是operator.attrgetter
不返回函数;它返回可调用对象。实际上,它是一个没有__get__
方法的可调用对象:
>>> hasattr(operator.attrgetter('_hash_cache'), '__get__')
False
缺少__get__
方法,这当然不会自动变成绑定方法。我们可以使用types.MethodType
从它生成一个绑定方法,但是在我们的类B
中使用它需要为每个对象实例创建一个绑定方法并将其分配给__hash__
。你知道吗
如果浏览CPython源代码,我们可以看到operator.attrgetter
没有__get__
的事实。我对cpythonapi不是很熟悉,但我相信现在的情况如下。attrgetter_type
的定义在Modules/_operator.c中,在我写这篇文章的第1439行。此类型将tp_descr_get
设置为0
。根据type object documentation,这意味着类型为attrgetter_type
的对象将没有__get__
。你知道吗
当然,如果我们给自己一个__get__
方法,那么一切都可以。这是上面第一个例子中的情况,其中__hash__
实际上是一个函数,而不仅仅是一个可调用函数。在其他一些情况下也是如此。例如,如果要查找类属性,可以编写以下代码:
class C:
y = 'spam'
get_y = classmethod(operator.attrgetter('y'))
如前所述,这是非常不符合python的(尽管如果有一个奇怪的习惯__getattr__
我们想为其提供方便的函数,这可能是可以辩护的)。但至少它给出了预期的结果:
>>> C.get_y()
'spam'
我想不出任何理由说明attrgetter_type
实现__get__
是不好的。但另一方面,即使它这样做了,也会有其他情况下,我们遇到麻烦。例如,假设我们有一个实例可调用的类:
class D:
def __call__(self, other):
...
我们不能将此类的实例用作类属性,而期望实例查找生成绑定方法。例如
d = D()
class E:
apply_d = d
调用D.__call__
时,它将接收self
,而不是other
,这将生成一个TypeError
。这个例子可能有点牵强,但如果没有人在实践中遇到过这样的事情,我会有点惊讶。它可以通过给D
一个__get__
方法来修复;但是如果D
来自第三方库,这可能会带来不便。你知道吗
似乎最简单的解决方案是使用instancemethod
函数。然后我们可以写__hash__ = instancemethod(operator.attrgetter('_hash_cache'))
和apply_d = instancemethod(d)
,它们都可以按预期工作。然而,据我所知,没有这样的功能存在。因此我的问题是:为什么没有instancemethod
函数?你知道吗
编辑:为了清楚起见,instancemethod
的功能相当于:
def instancemethod(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
这可以像上面原来的问题一样适用。我们还可以设想编写一个类装饰器,它可以应用于D
,从而为它提供一个__get__
方法;但这段代码并不能做到这一点。你知道吗
所以我不是说给Python添加一个新特性。实际上,问题是语言设计:为什么不提供它,比如说,functools.instancemethod
?如果答案很简单,“使用c案例太晦涩了,没人在意,“没关系。但我很乐意了解其他原因,如果有的话。你知道吗
没有
instancemethod
修饰符,因为这是类内声明的函数的默认行为。你知道吗因此,任何没有
__get__
方法的可调用方法都可以像这样包装到实例方法中。你知道吗因此,创建
instancemethod
修饰符只会为已经存在的特性添加另一种语法。这与there should be one and preferably only one obvious way to do it的说法背道而驰。你知道吗旁注
如果对实例进行哈希运算的成本太高,您可能希望避免在实例化时调用哈希函数,并在对对象进行哈希运算时将其延迟。你知道吗
一种方法是在
__hash__
中设置属性_hash_cache
,而不是__init__
。尽管如此,我还是建议使用一种更加独立的方法,它依赖于缓存散列。你知道吗使用^{} 可以确保当缓存的实例被垃圾收集时,缓存中的
id
被清除。你知道吗我对我的问题有一个满意的答案。Python确实具有
instancemethod
函数所需的内部接口,但它在默认情况下不公开。你知道吗它创建的
instancemethod
函数的工作方式与classmethod
和staticmethod
基本相同。这三个函数分别返回instancemethod
、classmethod
和staticmethod
类型的新对象。我们可以通过查看Objects/funcobject.c
来了解它们是如何工作的。这些对象都有存储可调用对象的__func__
成员。它们也有一个__get__
。对于staticmethod
对象,__get__
返回__func__
不变。对于classmethod
对象,__get__
返回绑定方法对象,其中绑定到类对象。对于staticmethod
对象,__get__
返回绑定方法对象,其中绑定到对象实例。这与函数对象的__get__
行为完全相同,正是我们想要的。你知道吗关于这些对象的唯一文档似乎在pythoncapihere中。我猜它们不会暴露是因为它们很少被需要。我认为
PyInstanceMethod_New
作为functools.instancemethod
提供会很好。你知道吗相关问题 更多 >
编程相关推荐