'Purposely trying to break Python class inheritance, don't understand why it breaks this way

I'm exploring the limits of Python class inheritance, so I wrote a small test to see how much I can get away with - redeclaring properties and overriding functions.

class A:
    val : int = 3
    def foo(x: int):
        print(x)

class B(A):
    val : str = 'python'
    def foo(x: str):
        print(x)

a = A()
b = B()
a.foo(5)
b.foo('test')
print(a.val)
print(b.val)

The resulting output is surprising. I would have expected some kind of exception for redeclaring the property, but instead I get:

Traceback (most recent call last):
  File "c:\Development\Playground\hello.py", line 12, in <module>
    a.foo(5)
TypeError: A.foo() takes 1 positional argument but 2 were given

I don't see how it interpreting that I am providing two positional arguments for a.foo(5). Granted I am trying to break it but I still want to understand.



Solution 1:[1]

You need a self parameter for instance methods.

class A:
    val : int = 3
    def foo(self, x: int):
        print(x)

class B(A):
    val : str = 'python'
    def foo(self, x: str):
        print(x)

a = A()
b = B()
a.foo(5)
b.foo('test')
print(a.val)
print(b.val)

Output:

5
test
3
python

Solution 2:[2]

Now that I know you are not looking for a fix, but an idea about what the interpreter is doing I can walk you through one line of your code:

a.foo(5)

This line is just a nice way for us programmers to express the idea of calling a method (foo) on an instance (a). This is syntactic sugar and I like to think of the interpreter transforming that text to this text:

A.foo(a, 5)

and then compiling that. Now you can see, when you compare that line of code to the method that you defined: def foo(x: int): that the interpreter is going to say that the method takes one positional argument (x) but you are giving it two: (a, 5)

Solution 3:[3]

I think the error is produced because self was automatically passed since it's a function of a class so your functions have to take self as their first argument

class A:
    val : int = 3
    def foo(self, x: int):
        print(x)

class B(A):
    val : str = 'python'
    def foo(self, x: str):
        print(x)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Fed_Dragon
Solution 2 quamrana
Solution 3