CharlyG
CharlyG

Reputation: 1

Confuse about self attribute and declaration in Python

I am currently trying to understand some basic concepts of Python.

Code Sample:

class BorgSingleton:
    _shared_borg_state = {}
    test = "toto"

    def __init__(self):
        self.__dict__ = self._shared_borg_state
        print("borg created")
        print(self.test)
    
class Point(BorgSingleton) :

    def __init__(self, lat, long):
        self.latitude = lat
        self.longitude = long
        super().__init__()
        print (self.latitude)
    
    def g(self) :
        return BorgSingleton.test +" " +str(self.latitude) + " " + str(self.longitude)
    
    
def main():
    point_1 = Point(5,2)
    point_2 = Point(3,3)
    print(point_1.g())
    print(point_2.g())
    point_1.test = "tata"
    print(point_1.g())
    print(point_2.g())

if (__name__ == "__main__"):
    main()

Code Execution:

borg created
toto
Traceback (most recent call last):
File "file1.py", line 32, in
main()
File "file1.py", line 23, in main
point_1 = Point(5,2)
File "file1.py", line 16, in init
print (self.latitude)
AttributeError: 'Point' object has no attribute 'latitude'

I'm sure I'm making a basic mistake in python, but am not sure why my instance point_1 of the class Point doesn't have the latitude attribute.

I read about Inheritance and Borg conception, but something is missing. I have also tried the -tt option for indentation mistake.

Upvotes: 0

Views: 186

Answers (2)

User051209
User051209

Reputation: 2503

Try to put super().__init__() as first instruction in __init__() method of class Point:

class BorgSingleton:
    _shared_borg_state = {}
    test = "toto"

    def __init__(self):
        self.__dict__ = self._shared_borg_state
        print("borg created")
        print(self.test)


class Point(BorgSingleton):

    def __init__(self, lat, long):
        super().__init__()
        self.latitude = lat
        self.longitude = long
        #super().__init__()
        print(self.latitude)

    def g(self):
        return BorgSingleton.test + " " + str(self.latitude) + " " + str(self.longitude)


def main():
    point_1 = Point(5, 2)
    point_2 = Point(3, 3)
    print(point_1.g())
    print(point_2.g())
    point_1.test = "tata"
    print(point_1.g())
    print(point_2.g())


if (__name__ == "__main__"):
    main()

With this modification the attributes latitute and longitude will be present in the instances of the class Point.

Upvotes: 1

Dmytro Novikov
Dmytro Novikov

Reputation: 31

The code you've wrote should've worked if self.__dict__ had not been overridden in the parent BorgSingleton class.

In Python, it is not a good practice to override magic methods (ones that start and end with double-unders) unless you know what you're doing.

To generalize what happened, lets see the below two classes:

class Parent:
    some_attribute = {}

    def __init__(self):
        self.__dict__ = self.some_attribute


class Child(Parent):
    def __init__(self, arg_1, arg_2):
        self.arg_1 = arg_1
        self.arg_2 = arg_2
        # at this point, an instance of class Child has arg_1 and arg_2
        # attributes, and self.__dict__ stores corresponding values
        super().__init__()
        # in the Parent class, self.__dict__ is overridden to an empty dict 
        # which makes arg_1 and arg_2 arguments unaccessible from the 
        # instance after a super call


test_instance = Child(1, 2)

Update based on additional question, asked in comments:

In order to change an attribute of several model instances, we have to iterate over a collection of these instances, setting the desired value to the corresponding attribute.

Having partially refactored your code, running this code:

class BorgSingleton:
    def __init__(self):
        """
        Assigning default value to attribute.
        """
        self.test = 'toto'


class Point(BorgSingleton):
    def __init__(self, lat, long):
        self.latitude = lat
        self.longitude = long
        super().__init__()

    def g(self):
        return f'{self.test} {self.latitude} {self.longitude}'


class Points:
    def __init__(self, collection_of_points):
        """
        Before we instantiate Points, check that collection_of_points is a list
        and all elements of a list are the instances of Point class.
        """
        if (isinstance(collection_of_points, list) and
                all([isinstance(x, Point) for x in collection_of_points])):
            self.points = collection_of_points
        else:
            raise ValueError

    def assign_new_value_for_test_attribute(self, value):
        """
        Iterate over all Point class instances, stored in self.points,
        and change values of test attribute.
        """
        [setattr(x, 'test', value) for x in self.points]


def main():
    point_1 = Point(5, 2)
    point_2 = Point(3, 3)
    print(point_1.g())
    print(point_2.g())
    points = Points([point_1, point_2])
    points.assign_new_value_for_test_attribute('tata')
    print(point_1.g())
    print(point_2.g())


if __name__ == "__main__":
    main()

results in this:

toto 5 2
toto 3 3
tata 5 2
tata 3 3

Here, we create an instance of Points class, which stores point_1 and point_2 instances in Points.points attribute. Then, calling Points.assign_new_value_for_test_attribute method, list comprehension is used to update test attribute of point_1 and point_2.

Upvotes: 2

Related Questions