Python dictionary reference returning something strange - python

I have a list of python dictionaries called checkout_items, created by a simple function (further simplified here, for ease of reading):
def checkout_items(request):
items = get_cart_items(request)
co_items = [] #a list of dictionaries to be used by the cart
#iterate through the items and homogenize them into a standardized checkout_item format
for i in items:
co_item = {'number': num,
'name': i.name,
'sku': i.sku,
'quantity': i.quantity,
'price': i.price}
I reference this list elsewhere in a view (again, simplified):
checkout_items = cart.checkout_items(request)
attributes = {}
for i in checkout_items:
attributes['item_name_'+ str(i['number'])] = str(i['name'])
attributes['item_number_'+ str(i['number'])] = str(i['sku'])
attributes['quantity_'+ str(i['number'])] = str(i['quantity'])
But the name variable is being set to this: <bound method CartItem.name of <CartItem: CartItem object>
Yet the sku (alphanumeric string, just like "name") comes through just fine, it seems. Both are coming directly from MySQL. Any ideas what's going on?

<bound method CartItem.name of <CartItem: CartItem object>
is an error usually given when you are calling to see if a method is there, not actually calling the method itself. I would try adding () so i.name()

as you say, the data you give to the function checkout_items() is from get_cart_items().
So items is not a stupid namespace-like object, but an object that has methods that you wrote, and all of them are CartItems.
Then when you build co_item, you give i.name to the dictionary, that is later printed out as bound method CartItem.name of <CartItem: CartItem object>.
It looks like your CartItem object has a method name! You should try i.name(). Or use whatever property that really has the name if name() is something else.

Related

Using replace method on list of model objects

I want to print out a list of objects, which at the moment looks like this: [<model.Name: value_of_field1>, <model.Name: value_of_field2>, etc]. I would like to get only the values, so I am hoping for an outcome like:
value_of_field1, value_of_field2, etc
My attempt to do this was by using: list = [item.replace('model.Name: ', '') for item in list], however, this resulted in the following error: 'model.Name' object has no attribute 'replace'.
How can I get a clean list, that I can later on use?
EDIT:
I forgot to mention that I tried using values_list(), however, it does not give the wanted result. My models look something like this:
class Article(models.Model):
title = models.ForeignKey(Title)
class Title(models.Model):
author = models.ForeignKey(User)
name = models.ForeignKey(NameArchive)
def __str__(self):
return '%s, %s' % (self.author, self.name)
where, author consists of 2 names. So, the values in the list (value_of_field1 etc) will look something like this - author_first_name author_last_name, article_name and values_list() gives only the last one and not all three.
The model's __repr__() method is reponsible for the additional <model:Name output that you're seeing.
You can call __str__() directly to give you the string representation you're after.
[str(o) for o in list_of_objects]

Python: Refer to objects dynamically

Apologies if this is a silly question.
I have a list of potential dictionary keys here:
form_fields = ['sex',
'birth',
'location',
'politics']
I am currently manually adding values to these keys like so:
self.participant.vars["sex"] = [Constants.fields_dict["sex"][0], Constants.fields_dict["sex"][1], self.player.name]
self.participant.vars["birth"] = [Constants.fields_dict["birth"][0], Constants.fields_dict["birth"][1],self.player.age]
self.participant.vars["location"] = [Constants.fields_dict["location"][0], Constants.fields_dict["location"][1],self.player.politics]
I'd like to be able to do a use a for loop to do this all at once like so:
for i in form_fields:
self.participant.vars[i] = [Constants.fields_dict[i][0], Constants.fields_dict[i][1], self.player.`i`]
Obviously, however, I can't reference the object self.player.i like that. Is there a way to reference that object dynamically?
use getattr, For example, getattr(x, 'foobar') is equivalent to x.foobar.
for i in form_fields:
self.participant.vars[i] = [Constants.fields_dict[i][0], Constants.fields_dict[i][1], getattr(self.player, i)]

Entire dictionary being updated when using "append" or "extend", despite accessing a single element in the dictionary

I have a dictionary in the format:
dictionary= {reference:annotation}
where the reference refers to a position, and the annotation contains information about that location.
I want to find reference positions that overlap, and update the annotation when that occurs. The annotation that I want to update is accessed by dictionary["reference"].qualifiers["ID"] (the annotation contains a second dictionary, where I can access the information I want).
When if I try to add another ID to the annotation using: d
dictionary[reference].qualifiers["ID"].extend(["new ID"])
or
dictionary[reference].qualifiers["ID"].append("new ID")
all reference annotations in my dictionary are being updated with that new ID.
However, if do this using basic list comprehension I get the desired result:
dictionary[reference].qualifiers["ID"] = dictionary[reference].qualifiers["ID"] + ["new ID"]
Only the annotation at that reference is updated. Can anyone explain why I am getting a different result using "append" or "extend"?
The first example you give as not working works for me:
class Annotation:
def __init__(self, initial_val):
self.qualifiers = {'ID': [initial_val]}
an1 = Annotation("foo")
an2 = Annotation("bar")
d = {'ref1' : an1, 'ref2': an2}
print d['ref1'].qualifiers['ID']
print d['ref2'].qualifiers['ID']
d['ref1'].qualifiers['ID'].extend(['new foo'])
print d['ref1'].qualifiers['ID']
print d['ref2'].qualifiers['ID']
results in:
~ mgregory$ python foo.py
['foo']
['bar']
['foo', 'new foo']
['bar']
~ mgregory$
I think you have something wrong with the way you are creating annotations - possibly mistaking a shallow copy for a deep one, or a similar data structure pitfall like that.
You need to post actual code that doesn't work.
As a side note, the code you described as a comprehension, is not. It's just use of the array operator +.

