可能在Python中 - 如果从实例而不是类调用,则可以检索实例的类方法?

时间:2018-03-05 15:11:54

标签: python

考虑到这一点,我想知道是否有可能(如果是这样,如何制作这样的装饰器等)有一个类方法,从实例调用IF,可以检索实例上的数据?或许更清楚一下staticmethod和classmethod装饰器的工作方式也会有所帮助(查看实现__builtin__.py没有帮助)

使用示例如下:

class A(object):

    def __init__(self, y):
        self.y = y

    @classmethod
    def f(cls, x, y=None):

        # if y is unspecified, retrieve it from cls which is presumably an instance 
        # (should throw an error if its a class because y is not set
        if y is None:
            y = cls.y

        return x + y

这样我们就可以做到:

>>>A.f(3, 5)
8

>>>a = A(5)
>>>a.f(3)
8

我想出了下面这个来模仿这个行为,但实现起来非常不方便:

class A(object):

    def __init__(self, y):
        self.y = y
        self.f = self.f_

    def f_(self, x):
        return x + self.y

    @classmethod
    def f(cls, x, y):
        return x + y

4 个答案:

答案 0 :(得分:2)

扩展@Adirio发表的评论你可以让装饰者动态完成这个。

在此特定实现中,当调用修饰方法时,它将对方法提供所提供参数的部分绑定,并使用方法的签名来确定尚未提供的参数。

对于任何未指定的参数,如果调用对象具有与未指定的参数名称匹配的属性,则该对象的属性值将被注入到该函数中。

class MyClass(object):
    def __init__(self, y):
        self.y = y

    @BindableConstructor
    def my_constructor(cls, x, y):
        return cls(x + y)

然后假设你有以下类使用这个装饰器

>>> a = MyClass(5)
>>> b = MyClass.my_constructor(3, 2)
>>> b
<__main__.MyClass object at 0x0605C770>
>>> b.y
5
>>> c = b.my_constructor(3) # c.y == b.y + 3
Method  <function MyClass.my_constructor at 0x05396420>  called from instance  <__main__.MyClass object at 0x0605C770>
>>> c.y
8

然后会发现以下行为

ba.apply_defaults

在这种特殊情况下,{<1}}在检查要注入的对象属性之前被称为。如果您希望对象的属性优先于默认值,请在参数注入逻辑之后调用ba.apply_defaults

答案 1 :(得分:1)

当您尝试示例时,您会收到错误消息 AttributeError:type object&#39; A&#39;没有属性&#39; ,因为在构造函数中,您将y指定为对象的属性而不是类的属性。

琐碎的修复:

class A(object):

    def __init__(self, y):
        A.y = y

    @classmethod
    def f(cls, x, y=None):

        # if y is unspecified, retrieve it from cls which is presumably an instance 
        # (should throw an error if its a class because y is not set
        if y is None:
            y = cls.y

        return x + y

确实会解决错误,但由于类一次只能识别一个对象,所以只要使用多个对象就会得到奇怪的结果:

>>> A.f(3,5)
8
>>> a = A(5)
>>> a.f(3)             # fine till there...
8
>>> b = A(7)
>>> a.f(3)             # last created object wins here!
10

所以唯一简单的方法是在每个对象中创建一个带有类函数名称的属性。因为你只调用一个类方法,所以lamdba就足够了:

class A(object):

    def __init__(self, y):
        self.y = y
        self.f = lambda x: A.f(x, y)   # declare a shortcut for the class method

    @classmethod
    def f(cls, x, y=None):
        return x + y

然后你可以安全地做到:

>>> A.f(3,5)
8
>>> a = A(5)
>>> a.f(3)
8
>>> b = A(7)
>>> a.f(3)
8
>>> b.f(3)
10

答案 2 :(得分:1)

不要忘记处理错误案例。

class InstanceAndClassMethod(object):

    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner=None):
        if instance is None:
            instance = owner
        def newfunc(*args, **kwargs):
            return self.f(instance, *args, **kwargs)
        return newfunc

class A(object):

    def __init__(self, y):
        self.y = y

    @InstanceAndClassMethod
    def f(cls, x, y=None):
        try:
            y = cls.y if y is None else y
        except AttributeError:
            raise TypeError("f() missing 1 required positional argument: 'y'")
        return x + y

答案 3 :(得分:0)

在docs.python.org/3/howto/descriptor.html的帮助下,我提出了这个,似乎有效:

class CoolerClassMethod(object):

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass):
        if obj is None:
            self_ = klass
        else:
            self_ = obj
        def newfunc(*args, **kwargs):
            return self.f(self_, *args, **kwargs)
        return newfunc

class A(object):

    def __init__(self, y):
        self.y = y

    @CoolerClassMethod
    def f(cls, x, y=None):
        y = cls.y if y is None else y
        return x + y

测试:

>>> a = A(5)
>>> A.f(3, 5)
8
>>> a.f(3)
8
>>> A.f(3, 5)
8