如何从同一类的类方法中调用实例方法

16

我有一个如下所示的类:

class MyClass(object):
    int = None
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.iteritems():
            setattr(self, k, v)

    def get_params(self):
        return {'int': random.randint(0, 10)}

    @classmethod
    def new(cls):
        params = cls.get_params()
        return cls(**params)

我希望能够做到以下事情:

>>> obj = MyClass.new()
>>> obj.int  # must be defined
9

我的意思是不创建MyClass的新实例,但很明显这并不简单,因为调用MyClass.new()会抛出TypeError: unbound method get_params() must be called with MyClass instance as first argument (got nothing instead)的错误。

有没有什么方法可以做到这一点呢? 谢谢。


5
为什么不将get_params也设为类方法? - BrenBarn
MyClass只是一个例子,get_params代表一个在我的类中被很多实例方法调用的方法,我认为应该有一种方式可以从classmethod中调用它,而不用改变当前调用它的所有方法。无论如何,谢谢 :) - Gerard
2
get_params 是做什么的?如果它是一个实例方法,你需要有一个实例才能调用它。如果没有,它可能不应该是一个实例方法。但你可以通过将类的实例作为显式的第一个参数来从任何其他范围调用实例方法。 - Silas Ray
请注意,您可以从实例中调用类和静态方法,因此将get_params设置为classmethod不应该破坏任何调用instance.get_params()的代码。但是,它会破坏那些只调用TheClass.get_params(instance)的代码。 - Bakuriu
2
你不能在没有实例的情况下调用实例方法。如果 get_params 不需要访问实例,则将其设置为类方法即可。这可能需要在其他地方进行一些更改,但是没有其他办法。请注意,您可以从实例方法中无问题地调用类方法,因此您实际上可能不必进行太多更改。 - BrenBarn
显示剩余2条评论
4个回答

17
不,你不能也不应该在没有实例的情况下从类中调用实例方法。这将是非常糟糕的。但是,你可以从实例方法中调用类方法。有以下两个选项:
  1. get_param 设为类方法并修复对它的引用
  2. 既然 get_param 是实例方法,那么将其调用放到 __init__
此外,你可能会对 AttrDict 感兴趣,因为它看起来像是你想要做的事情。

3
请注意:给出的选项没有解决我的问题,但可能对其他人有用。接受这个答案是因为它非常清楚地解释了为什么你不能这样做 :) - Gerard

1

当将实例方法视为实例方法并将类添加为实例时,您可以使用classmethod调用实例方法。我个人认为这是非常糟糕的做法,但它可以解决您的问题。

@classmethod
def new(cls):
    params = cls.get_params(cls)
    return cls(**params)

由于get_params忽略它的参数,因此您可以传递任何内容,而不仅仅是cls。这就是为什么将get_params作为静态方法会是更好的解决方案,因为这不需要任何类似的虚拟参数。(然而,OP在评论中提到,“真正的”get_params实际上确实使用了“self”,因此这个答案在他们的情况下行不通。) - chepner
我有一个实例方法@chepner,它访问实例和类变量,但在启动时,当还没有实例时,它只访问类变量。我可以使用一个实例代替类,并为当前实例创建一个额外的子类,但我使用类是因为我想要一个单例,而创建一个中间子类的唯一原因就是为了这个方法。我可以添加一个classmethod并在子类中重写该方法,但是子类化和重写classmethod只是为了从类中调用一个实例方法。我猜这就是我必须做的。 - undefined

0
虽然有一些针对OP情况的答案,合理地建议不要从类中调用实例方法, 但我有一个类似的用例,确实需要从同一个类中调用实例方法。
因为这是不可能的,或者说是一种取巧的方法,我已经找到了一种解决办法。
我认为这可能是最简单的合理方法,但如果有任何改进或修正,那将是非常好的。
用例演示(不可用)
class MyClass:
    context = {"a": 1}  # establish class variable

    def __init__(self, details):
        self.details = details  # establish instance variable

    def get(self, key):
        val = self.details.get(key)  # access instance variable
        if val is None:
            val = self.context.get(key)  # access class variable
        return val

my = MyClass({"a": 2})
print(my.get("a"))  # call instance method from instance

print(MyClass.get("a"))  # call instance method from same class

从实例运行这个可以工作,但从类运行不起作用。 这展示了所寻求的大致功能。
" a "参数被分配给" self "参数,Python 抱怨没有参数留给" key "参数。
2
Traceback (most recent call last):
  File "myclass.py", line 17, in <module>
    print(MyClass.get("a"))
TypeError: MyClass.get() missing 1 required positional argument: 'key'

解决方案概念
需要一个同名的独立类方法和实例方法。
但是,在同一对象上定义两个同名方法只能意味着后定义的方法会覆盖先定义的方法。一个单一对象肯定不会支持两个同名的不同属性。
解决方案是在不同的对象上有两个独立的同名方法。一个是普通的类方法,一个是普通的实例方法。
解决方案设计
为了实现这一点,可以引入一个子类,让子类重新定义同名方法。Python面向对象编程会适当地调用它们。
class MyClass:
    context = {"a": 1}

    @classmethod
    def get(cls, key):
        val = cls.context.get(key)
        return val


class MySubClass(MyClass):
    def __init__(self, details):
        self.details = details

    def get(self, key):
        val = self.details.get(key)
        if val is None:
            # val = super().get(key)
            val = self.__class__.get(key)
        return val

my = MySubClass({"a": 2})
print(my.get("a"))

print(MyClass.get("a"))

这个解决方案有效。
2
1

类的单例行为仍然可用,但不再创建该类的实例,而是使用子类的实例。
这意味着需要定义一个额外的子类,并且需要将方法的实现分散开来,但这似乎是必要且适当的,以处理类和实例之间的差异。

0
你需要像下面这样从类方法中传递 cls 到实例方法,以便从类方法调用实例方法:
class Person:
    def test1(self):
        print("Test1")
    
    @classmethod
    def test2(cls):
        cls.test1(cls) # Needed to pass "cls"

obj = Person()
obj.test2()

输出:

Test1

1
你不需要特别传递 cls;只要传递 任何值 就可以了。如果 test1 真的忽略它的参数,那么任何值都可以。 - chepner

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