Python方法适用于已实例化/未实例化的类

5

我有一个类,如果已经使用id实例化,则使用details方法获取详细信息并填充类信息。如果尚未实例化,则希望它使用传递给details的参数作为id并返回一个新实例化的对象。代码如下:

f = Foo()
f.id = '123'
f.details()

但也要允许:
f = Foo.details(id='123')

我可以使用相同的details方法来完成这个吗?还是需要创建两个单独的方法并使其中一个成为@classmethod?如果我将其中一个声明为@classmethod,另一个不声明,它们可以使用相同的名称吗?

2个回答

10

您需要创建自己的描述符来处理此问题;如果没有实例可用,它必须绑定到类,否则绑定到实例:

class class_or_instance_method(object):
    def __init__(self, func, doc=None):
        self.func = func
        self.cmdescriptor = classmethod(func)
        if doc is None:
            doc = func.__doc__
        self.__doc__ = doc

    def __get__(self, instance, cls=None):
        if instance is None:
            return self.cmdescriptor.__get__(None, cls)
        return self.func.__get__(instance, cls)

如果没有实例可用,此描述符将委托给classmethod()对象以产生正确的绑定。

请像这样使用:

class Foo(object):
    @class_or_instance_method
    def details(cls_or_self, id=None):
        if isinstance(cls_or_self, type):
            # called on a class
        else:
            # called on an instance
你可以通过返回自己的类似方法的包装对象,并传递绑定的关键字参数,使其变得更加华丽。
演示:

Demo:


>>> class Foo(object):
...     @class_or_instance_method
...     def details(cls_or_self, id=None):
...         if isinstance(cls_or_self, type):
...             return 'Class method with id {}'.format(id)
...         else:
...             return 'Instance method with id {}'.format(cls_or_self.id)
... 
>>> Foo.details(42)
'Class method with id 42'
>>> f = Foo()
>>> f.id = 42
>>> f.details()
'Instance method with id 42'

函数本身中的测试有些繁琐; 你可以从属性装饰器是如何工作的中借鉴一下,附加一个单独的函数来处理类绑定的情况:

class class_or_instance_method(object):
    def __init__(self, instf, clsf=None, doc=None):
        self.instf = instf
        self.clsf = clsf
        self.cmdescriptor = classmethod(clsf or instf)
        if doc is None:
            doc = instf.__doc__
        self.__doc__ = doc

    def __get__(self, instance, cls=None):
        if instance is None:
            return self.cmdescriptor.__get__(None, cls)
        return self.instf.__get__(instance, cls)

    def classmethod(self, clsf):
        return type(self)(self.instf, clsf, doc=self.__doc__)

    def instancemethod(self, instf):
        return type(self)(instf, self.clsf, doc=self.__doc__)

这将为两个类或实例都调用最初装饰的函数(就像上面描述符的实现一样),但它允许您注册一个可选的、单独的函数来处理当您使用@methodname.classmethod装饰器时绑定到一个类:

class Foo(object):
    @class_or_instance_method
    def details(self):
        # called on an instance

    @details.classmethod
    def details(cls, id):
        # called on a class, takes mandatory id argument

这样做的额外优势是现在你可以为这两种方法实现提供不同的参数;Foo.details() 接受上述的 id 参数,而 instance.details() 则不包括:

>>> class Foo(object):
...     @class_or_instance_method
...     def details(self):
...         return 'Instance method with id {}'.format(self.id)
...     @details.classmethod
...     def details(self, id):
...         return 'Class method with id {}'.format(id)
...
>>> Foo.details(42)
'Class method with id 42'
>>> f = Foo()
>>> f.id = 42
>>> f.details()
'Instance method with id 42'

太棒了!谢谢你! - mgoffin

0

如果你想将类方法和实例方法定义分开,可以这样做:

class overriding_instance_method(object):
    """
    can be used as a decorator: see example below in __main__
    """
    def __init__(self, class_method_func, instance_method_func=None):
        self.class_method_func = class_method_func
        self.instance_method_func = instance_method_func

    def __call__(self, instance_method_func):
        return type(self)(self.class_method_func, 
                          instance_method_func=instance_method_func)

    def __get__(self, instance, cls=None):
        if instance is None:
            return classmethod(self.class_method_func).__get__(None, cls)
        return self.instance_method_func.__get__(instance, cls)

使用方法如下:

class OverridingClassMethodTest(object):

    def print_me(cls):
        print 'class: {}'.format(cls)

    @overriding_instance_method(print_me)
    def print_me(self):
        print 'instance: {}'.format(self)

OverridingClassMethodTest.print_me()
OverridingClassMethodTest().print_me()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接