Bill
Bill

Reputation: 663

Missing class attribute when calling other method in class

I'm a novice at all this stuff so please go easy on me!

I wrote a class to compute various vector results. Several methods call on other methods within the class to construct the results. Most of this works fine, except for a peculiar problem. When I call one method from another method, the attribute for that method is somehow removed or missing and I get the error: AttributeError: 'list' object has no attribute 'dot_prod', even though the method 'dot_prod' is defined in the class. The only way I found to get around this was to establish a new instance of the object using the returned result of the original method call. In my code, I included both the problem code and workaround by comment switches, as well as comments in context to try to explain the issue.

from math import sqrt, acos, pi

class Vector:

def __init__(self, coordinates):
    try:
        if not coordinates:
            raise ValueError
        self.coordinates = tuple([x for x in coordinates])
        self.dimension = len(coordinates)

    except ValueError:
        raise ValueError('The coordinates must be nonempty')

    except TypeError:
        raise TypeError('The coordinates must be an iterable')


def scalar_mult(self, c):
    new_coordinates = [c*x for x in self.coordinates]
    return new_coordinates


def magnitude(self):
    coord_squared = [x**2 for x in self.coordinates]
    return sqrt(sum(coord_squared))


def normalize(self):
    try:
        mag = self.magnitude()
        norm = self.scalar_mult(1.0/mag)
        return norm

    except ZeroDivisionError:
        return 'Divide by zero error'


def dot_prod(self, v):
    return sum([x*y for x,y in zip(self.coordinates, v.coordinates)])


def angle(self, v):

## This section below is identical to an instructor example using normalized unit
## vectors but it does not work, error indication is 'dot_prod' not
## valid attribute for list object as verified by print(dir(u1)). Instructor is using v2.7, I'm using v3.6.2.
## Performing the self.normalize and v.normalize calls removes the dot_prod and other methods from the return.
## My solution was to create new instances of Vector class object on  self.normalize and v.normalize as shown below:
##        u1 = self.normalize()    # Non working case
##        u2 = v.normalize()       # Non working case
    u1 = Vector(self.normalize())
    u2 = Vector(v.normalize())

    unit_dotprod = round((u1.dot_prod(u2)), 8)

    print('Unit dot product:', unit_dotprod)

    angle = acos(unit_dotprod)
    return angle

#### Test Code #####

v1 = Vector([-7.579, -7.88])
v2 = Vector([22.737, 23.64])


print('Magnitude v1:', v1.magnitude())
print('Normalized v1:', v1.normalize())
print()

print('Magnitude v2:', v2.magnitude())
print('Normalized v2:', v2.normalize())
print()
print('Dot product:', v1.dot_prod(v2))
print('Angle_rad:', v1.angle(v2))

The method 'angle(self, v)' is where the problem lies as far as I can tell, the comment notes in the code explains a bit more. The variables u1 and u2 have a comment switch to toggle between, you'll see that in the working case, I created the new instances of the Vector object. I simply do not know what is the reason why the attributes are missing from the original method calls. The next line when u1.dot_prod(u2) is called is where the trace back error manifests, 'dot_prod' is missing from the attributes as verified by doing dir(u1) in the non-working case.

Appreciate the insights people have here. I don't know the technical jargon very well so hopefully I can follow along.

Upvotes: 0

Views: 1709

Answers (1)

HFBrowning
HFBrowning

Reputation: 2336

You were trying to pass in a list instead of a Vector to your dot_prod method (at command u2 = v.normalize(); the return object from that method is a list). Your issue, I think, is that you assumed that u2 would attach as an attribute to the class, but you must call self as some point to do that. There are two correct ways to call a method and reattach the output as an attribute:

(1) You can call it after the class is instantiated (created) like so:

    vec = Vector([-7.579, -7.88])
    vec.normal_coords = vec.normalize()

This approach works better if you aren't expecting to need to do it for every Vector instance, and if you don't need to use the attribute in a bunch of other methods. Since you need the normalized coordinates to find the angle, I would instead recommend:

(2) Attaching as an attribute during instantiation (long code below, to fully show how this would work):

from math import sqrt, acos, pi

class Vector(object):

    def __init__(self, coordinates):
        try:
            if not coordinates:
                raise ValueError
            self.coordinates = tuple([x for x in coordinates])
            self.dimension = len(coordinates)

            # Next line is what you want - and it works, even though
            # it *looks like* you haven't defined normalize() yet :)
            self.normalized = self.normalize()

        except ValueError:
            raise ValueError('The coordinates must be nonempty')

        except TypeError:
            raise TypeError('The coordinates must be an iterable')

   [...]

    def dot_prod(self, v):
        # v is class Vector here and in the angle() method
        return sum([x*y for x,y in zip(self.normalized, v.normalized)])


    def angle(self, v):
        unit_dotprod = round((self.dot_prod(v)), 8)

        print('Unit dot product:', unit_dotprod)
        angle = acos(unit_dotprod)

        return angle

Upvotes: 1

Related Questions