Python - call super() as attribute of subclass

Marshall McQuillen Source
class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

class Triangle(Point):

    def __init__(self, v1, v2, v3):
        """Create a new Triangle with vertices (v1, v2, v3)."""
        self.v1 = super().__init__(x=v1[0], y=v1[1])
        self.v2 = super().__init__(x=v2[0], y=v2[1])
        self.v3 = super().__init__(x=v3[0], y=v3[1])

tri = Triangle((1,1),(1,10),(1,5))

I'm trying call super().__init__() as an attribute of a subclass, however when I execute the above code, I get an error that reads:

__init__() missing 1 required positional argument: 'y'

Which points to the line where self.v1 is defined in the subclass. Is there something glaringly wrong here? I have tried adding self and other test arguments to the super().__init__() call just to test things out, but I always get the same error.

pythonpython-3.xsuperclass

Answers

answered 3 months ago Alexander Kamyanskiy #1

when you are called

self.v1 = super().__init__(x=v1[0], y=v1[1])

it calls __init__() method of Point class, but doesn't return an instance of Point, it returns None (and adds x,y attribute to your tri instance of Triangle class).

To instantiate Triangle with 3 Points code might looks like:

class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

class Triangle(object):

    def __init__(self, v1, v2, v3):
        """Create a new Triangle with vertices (v1, v2, v3)."""
        self.v1 = Point(x=v1[0], y=v1[1])
        self.v2 = Point(x=v2[0], y=v2[1])
        self.v3 = Point(x=v3[0], y=v3[1])

tri = Triangle((1,1),(1,10),(1,5))

     17         self.v1 = Point(x=v1[0], y=v1[1])
---> 18         self.v2 = Point(x=v2[0], y=v2[1])
     19         self.v3 = Point(x=v3[0], y=v3[1])

ipdb> self.v1
<__main__.Point object at 0x7f18c48da048>
ipdb> self.v1.x
1

answered 3 months ago Jim Wright #2

There are a couple of problems with your code as it is.

super().__init__ isn't doing what you think it is. On each call to __init__ you are essentially running Point.__init__ in the scope of tri. If you print(tri) you will see that tri has both x and y attributes.

tri = Triangle((1,1),(1,10),(1,5))
print(tri.x)  # 1
print(tri.y)  # 5

What you actually want to do is create an instance of Point for each vertices. To do this and use super you can use self.v1 = super().__new__(x=v1[0], y=v1[1]). __new__ will construct a new object of super's class and assign that object to self.v1.

class Triangle(Point):
    def __init__(self, v1, v2, v3):
        """Create a new Triangle with vertices (v1, v2, v3)."""
        self.v1 = super().__new__(x=v1[0], y=v1[1])
tri = Triangle((1,1),(1,10),(1,5))
print(tri.v1)  # Point (x=1, y=1
print(tri.v2)  # Point (x=1, y=10)

Now you have a working example, but you could still make is better. Seeing as the only part of Point you are using in Triangle is __new__ why should we even inherit from the class? In this case I would remove the inheritance and just create new instances without the call to super().

Also, in python 3 you do not need to inherit from object.

class Point():

    def __init__(self, x, y):
        self.x = x
        self.y = y

class Triangle():

    def __init__(self, v1, v2, v3):
        """Create a new Triangle with vertices (v1, v2, v3)."""
        self.v1 = Point(x=v1[0], y=v1[1])
        self.v2 = Point(x=v2[0], y=v2[1])
        self.v3 = Point(x=v3[0], y=v3[1])

tri = Triangle((1,1),(1,10),(1,5))
print(tri.v1)  # Point (x=1, y=1
print(tri.v2)  # Point (x=1, y=10)

As an extra - I would recommend @Oliver's implementation. It is cleaner and not as many objects will be created. The only side effect would be renaming v1 to x etc.

answered 3 months ago Olivier Melançon #3

Your problem here is not really with super, but with inheritance at large.

A class A should inherit from a class B if an object of A is actually defined as an object of B with extra properties. Inheritance extends a definition.

In the case of Triangle and Point, the code you actually want, which has been given in Alexander Kamyanskiy and Jim Wright's answers, does not need inheritance. Furthermore, mathematically a triangle is not a point with extra properties.

Here is an example of inheritance that is meaningful.

class Point:
    def __init__(self, x, y)
        self.x = x
        self.y = y

# A point in 3D really is a point in 2D with an extra coordinate
class Point3D(Point)
    def __init__(self, x, y, z):
        self.z = z
        super().__init__(x, y)

Here is an example of inheritance that while programmatically correct, is not really meaningful.

class Plant:
    def __init__(self, type):
        self.type= type

# Let me check my biology textbooks...
class Animal(Plant):
    def __init__(self, kind, favorite_food):
        self.kind = kind
        # Herbivores only please
        self.favorite_food = Plant(favorite_food)

Using another class as attribute does not force you to inherite from it. In the above example, you tell Python that any Animal is also an instance of Plant which according to my little biology background is incorrect.

comments powered by Disqus