python: replace datetime.date with isoformat() in list - python

I want to replace the datetime objects in this list:
a = [
[datetime.date(2019,1,1), 100],
[datetime.date(2019,1,2), 187],
]
So I have created this function:
def date_to_iso(rows):
for line in rows:
for item in line:
if isinstance(item, datetime.date):
item = item.date().isoformat()
return tempL
Can anybody tell me why this function is not changing the list?

when you're assigning back to item, you're just re-using the item name, but the old reference is "lost". Assign to the original list with the index instead (using the idiomatic method with enumerate to yield the index and the element:
def date_to_iso(rows):
for line in rows:
for i,item in enumerate(line):
if isinstance(item, datetime.date):
line[i] = item.date().isoformat()
this code uses item as read-only, until it's necessary to replace it in the original list.
Note that line[i] = ... still uses the original line object, that's why it works.
Another way would be to use a list comprehension to fully rebuild the data, which would require slice assignment to rows[:] to simulate in-place change for rows:
def date_to_iso(rows):
rows[:] = [[item.date().isoformat() if isinstance(item, datetime.date)
else item for item in line] for line in rows]

Related

Why does this function to convert all items in a list to strings not work?

I have a two dimensional array and I try to convert all items within each array to strings.
First I tried to use a function to_str and this approach didn't work. I do not understand why it doesn't work (it returns the input unchanged):
lst = [['test1', 555], ['test2', 3333]]
def to_str(item):
for i in item:
if not isinstance(i, str):
i = str(i)
return item
output = list(map(lambda item:to_str(item), lst))
output: [['test1', 555], ['test2', 3333]]
Then I used a list comprehension instead, and it worked:
output = list(map(lambda item: [str(i) for i in item], lst))
output: [['test1', '555'], ['test2', '3333']]
Why does the first approach using to_str not work?
You're trying to modify the iteration variable named i. This has no effect at all, you're just rewriting the value of a local variable that points to a list element, but not changing the list itself. For this to work you have to modify the list elements at each index position, something like this:
def to_str(item):
# iterate over the indexes in the item
for i in range(len(item)):
# we can remove this check, and simply convert everything to str
if not isinstance(item[i], str):
item[i] = str(item[i])
return item
Or we can create a new list with the results, instead of overwriting the original (but this will be equivalent to using a list comprehension, better use a list comprehension):
def to_str(item):
result = []
for element in item:
# we can remove this check, and simply convert everything to str
if not isinstance(element, str):
result.append(str(element))
else:
result.append(element)
return result
Also, regarding your second approach: it'd be better if you avoid using list, map and lambda, if what you want is to create a new list as a result use a list comprehension directly. This is a more idiomatic way to solve the problem, also removing the unnecessary string check:
[[str(i) for i in item] for item in lst]
=> [['test1', '555'], ['test2', '3333']]
Converted value i is not used anywhere in approach #1 and function just returns input
def to_str(item):
result = []
for i in item:
if not isinstance(i, str):
i = str(i)
result.append(i)
return result

python 3: lists dont change their values

So I am trying to change a bunch of list items by a random percentage using a for loop.
import random as rdm
list = [1000, 100, 50, 25]
def change():
for item in list:
item = item + item*rdm.uniform(-10, 10)
change()
print(list)
(also I dont get how to paste multiple lines of code so I did them one by one, would appreciate help with that too)
And now when it prints the list it only consists of the numbers it started with.
Your item =.... line, as it stands, just associates a new object with the name item in the function's namespace. There is no reason why this operation would change the content of the list object from which the previous value of item was extracted.
Here is a listing that changes a list in-place:
import random
lyst = [1000,100,50,25]
def change(lyst):
for i, item in enumerate(lyst):
item = item + item * random.uniform(-10, 10)
lyst[i] = item
print(lyst)
change(lyst)
print(lyst)
The lyst[i] =... assignment is the key line that actually changes the list's content. Of course you can collapse the two assignments into one line if you want: lyst[i] = item =..... Or you can omit the reassignment to item if you're not going to use it again in the loop: lyst[i] = item + item *...
Note that I performed two minor fixes in addition: I changed the variable name from list to lyst so that it doesn't overshadow your builtin reference to the list class. I have also altered your function so that it takes the list object as an argument, rather than relying on referring to it using a hard-coded global variable name. Both of these are just good practice; nothing to do with your problem.
Finally, note that you can do all of this much more succinctly and transparently with a so-called list comprehension. If you don't have to modify the list in-place, i.e. if it's OK to end up with a modified copy of the original list:
lyst = [ item + item * random.uniform(-10, 10) for item in lyst ]
If you need to modify the list in-place (e.g. if other references to the original list exist elsewhere and they, too, should point to the updated content after change() is called) then you can follow the suggestion in Padraic's comment:
lyst[:] = [ item + item * random.uniform(-10, 10) for item in lyst ]
In that last case, you can even save memory (which will only be a concern for very large lists) if you change the bracket shape and thereby use a generator expression instead:
lyst[:] = ( item + item * random.uniform(-10, 10) for item in lyst )
You need to store the computed value back to the list.
So replace:
for item in list:
item = item + item*rdm.uniform(-10, 10)
With:
for index, item in enumerate(list):
list[index] = item + item*rdm.uniform(-10, 10);
With the modified loop, you will be looping through each element in the list with its index. You can now assign back the computed value to that particular index in the list.
using functional approach and list comprehensions instead of for loop, you can create a new list with the changed items (unless you NEED to modify the existing list in place):
import random as rdm
items = [1000, 100, 50, 25]
def change(items, multiply_min=-10, multiply_max=10):
return [i * rdm.uniform(multiply_min, multiply_max) for i in items]
items = change(items)
print(items)

How to do a one-line dict delete operation

Is there a way to do the following in one line?
[del item for item in new_json if item['Country'] in countries_to_remove]
The above gives me a SyntaxError.
del is a statement and you cannot use that as an expression in list comprehenstion. That is why you are getting a SyntaxError.
You can use list comprehension to create a new list, without the elements you don't want, like this
[item for item in new_json if item['Country'] not in countries_to_remove]
This is actually equivalent to,
result = []
for item in new_json:
if item['Country'] not in countries_to_remove:
result.append(item)
This kind of operation is called filtering a list and you can use the builtin filter function, like this
list(filter(lambda x: x['Country'] not in countries_to_remove, new_json))
As suggested by mgilson in the comments section, if you just want to mutate the original list, then you can use slicing assignment, like this
new_json[:] = [x for x in new_json if x['Country'] not in countries_to_remove]
del is a statement in python, and you cannot have statements inside list comprehension (You can only have expressions there). Why not just create new_json as a new list or dictionary that does not include the items you want to delete. Example =
new_json = [item for item in new_json if item['Country'] not in countries_to_remove]

Python logic error?

Basically, I'm trying to flatten a list in my function but ignore that (you can also ignore the print functions I put in).
take x = [[1,2,3],4,5] to be my variable.
I call prob7(x) but the issue is that when type([1,2,3]) gets checked == list, it returns false. Why is that? I explicitly check this on the interpreter command line and it returns true. But inside the function, I get a false.
Just a bug that I missed because I'm sleepy or am I misunderstanding some part of the Python language? I run version 2.6 if it matters.
def prob7(list): # flatten a list
tempList = []
if list: # meaning if there are elements in the list and it is not empty
for i in list:
if type(i) != list:
print tempList,'if',i,type(i)==list
tempList.append(i)
else:
print tempList,'else',i
tempList.extend(prob7(i))
return tempList
Just not use 'list' as a variable name and use isinstance(var, list) instead of type(var) == list.
Please find corrected sample below.
def prob7(mylist): # flatten a list
tempList = []
if mylist: # meaning if there are elements in the list and it is not empty
for i in mylist:
if not isinstance(i, list):
print tempList, 'if', i, isinstance(i, list)
tempList.append(i)
else:
print tempList, 'else', i
tempList.extend(prob7(i))
return tempList
Or if you don't really required to use recursion and you don't care about values order then you can use something like this:
lVals = [[1,2,3],4,5, [1,[4,7]]]
def make_flat(mylist): # flatten a list
while any(isinstance(x, list) for x in mylist):
for i, val in enumerate(mylist):
if isinstance(val, list):
mylist.extend(mylist.pop(i))
break
return mylist
make_flat(lVals)
>>> [4, 5, 1, 2, 3, 1, 4, 7]
Artisom has your answer. In addtion, type checks are not very Pythonic. Duck typing often is the way to go. In case your elements are numbers only, the following does the job too, without explicit type checks but behavior checks:
def prob7(inlist): # flatten a list
outlist = []
for x in inlist:
try:
outlist += x
except TypeError:
outlist.append(x)
return outlist
Note that string elements in this implementation would behave like nested lists. Anyway, just wanted to illustrate what it means to expect behavior, not types.
Some alternate approaches:
# Iterative, but more functional-style
def flatten(a_list):
while any(isinstance(x, list) for x in a_list):
a_list = sum((x if isinstance(x, list) else [x] for x in a_list), [])
return a_list
# Using a generator recursively,
# then evaluating the generator to produce the list
# instead of explicitly appending each element.
def flatten_gen(a_list):
for x in a_list:
if isinstance(x, list):
for y in flatten_gen(x): yield y
else: yield x
def flatten(a_list): return list(flatten_gen(a_list))
The problem here is you are using a local variable name (list) that is the same as the global list type. You should change your variable name. Also, when checking types like that you can use the is operator.
type(l) is list
But here's my version of flatten.
def flatten(alist):
rv = []
for val in alist:
if isinstance(val, list):
rv.extend(flatten(val))
else:
rv.append(val)
return rv
This does not alter the original list, but returns a new list. This is consistent with most other patterns.

python modify item in list, save back in list

I have a hunch that I need to access an item in a list (of strings), modify that item (as a string), and put it back in the list in the same index
I'm having difficulty getting an item back into the same index
for item in list:
if "foo" in item:
item = replace_all(item, replaceDictionary)
list[item] = item
print item
now I get an error
TypeError: list indices must be integers, not str
due to this line list[item] = item
which makes sense! but I do not know how to put the item back into the list at that same index using python
what is the syntax for this? Ideally the for loop can keep track of the index I am currently at
You could do this:
for idx, item in enumerate(list):
if 'foo' in item:
item = replace_all(...)
list[idx] = item
You need to use the enumerate function: python docs
for place, item in enumerate(list):
if "foo" in item:
item = replace_all(item, replaceDictionary)
list[place] = item
print item
Also, it's a bad idea to use the word list as a variable, due to it being a reserved word in python.
Since you had problems with enumerate, an alternative from the itertools library:
for place, item in itertools.zip(itertools.count(0), list):
if "foo" in item:
item = replace_all(item, replaceDictionary)
list[place] = item
print item
A common idiom to change every element of a list looks like this:
for i in range(len(L)):
item = L[i]
# ... compute some result based on item ...
L[i] = result
This can be rewritten using enumerate() as:
for i, item in enumerate(L):
# ... compute some result based on item ...
L[i] = result
See enumerate.
For Python 3:
ListOfStrings = []
ListOfStrings.append('foo')
ListOfStrings.append('oof')
for idx, item in enumerate(ListOfStrings):
if 'foo' in item:
ListOfStrings[idx] = "bar"

Categories

Resources