在 class 与实例上表现不同的函数

Function to behave differently on class vs on instance

我希望某个特定函数可以作为类方法调用,并且在调用实例时表现不同。

例如,如果我有一个 class Thing,我希望 Thing.get_other_thing() 可以工作,但 thing = Thing(); thing.get_other_thing() 的行为也不同。

我认为在初始化时覆盖 get_other_thing 方法应该可行(见下文),但这似乎有点老套。有没有更好的方法?

class Thing:

    def __init__(self):
        self.get_other_thing = self._get_other_thing_inst()

    @classmethod
    def get_other_thing(cls):
        # do something...

    def _get_other_thing_inst(self):
        # do something else

这里有一个有点老套的解决方案:

class Thing(object):
    @staticmethod
    def get_other_thing():
        return 1

    def __getattribute__(self, name):
        if name == 'get_other_thing':
            return lambda: 2
        return super(Thing, self).__getattribute__(name)

print Thing.get_other_thing()  # 1
print Thing().get_other_thing()  # 2

如果我们在class,执行staticmethod。如果我们在实例中,__getattribute__ 首先被执行,所以我们可以 return 而不是 Thing.get_other_thing 而是其他一些函数(lambda 在我的例子中)

好问题!使用 描述符 .

可以轻松完成您要查找的内容

Descriptors 是 Python 实现 描述符协议 的对象,通常以 __get__().

它们的存在主要是为了在不同的 class 上设置为 class 属性。访问它们时,会调用它们的 __get__() 方法,并传入实例和所有者 class。

class DifferentFunc:
    """Deploys a different function accroding to attribute access

    I am a descriptor.
    """

    def __init__(self, clsfunc, instfunc):
        # Set our functions
        self.clsfunc = clsfunc
        self.instfunc = instfunc

    def __get__(self, inst, owner):
        # Accessed from class
        if inst is None:
            return self.clsfunc.__get__(None, owner)

        # Accessed from instance
        return self.instfunc.__get__(inst, owner)


class Test:
    @classmethod
    def _get_other_thing(cls):
        print("Accessed through class")

    def _get_other_thing_inst(inst):
        print("Accessed through instance")

    get_other_thing = DifferentFunc(_get_other_thing,
                                    _get_other_thing_inst)

现在看结果:

>>> Test.get_other_thing()
Accessed through class
>>> Test().get_other_thing()
Accessed through instance

这很简单!

顺便说一句,你注意到我在 class 和实例函数上使用 __get__ 了吗?你猜怎么着?函数也是描述符,这就是它们的工作方式!

>>> def func(self):
...   pass
...
>>> func.__get__(object(), object)
<bound method func of <object object at 0x000000000046E100>>

访问函数属性时,它会被调用 __get__,这就是您获得函数绑定的方式。

有关更多信息,我强烈建议阅读上面链接的 Python manual and the "How-To"。描述符是 Python 最强大的功能之一,甚至鲜为人知。


为什么不在实例化时设置函数?

或者为什么不在__init__里面设置self.func = self._func

在实例化时设置函数有很多问题:

  1. self.func = self._func导致循环引用。该实例存储在 self._func 返回的函数对象中。另一方面,这在分配期间存储在实例上。最终结果是实例引用自身并将以更慢和更重的方式清理。
  2. 与您的 class 交互的其他代码可能会尝试直接从 class 中获取函数,并使用 __get__()(通常预期的方法)来绑定它。他们将收到错误的功能。
  3. 不适用于 __slots__
  4. 虽然使用描述符您需要了解其机制,但在 __init__ 上设置它并不干净,需要在 __init__.
  5. 上设置多个函数
  6. 占用更多内存。您不是存储一个函数,而是为每个实例存储一个绑定函数。
  7. 不适用于 properties

随着列表的不断增加,还有很多我没有添加。