2

I've run into an odd issue. It started with trying to create objects in django in a loop. I have an override on the model save() that does something like

self.history.append(new_status)

Now, I thought self meant the current instance being evaluated. But, when run in a loop (on a single python process) self.history caches from the last time it was evaluated.

So, in the example:

for i in range(3):
    job = Job(...properties, exluding history, as that's done in the save() method)
    job.save()

The jobs stored in the database look like this:

job1 - id: 1, history: ['pending']
job2 - id: 2, history: ['pending', 'pending']
job3 - id: 3, history: ['pending', 'pending', 'pending']
... so on

Now, if the django server is not restarted, and this controller is invoked again, then the jobs will be created, starting with history of the last object it created (so it would start with 4 pendings, then the next 5 and so on).

Essentially, Previous X + current N records

I have been able to largely replicate this in simple Python:

class Base(object):
    id = None
    name = None
    history = list()

    def __init__(self, id=None, name=None):
        self.id = id
        self.name = name

    def save(self, **kwargs):
        for arg in kwargs:
            self.__setattr__(arg, kwargs.get(arg))


class Queue(Base):
    def save(self, **kwargs):
        self.history.append({'status': 'test'})
        super(Queue, self).save()


items = []
for i in range(5):

    items.append(Queue(
        id=i,
        name=str(i) + '-test'
    ))

for _ in items:
    _.save()
    print(_.history)

when run in a debugger, you get this:

[{'status': 'test'}]
[{'status': 'test'}, {'status': 'test'}]
[{'status': 'test'}, {'status': 'test'}, {'status': 'test'}]
[{'status': 'test'}, {'status': 'test'}, {'status': 'test'}, {'status': 'test'}]
[{'status': 'test'}, {'status': 'test'}, {'status': 'test'}, {'status': 'test'}, {'status': 'test'}]

I really can't figure this out - it smells like this can't be a python issue, but a lack of understanding of how objects/referencing works in Python.

Any help would be great! Thanks.

Trent
  • 2,909
  • 1
  • 31
  • 46
  • Looks like this https://stackoverflow.com/questions/38288214/how-to-append-values-to-python-list-instead-of-replacing-the-values-with-latest. The OP has you issue, but inverted – Giancarlo Ventura Jan 15 '19 at 03:40

1 Answers1

1

The reason is that history is a class variable, so it's the same variable along all the class instances, so everytime you call self.history.append({'status': 'test'}) in Queue, the change is in the scope of all instances.

If you want history to be an instance variable, it should be declared inside a method.

EDIT

class Base(object):
    id = None
    name = None
    history = list() # <-------- Class variable, global to all instances.

    def __init__(self, id=None, name=None):
        self.id = id
        self.name = name
        self.instance_variable = None # <------- Instance variable, global to all methods of the instance.

        local_variable = None # <------ Local variable, local to the method of the instance

    def save(self, **kwargs):
        for arg in kwargs:
            self.__setattr__(arg, kwargs.get(arg))
edin
  • 52
  • 8
HuLu ViCa
  • 5,077
  • 10
  • 43
  • 93
  • So in django, when you define models - how can you ever create a totally new instance with clean class variables? history = JSONField - but it's being built in the save() method? Wouldn't this be a major issue? – Trent Jan 15 '19 at 04:06
  • I think I am not getting your point. The scope of class variables is all instances of the class, so you can not create two class variables with the same name. It would be like creating two variables with the same name in a method. Maybe you mean **instance variables**, which scope is the instance and are global to all the methods of it. In the body of my answer I added how to declare variables with different scopes. – HuLu ViCa Jan 15 '19 at 04:14