函数在类和实例上的行为不同

时间:2018-06-29 21:18:00

标签: python function class methods python-descriptors

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

例如,如果我有一个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

2 个答案:

答案 0 :(得分:6)

好问题!您可以使用描述符轻松完成搜索。

Descriptors 是实现 descriptor协议的Python对象,通常以__get__()开头。

大多数情况下,它们存在于不同的类中,被设置为类属性。访问它们后,将调用其__get__()方法,并传入实例和所有者类。

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

那很容易!

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

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

访问函数属性后,将调用__get__,这就是获取函数绑定的方式。

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


为什么不设置实例化功能?

还是为什么不在self.func = self._func中设置__init__

设置实例化功能会带来很多问题:

  1. self.func = self._func引起循环引用。实例存储在self._func返回的函数对象中。另一方面,这是在分配期间存储在实例上的。最终结果是该实例引用了自己,并且将以慢得多,更沉重的方式进行清理。
  2. 与您的类进行交互的其他代码可能会尝试将函数直接带出类,并使用__get__()(通常的预期方法)对其进行绑定。他们将收到错误的功能。
  3. 将无法与__slots__一起使用。
  4. 尽管需要使用描述符来理解机制,但在__init__上进行设置并不是那么干净,需要在__init__上设置多个功能。
  5. 占用更多内存。您不必为每个实例存储一个绑定函数,而不必存储一个函数。
  6. 将无法与properties一起使用。

随着列表的不断增加,我没有添加更多内容。

答案 1 :(得分:1)

这是一个有点棘手的解决方案:

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

如果我们在上课,则将执行staticmethod。如果我们在实例上,首先要执行__getattribute__,所以我们不能返回Thing.get_other_thing,而是返回其他函数(在我的情况下,返回lambda