Partial list unpack in Python - python

In Python, the assignment operator can unpack a list or a tuple into variables, like this:
l = (1, 2)
a, b = l # Here goes auto unpack
But I need to specify exactly the same amount of names to the left as an item count in the list to the right. But sometimes I don't know the size of the list to the right, for example, if I use split().
Example:
a, b = "length=25".split("=") # This will result in a="length" and b=25
But the following code will lead to an error:
a, b = "DEFAULT_LENGTH".split("=") # Error, list has only one item
Is it possible to somehow unpack the list in the example above so I can get a = "DEFAULT_LENGTH" and b equals to None or not set? A straightforward way looks kind of long:
a = b = None
if "=" in string :
a, b = string.split("=")
else :
a = string

This may be of no use to you unless you're using Python 3. However, for completeness, it's worth noting that the extended tuple unpacking introduced there allows you to do things like:
>>> a, *b = "length=25".split("=")
>>> a,b
("length", ['25'])
>>> a, *b = "DEFAULT_LENGTH".split("=")
>>> a,b
("DEFAULT_LENGTH", [])
I.e. tuple unpacking now works similarly to how it does in argument unpacking, so you can denote "the rest of the items" with *, and get them as a (possibly empty) list.
Partition is probably the best solution for what you're doing however.

# this will result in a="length" and b="25"
a, b = "length=25".partition("=")[::2]
# this will result in a="DEFAULT_LENGTH" and b=""
a, b = "DEFAULT_LENGTH".partition("=")[::2]

This is slightly better than your solution but still not very elegant; it wouldn't surprise me if there's a better way to do it.
a, b = (string.split("=") + [None])[:2]

The nicest way is using the partition string method:
Split the string at the first occurrence of sep, and return a 3-tuple containing the part before the separator, the separator itself, and the part after the separator. If the separator is not found, return a 3-tuple containing the string itself, followed by two empty strings.
New in version 2.5.
>>> inputstr = "length=25"
>>> inputstr.partition("=")
('length', '=', '25')
>>> name, _, value = inputstr.partition("=")
>>> print name, value
length 25
It also works for strings not containing the =:
>>> inputstr = "DEFAULT_VALUE"
>>> inputstr.partition("=")
('DEFAULT_VALUE', '', '')
If for some reason you are using a version of Python before 2.5, you can use list-slicing to do much the same, if slightly less tidily:
>>> x = "DEFAULT_LENGTH"
>>> a = x.split("=")[0]
>>> b = "=".join(x.split("=")[1:])
>>> print (a, b)
('DEFAULT_LENGTH', '')
..and when x = "length=25":
('length', '25')
Easily turned into a function or lambda:
>>> part = lambda x: (x.split("=")[0], "=".join(x.split("=")[1:]))
>>> part("length=25")
('length', '25')
>>> part('DEFAULT_LENGTH')
('DEFAULT_LENGTH', '')

You could write a helper function to do it.
>>> def pack(values, size):
... if len(values) >= size:
... return values[:size]
... return values + [None] * (size - len(values))
...
>>> a, b = pack('a:b:c'.split(':'), 2)
>>> a, b
('a', 'b')
>>> a, b = pack('a'.split(':'), 2)
>>> a, b
('a', None)

But sometimes I don't know a size of the list to the right, for example if I use split().
Yeah, when I've got cases with limit>1 (so I can't use partition) I usually plump for:
def paddedsplit(s, find, limit):
parts= s.split(find, limit)
return parts+[parts[0][:0]]*(limit+1-len(parts))
username, password, hash= paddedsplit(credentials, ':', 2)
(parts[0][:0] is there to get an empty ‘str’ or ‘unicode’, matching whichever of those the split produced. You could use None if you prefer.)

Don't use this code, it is meant as a joke, but it does what you want:
a = b = None
try: a, b = [a for a in 'DEFAULT_LENGTH'.split('=')]
except: pass

Many other solutions have been proposed, but I have to say the most straightforward to me is still
a, b = string.split("=") if "=" in string else (string, None)

As an alternative, perhaps use a regular expression?
>>> import re
>>> unpack_re = re.compile("(\w*)(?:=(\w*))?")
>>> x = "DEFAULT_LENGTH"
>>> unpack_re.match(x).groups()
('DEFAULT_LENGTH', None)
>>> y = "length=107"
>>> unpack_re.match(y).groups()
('length', '107')
If you make sure the re.match() always succeeds, .groups() will always return the right number of elements to unpack into your tuple, so you can safely do
a,b = unpack_re.match(x).groups()

I don't recommend using this, but just for fun here's some code that actually does what you want. When you call unpack(<sequence>), the unpack function uses the inspect module to find the actual line of source where the function was called, then uses the ast module to parse that line and count the number of variables being unpacked.
Caveats:
For multiple assignment (e.g. (a,b) = c = unpack([1,2,3])), it only uses the first term in the assignment
It won't work if it can't find the source code (e.g. because you're calling it from the repl)
It won't work if the assignment statement spans multiple lines
Code:
import inspect, ast
from itertools import islice, chain, cycle
def iter_n(iterator, n, default=None):
return islice(chain(iterator, cycle([default])), n)
def unpack(sequence, default=None):
stack = inspect.stack()
try:
frame = stack[1][0]
source = inspect.getsource(inspect.getmodule(frame)).splitlines()
line = source[frame.f_lineno-1].strip()
try:
tree = ast.parse(line, 'whatever', 'exec')
except SyntaxError:
return tuple(sequence)
exp = tree.body[0]
if not isinstance(exp, ast.Assign):
return tuple(sequence)
exp = exp.targets[0]
if not isinstance(exp, ast.Tuple):
return tuple(sequence)
n_items = len(exp.elts)
return tuple(iter_n(sequence, n_items, default))
finally:
del stack
# Examples
if __name__ == '__main__':
# Extra items are discarded
x, y = unpack([1,2,3,4,5])
assert (x,y) == (1,2)
# Missing items become None
x, y, z = unpack([9])
assert (x, y, z) == (9, None, None)
# Or the default you provide
x, y, z = unpack([1], 'foo')
assert (x, y, z) == (1, 'foo', 'foo')
# unpack() is equivalent to tuple() if it's not part of an assignment
assert unpack('abc') == ('a', 'b', 'c')
# Or if it's part of an assignment that isn't sequence-unpacking
x = unpack([1,2,3])
assert x == (1,2,3)
# Add a comma to force tuple assignment:
x, = unpack([1,2,3])
assert x == 1
# unpack only uses the first assignment target
# So in this case, unpack('foobar') returns tuple('foo')
(x, y, z) = t = unpack('foobar')
assert (x, y, z) == t == ('f', 'o', 'o')
# But in this case, it returns tuple('foobar')
try:
t = (x, y, z) = unpack('foobar')
except ValueError as e:
assert str(e) == 'too many values to unpack'
else:
raise Exception("That should have failed.")
# Also, it won't work if the call spans multiple lines, because it only
# inspects the actual line where the call happens:
try:
(x, y, z) = unpack([
1, 2, 3, 4])
except ValueError as e:
assert str(e) == 'too many values to unpack'
else:
raise Exception("That should have failed.")

Have you tried this?
values = aString.split("=")
if len(values) == 1:
a = values[0]
else:
a, b = values

Related

how to get the variable from an any() function

I am looking to get the list_select variable that meets the criteria and do an append in the next line.
How can I make this available in the list_select.append(dupe) line?
if any(list_select in dupe for list_select in pattern_dict):
list_select.append(dupe)
You can't with any, which just returns a boolean. Use a generator expression instead:
gen = (x for x in pattern_dict if x in dupe)
list_select = next(gen, None)
if list_select is not None:
...
The any function is supposed to return a boolean, so if you want the slightly different behaviour of returning the first match, write a function with that behaviour:
def first(seq):
return next(iter(seq), None)
Usage:
>>> first( i for i in range(10) if i**2 > 10 )
4
>>> first( c for c in 'Hello, world!' if c.islower() )
'e'
>>> first( i for i in range(10) if i == 100 ) is None
True
To use in your example, you would write something like:
list_select = first( x for x in pattern_dict if x in dupe )
if list_select is not None:
list_select.append(dupe)
If you are using Python 3.8 or later, the dreaded "walrus" operator allows for a more direct solution:
if any((list_select := x) in dupe for x in pattern_dict):
list_select.append(dupe)
This situation happens to be one of the motivating examples for introducing the walrus operator.

Pyparsing parseaction and lists typerror

For a small language I want to parse expressions of the form "X [Y,Z,V]" where X, Y, Z, V are natural numbers.
Below is my attempt.
from pyparsing import *
class Y():
def __init__(self, ls):
self.ls = ls
def MakeCombinedList(tokens):
print(len(tokens)) # prints 4
print(tokens) # [5, 1, 2, 3]
clist = tokens[1]
clist.append(tokens[0]) # 'int' attribute object has no attribute 'append'
return clist
def MakeIntList(tokens):
nlist = tokens[0].split(",")
ilist = []
for n in nlist:
ilist.append(int(n))
return ilist
def MakeY(tokens):
Yobj = Y(tokens[0])
return Yobj
LEFT_BRACK = Suppress(Literal("["))
RIGHT_BRACK = Suppress(Literal("]"))
NATURAL = Word(nums).addParseAction(lambda n: int(n[0]))
NATURAL_LIST = delimitedList(NATURAL, combine = True)
NATURAL_VEC = LEFT_BRACK + NATURAL_LIST + RIGHT_BRACK
NATURAL_VEC.addParseAction(MakeIntList)
X = NATURAL + NATURAL_VEC
X.addParseAction(MakeCombinedList)
Y = X
Y.addParseAction(MakeY)
print(Y.parseString("5 [1,2,3]").ls)
MakeIntList is supposed to transform a string such as "1,2,3" into the list [1,2,3].
MakeCombinedList is then supposed to append an integer to this list, but the tokens received by MakeCombinedList are not the single integer and the integer list created from MakeIntList, but a list of all the integers, as indicated by my comment.
How can I make tokens[1] inside MakeCombinedList be the result of calling MakeIntList?
These two lines are working against each other, since you use the first to parse separate numeric strings into ints, and then the second just combines them back into a comma-separated string.
NATURAL = Word(nums).addParseAction(lambda n: int(n[0]))
NATURAL_LIST = delimitedList(NATURAL, combine=True)
The feature you are looking for is Group:
NATURAL = Word(nums).addParseAction(lambda n: int(n[0]))
NATURAL_LIST = Group(delimitedList(NATURAL))
NATURAL_VEC = LEFT_BRACK + NATURAL_LIST + RIGHT_BRACK
# no MakeIntList parse action required
Now instead of creating a new string and then re-parsing it in a parse action, you use Group to tell pyparsing to make a sub-structure of the resulting tokens.
There is also a little confusion going on here:
Y = X
Y.addParseAction(MakeY)
This will redefine Y from the class defined at the top to a pyparsing expression, and you get some weird traceback when trying to accessing its ls attribute.
Y_expr = X
Y_expr.addParseAction(MakeY)
I wrote the runTests method to make it easier to do simple expression testing and printing, without having to deal with Py2/Py3 print differences:
Y_expr.runTests("""\
5 [1,2,3]
""")
Shows:
5 [1,2,3]
[<__main__.Y object at 0x00000241C57B7630>]
Since your Y class just uses the default __repr__ behavior, you can see the contents better if you define your own:
class Y():
def __init__(self, ls):
self.ls = ls
def __repr__(self):
return "{}: {}".format(type(self).__name__, vars(self))
Now runTests shows:
5 [1,2,3]
[Y: {'ls': 5}]
If the purpose of the Y class is to just give you attribute names for your parsed fields, consider using results names instead:
X = NATURAL('ls') + NATURAL_VEC
Y_expr = X
#~ Y_expr.addParseAction(MakeY)
# what you had written originally
print(Y_expr.parseString("5 [1,2,3]").ls)
Will just print:
5

Python I explicitly returned None but got nothing

I explicitly returned None in the base case but the doctest tell it got nothing.
Here is my code:
def find_triple(ilist):
""" Find a triple of integers x, y, z in the list ilist such that x + y = z.
Return the tuple (x, y). If the triple does not exist, return None.
>>> find_triple([4,5,9]) in [(4,5), (5,4)]
True
>>> li = [(30,70), (70,30), (20,50), (50,20), (20,30), (30,20)]
>>> find_triple([20,40,100,50,30,70]) in li
True
>>> find_triple([6,11,7,2,3])
None
>>> find_triple([1, 1, 3])
None
"""
# define a yield function to reduce the cost of time and space
def yield_pair(ilist):
""" enumerate all the two pairs in the list.
>>> g = yield_pair([4,5,9])
>>> next(g)
(4, 5)
>>> next(g)
(4, 9)
>>> next(g)
(5, 9)
>>> next(g)
Traceback (most recent call last):
...
StopIteration
"""
for i in range(len(ilist) - 1):
for j in range(i, len(ilist) - 1):
yield (ilist[i], ilist[j + 1])
# first turn the ilist into a set, so the `in` operation is much more efficient
iset = set(ilist)
g = yield_pair(ilist)
while True:
try:
pair = next(g)
if sum(pair) in iset:
return pair
except StopIteration:
return None # ******** problems here ****************
except:
return None # ******** verbose I just try to show that it does not return None *******
Here is my error message:
Failed example:
find_triple([6,11,7,2,3])
Expected:
None
Got nothing
The None value will be ignored. You can use the following:
""" Find a triple of integers x, y, z in the list ilist such that x + y = z.
Return the tuple (x, y). If the triple does not exist, return None.
>>> find_triple([6,11,7,2,3]) is None
True
>>> find_triple([1, 1, 3]) is None
True
"""
The REPL always ignores None as a return value, printing nothing. Either skip the output for that line or explicitly print the return value.
Shells never display a None result. But "no output at all" is also a result doctest can check for. So instead of this part:
>>> find_triple([6,11,7,2,3])
None
>>> find_triple([1, 1, 3])
None
"""
you can just remove the "None" lines:
>>> find_triple([6,11,7,2,3])
>>> find_triple([1, 1, 3])
"""
Then doctest will complain if they don't return None. Or you can explicitly print the result:
>>> print(find_triple([6,11,7,2,3]))
None
>>> print(find_triple([1, 1, 3]))
None
"""
Or, as already suggested, you could tack on is None and show that you expect a True result. Or ...
Which one is best? Whichever way you find clearest. I would have done it the first way above, but then I never expected None to be displayed to begin with ;-)

Generate variables for return

I have to return many variables from my function:
a = 5
b = 6
c = 8
d = 6
...
return a,b,c,d,e,f,g,h
Problem is that I would like to do it through array to save a code lines:
for (...):
ar[x] = ... # For x = a,b,c,d,e, ...
But I have to return variables in format 'return a,b,c,d,e' and I do not want to write it as:
return ar[a], ar[b], ar[c], ...
So can I use a generator for it and how? Somethig like:
return item for item in len(ar)
When you are "returning multiple variables", you are actually just returning a tuple. You can do this:
return tuple(ar)
or even
return ar
Assuming, of course, that your array contains everything you want to return in the correct order, and only that.
It would be nice to see more code, but it is likely that if you can fill that list using a loop, then it's semantically a single value (a homogenous collection) anyway.
You can do this:
def func(x):
# do something
lis = (#your values)
return lis
a,b,c,d,e = func(x)
This will return the values as a tuple, and unpacked at the other end
Use a generator expression and tuple:
return tuple(ar[x] for x in (a,b,c,d,e))
If indexes are continuous then you can use slicing:
return tuple(ar[2:8]) #If `ar` is a already a tuple then remove the tuple call
If you want to return the whole tuple/list itself then simply return it as is:
return ar #or return tuple(ar)
Note that returning a tuple is not compulsory, more discussion on it here: Python: Return tuple or list?
def values():
a = 3
b = 5
c = 9
d = 3
e = 5
return(a, b, c, d, e)
a1 = values()
print list(a1)
and you can edit the above function to return the tuple of variables
tuple(a,b,c,d,e)

Generating evalable python code: all combinations of functions in disjunctive normal form

(A,B,C) = (100, 200, 300)
def f1(p): return p+50
def f2(p): return p*1.5
def f3(p): return p*p
vars_ = (A,B,C)
funcs_ = [f1, f2, f3]
logic_ = ["and","or"]
vol_lmt_ = [200, 300]
op_ = [">","<","="]
I want generate the assert code string for eval() to test the validity, take below one for example:
"f1(A)>200 and f1(B)>200 and f1(C)>200" # True
-^-------------^-------------^------------: funcs_
----^-------------^-------------^---------: vars_
------^-------------^-------------^-------: op_
--------^-------------^-------------^-----: vol_lmt_
------------^-------------^---------------: logic_
My questions are:
how to generate the code string I wanted based on those vars above?
how to enumerate all test logic possibility for above (A,B,C)? For example:
"f1(A)>200 and f1(B)>200 and f1(C)>200"
"f1(A)<300 and f2(B)=200 or f3(C)>200"
is it possible to replace the the name of function to the list entry when generate the code?
"f(A)>200 and f1(B)>200 and f1(C)>200"
To
"funcs_[0](A)>200 and funcs_[0](B)>200 and funcs_[0](C)>200"
This is equivalent to taking the outer/cartesian product, "summing" across the "var" dimension, and interspersing those with the outer product of logic operators. You can use itertools.product or just the normal list comprehensions. The following will work for any number of variables, functions, comparators, logic operators, and numeric thresholds. It is also easily extensible if you choose to make more complicated expressions:
#!/usr/bin/python3
from pprint import pprint as pp
from itertools import *
VARS = 'XYZ'
FUNCS = range(2)
COMPARE = '><='
LOGIC = ['and', 'or']
NUMS = [200, 300]
def listJoin(iter):
return sum(map(list,iter), [])
terms = [
[
'func[{func}]({var}){compare}{num}'.format(func=func, var=var, compare=compare, num=num)
for var in VARS
]
for func in FUNCS
for compare in COMPARE
for num in NUMS
]
def intersperse(iter, joiners):
iter = list(iter)
for tokens in product(*(joiners for _ in iter[:-1])):
yield ' '.join(listJoin(zip(iter,tokens))+[iter[-1]])
formulas = listJoin(intersperse(t, LOGIC) for t in terms)
pp(formulas)
Result:
['func[0](X)>200 and func[0](Y)>200 and func[0](Z)>200',
'func[0](X)>200 and func[0](Y)>200 or func[0](Z)>200',
'func[0](X)>200 or func[0](Y)>200 and func[0](Z)>200',
'func[0](X)>200 or func[0](Y)>200 or func[0](Z)>200',
'func[0](X)>300 and func[0](Y)>300 and func[0](Z)>300',
'func[0](X)>300 and func[0](Y)>300 or func[0](Z)>300',
'func[0](X)>300 or func[0](Y)>300 and func[0](Z)>300',
'func[0](X)>300 or func[0](Y)>300 or func[0](Z)>300',
'func[0](X)<200 and func[0](Y)<200 and func[0](Z)<200',
'func[0](X)<200 and func[0](Y)<200 or func[0](Z)<200',
'func[0](X)<200 or func[0](Y)<200 and func[0](Z)<200',
'func[0](X)<200 or func[0](Y)<200 or func[0](Z)<200',
'func[0](X)<300 and func[0](Y)<300 and func[0](Z)<300',
'func[0](X)<300 and func[0](Y)<300 or func[0](Z)<300',
'func[0](X)<300 or func[0](Y)<300 and func[0](Z)<300',
'func[0](X)<300 or func[0](Y)<300 or func[0](Z)<300',
'func[0](X)=200 and func[0](Y)=200 and func[0](Z)=200',
'func[0](X)=200 and func[0](Y)=200 or func[0](Z)=200',
'func[0](X)=200 or func[0](Y)=200 and func[0](Z)=200',
'func[0](X)=200 or func[0](Y)=200 or func[0](Z)=200',
'func[0](X)=300 and func[0](Y)=300 and func[0](Z)=300',
'func[0](X)=300 and func[0](Y)=300 or func[0](Z)=300',
'func[0](X)=300 or func[0](Y)=300 and func[0](Z)=300',
'func[0](X)=300 or func[0](Y)=300 or func[0](Z)=300',
'func[1](X)>200 and func[1](Y)>200 and func[1](Z)>200',
'func[1](X)>200 and func[1](Y)>200 or func[1](Z)>200',
'func[1](X)>200 or func[1](Y)>200 and func[1](Z)>200',
'func[1](X)>200 or func[1](Y)>200 or func[1](Z)>200',
'func[1](X)>300 and func[1](Y)>300 and func[1](Z)>300',
'func[1](X)>300 and func[1](Y)>300 or func[1](Z)>300',
'func[1](X)>300 or func[1](Y)>300 and func[1](Z)>300',
'func[1](X)>300 or func[1](Y)>300 or func[1](Z)>300',
'func[1](X)<200 and func[1](Y)<200 and func[1](Z)<200',
'func[1](X)<200 and func[1](Y)<200 or func[1](Z)<200',
'func[1](X)<200 or func[1](Y)<200 and func[1](Z)<200',
'func[1](X)<200 or func[1](Y)<200 or func[1](Z)<200',
'func[1](X)<300 and func[1](Y)<300 and func[1](Z)<300',
'func[1](X)<300 and func[1](Y)<300 or func[1](Z)<300',
'func[1](X)<300 or func[1](Y)<300 and func[1](Z)<300',
'func[1](X)<300 or func[1](Y)<300 or func[1](Z)<300',
'func[1](X)=200 and func[1](Y)=200 and func[1](Z)=200',
'func[1](X)=200 and func[1](Y)=200 or func[1](Z)=200',
'func[1](X)=200 or func[1](Y)=200 and func[1](Z)=200',
'func[1](X)=200 or func[1](Y)=200 or func[1](Z)=200',
'func[1](X)=300 and func[1](Y)=300 and func[1](Z)=300',
'func[1](X)=300 and func[1](Y)=300 or func[1](Z)=300',
'func[1](X)=300 or func[1](Y)=300 and func[1](Z)=300',
'func[1](X)=300 or func[1](Y)=300 or func[1](Z)=300']
Maybe this can summaries what you are trying to do (using python2 syntax):
import itertools
arguments = ('A', 'B', 'C', 'D')
funcs_ = [f1, f2, f3, f4]
logic_ = ["and","or"]
op_ = [">","<","="]
vol_lmt_ = [200, 300]
num_func = len(funcs_)
assert num_func == len(arguments), ("The number of argument should be the same as "
"the number of function.")
operands = itertools.product(["funcs_[%d]" % i for i in range(num_func)],
arguments,
op_,
vol_lmt_)
def comp(operands):
templ = "{func}({arg}){op}{val}"
for operand in operands:
yield templ.format(func=operand[0], arg=operand[1],
op=operand[2], val=operand[3])
new_operands = map(comp, itertools.tee(operands, num_func))
# construct the argument to pass to itertools.product.
args = []
for operand in new_operands:
args.append(operand)
args.append(logic_)
args.pop() # Remove the last logic operator.
res = itertools.product(*args)
print " ".join(res.next())
# funcs_[0](A)>200 and funcs_[0](A)>200 and funcs_[0](A)>200 and funcs_[0](A)>200
...
In this method i just cheated by replacing vars_ with ('A', 'B', 'C'). beside that i think it should work.
If you don't like my way of cheating by hard coding the the vars_ list and the funcs_ name you can get the name of your variable from the globals dictionary something like this:
def get_name(obj):
"""Get the name of an object (variable) from the globals dict.
Argument:
- obj : The variable that we want to get the name of.
Return:
- A string representing the name of the object if it was found else return None.
"""
for name, value in globals().items():
if value is obj:
return name
First of all, I would start by saying eval is a bad idea.There is always another way to do it.
Answer to your question:
Question 1:
Function name,
You can get using f.func_name.
Variable name,
Not so simple,but this should work,
import gc, sys
def find_names(obj):
frame = sys._getframe()
for frame in iter(lambda: frame.f_back, None):
frame.f_locals
result = []
for referrer in gc.get_referrers(obj):
if isinstance(referrer, dict):
for k, v in referrer.iteritems():
if v is obj:
result.append(k)
return result
a = 97
print find_names(a)[0]
It doesn't matter if it returns the wrong name since their value will be equal.
The operator and vol_limit is easily generated.
2nd Question.
No obvious solution out of mind. Iterate through all of them?
3rd Question.
Yes it is possible.
Check out Decorators.

Categories