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

mgo*_*fin 5 python methods class-method

我有一个类,它获取细节并使用信息填充类,如果它已经id使用details方法实例化了.如果它没有实例化,我希望它改为使用传入的参数details作为id并返回一个新的实例化对象.类似于以下内容:

f = Foo()
f.id = '123'
f.details()
Run Code Online (Sandbox Code Playgroud)

但也允许:

f = Foo.details(id='123')
Run Code Online (Sandbox Code Playgroud)

我可以使用相同的details方法来完成此任务吗?或者我是否需要创建两个单独的方法并制作一个@classmethod?如果我将一个声明为a @classmethod而另一个不声明,它们可以具有相同的名称吗?

Mar*_*ers 8

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

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)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

您可以通过返回自己的类似方法的包装器对象来更加花哨,该对象传递绑定的关键字参数.

演示:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

功能本身的测试有点单调乏味; 你可以采取从叶如何property对象进行操作,并附加一个单独的函数来处理类结合的情况下:

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__)
Run Code Online (Sandbox Code Playgroud)

这将调用类或实例的初始修饰函数(就像上面描述符的实现一样),但它允许您在使用@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
Run Code Online (Sandbox Code Playgroud)

这有一个额外的好处,现在你可以给两个方法实现不同的参数; 在上面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'
Run Code Online (Sandbox Code Playgroud)