How can i add extra data in object list in django python

I am extending the list view and in every object i need to add a dictionary.
My dic look like this
mydict1['var1'] = {'value':20, 'result':None}
mydict1['var2'] = {'value':20, 'result':None}
mydict1['var3'] = {'value':20, 'result':None}
now i want to add that dictionary to all objects like
for myobj in self.get_queryset():
myobj.add(mydict1)
so that i access in my template like this
{{myobj.dict1.var1.value}}
It looks like you just want to set an attribute on your objects?
So this should work:
for myobj in self.get_queryset():
myobj.dict1 = mydict1

dict.get() method returns a pointer

Let's say I have this code:
my_dict = {}
default_value = {'surname': '', 'age': 0}
# get info about john, or a default dict
item = my_dict.get('john', default_value)
# edit the data
item[surname] = 'smith'
item[age] = 68
my_dict['john'] = item
The problem becomes clear, if we now check the value of default_value:
>>> default_value
{'age': 68, 'surname': 'smith'}
It is obvious, that my_dict.get() did not return the value of default_value, but a pointer (?) to it.
The problem could be worked around by changing the code to:
item = my_dict.get('john', {'surname': '', 'age': 0})
but that doesn't seem to be a nice way to do it. Any ideas, comments?
item = my_dict.get('john', default_value.copy())
You're always passing a reference in Python.
This doesn't matter for immutable objects like str, int, tuple, etc. since you can't change them, only point a name at a different object, but it does for mutable objects like list, set, and dict. You need to get used to this and always keep it in mind.
Edit: Zach Bloom and Jonathan Sternberg both point out methods you can use to avoid the call to copy on every lookup. You should use either the defaultdict method, something like Jonathan's first method, or:
def my_dict_get(key):
try:
item = my_dict[key]
except KeyError:
item = default_value.copy()
This will be faster than if when the key nearly always already exists in my_dict, if the dict is large. You don't have to wrap it in a function but you probably don't want those four lines every time you access my_dict.
See Jonathan's answer for timings with a small dict. The get method performs poorly at all sizes I tested, but the try method does better at large sizes.
Don't use get. You could do:
item = my_dict.get('john', default_value.copy())
But this requires a dictionary to be copied even if the dictionary entry exists. Instead, consider just checking if the value is there.
item = my_dict['john'] if 'john' in my_dict else default_value.copy()
The only problem with this is that it will perform two lookups for 'john' instead of just one. If you're willing to use an extra line (and None is not a possible value you could get from the dictionary), you could do:
item = my_dict.get('john')
if item is None:
item = default_value.copy()
EDIT: I thought I'd do some speed comparisons with timeit. The default_value and my_dict were globals. I did them each for both if the key was there, and if there was a miss.
Using exceptions:
def my_dict_get():
try:
item = my_dict['key']
except KeyError:
item = default_value.copy()
# key present: 0.4179
# key absent: 3.3799
Using get and checking if it's None.
def my_dict_get():
item = my_dict.get('key')
if item is None:
item = default_value.copy()
# key present: 0.57189
# key absent: 0.96691
Checking its existance with the special if/else syntax
def my_dict_get():
item = my_dict['key'] if 'key' in my_dict else default_value.copy()
# key present: 0.39721
# key absent: 0.43474
Naively copying the dictionary.
def my_dict_get():
item = my_dict.get('key', default_value.copy())
# key present: 0.52303 (this may be lower than it should be as the dictionary I used was one element)
# key absent: 0.66045
For the most part, everything except the one using exceptions are very similar. The special if/else syntax seems to have the lowest time for some reason (no idea why).
In Python dicts are both objects (so they are always passed as references) and mutable (meaning they can be changed without being recreated).
You can copy your dictionary each time you use it:
my_dict.get('john', default_value.copy())
You can also use the defaultdict collection:
from collections import defaultdict
def factory():
return {'surname': '', 'age': 0}
my_dict = defaultdict(factory)
my_dict['john']
The main thing to realize is that everything in Python is pass-by-reference. A variable name in a C-style language is usually shorthand for an object-shaped area of memory, and assigning to that variable makes a copy of another object-shaped area... in Python, variables are just keys in a dictionary (locals()), and the act of assignment just stores a new reference. (Technically, everything is a pointer, but that's an implementation detail).
This has a number of implications, the main one being there will never be an implicit copy of an object made because you passed it to a function, assigned it, etc. The only way to get a copy is to explicitly do so. The python stdlib offers a copy module which contains some things, including a copy() and deepcopy() function for when you want to explicitly make a copy of something. Also, some types expose a .copy() function of their own, but this is not a standard, or consistently implemented. Others which are immutable tend to sometimes offer a .replace() method, which makes a mutated copy.
In the case of your code, passing in the original instance obviously doesn't work, and making a copy ahead of time (when you may not need to) is wasteful. So the simplest solution is probably...
item = my_dict.get('john')
if item is None:
item = default_dict.copy()
It would be useful in this case if .get() supported passing in a default value constructor function, but that's probably over-engineering a base class for a border case.
because my_dict.get('john', default_value.copy()) would create a copy of default dict each time get is called (even when 'john' is present and returned), it is faster and very OK to use this try/except option:
try:
return my_dict['john']
except KeyError:
return {'surname': '', 'age': 0}
Alternatively, you can also use a defaultdict:
import collections
def default_factory():
return {'surname': '', 'age': 0}
my_dict = collections.defaultdict(default_factory)

Categories