How to change list of objects into list of dicts? - python

I am quite surprised at the behavior of this code, which is inside a function:
for user in full_details["users"]:
user = collections.defaultdict(lambda: False, user)
if user["comments"]:
user["comments"] = [comment.__dict__ for comment in user["comments"]]
print("just converted user comments to dict objects")
print(user["comments"])
print("printing full details")
print(full_details)
My understanding was that if I modified a dictionary or a list, that modification applied to the object and would remain. However, when I change user["comments"] for each user in full_details["users"] within my if I am not then seeing those same changes again reflected in full_details just immediately after. Why is that? I thought that whenever you create a new list and assign it to a passed-in parameter, that new list will persist outside the function.
My trouble is that the change made here does not persist:
user["comments"] = [comment.__dict__ for comment in user["comments"]]
Also, full_details is a default_dict:
full_details = collections.defaultdict(lambda: False, thread_details.__dict__)

You are assigning "user" twice. First in the for statement. Then in the first line of the body of the for loop, you are creating a new object and also assigning it to "user". At this point, you have lost your reference to the original object.

Related

Later lines of code seemingly affecting how earlier lines are executed. What's going on?

The below block of code works as intended
patient = Patient()
patient.log = []
patient.id = "xyz"
patient.example_attribute = []
ALL_PATIENT_LOG = []
def update(patient, attribute, argument, replace = False):
now = datetime.now()
entry = {"argument": argument, "attribute": attribute, "time": now.strftime("%H:%M on %d/%m/%y")}
patient.log.append(entry)
But when I add the following to the end of update
entry["patient_id"] = patient.id # we also need to include the patient's ID for the global log
ALL_PATIENT_LOG.append(entry)
if replace:
patient.__dict__[attribute] = [argument]
else:
patient.__dict__[attribute].append(argument)
the patient.log entry is changed such that it also contains the patient id
This violates my understanding of how python works, as I thought that later lines cannot affect how earlier lines are executed. What have I misunderstood which prevents me from understanding why this executes the way it does? How could I change this code to get the desired behaviour? (two different log entries depending on whether its being appended to the patient log or the ALL_PATIENT_LOG)
The line
patient.log.append(entry)
Appends the entry dictionary to the patient.log, it does not "add" the elements of the entry dictionary to the list. So when you in the next line call
entry["patient_id"] = patient.id
You are changing the entry dictionary, and since the patient.log is referencing to that dictionary, it will have the updated information when you look up that list.
A way to solve this, is to create a copy of the dictionary, instead of a reference, also see this post :
patient.log.append(entry.copy())
As that post also mentions, make sure you understand how copy works, as it might not always work as intended. Especially if there are references within the object you copy.

How can I modify this class to check if a list of lists is empty or not?

I am trying to build a class object that can take in a list of lists and check the entries to see if one is empty, and if it does, returns a message.
class EmptyCheck():
def __init__(self, some_list):
self.some_list = some_list
def check_empty(some_list):
for item in some_list:
if '' in item:
print('please fill out survey')
data = [[1],[2],[''],['apples']]
x = EmptyCheck(data)
x.check_empty()
The problem seems to be that the EmptyCheck object is not iterable. But this is where I get a bit confused because I am not trying to iterate over the EmptyCheck object, I am trying to iterate over some_list. So I am hoping someone could help to clarify what is going on and help me to understand this issue a bit deeper. I suspect I will need to add some of the special dunder methods, but maybe I don't?
Your check_empty method checks a new list passed as a function argument, not the list you store as an instance attribute. Change it to:
def check_empty(self):
for item in self.some_list:
As a side note, you should return right after the print statement so that you don't print the same message multiple times in case there are multiple sub-lists with empty strings.

append to request.sessions[list] in Django

