简体   繁体   中英

Python: how to call class methods through a class object passed as a parameter

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!

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. ;)

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)

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM