Dynamically responding to an unpacking assignment statement - python

In an unpacking assignment statement, can the assigned object inspect the number of variables it is being assigned to?
class MyObject:
def __iter__(self):
n = some_diabolical_hack()
print(f"yielding {n} vals")
return iter(["potato"]*n)
Something like:
>>> x, y = MyObject()
yielding 2 vals
>>> a, b, c = MyObject()
yielding 3 vals
In the more general case, can it introspect the "shape" of the target_list being used in an assignment?
>>> first, *blob, d[k], (x, y), L[3:7], obj.attr, last = MyObject()
unpacking to <_ast.Tuple object at 0xcafef00d>
Example potential use case: an improved MagicMock() which doesn't need to be pre-configured with a fixed iteration length when being used to patch out some object on the right hand side of an assignment statement.

You could use the traceback module:
import traceback
def diabolically_invoke_traceback():
call = traceback.extract_stack()[-2]
print call[3]
unpackers = call[3].split('=')[0].split(',')
print len (unpackers)
return range(len(unpackers))
In [63]: a, b, c = diabolically_invoke_traceback()
a, b, c = diabolically_invoke_traceback()
3
In [64]: a
Out[64]: 0
In [65]: b
Out[65]: 1
In [66]: c
Out[66]: 2

(Disclaimer: I don't recommend using diabolical techniques in production-quality code. Everything in this answer might not work on a different computer from mine, or a different Python version from mine, or on a non-CPython distribution, and it might not work tomorrow morning.)
Perhaps you could do this by inspecting the calling frame's bytecode. If I'm reading the bytecode guide correctly, multiple assignment is handled by the instructions UNPACK_SEQUENCE or UNPACK_EX, depending on whether the target list has a starred name. Both of these instructions provide information about the shape of the target list in their arguments.
You could write your diabolical function to climb the frame hierarchy until it finds the calling frame, and inspect the bytecode instruction that occurs after the FUNCTION_CALL that represents the right-hand-side of the assignment. (this is assuming that your call to MyObject() is the only thing on the right side of the statement). Then you can extract the target list size from the instruction's argument and return it.
import inspect
import dis
import itertools
def diabolically_retrieve_target_list_size():
#one f_back takes us to `get_diabolically_sized_list`'s frame. A second one takes us to the frame of the caller of `get_diabolically_sized_list`.
frame = inspect.currentframe().f_back.f_back
#explicitly delete frame when we're done with it to avoid reference cycles.
try:
#get the bytecode instruction that immediately follows the CALL_FUNCTION that is executing right now
bytecode_idx = frame.f_lasti // 2
unresolved_bytecodes = itertools.islice(dis.get_instructions(frame.f_code), bytecode_idx+1, bytecode_idx+3)
next_bytecode = next(unresolved_bytecodes)
if next_bytecode.opname == "UNPACK_SEQUENCE": #simple multiple assignment, like `a,b,c = ...`
return next_bytecode.arg
elif next_bytecode.opname == "EXTENDED_ARG": #multiple assignment with splat, like `a, *b, c = ...`
next_bytecode = next(unresolved_bytecodes)
if next_bytecode.opname != "UNPACK_EX":
raise Exception(f"Expected UNPACK_EX after EXTENDED_ARG, got {next_bytecode.opname} instead")
args_before_star = next_bytecode.arg % 256
args_after_star = next_bytecode.arg >> 8
return args_before_star + args_after_star
elif next_bytecode.opname in ("STORE_FAST", "STORE_NAME"): #single assignment, like `a = ...`
return 1
else:
raise Exception(f"Unrecognized bytecode: {frame.f_lasti} {next_bytecode.opname}")
finally:
del frame
def get_diabolically_sized_list():
count = diabolically_retrieve_target_list_size()
return list(range(count))
a,b,c = get_diabolically_sized_list()
print(a,b,c)
d,e,f,g,h,i = get_diabolically_sized_list()
print(d,e,f,g,h,i)
j, *k, l = get_diabolically_sized_list()
print(j,k,l)
x = get_diabolically_sized_list()
print(x)
Result:
0 1 2
0 1 2 3 4 5
0 [] 1
[0]

Related

pythonic swap x,y = y,x doesn't work?

I just realized that the pythonic swap doesn't always work.
def swap(x,y):
x,y = y,x
a = 1
b = 2
swap(a,b)
print b
result: 2
Why does the pythonic way to swap variables not work in this case? Do I need temporary a variable?
In the first line of the function definition
def swap(x,y):
x and y are so called formal parameters.
When you call
swap(a, b)
you are passing in a and b as actual arguments.
What happens now is that Python creates new names for the actual arguments you have passed in.
Inside the function body x is now a new name for the integer object in memory which also has the name a. y is now a new name for the integer object in memory which also goes by the name b. This is why Python is neither call-by-value nor call-by-reference, but best described as call-by-assignment.
Contrary to popular belief, calling functions and assignment works exactly the same way for mutable and immutable parameters. You will see the same behavior you are observing for mutable values:
>>> def swap(x,y):
... x,y = y,x
...
>>> a = [1]
>>> b = [2]
>>> swap(a,b)
>>> a
[1]
>>> b
[2]
So once you understand how assignments work in Python, you will also have understood how function calls work.
To make an example: If you did not write a function, your code would have been equivalent to:
a = 1
b = 2
x = a
y = b
x,y = y,x
del x
del y
In line 3, you are creating a new name x for the integer object which also goes by the name a. Line 4 follows the same logic.
In line 5, you are creating the tuple (y, x) with the value (2, 1) on the right hand side, which is then unpacked, i.e. the name x is reassigned to the value 2 and the name y is reassigned to the value 1:
>>> a = 1
>>> b = 2
>>> x = a
>>> y = b
>>> x,y = y,x
>>> x
2
>>> y
1
The important thing to note here is that a and b never stopped to be names for the value 1 and 2 respectively:
>>> a
1
>>> b
2
The last two lines just unbind the names x and y which is roughly equivalent to what happens in your function once you exit its scope. Note that the names a and b are still bound after unbinding x and y.
>>> a
1
>>> b
2
You never returned and assigned the results. Otherwise, as Joran said, you are only creating local variables in the function. For example:
def swap(a, b):
print "swap called"
return (b,a)
a = 1
b = 2
print a,b
a,b = swap(a,b)
print a,b
Results in the following being displayed:
1 2
swap called
2 1
You need to return something; the swapped vars

How to get name of function's actual parameters in Python?

For example:
def func(a):
# how to get the name "x"
x = 1
func(x)
If I use inspect module I can get the stack frame object:
import inspect
def func(a):
print inspect.stack()
out:
[
(<frame object at 0x7fb973c7a988>, 'ts.py', 9, 'func', [' stack = inspect.stack()\n'], 0)
(<frame object at 0x7fb973d74c20>, 'ts.py', 18, '<module>', ['func(x)\n'], 0)
]
or use inspect.currentframe() I can get the current frame.
But I can only get the name "a" by inspect the function object.
Use inspect.stack I can get the call stack:"['func(x)\n']",
How can I get the name of actual parameters("x" here) when call the func by parsing the "['func(x)\n']"?
If I call func(x) then I get "x"
if I call func(y) then I get "y"
Edit:
A example:
def func(a):
# Add 1 to acttual parameter
...
x = 1
func(x)
print x # x expected to 2
y = 2
func(y)
print y # y expected to 3
Looking at your comment explanations, the reason you are trying to do this is:
Because I want to impelment Reference Parameters like in C++
You can't do that. In python, everything is passed by value, but that value is a reference to the object you pass. Now, if that object is mutable (like a list), you can modify it, but if it's immutable, a new object is created and set. In your code example, x is an int which is immutable, so if you want to change the value of x it, just return its new value from the function you are invoking:
def func(a):
b = 5 * a
# ... some math
return b
x = 1
x = func(x)
So what if you want to return multiple values? Use a tuple!
def func(a, b):
a, b = 5 * b, 6 * a
# ...
return b, a
a, b = 2, 3
a, b = func(a, b)
That is feasible, up to a point - but nonetheless,useless in any kind of "real code".
You can use it for backyard magic tricks:
Just retrieve the calling stack_frame like you are doing,
then loop linearly through the variables available in the calling frame for
one that references the same object you got as a parameter. The variables are in the
f_locals and f_globals dictionaries which are attributes of the frame object.
Of course, the parameter passed may not be in a variable name to start with:
it might be a constant in the caller, or it might be inside a dict or a list.
Either way, as #Nasser put it in the answer: it is not the way Python works. Objects exist in memory, and variable names, in each scope, just point to those objects. The names themselves are meaningless otherwise.
import inspect
from itertools import chain
def magic(x):
# note that in Python2, 'currentframe' could get a parameter
# to indicate which frame you want on the stack. Not so in Python3
fr = inspect.currentframe().f_back
for var_name, value in chain(fr.f_locals.items(), fr.f_globals.items()):
if value is x:
print ("The variable name in the caller context is {}".format(var_name))
def caler():
y = "My constant"
magic(y)
use lists as references
def func(a):
a[0] = a[0] + 1
x = [1]
func(x)
print x[0] # x expected to 2
y = [2]
func(y)
print y[0] # y expected to 3
This is my final solution. Use ast to parse the function statement and get the args.:
import inspect
import re
import ast
def func(a,b,c):
stack = inspect.stack()
stack_pre = stack[1]
function_call_string = ';'.join( stack_pre[4] ).strip('\n')
ret = re.search(r'(%s[ ]*\(.*\))' % func.func_name, function_call_string)
striped_function_call_string = ret.group()
parser = ast.parse(striped_function_call_string)
frame = inspect.currentframe()
for arg in parser.body[0].value.args:
iter_frame = frame.f_back
while iter_frame:
if arg.id in iter_frame.f_locals:
iter_frame.f_locals[arg.id] += 1
break
iter_frame = iter_frame.f_back
x=1;y=2;z=3;func(x,y,z)
print x,y,z
Out:
2 3 4

Send by ref/by ptr in python? [duplicate]

This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 9 years ago.
i need help-i try to send value to method like in c++ by ref/by ptr
how can i do it?
to exmple:
def test(x):
x=3
x=2
test(x)
print(x)
In this case x a local variable in test method and will not change the "original" X
so how can i change the "original" X?
thanks
In some ways, all calls in Python are called with references. In fact, all variables are references in a sense. But some types, like int from your example, cannot be changed.
In the case of, say, a list, the functionality you're looking for is trivial:
def change_it(some_list):
some_list.append("world")
foo = ["hello"]
change_it(foo)
print(foo) # prints ['hello', 'world']
Note, however, that reassigning the parameter variable some_list does not change the value in the calling context.
If you're asking this question, though, you're probably looking to do something like set two or three variables using one function. In that case, you're looking for something like this:
def foo_bar(x, y, z):
return 2*x, 3*y, 4*z
x = 3
y = 4
z = 5
x, y, z = foo_bar(x, y, z)
print(y) # prints 12
Of course, you can do anything in Python, but that doesn't mean you should. In the fashion of the TV show Mythbusters, here's something that does what you're looking for
import inspect
def foo(bar):
frame = inspect.currentframe()
outer = inspect.getouterframes(frame)[1][0]
outer.f_locals[bar] = 2 * outer.f_locals[bar]
a = 15
foo("a")
print(a) # prints 30
or even worse:
import inspect
import re
def foo(bar):
# get the current call stack
my_stack = inspect.stack()
# get the outer frame object off of the stack
outer = my_stack[1][0]
# get the calling line of code; see the inspect module documentation
# only works if the call is not split across multiple lines of code
calling_line = my_stack[1][4][0]
# get this function's name
my_name = my_stack[0][3]
# do a regular expression search for the function call in traditional form
# and extract the name of the first parameter
m = re.search(my_name + "\s*\(\s*(\w+)\s*\)", calling_line)
if m:
# finally, set the variable in the outer context
outer.f_locals[m.group(1)] = 2 * outer.f_locals[m.group(1)]
else:
raise TypeError("Non-traditional function call. Why don't you just"
" give up on pass-by-reference already?")
# now this works like you would expect
a = 15
foo(a)
print(a)
# but then this doesn't work:
baz = foo_bar
baz(a) # raises TypeError
# and this *really*, disastrously doesn't work
a, b = 15, 20
foo_bar, baz = str, foo_bar
baz(b) and foo_bar(a)
print(a, b) # prints 30, 20
Please, please, please, don't do this. I only put it in here to inspire the reader to look into some of the more obscure parts of Python.
As far as I am aware, this doesn't exist in Python (although a similar thing occurs if you pass mutable objects to a function). You would do either
def test():
global x
x = 3
test()
or
def test(x):
return 3
x = test(x)
The second of these is much preferred.

Find out into how many values a return value will be unpacked

I have a function, and when it is called, I'd like to know what the return value is going to be assigned to - specifically when it is unpacked as a tuple. So:
a = func() # n = 1
vs.
a, b, c = func() # n = 3
I want to use the value of n in func. There must be some magic with inspect or _getframe that lets me do this. Any ideas?
Disclaimer (because this seems to be neccessary nowadays): I know this is funky, and bad practice, and shouldn't be used in production code. It actually looks like something I'd expect in Perl. I'm not looking for a different way to solve my supposed "actual" problem, but I'm curious how to achive what I asked for above. One cool usage of this trick would be:
ONE, TWO, THREE = count()
ONE, TWO, THREE, FOUR = count()
with
def count():
n = get_return_count()
if not n:
return
return range(n)
Adapted from http://code.activestate.com/recipes/284742-finding-out-the-number-of-values-the-caller-is-exp/:
import inspect
import dis
def expecting(offset=0):
"""Return how many values the caller is expecting"""
f = inspect.currentframe().f_back.f_back
i = f.f_lasti + offset
bytecode = f.f_code.co_code
instruction = ord(bytecode[i])
if instruction == dis.opmap['UNPACK_SEQUENCE']:
return ord(bytecode[i + 1])
elif instruction == dis.opmap['POP_TOP']:
return 0
else:
return 1
def count():
# offset = 3 bytecodes from the call op to the unpack op
return range(expecting(offset=3))
Or as an object that can detect when it is unpacked:
class count(object):
def __iter__(self):
# offset = 0 because we are at the unpack op
return iter(range(expecting(offset=0)))
There is little magic about how Python does this.
Simply put, if you use more than one target name on the left-hand side, the right-hand expression must return a sequence of matching length.
Functions that return more than one value really just return one tuple. That is a standard Python structure, a sequence of a certain length. You can measure that length:
retval = func()
print len(retval)
Assignment unpacking is determined at compile time, you cannot dynamically add more arguments on the left-hand side to suit the function you are calling.
Python 3 lets you use a splat syntax, a wildcard, for capturing the remainder of a unpacked assignment:
a, b, *c = func()
c will now be a list with any remaining values beyond the first 2:
>>> def func(*a): return a
...
>>> a, b, *c = func(1, 2)
>>> a, b, c
(1, 2, [])
>>> a, b, *c = func(1, 2, 3)
>>> a, b, c
(1, 2, [3])
>>> a, b, *c = func(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 1 value to unpack

Behaviour of increment and decrement operators in Python

How do I use pre-increment/decrement operators (++, --), just like in C++?
Why does ++count run, but not change the value of the variable?
++ is not an operator. It is two + operators. The + operator is the identity operator, which does nothing. (Clarification: the + and - unary operators only work on numbers, but I presume that you wouldn't expect a hypothetical ++ operator to work on strings.)
++count
Parses as
+(+count)
Which translates to
count
You have to use the slightly longer += operator to do what you want to do:
count += 1
I suspect the ++ and -- operators were left out for consistency and simplicity. I don't know the exact argument Guido van Rossum gave for the decision, but I can imagine a few arguments:
Simpler parsing. Technically, parsing ++count is ambiguous, as it could be +, +, count (two unary + operators) just as easily as it could be ++, count (one unary ++ operator). It's not a significant syntactic ambiguity, but it does exist.
Simpler language. ++ is nothing more than a synonym for += 1. It was a shorthand invented because C compilers were stupid and didn't know how to optimize a += 1 into the inc instruction most computers have. In this day of optimizing compilers and bytecode interpreted languages, adding operators to a language to allow programmers to optimize their code is usually frowned upon, especially in a language like Python that is designed to be consistent and readable.
Confusing side-effects. One common newbie error in languages with ++ operators is mixing up the differences (both in precedence and in return value) between the pre- and post-increment/decrement operators, and Python likes to eliminate language "gotcha"-s. The precedence issues of pre-/post-increment in C are pretty hairy, and incredibly easy to mess up.
Python does not have pre and post increment operators.
In Python, integers are immutable. That is you can't change them. This is because the integer objects can be used under several names. Try this:
>>> b = 5
>>> a = 5
>>> id(a)
162334512
>>> id(b)
162334512
>>> a is b
True
a and b above are actually the same object. If you incremented a, you would also increment b. That's not what you want. So you have to reassign. Like this:
b = b + 1
Many C programmers who used python wanted an increment operator, but that operator would look like it incremented the object, while it actually reassigns it. Therefore the -= and += operators where added, to be shorter than the b = b + 1, while being clearer and more flexible than b++, so most people will increment with:
b += 1
Which will reassign b to b+1. That is not an increment operator, because it does not increment b, it reassigns it.
In short: Python behaves differently here, because it is not C, and is not a low level wrapper around machine code, but a high-level dynamic language, where increments don't make sense, and also are not as necessary as in C, where you use them every time you have a loop, for example.
While the others answers are correct in so far as they show what a mere + usually does (namely, leave the number as it is, if it is one), they are incomplete in so far as they don't explain what happens.
To be exact, +x evaluates to x.__pos__() and ++x to x.__pos__().__pos__().
I could imagine a VERY weird class structure (Children, don't do this at home!) like this:
class ValueKeeper(object):
def __init__(self, value): self.value = value
def __str__(self): return str(self.value)
class A(ValueKeeper):
def __pos__(self):
print 'called A.__pos__'
return B(self.value - 3)
class B(ValueKeeper):
def __pos__(self):
print 'called B.__pos__'
return A(self.value + 19)
x = A(430)
print x, type(x)
print +x, type(+x)
print ++x, type(++x)
print +++x, type(+++x)
TL;DR
Python does not have unary increment/decrement operators (--/++). Instead, to increment a value, use
a += 1
More detail and gotchas
But be careful here. If you're coming from C, even this is different in python. Python doesn't have "variables" in the sense that C does, instead python uses names and objects, and in python ints are immutable.
so lets say you do
a = 1
What this means in python is: create an object of type int having value 1 and bind the name a to it. The object is an instance of int having value 1, and the name a refers to it. The name a and the object to which it refers are distinct.
Now lets say you do
a += 1
Since ints are immutable, what happens here is as follows:
look up the object that a refers to (it is an int with id 0x559239eeb380)
look up the value of object 0x559239eeb380 (it is 1)
add 1 to that value (1 + 1 = 2)
create a new int object with value 2 (it has object id 0x559239eeb3a0)
rebind the name a to this new object
Now a refers to object 0x559239eeb3a0 and the original object (0x559239eeb380) is no longer refered to by the name a. If there aren't any other names refering to the original object it will be garbage collected later.
Give it a try yourself:
a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
In python 3.8+ you can do :
(a:=a+1) #same as ++a (increment, then return new value)
(a:=a+1)-1 #same as a++ (return the incremented value -1) (useless)
You can do a lot of thinks with this.
>>> a = 0
>>> while (a:=a+1) < 5:
print(a)
1
2
3
4
Or if you want write somthing with more sophisticated syntaxe (the goal is not optimization):
>>> del a
>>> while (a := (a if 'a' in locals() else 0) + 1) < 5:
print(a)
1
2
3
4
It will return 0 even if 'a' doesn't exist without errors, and then will set it to 1
Python does not have these operators, but if you really need them you can write a function having the same functionality.
def PreIncrement(name, local={}):
#Equivalent to ++name
if name in local:
local[name]+=1
return local[name]
globals()[name]+=1
return globals()[name]
def PostIncrement(name, local={}):
#Equivalent to name++
if name in local:
local[name]+=1
return local[name]-1
globals()[name]+=1
return globals()[name]-1
Usage:
x = 1
y = PreIncrement('x') #y and x are both 2
a = 1
b = PostIncrement('a') #b is 1 and a is 2
Inside a function you have to add locals() as a second argument if you want to change local variable, otherwise it will try to change global.
x = 1
def test():
x = 10
y = PreIncrement('x') #y will be 2, local x will be still 10 and global x will be changed to 2
z = PreIncrement('x', locals()) #z will be 11, local x will be 11 and global x will be unaltered
test()
Also with these functions you can do:
x = 1
print(PreIncrement('x')) #print(x+=1) is illegal!
But in my opinion following approach is much clearer:
x = 1
x+=1
print(x)
Decrement operators:
def PreDecrement(name, local={}):
#Equivalent to --name
if name in local:
local[name]-=1
return local[name]
globals()[name]-=1
return globals()[name]
def PostDecrement(name, local={}):
#Equivalent to name--
if name in local:
local[name]-=1
return local[name]+1
globals()[name]-=1
return globals()[name]+1
I used these functions in my module translating javascript to python.
In Python, a distinction between expressions and statements is rigidly
enforced, in contrast to languages such as Common Lisp, Scheme, or
Ruby.
Wikipedia
So by introducing such operators, you would break the expression/statement split.
For the same reason you can't write
if x = 0:
y = 1
as you can in some other languages where such distinction is not preserved.
Yeah, I missed ++ and -- functionality as well. A few million lines of c code engrained that kind of thinking in my old head, and rather than fight it... Here's a class I cobbled up that implements:
pre- and post-increment, pre- and post-decrement, addition,
subtraction, multiplication, division, results assignable
as integer, printable, settable.
Here 'tis:
class counter(object):
def __init__(self,v=0):
self.set(v)
def preinc(self):
self.v += 1
return self.v
def predec(self):
self.v -= 1
return self.v
def postinc(self):
self.v += 1
return self.v - 1
def postdec(self):
self.v -= 1
return self.v + 1
def __add__(self,addend):
return self.v + addend
def __sub__(self,subtrahend):
return self.v - subtrahend
def __mul__(self,multiplier):
return self.v * multiplier
def __div__(self,divisor):
return self.v / divisor
def __getitem__(self):
return self.v
def __str__(self):
return str(self.v)
def set(self,v):
if type(v) != int:
v = 0
self.v = v
You might use it like this:
c = counter() # defaults to zero
for listItem in myList: # imaginary task
doSomething(c.postinc(),listItem) # passes c, but becomes c+1
...already having c, you could do this...
c.set(11)
while c.predec() > 0:
print c
....or just...
d = counter(11)
while d.predec() > 0:
print d
...and for (re-)assignment into integer...
c = counter(100)
d = c + 223 # assignment as integer
c = c + 223 # re-assignment as integer
print type(c),c # <type 'int'> 323
...while this will maintain c as type counter:
c = counter(100)
c.set(c + 223)
print type(c),c # <class '__main__.counter'> 323
EDIT:
And then there's this bit of unexpected (and thoroughly unwanted) behavior,
c = counter(42)
s = '%s: %d' % ('Expecting 42',c) # but getting non-numeric exception
print s
...because inside that tuple, getitem() isn't what used, instead a reference to the object is passed to the formatting function. Sigh. So:
c = counter(42)
s = '%s: %d' % ('Expecting 42',c.v) # and getting 42.
print s
...or, more verbosely, and explicitly what we actually wanted to happen, although counter-indicated in actual form by the verbosity (use c.v instead)...
c = counter(42)
s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.
print s
There are no post/pre increment/decrement operators in python like in languages like C.
We can see ++ or -- as multiple signs getting multiplied, like we do in maths (-1) * (-1) = (+1).
E.g.
---count
Parses as
-(-(-count)))
Which translates to
-(+count)
Because, multiplication of - sign with - sign is +
And finally,
-count
A straight forward workaround
c = 0
c = (lambda c_plusplus: plusplus+1)(c)
print(c)
1
No more typing
c = c + 1
Also, you could just write
c++
and finish all your code and then do search/replace for "c++", replace with "c=c+1". Just make sure regular expression search is off.
Extending Henry's answer, I experimentally implemented a syntax sugar library realizing a++: hdytto.
The usage is simple. After installing from PyPI, place sitecustomize.py:
from hdytto import register_hdytto
register_hdytto()
in your project directory. Then, make main.py:
# coding: hdytto
a = 5
print(a++)
print(++a)
b = 10 - --a
print(b--)
and run it by PYTHONPATH=. python main.py. The output will be
5
7
4
hdytto replaces a++ as ((a:=a+1)-1) when decoding the script file, so it works.

Categories

Resources