3

I would like to pass a class as an argument to another class and then call methods of the class that I passed but I am getting different errors. I started with this but my code keeps giving me errors.

Consider the following situation:

class A:
    A_class_var = "I am an A class variable"

    def __init__(self):
        self.A_inst_var = "I am an A instance variable"

    def GetClassVar(self):
        return A_class_var

    def GetInstVar(self):
        return self.A_inst_var

class B:

    def __init__(self, cls):
        print cls.GetClassVar()
        print cls.GetInstVar()

1) When I call b = B(A) I get an "unbound method GetClassVar() must be called with A instance as first argument (got nothing instead)." So I figure that GetClassVar() and GetInstVar() expected an A instance and tried adding cls to the arguments of the two calls in B's __init__. Same error.

2) When I tried calling b = B(A()) I get the error "global name A_class_var" is not defined.

3) When I tried calling a = A() followed by b = B(a) I got the same error as in #2.

4) When I tried to do the suggestion in the SO answer I linked above, namely change the definition of B to:

class B:
    def __init__(self, cls):
        self.my_friend = cls

    def GetFriendsVariables(self):
        print my_friend.GetClassVar()
        print my_friend.GetInstVar()

and then call b = B(A) followed by b.GetFriendsVariables() (also following that SO I linked) I get the error "global name my_friend is not defined" in GetFriendsVariables.

I'd really like to understand how to properly pass classes as arguments so that I can access variables and methods of those classes and would love to understand why my various attempts above don't work (and yet the SO link I sent does?)

Thanks very much!

7
  • for 1: just check with a = A(), then pass b=B(a) That is a is instance of A Commented Feb 15, 2015 at 6:31
  • @latheefitzmeontv then I am in #3 which gives an error, albeit a different one. Also I'd like to understand what is going on here and why these are failing and why the correct way works. Commented Feb 15, 2015 at 6:35
  • for 2: pleaes put self.A_class_var Commented Feb 15, 2015 at 6:35
  • for 3: ithink this now fixed ;) Commented Feb 15, 2015 at 6:36
  • @latheefitzmeontv hmm, that does work now and also preserves A_class_var as a class variable (when I make the change self.A_class_var inside A::GetClassVar() only). Thanks, but would really like to understand what is going on under the hood here. I want to learn more than I want it to run. :) Commented Feb 15, 2015 at 6:38

3 Answers 3

4

Okay. There's many points to address here! :)

  • 1) You're right, GetClassVar and GetInstVar do expect an A instance. You'll never make it work by calling with the class, unless you do:

    class A:
    
        @classmethod
        def GetClassVar(cls):
            return cls.A_class_var # use `cls.` to get the variable
    

    If you're not familiar, the classmethod decorator will allow you pass the class as the first argument automatically (instead of the instance). So, calling the methods with the class name prepended (like cls.GetClassVar()) is now guaranteed to work as you expected.


  • 2) You're getting that error exactly for the reason the interpreter says. There is no A_class_var declared in the global namespace. Remember, it's inside of class A. There are two solutions here (one is more implicit in nature, and the other more explicit).

    Implicit:

    def GetClassVar(self):
        return self.A_class_var
    

    This works because when you specify an instance attribute, the instance searches its own __dict__ and if it can't find the attribute, it then searches the class' __dict__.

    Explicit:

    def GetClassVar(self):
        return self.__class__.A_class_var
    

    As you can see, it's possible to fetch the class object directly from the instance. This searches the attribute directly inside of the class as opposed to checking if it exists inside the instance first. This has the added benefit of being clearer and avoiding issues where an instance and class attribute name may be the same.

    Even more explicit: (Expanding on user 6502's answer)

    def GetClassVar(self):
        return A.A_class_var
    

    Personally, I don't really like doing it this way. Having to repeat the name of the class feels wrong to me, but depending on your circumstances this may not be such a bad thing.


  • 3) You're getting the error for the same reasons I've just outlined.

  • 4) This won't work any better, again, for the same reasons as above. However, you do have issues to resolve here as well:

    class B:
        def __init__(self, cls):
            self.my_friend = cls
    
        def GetFriendsVariables(self):
            print my_friend.GetClassVar() # should be: self.my_friend.GetClassVar()
            print my_friend.GetInstvar() # should be: self.my_friend.GetInstvar()
    

    Notice you still have to prepend the my_friend variable with self.. But as I've told you, calling it this way will get you the same result. The problem is with how you've defined the method GetClassVar(). You'll see that if you use the @classmethod decorator (or pass an instance and change the attribute fetch to any of the two solutions I've given you above---the implicit or explicit forms), it'll start working. ;)

Sign up to request clarification or add additional context in comments.

4 Comments

@river_jones That's right. Using __class__ is only for when you're passing an instance, so that's with b = B(A()). If you want to do b = B(A), you have to use the @classmethod. Unfortunately, what you're asking for in the case of GetInstVar() is impossible. Even if you give it the @classmethod decorator so it can accept a class argument instead of an instance, what would be the point? GetInstVar() will be trying to fetch an instance attribute and if you're trying to use it via a class, it wouldn't have an instance to fetch the attribute from. Make sense?
Yes, totally clear. So basically any method without an @classmethod decorator is tied to an instance and can't be called without instantiating an object of the class?
@river_jones You got it. ;). The other alternative is using a @staticmethod, although it's just... less useful. It's the same in that you can call it with A.method and not A().method, but it doesn't pass any parameter to the function (not the class, not the instance, nothing).
also, sorry, I deleted my first comment just after posting it because I realized exactly the thing you pointed out: making GetInstVar() accessible as a class method makes no sense because we are talking about instance variables there. What I was curious about was regarding how to be able to access methods via b = B(A) and what you said is perfectly clear, you have to make it a class method with the @classmethod decorator. Thanks, awesome awesome answer!
0

Python scope rules are different from C++. More specifically like you always have to use explicitly self.<membername> when accessing instance members you also have to explicitly use <classname>.<membername> when accessing class members:

class A:
    A_class_var = "I am an A class variable"

    def __init__(self):
        self.A_inst_var = "I am an A instance variable"

    def GetClassVar(self):
        return A.A_class_var

    def GetInstVar(self):
        return self.A_inst_var

class B:
    def __init__(self, cls):
        print cls.GetClassVar()
        print cls.GetInstVar()

With this small change A.A_class_var the code works as you expect

Comments

0

A direct call may doesn't work, but you can use getattr and hasattr to make it work.

class A:
    A_class_var = "I am an A class variable"

    def __init__(self):
        self.A_inst_var = "I am an A instance variable"

    @staticmethod  # If it's a classmethod, use the staticmethod to decorate the method
    def GetClassVar(cls):
        return cls.A_class_var

    def GetInstVar(self):
        return self.A_inst_var


class B:
    def __init__(self, cls):
        if hasattr(cls, "GetClassVar"):
            func = getattr(cls, "GetClassVar")
            func(cls)
        b = cls()
        b.GetInstVar()

if __name__ == '__main__':
    b  = B(A)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.