Convert an Array, converted to a String, back to an Array - python

I recently found an interesting behaviour in python due to a bug in my code.
Here's a simplified version of what happened:
a=[[1,2],[2,3],[3,4]]
print(str(a))
console:
"[[1,2],[2,3],[3,4]]"
Now I wondered if I could convert the String back to an Array.Is there a good way of converting a String, representing an Array with mixed datatypes( "[1,'Hello',['test','3'],True,2.532]") including integers,strings,booleans,floats and arrays back to an Array?

There's always everybody's old favourite ast.literal_eval
>>> import ast
>>> x = "[1,'Hello',['test','3'],True,2.532]"
>>> y = ast.literal_eval(x)
>>> y
[1, 'Hello', ['test', '3'], True, 2.532]
>>> z = str(y)
>>> z
"[1, 'Hello', ['test', '3'], True, 2.532]"

ast.literal_eval is better. Just to mention, this is also a way.
a=[[1,2],[2,3],[3,4]]
string_list = str(a)
original_list = eval(string_list)
print original_list == a
# True

Related

Regarding "f-strings now support = for quick and easy debugging", how to print `f'{array[{i=}]=}'` with index also "="-expanded?

For simplicity, we have an array
>>> arr = [1,2,3]
>>> for i in range(len(arr)):
>>> print(f'{arr[i]=}')
we get
>>> arr[i]=1
>>> arr[i]=2
>>> arr[i]=3
Would it be possible to expand to output like this
>>> arr[i=0]=1
>>> arr[i=1]=2
>>> arr[i=2]=3
or
>>> arr[0]=1
>>> arr[1]=2
>>> arr[2]=3
The real practice is to debug the code and check the array with >1000 elements.
Neither print(f'{arr[{i=}]=}') nor print(f'{arr[{i}]=}') can work for me.
I get the idea, but isn't it much more readable to just do:
for i, x in enumerate(arr):
print(f'arr[{i}]={x}')

Changing all values of string to a certain value

