【问题标题】:Python method available for both instantiated/uninstantiated class可用于实例化/未实例化类的 Python 方法
【发布时间】:2015-04-06 14:20:11
【问题描述】:

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

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

但也允许:

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

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

【问题讨论】:

    标签: python methods class-method


    【解决方案1】:

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

    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
    

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

    演示:

    >>> 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'
    

    函数本身的测试有点繁琐;你可以从how property objects operate 中取出一片叶子并附加一个单独的函数来处理类绑定案例:

    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'
    

    【讨论】:

    • 就像一个魅力!谢谢!
    【解决方案2】:

    如果您想将类和实例方法定义分开,您可以执行以下操作:

    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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-11
      • 1970-01-01
      相关资源
      最近更新 更多