How to call an instance method from a class method of the same class
Asked Answered
O

4

16

I have a class as follows:

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)

and I would like to be able to do:

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

I mean without creating a new instance of MyClass, but obviously it's not that simple, because calling MyClass.new() throws TypeError: unbound method get_params() must be called with MyClass instance as first argument (got nothing instead)

Is there any way to accomplish so? Thanks.

Oistrakh answered 7/5, 2013 at 19:52 Comment(7)
Why don't you make get_params a classmethod also?Fractional
MyClass is juts an example, get_params represents a method that is widely called from other instance methods in my class, and I think there should be some way to call it from a classmethod instead of changing ALL the methods that currently call it. Thanks anyway :)Oistrakh
What does get_params do? If it's an instance method, you should need to have an instance to call it. If you don't, it probably shouldn't be an instance method. You can call an instance method from any other scope though, simply by providing an instance of the class as an explicit first argument.Prismatic
@Oistrakh Note that you can call class and static methods from instances, hence making get_params a classmethod shouldn't break any code that does instance.get_params(). It will break code that do TheClass.get_params(instance) only.Coruscate
You can't call an instancemethod without an instance. If get_params doesn't need access to the instance, then just make it a classmethod. That may require making some changes elsewhere, but there's no way around that. You can't call an instancemethod without an instance. Note that you can call a classmethod from an instance method with no problems, so you may not actually have to make as many changes as you think.Fractional
Problem is get_params does need access. I'll have to figure out another way to solve this. Thank you all :)Oistrakh
What exactly are you trying to do?Sandlin
M
17

No, you can't and shouldn't call an instance method from a class without an instance. This would be very bad. You can, however call, a class method from an instance method. Options are

  1. make get_param a class method and fix references to it
  2. have __init__ call get_param, since it is a instance method

Also you may be interested in an AttrDict since that looks like what you are trying to do.

Muumuu answered 7/5, 2013 at 20:4 Comment(1)
Note: The given choices didn't solve MY problem but may be useful for somebody else. Accepting because the explanation is very clear of why you can NOT do this :)Oistrakh
T
1

You can call instance method with classmethod when treating it like instance method and add class as instance. IMO it is really bad practice, but it solves your problem.

@classmethod
def new(cls):
    params = cls.get_params(cls)
    return cls(**params)
Tchad answered 17/8, 2018 at 6:58 Comment(2)
Since get_params ignores its argument, you can pass literally anything, not just cls. This is why making get_params a static method, which wouldn't require any such dummy argument, would be a better solution. (The OP mentioned in a comment, though, that the "real" get_params actually does use self, so this answer wouldn't work in their case.)Glacialist
I have an instance method @Glacialist that accesses instance & class variables but at start-up it accesses only class variables when there are no instances yet. I could have an instance instead of the class and an additional sub-class for the current instances but I use a class because I want a singleton and the only reason to create an intermediate sub-class would be for this method. I could add a classmethod and override the method in a sub-class but sub-classing and overriding classmethod just to call an instance method from the class. I guess that's what I'd have to do.Febrifugal
S
0

You need to pass cls to an instance method from a class method as shown below to call the instance method from the class method:

class Person:
    def test1(self):
        print("Test1")
    
    @classmethod
    def test2(cls):
        cls.test1(cls) # Needed to pass "cls"

obj = Person()
obj.test2()

Output:

Test1
Sisile answered 17/11, 2022 at 7:34 Comment(1)
You don't need to pass cls specifically; you just need to pass something. Any value would suffice if test1 truly ignores its argument.Glacialist
F
0

While there are answers specific to the OP's situation that legitimately advise away from calling an instance method from a class, I have a similar use-case that really needs to call an instance method from the same class.

Because that's impossible, or hacky, I have worked this way of doing it.

I think it's probably the shortest legitimate route, but it would be great to get any improvements or corrections.

Use case demo (non-working)

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

Running this works from the instance but not from the class. It demonstrates the rough functionality that is being sought.

The "a" argument is assigned to the self parameter and Python complains that there are no arguments left for the key parameter.

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'

Solution concept

A separate class method and instance method of the same name are needed.

But defining two methods with the same name on the same object can only mean that the one defined later over-writes the one defined earlier. A single object certainly won't support two different attributes of the same name.

The solution is to have two separate methods, both with the same name, on separate objects. One is a normal class method and one a normal instance method.

Solution design

To allow this a sub-class can be introduced so that the sub-class redefines the method with the same name. Python OO takes care of calling them appropriately.

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

This solution works.

2
1

The singleton behaviour of the class is still available for use, but instead of making instances of that class, instances of a sub-class are used.

This means that an additional sub-class is defined, and it means the implementation of the method is spread out, but that seems necessary and appropriate to handle the differences between a class and an instance.

Febrifugal answered 1/12, 2023 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.