2

I know that in Python you can swap the values of variables by doing

x,y=y,x

But I want a function that makes this swap only giving to it the variables, not having to return the values to achieve this swap. Let me be more clear with an example swapping x,y:

x,y=1,0
print(x,y)  # This prints 1,0

def swapper(a,b):
    #Something that swaps the variables and doesn't return anything.

swapper(x,y)
print(x,y)  # This prints 0,1

Is this possible? If not, why not?

EDIT: Yes, I want to know if it's possible exactly what I wrote, not another implementation, but the function should be able to swap any two variables. You can think about this as an empty file, it won't be used for a bigger program. It's merely for learning purpose.

iam_agf
  • 639
  • 2
  • 9
  • 20
  • Does this answer your question? [Is there a standardized method to swap two variables in Python?](https://stackoverflow.com/questions/14836228/is-there-a-standardized-method-to-swap-two-variables-in-python) – sushanth Jul 02 '20 at 04:24
  • I added an edit to explain that I just want to know if it's possible to do exactly what I mentioned. – iam_agf Jul 02 '20 at 04:28

5 Answers5

1

Mandatory Disclaimer: Code shown here is educational and not even particularly good. "Here be dragons" and all that.

Doing exactly what you want isn't possible without fiddling around with Python's internals because when you pass a variable into a function you're only passing its value (or a reference to its value), not the name of the variable. Because of that you've lost the information that allows you to precisely swap two variables.

If you're willing to accept some collateral damage (assuming your code isn't nested inside another function or something, which would complicate the code a ton with nonlocal variables), you can actually get your example to work. We don't know which variables to switch, so we just switch all of them.

def swapper(a, b):
    # find all variables with the same values as the variables passed into swapper()
    a_keys = {k for k,v in globals().items() if v==a}
    b_keys = {k for k,v in globals().items() if v==b}

    # swap _every_ pair of variables with those values to be sure we got the two
    # that were passed in.
    for k in a_keys:
        globals()[k] = b
    for k in b_keys:
        globals()[k] = a

While we're at it, Python actually exposes a lot of its internals, and you can actually pinpoint the exact variables you're trying to swap. I'm lazy and don't really want to write/use a full Python parser, so I'm going to assume you're only calling swapper() exactly as you have written in your example (nothing else on the line). The following idea reads the current stack trace to figure out how you called swapper() to figure out the variable names to swap.

import traceback

def swapper(a, b):
    # if you call swapper(x, y), then f_call == 'swapper(x, y)'
    f_call = traceback.StackSummary.extract(traceback.walk_stack(None))[1].line
    
    # parse out the arguments
    first, second = (s.strip() for s in f_call.replace('swapper(', '').replace(')', '').split(','))
    
    # actually swap the variables now that we know what they are
    globals()[first], globals()[second] = globals()[second], globals()[first]
Hans Musgrave
  • 6,613
  • 1
  • 18
  • 37
1

I think this is best explained by comparison with C/C++. Take this function:


void swap(int &x, int &y) {
   int temp = *x;
   *x = *y;
   *y = temp;
}

This code does what you want it to do, but it's impossible in Python. Here's why:

The critical part of the C function is the function signature *x, *y which makes the arguments a pointer. When you do *x = *y, you are overwriting the memory that x points to. In python, you can't pass a pointer, only a reference. The difference is that overwriting a reference does nothing to the original object. For example,

x = "foo"
y = x
x = "bar"
print(x, y) # bar, foo not bar, bar

Any approach to solving this problem must deal with this issue, such as @adamgy's answer. His approach uses a double reference. You pass something that can i) hold references and is ii) mutable. He uses a list, but you could use a dict or any mutable object.

PS: There might be a way to do this using the CPython API, which is a little extreme.

All Python objects in the standard implementation have an underlying C object. You can implement a python function in C.

static PyObject * swap(PyObject *a, PyObject *b)
{
 // logic
}

It might be possible to design a function that takes a pointer to a pointer.


static PyObject * swap(PyObject *a, PyObject *b)
{
   swap_internal(&a, &b);
   // more logic
}

static PyObject * swap_internal(PyObject **a, PyObject **b)
{
 // logic
}

This would have the necessary double reference semantics, but I haven't checked if it actually works.

fizzybear
  • 1,197
  • 8
  • 22
0

This isn't possible in Python with the data types as is.

You can read more in this question: Passing an integer by reference in Python

Uy Tran
  • 11
  • 1
0

If you really want to use a separate function without returning anything, one workaround could be to use a list to store the two variables:

def swap(x):
    x[0], x[1] = x[1], x[0]

x = [0, 1]
swap(x)
print(x)
# [1, 0]

This works because in this case you are passing a reference to a mutable object (a list), which you can modify inside of the function.

adamgy
  • 4,543
  • 3
  • 16
  • 31
0

Python doesn't allow to pass integers by reference, unlike C where you can pass the pointer directly and modify the value.

Changes towards x,y won't be reflected in the global scope, as the changes would only be done inside the function scope. There are two solutions to your problem.

The best way in my opinion is to simply assign the values with the x,y = y,x, however if you trully wish to have a function to swap values you could actually pass value by reference with data types such as a python list. Which is only a glorified pointer to the start of the list.

a = [0,1]  # each index of the list being the variable
def swap(lst):
    # lst being a list and of lenght 2
    a[0],a[1] = a[1],a[0]

swap(a)
print(a)  # prints [1,0] 

Again, I just showed you a way to achieve what you want, but Python itself doesn't natively supports to pass values by reference in functions. Would be best to stick to the x,y = y,x or assign the values to the function return.

Jose J
  • 13
  • 2