Something is bugging me.
I'm following along with this beginner tutorial for django (cs50) and at some point we receive a string back from a form submission and want to add it to a list:
https://www.youtube.com/watch?v=w8q0C-C1js4&list=PLhQjrBD2T380xvFSUmToMMzERZ3qB5Ueu&t=5777s
def add(request):
if 'tasklist' not in request.session:
request.session['tasklist'] = []
if request.method == 'POST':
form_data = NewTaskForm(request.POST)
if form_data.is_valid():
task = form_data.cleaned_data['task']
request.session['tasklist'] += [task]
return HttpResponseRedirect(reverse('tasks:index'))
I've checked the type of request.session['tasklist']and python shows it's a list.
The task variable is a string.
So why doesn't request.session['tasklist'].append(task) work properly? I can see it being added to the list via some print statements but then it is 'forgotten again' - it doesn't seem to be permanently added to the tasklist.
Why do we use this request.session['tasklist'] += [task] instead?
The only thing I could find is https://ogirardot.wordpress.com/2010/09/17/append-objects-in-request-session-in-django/ but that refers to a site that no longer exists.
The code works fine, but I'm trying to understand why you need to use a different operation and can't / shouldn't use the append method.
Thanks.
The reason why it does not work is because django does not see that you have changed anything in the session by using the append() method on a list that is in the session.
What you are doing here is essentially pulling out the reference to the list and making changes to it without the session backend knowing anything about it. An other way to explain:
The append() method is on the list itself not on the session object
When you call append() on the list you are only talking to the list and the list's parent (the session) has no idea what you guys are doing
When you however do an assignment on the session itself session['whatever'] = 'something' then it knows that something is up and changes are made
So the key here is that you need to operate on the session object directly if you want your changes to be updated automatically
Django only thinks it needs to save a changed session item if the item got reassigned to the session. See here: django session base code the __setitem__ method containing a self.modified = True statement.
The session['list'] += [new_element] adds a new list item (mutates the list stored in the session, so the list reference stays the same) and then gets it reassigned to the session again -> thus triggering first a __getitem__ call -> then your += / __iadd__ runs on the value read -> then a __setitem__ call is made (with the list ref. passed to it). You can see it in the django codebase that it marks the session after each __setitem__ call as modified.
The session['list'] = session['list'] + [new_item] mode of doing the same does create a new list every time it's run so its a bit less efficient, but you should not store hundreds of items in the session anyway. So you're probably fine. This also works exactly as above.
However if you use sub-keys in the session like session['list']['x'] = 'whatever' the session will not see itself as modified so you need to mark it as by request.session.modified = True
Short answer: It's about how Python chooses to implement the dict data structure.
Long answer:
Let's start by saying that request.session is a dictionary.
Quoting Django's documentation, "By default, Django only saves to the session database when the session has been modified – that is if any of its dictionary values have been assigned or deleted". Link
So, the problem is that the session database is not being modified by
request.session['tasklist'].append(task)
Seeing the related parts Django's Session base code (as posted by #Csaba K. in an answer), the variable self.modified is to be set True when setitem dunder method is called.
Now, at this step the problem seems like the setitem dunder method is not being called with request.session['tasklist'].append(task) but with request.session['tasklist'] += [task] it gets called. It is not due to if the reference of request.session['tasklist'] is changing or not as pointed out by another answer, because the reference to the underlying list remains the same.
To confirm, let's create a custom dictionary which extends the Python dict, and print something when setitem dunder method is called.
class MyDict(dict):
def __init__(self, globalVar):
super().__init__()
self.globalVar = globalVar
def __setitem__(self, key, value):
super().__setitem__(key, value)
print("Called Set item when: ", end="")
myDict = MyDict(0)
print("Creating Dict")
print("-----")
myDict["y"] = []
print("Adding a new key-value pair")
print("-----")
myDict["y"] += ["x"]
print(" using +=")
print("-----")
myDict["y"].append("x")
print("append")
print("-----")
myDict["y"].extend(["x"])
print("extend")
print("-----")
myDict["y"] = myDict["y"] + ["x"]
print(" using +",)
print("-----")
It prints:
Creating Dict
-----
Called Set item when: Adding a new key-value pair
-----
Called Set item when: using +=
-----
append
-----
extend
-----
Called Set item when: using +
-----
As we can see, setitem dunder method is called and in turn self.modified is set true only when adding a new key-value pair, or using += or using +, but not when initializing, appending or extending an iterable (in this case a list). Now, the operator + and += do very different things in Python, as explained in the other answer. += behaves more like the append method but in this case, I guess it's more about how Python chooses to implement the dict data structure rather than how +, += and append behave on lists.
I found this while doing some more searching:
https://code.djangoproject.com/wiki/NewbieMistakes
Scroll to 'Appending to a list in session doesn't work'
Again, it is a very dated entry but still seems to hold true.
Not completely satisfied because this does not answer the question as to 'why' this doesn't work, but at the very least confirms 'something's up' and you should probably still use the recommendations there.
(if anyone out there can actually explain this in a more verbose manner then I'd be happy to hear it)

Keep data in a variable after deletion of an object in Django

I am on Django 1.9
I would like to keep a list of ids after I deleted the objects having these ids (to be sent back to an Ajax function).
But because I delete these objects, the list is also emptied.
Here is my code:
relatedsteps = Step.objects.filter(theplace=theplaceclicked)
listofrelatedstepsid = relatedsteps.values('id')
response_data = {}
response_data['listofrelatedstepsid'] = listofrelatedstepsid
print(response_data['listofrelatedstepsid'])
relatedsteps.delete()
print(response_data['listofrelatedstepsid'])
The first
print(response_data['listofrelatedstepsid'])
returns
[{u'id': 589}]
But the second one returns:
[]
Any clue? Thanks a lot
QuerySet.values does not actually return a list, it returns a clone of the queryset. Each time you iterate on it (e.g. what print does) it hits the db, so the second print re-executes the query after delete.
What you should do instead is:
response_data['listofrelatedstepsid'] = list(listofrelatedstepsid)
As stated in django documentation
values(*fields)
Returns a ValuesQuerySet — a QuerySet subclass that
returns dictionaries when used as an iterable, rather than
model-instance objects.
So to fix your problem you have to use relatedsteps.values('id') as iterable, calling list or tuple on it is completely fine for you:
response_data['listofrelatedstepsid'] = list(listofrelatedstepsid)
print(response_data['listofrelatedstepsid'])
relatedsteps.delete()
print(response_data['listofrelatedstepsid'])

in python, how to check if list is not empty, and empty it if so?

I've seen several answers about how to check if list is empty, but didn't find excatly what i need.
shortly - in python, I need a way to check if list is full, and then empty it, but i need that the check will start just after i fill the list.
I'm define list by call my class - Packet()
class Packet(object):
"""description of class"""
def __init__(self):
self.newPacket = []
newPacket = Packet()
I have menu, which one of the options is to call function in the class to fill the list.
but, if the function get chose again, i need to empty the instance, and start a new one.
I've tried to do that:
if newPacket:
del newPacket
newPacket.MakePacket()
but this don't let me start the list by call the function..
if i disable the
if newPacket:
del newPacket
the function works just fine.
You appear to be confusing a particular Packet instance that you have created and chosen to name newPacket, with its attribute of the same name. Rather than delete the instance, or even delete the list, it sounds like you want to empty the list. Because you've given two different things the same name, the list in question is now accessible from your command-line as newPacket.newPacket (although which the object itself likes to refer to it, in its own methods, as self.newPacket).
So. When you del newPacket, you are removing the reference to the object newPacket from the current workspace. The interpreter will then raise a NameError if you try to do anything with that symbol, such as newPacket.MakePacket() - because that variable no longer exists in the current workspace.
If you want to implement Packet methods that count the items in the self.newPacket list attribute, or empty it, you could say:
class Packet(object):
# ...
def count( self ):
return len( self.newPacket )
def clear( self ):
del self.newPacket[:]
That incidentally illustrates one way of emptying a list, while retaining a reference to the now-empty list: del myList[:]
values = input("Enter a list (U CAN ALSO CREATE AN EMPTY LIST. IF YOU WANT THEN SIMPLY PRESS 'ENTER'). If not then write e.g. ABC or 1, 2 or 2019 to insert: ")
list = list(values)
if list == []:
print("Your list", list, "is an empty list!")
else:
print("Your list", list, "is not an empty list.")
Firstly, it'll take an input from user. Secondly, after giving the input it'll check the given input. Finally, after the checking process completion it'll give a msg to the user (will show a msg if the given input empty or not? Whatever the scenario is).

Categories

Resources