Search code examples
pythontypeerrorattributeerrorself

Explanation about TypeError and AttributeError with classes


I am not so new to python, used classes for the last two projects, but there are two concepts that are not so clear for me. I'm writing a compiler, and my code is 1K+ lines so I made a simple code that sum up it where I get the two errors I don't understand:

class A:
    def __init__(self):
        self.var = 15

    def methodA(self):
        self.var = 0

class B:
    def __init__(self):
        self.a = A()

    def methodB(self):
        self.a.methodA()

    def run(self):
        self.methodB()

B.run()

The errors I get are:

AttributeError: type object 'B' has no attribute 'a'

and

TypeError: run() missing 1 required positional argument: 'self'

I know that I can fix the code as follows

class A:
    def __init__(self):
        self.var = 15

    def methodA(self):
        self.var = 0

class B:
    def __init__(self):
        pass

    def methodB(self):
        A.methodA(A)

    def run(self):
        B.methodB(B)

A.__init__(A)
B.run(B)

But I can't understand why I can't create A class instance as B attribute, and why I can't use self.method instead of B.method(B).


Solution

  • You are calling the method with a class, not an object. You need to call it like this:

    B().run()
    

    Don't confuse between B and B(). One is the instance of B's metaclass, i.e. a class while the second one is an instance of class B. Since the method is not declared as a classmethod, you need an instance of class B to call it.

    Your second code snippet works because in doing A.__init__(A), you basically add a class attribute A.var = 15. When you call B.run(B), you basically do A.method(A), which simply sets A.var = 0 (which you set as 15 before). You are mixing up classes with instances here, but it still works anyway since in the end, both are essentially objects.

    For example, from here

    >>> class A:
    ...     def f(self):
    ...         print(1)
    >>> A().f
    <bound method A.f of <__main__.A object at 0x10b641b50>>
    >>> A.f
    <function A.f at 0x10b759c10>
    

    The first case clearly shows that it is a bound method to A's instance that doesn’t need an argument when called with A(), while in the second case, A.f is not treated as a method but as a function, which would need an argument in order to be invoked. So you can call it in any way like a plain function accepting one absolute argument (well as long as you don’t do something like print(self.some_attribute) or self.some_attribute = some_val inside):

    >>> A.f(123123)
    1
    >>> A.f(33234)
    1