Koko. N
Koko. N

Reputation: 47

Add from_tuple() classmethod that allows objects to be created with a tuple?

Circle class uses a Point object as the center. A @classmethod called from_tuple is needed to allow Circle objects to be created with tuple instead of a Point as the center. This is how the output should look like for from_tuple()

I apologize for the messy codes, having two getters and one setter for center. I got really confused toward the end, knowing that center must be a tuple. Please help me with resolving the AttributeError in the getter when accessing from_tuple().

Error message is shown below

>>> center_point=3,4
>>> circle=Circle.from_tuple(center=center_point)
>>> circle
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\hole1\Desktop\ProgramIntro\shapes.py", line 54, in __repr__
    return "Circle(center=Point({0}, {1}), radius={2})".format(self.center[0],self.center[1],self.radius)
  File "C:\Users\hole1\Desktop\ProgramIntro\shapes.py", line 65, in center
    return(self._center)
AttributeError: 'Circle' object has no attribute '_center'

The below is class Point:

 import math
class Point:
    def __init__(self,x=0,y=0):
        self.x = x
        self.y = y
        self.data=[x,y]
    def __getitem__(self, index):
        return self.data[index]
    def __iter__(self):
        yield self.x
        yield self.y
    def __add__(self,other):
        return Point(self.x+other.x,self.y+other.y) 
    def __mul__(self,n):
        return Point(self.x*n,self.y*n) 
    def __rmul__(self,n):
        return Point(n*self.x,n*self.y) 
    @classmethod
    def from_tuple (cls, self=(0,0)):
        return cls(*self)   
    def loc_from_tuple(self,t=(0,0)):
        self.x=t[0]
        self.y=t[1] 
    def __str__(self):
        return "Point at ({0}, {1})".format(self.x,self.y)  
    def __repr__(self):
        return"Point(x={0}, y={1})".format(self.x,self.y)   

The below is class Circle.

 class Circle(Point):
        def __init__(self, center=(0,0), radius=1):
            try:
                x, y = center
                super(Circle, self).__init__(x,y)
                Point.__init__(self,center)
                self.radius=radius
                self.center=center
            except (UnboundLocalError, TypeError, AttributeError) as e:
                    pass
            if not center == (0,0):
                if not isinstance(center,Point):
                    if not isinstance(center, tuple):
                        raise TypeError("The center must be a oint!")
        def __str__(self):
            return "Circle with center at ({0}, {1}) and radius {2}".format(self.center.x, self.center.y, self.radius)
        def __repr__(self):
            return "Circle(center=Point({0}, {1}), radius={2})".format(self.center[0],self.center[1],self.radius)
        @property
        def center(self):
            return self.x, self.y
        @property
        def center(self):
            return(self._center)
        @center.setter
        def center(self, center):
            if not center == (0,0):
                if not isinstance(center,Point):
                    raise TypeError("The center must be a Point!")
            self._center=center
        @property
        def radius(self):
            return self._radius
        @radius.setter
        def radius(self, radius):
            if radius<0:
                raise ValueError('The radius cannot be negative')
            self._radius=radius
        @classmethod
        def from_tuple(cls, center=(0,0),radius=1):
            return cls(center,radius)

Upvotes: 1

Views: 499

Answers (1)

zwer
zwer

Reputation: 25789

There are a lot of things wrong with your code - from logical (i.e. why does a Circle inherit from a Point?) to structural (using undeclared instance properties) to downright weird (why are you defining your Circle.center getter twice?) - but the main culprit for the error in question is the very bad idea of capturing all exceptions just to ignore them because if you haven't done so you'd notice the error yourself.

Namely, when you call your Circle.from_tuple() class method, it in turn creates a new Circle instance and its __init__() method gets called with the passed passed center and radius (btw. why do you need Circle.from_tuple() when you're just forwarding your arguments - why not just call Circle() directly?). Given that you're passing a (3, 4) as center to it, it's as if you've effectively called: Circle.__init__(center=(3, 4)).

And there you get a slew of problems. First, you call the super.__init__() method to pass the center's x and y - fair enough, it will initiate self.x, self.y and self.data (redundant, but lets not get into it). Then for some unknown reason you attempt to call it again, using a different syntax and passing your tuple only as the first parameter, which in turn now turns self.x to have the value of center ((3, 4)). But the real problem lies when you call your Circle.center() setter passing it the same (3, 4) tuple. If you look at your method:

@center.setter
def center(self, center):
    if not center == (0, 0):
        if not isinstance(center, Point):
            raise TypeError("The center must be a Point!")
    self._center = center

What do you think is going to happen? Given that the passed center ((3, 4)) is not (0, 0), and most definitely not an instance of Point (it's a tuple) it will raise a TypeError and self._center will never be set.

But wait, since you've decided to capture all UnboundLocalError, TypeError and AttributeError exceptions in your Circle.__init__() and just pass them through you never get informed that your center wasn't set, so when you attempt to print it out in REPL Circle.__repr__() gets called, which in turn attempts to access the undefined Circle._center and you get your nice error.

A quick & dirty solution, since you're already inheriting from Point, is to just forget about the center altogether - you already have self.x and self.y (and self.data being a list based on those) so remove any references to center and you're golden:

class Circle(Point):

    def __init__(self, center=(0, 0), radius=1):
        super(Circle, self).__init__(*center)
        self.radius = radius

    def __str__(self):
        return "Circle with center at ({0}, {1}) and radius {2}".format(self.x, self.y, self.radius)

    def __repr__(self):
        return "Circle(center=Point({0}, {1}), radius={2})".format(self.x, self.y, self.radius)

    @property
    def center(self):
        return Point(self.x, self.y)

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, radius):
        if radius < 0:
            raise ValueError('The radius cannot be negative')
        self._radius = radius

And now you can instantiate it both with a Point or a tuple for its center.

But even better solution would be to rethink what are you trying to achieve here and, when you get stuck, to attempt good old Rubber duck debugging to capture errors such as the one in your code.

Upvotes: 1

Related Questions