4

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

例如,如果我有一个class Thing,我想Thing.get_other_thing()工作,但也要thing = Thing(); thing.get_other_thing()表现得与众不同。

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

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
4

2 回答 2

6

好问题!使用描述符可以轻松完成您所寻求的工作。

描述符是实现描述符协议的 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 手册和上面链接的操作方法” 。描述符是 Python 最强大的功能之一,甚至鲜为人知。


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

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

在实例化时设置函数会带来很多问题:

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

随着列表的继续,还有很多我没有添加。

于 2018-06-29T21:48:29.440 回答
1

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

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在我的例子中)

于 2018-06-29T21:33:56.183 回答