input = ["AB0","A","BBBB"]
output = ["000","0","0000"]
Is there a function like .replace("", "") which could take in any input and give a string of zeros with the same number of characters?
There is no such built-in function, but you can easily write a list comprehension for that:
>>> input = ["AB0","A","BBBB"]
>>>
>>> ["0" * len(item) for item in input]
['000', '0', '0000']
Another way to do this (mostly for fun):
>>> input = ["AB0", "A", "BBBB"]
>>> zeros = ''.zfill
>>> [zeros(len(s)) for s in input]
['000', '0', '0000']
Note that this only works for filling with 0. If you want to fill with different characters then this method won't work.
You could use ljsut or rjust to fill with different characters...
>>> input = ["AB0", "A", "BBBB"]
>>> pad = ''.ljust
>>> [pad(len(s), '1') for s in input]
['111', '1', '1111']
However, most of these are really just clever ways to do it. They aren't faster:
>>> timeit.timeit("[pad(len(s), '1') for s in input]", 'from __main__ import pad, input')
1.3355789184570312
>>> timeit.timeit("['1' * len(s) for s in input]", 'from __main__ import pad, input')
0.8812301158905029
>>> zeros = ''.zfill
>>> timeit.timeit("[zeros(len(s)) for s in input]", 'from __main__ import zeros, input')
1.110482931137085
though, depending on your particular preferences/background, you might find one way clearer to understand than another (and that's worth something)...
FWIW, my first instinct is to use the multiplication method as proposed in
Selcuk's answer so that's probably what I find most easy to read and understandable...
This will work:
input = ["AB0","A","BBBB"]
output = ["0"*len(x) for x in input]
or the same:
input = ["AB0","A","BBBB"]
output = []
for x in input:
output.append("0"*len(x))
You can use python 're' module, like following:
import re
input = ["AB0","A","BBBB"]
output = []
for value in input:
str = re.sub(".","0",value)
output.append(str)
print output
map(lambda x:"0"*len(x),["AB0","A","BBB"])

Split string to various data types

I would like to convert the following string:
s = '1|2|a|b'
to
[1, 2, 'a', 'b']
Is it possible to do the conversion in one line?
Is it possible to do the conversion in one line?
YES, It is possible. But how?
Algorithm for the approach
Split the string into its constituent parts using str.split. The output of this is
>>> s = '1|2|a|b'
>>> s.split('|')
['1', '2', 'a', 'b']
Now we have got half the problem. Next we need to loop through the split string and then check if each of them is a string or an int. For this we use
A list comprehension, which is for the looping part
str.isdigit for finding if the element is an int or a str.
The list comprehension can be easily written as [i for i in s.split('|')]. But how do we add an if clause there? This is covered in One-line list comprehension: if-else variants. Now that we know which all elements are int and which are not, we can easily call the builtin int on it.
Hence the final code will look like
[int(i) if i.isdigit() else i for i in s.split('|')]
Now for a small demo,
>>> s = '1|2|a|b'
>>> [int(i) if i.isdigit() else i for i in s.split('|')]
[1, 2, 'a', 'b']
As we can see, the output is as expected.
Note that this approach is not suitable if there are many types to be converted.
You cannot do it for negative numbers or lots of mixed types in one line but you could use a function that would work for multiple types using ast.literal_eval:
from ast import literal_eval
def f(s, delim):
for ele in s.split(delim):
try:
yield literal_eval(ele)
except ValueError:
yield ele
s = '1|-2|a|b|3.4'
print(list(f(s,"|")))
[1, -2, 'a', 'b', 3.4]
Another way, is using map built-in method:
>>> s='1|2|a|b'
>>> l = map(lambda x: int(x) if x.isdigit() else x, s.split('|'))
>>> l
[1, 2, 'a', 'b']
If Python3, then:
>>> s='1|2|a|b'
>>> l = list(map(lambda x: int(x) if x.isdigit() else x, s.split('|')))
>>> l
[1, 2, 'a', 'b']
Since map in Python3 would give a generator, so you must convert it to list
It is possible to do arbitrarily many or complex conversions "in a single line" if you're allowed a helper function. Python does not natively have a "convert this string to the type that it should represent" function, because what it "should" represent is vague and may change from application to application.
def convert(input):
converters = [int, float, json.loads]
for converter in converters:
try:
return converter(input)
except (TypeError, ValueError):
pass
# here we assume if all converters failed, it's just a string
return input
s = "1|2.3|a|[4,5]"
result = [convert(x) for x in s.split("|")]
If you have all kinds of data types(more than str and int), I believe this does the job.
s = '1|2|a|b|[1, 2, 3]|(1, 2, 3)'
print [eval(x) if not x.isalpha() else x for x in s.split("|")]
# [1, 2, 'a', 'b', [1, 2, 3], (1, 2, 3)]
This fails if there exists elements such as "b1"

How To Change Lists of Unicode to ASCII

I'm trying to change these lists I'm getting from my school grade site from Unicode to ASCII.
The lists look like this:
x = grades[1]
print x #Input
[u'B', u'84'] #Output
But I don't want the u in there. I've tried to use
a.encode('ascii','ignore')
But I get
Traceback (most recent call last):
File "C:\Python27\Project.py", line 33, in <module>
L1.encode('ascii','ignore')
AttributeError: 'list' object has no attribute 'encode'
is there anyway to do this?
The problem is that you're trying to encode a list full of strings, not a string. You can't call string methods on a list of strings; you have to call them on each string.
The pythonic way to do this is with a list comprehension:
>>> x = [u'B', u'84']
>>> y = [s.encode('ascii', 'ignore') for s in x]
>>> y
['B', '84']
Under the covers, this is basically the same as:
>>> x = [u'B', u'84']
>>> y = []
>>> for s in x:
... y.append(s.encode('ascii', 'ignore'))
>>> y
['B', '84']
But it's more concise, harder to get wrong, and (once you get the basic idea of list comprehensions) easier to read.
It's also the same as either of the following:
y = map(lambda s: s.encode('ascii', 'ignore'), x)
y = map(partial(unicode.encode, encoding='ascii', errors='ignore'), x)
Generally, if you need to write a lambda or non-trivial partial, a list comprehension will be more readable than a map call. But in cases where you have a function ready to use, map is often nicer.
You could simply apply the str() function to each element of your list. In python 2, str objects are ASCII encoded. For example (in a python shell):
>>> x = [u'B', u'84']
>>> x
[u'B', u'84']
>>> x[0]
u'B'
>>> x[1]
u'84'
>>> str(x[0])
'B'
>>> str(x[1])
'84'
And if a list is needed you can use a list comprehension :
>>> y = [str(i) for i in x]
>>> y
['B', '84']
or the map function:
>>> z = map(str, x)
>>> z
['B', '84']
>>>
Hope this is what your looking for. Regards!

Interpreting Strings as Other Data Types in Python

I'm reading a file into python 2.4 that's structured like this:
field1: 7
field2: "Hello, world!"
field3: 6.2
The idea is to parse it into a dictionary that takes fieldfoo as the key and whatever comes after the colon as the value.
I want to convert whatever is after the colon to it's "actual" data type, that is, '7' should be converted to an int, "Hello, world!" to a string, etc. The only data types that need to be parsed are ints, floats and strings. Is there a function in the python standard library that would allow one to make this conversion easily?
The only things this should be used to parse were written by me, so (at least in this case) safety is not an issue.
First parse your input into a list of pairs like fieldN: some_string. You can do this easily with re module, or probably even simpler with slicing left and right of the index line.strip().find(': '). Then use a literal eval on the value some_string:
>>> import ast
>>> ast.literal_eval('6.2')
6.2
>>> type(_)
<type 'float'>
>>> ast.literal_eval('"Hello, world!"')
'Hello, world!'
>>> type(_)
<type 'str'>
>>> ast.literal_eval('7')
7
>>> type(_)
<type 'int'>
You can attempt to convert it to an int first using the built-in function int(). If the string cannot be interpreted as an int a ValueError exception is raised. You can then attempt to convert to a float using float(). If this fails also then just return the initial string
def interpret(val):
try:
return int(val)
except ValueError:
try:
return float(val)
except ValueError:
return val
You can use yaml to parse the literals which is better than ast in that it does not throw you an error if strings are not wrapped around extra pairs of apostrophes or quotation marks.
>>> import yaml
>>> yaml.safe_load('7')
7
>>> yaml.safe_load('Hello')
'Hello'
>>> yaml.safe_load('7.5')
7.5
For older python versions, like the one being asked, the eval function can be used but, to reduce evilness, a dict to be the global namespace should be used as second argument to avoid function calls.
>>> [eval(i, {"__builtins__":None}) for i in ['6.2', '"Hello, world!"', '7']]
[6.2, 'Hello, world!', 7]
Since the "only data types that need to be parsed are int, float and str", maybe somthing like this will work for you:
entries = {'field1': '7', 'field2': "Hello, world!", 'field3': '6.2'}
for k,v in entries.items():
if v.isdecimal():
conv = int(v)
else:
try:
conv = float(v)
except ValueError:
conv = v
entries[k] = conv
print(entries)
# {'field2': 'Hello, world!', 'field3': 6.2, 'field1': 7}
There is strconv lib.
In [22]: import strconv
/home/tworec/.local/lib/python2.7/site-packages/strconv.py:200: UserWarning: python-dateutil is not installed. As of version 0.5, this will be a hard dependency of strconv fordatetime parsing. Without it, only a limited set of datetime formats are supported without timezones.
warnings.warn('python-dateutil is not installed. As of version 0.5, '
In [23]: strconv.convert('1.2')
Out[23]: 1.2
In [24]: type(strconv.convert('1.2'))
Out[24]: float
In [25]: type(strconv.convert('12'))
Out[25]: int
In [26]: type(strconv.convert('true'))
Out[26]: bool
In [27]: type(strconv.convert('tRue'))
Out[27]: bool
In [28]: type(strconv.convert('12 Jan'))
Out[28]: str
In [29]: type(strconv.convert('12 Jan 2018'))
Out[29]: str
In [30]: type(strconv.convert('2018-01-01'))
Out[30]: datetime.date
Hope this helps to do what you are trying to do:
#!/usr/bin/python
a = {'field1': 7}
b = {'field2': "Hello, world!"}
c = {'field3': 6.2}
temp1 = type(a['field1'])
temp2 = type(b['field2'])
temp3 = type(c['field3'])
print temp1
print temp2
print temp3
Thanks to wim for helping me figure out what I needed to search for to figure this out.
One can just use eval():
>>> a=eval("7")
>>> b=eval("3")
>>> a+b
10
>>> b=eval("7.2")
>>> a=eval("3.5")
>>> a+b
10.699999999999999
>>> a=eval('"Hello, "')
>>> b=eval('"world!"')
>>> a+b
'Hello, world!'

Categories