0

So, I've written a class to represent vectors called Vector, and i'm trying to write a subclass called Visual_vector that contains additional attributes pertaining to how the vector will be displayed on screen, such as the origin and endpoint of the line to be drawn. My issue is that when I create an instance of Visual_vector, it loses self.origin and self.end_point:

>>> a = Visual_vector((0, 0), (45, 330))
>>> a.x
45
>>> a.y
330
>>> a.length
333.0540496676178
>>> a.origin
>>> a.end_point

I suspect this might have something to do with my use of getattr and setattr in the super class, but other than that I have no idea what the issue is

Vector class:

class Vector():
    """A class to represent a 2D vector"""
    def __init__(self, tup):
        self.x = tup[0]
        self.y = tup[1]

    # retrieving attributes
    def __getattr__(self, name):
        if name == 'vector':
            # returns the vector as a tuple
            # (x, y)
            return (self.x, self.y)

        elif name == 'length' or name == 'magnitude':
            # returns the length as a float
            # sqrt(x^2 + y^2)
            return float( sqrt(self.x**2 + self.y**2) )

        elif name == 'normal':
            # returns a normalized vector as a tuple
            # (x/l, y/l)
            if self.length == 0:
                return (0, 0)
            else:
                x = self.x / self.length
                y = self.y / self.length
                return (x, y)
                #return (self.x / self.length, self.y / self.length) 

    # setting attributes
    def __setattr__(self, name, value):
        if name == 'x' or name == 'y':
            # assign value normally
            self.__dict__[name] = value

        elif name == 'length':
            # |(x, y)| * length
            # create a new, normalized vector of the same trajectory
            new_vector = Vector(self.normal)
            # then multiply it by a scalar to get the desired length
            self.x = new_vector.x * value
            self.y = new_vector.y * value

        elif name == 'vector':
            # does the same as setting both x and y at the same time
            if len(value) == 2:     # must be a list or tuple
                self.x = value[0]
                self.y = value[1]

    # mathematical operations
    def __add__(self, other):
        # (x1 + x2, y1 + y2)
        x = self.x + other.x
        y = self.y + other.y
        return Vector((x, y))

    def __iadd__(self, other):
        return self + other

    def __sub__(self, other):
        # (x1 - x2, y1 - y2)
        x = self.x - other.x
        y = self.y - other.y
        return Vector((x, y))

    def __isub__(self, other):
        return self - other

Visual_vector class:

class Visual_vector(Vector):
    """A class to represent the vectors shown on screen"""
    def __init__(self, origin, end_point):
        # qualities relative to a coord system
        Vector.__init__(self, end_point)

        # qualities relative to the screen
        self.origin = origin
        self.end_point = end_point
user151768
  • 11
  • 2
  • 1
    Your `__setattr__` silently refuses to set any attribute other than `x`, `y`, `length`, or `vector`. – user2357112 Mar 29 '17 at 17:39
  • 1
    And what do you think will be returned from getattr if name is not vector, length or normal? – Daniel Roseman Mar 29 '17 at 17:40
  • 1
    You should not use such a `__setattr__`-Method because of your observed wired behaviour. Replace it with a `length`- or `vector`-property – Daniel Mar 29 '17 at 17:42
  • How would I get a self.length attribute to calculate the length every time it's called? And also, you guys imply that there's a way to change the 'silently refusing to set any other attribute' behavior. How? – user151768 Mar 29 '17 at 17:46

1 Answers1

1

You should not use such a __setattr__-Method because of your observed wired behaviour. Replace it with a length- or vector-property

class Vector(object):
    """A class to represent a 2D vector"""
    def __init__(self, tup):
        self.x = tup[0]
        self.y = tup[1]

    # retrieving attributes
    @property
    def vector(self):
        # returns the vector as a tuple
        # (x, y)
        return (self.x, self.y)

    @vector.setter
    def vector(self, value):
        self.x = value[0]
        self.y = value[1]

    @property
    def length(self):
        # returns the length as a float
        # sqrt(x^2 + y^2)
        return float( sqrt(self.x**2 + self.y**2) )
    magnitude = length

    @length.setter
    def length(self, value):
        # |(x, y)| * length
        # create a new, normalized vector of the same trajectory
        x, y = self.normal
        # then multiply it by a scalar to get the desired length
        self.x = x * value
        self.y = y * value

    @property
    def normal(self):
        # returns a normalized vector as a tuple
        # (x/l, y/l)
        len = self.length
        if len == 0:
            return (0, 0)
        else:
            x = self.x / len
            y = self.y / len
            return (x, y)

    # mathematical operations
    def __add__(self, other):
        # (x1 + x2, y1 + y2)
        x = self.x + other.x
        y = self.y + other.y
        return Vector((x, y))

    def __iadd__(self, other):
        self.x += other.x
        self.y += other.y
        return self

    def __sub__(self, other):
        # (x1 - x2, y1 - y2)
        x = self.x - other.x
        y = self.y - other.y
        return Vector((x, y))

    def __isub__(self, other):
        self.x -= other.x
        self.y -= other.y
        return self

class Visual_vector(Vector):
    """A class to represent the vectors shown on screen"""
    def __init__(self, origin, end_point):
        # qualities relative to a coord system
        Vector.__init__(self, end_point)

        # qualities relative to the screen
        self.origin = origin
        self.end_point = end_point
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • Note that since you put `magnitude = length` before you defined the `length` setter, `magnitude` doesn't support assignment. This matches the behavior of the questioner's code, but it's not clear whether this was deliberate on either side. – user2357112 Mar 29 '17 at 18:17
  • That was not intended on my part, but thank you for pointing it out! – user151768 Mar 29 '17 at 18:25
  • are there some situations in which `__setattr__` is more suited than `@property` (and vice versa), or is it a matter of preference? – user151768 Mar 29 '17 at 18:51
  • `__setattr__` is useful if you don't know the names of the attributes at design time. – Daniel Mar 29 '17 at 18:55