1

I know python function arguments are passed by reference and mutable arguments can modify the out-of-scope variable inside the function, except if you rebind the local variable.

My issue is that I want to cast the function argument to a different type and affect the out-of-scope variable that got passed. But both ways that I tried just rebind the local in-scope argument.

def change_type(my_var):
    try:
        if float(my_var) > 0:
            my_var = float(my_var) #just rebinds!
    except ValueError:
        pass


some_var = '0.5'
change_type(some_var)
print(type(some_var)) #gives 'str'; I want it to be 'float'

I also tried just doing float(my_var) without assignment in the if block but that didn't do anything either.

Is there any way for me to modify the data type of an out-of-scope variable like this, or must I do it directly on some_var outside of my function?

Thanks!

kilgoretrout
  • 3,547
  • 5
  • 31
  • 46
  • 2
    What are you trying to achieve? – Peter Wood Sep 16 '15 at 15:03
  • 1
    Just return the converted value... use `some_var = convert(some_var)`. bonus points: you are able to keep the old value if you want to. – Bakuriu Sep 16 '15 at 15:06
  • This is a common method that is used in several places to check the validity (in this simplified example, > 0) of a variable. I just wanted to support both strings and numeric types for that variable in all those places. If the check passes though, the variable (back in those places) needs to be arithmetically modified hence needs to be cast to int or float. My hope was just to do it in the check function so I don't have to write `my_var = float(my_var)` in all those other places if the check passes. Obviously I can, was just curious to learn if it's possible to do that out-of-scope somehow. – kilgoretrout Sep 16 '15 at 15:10
  • @Bakuriu, I realize that I can return it of course. This was more a learning question. I just wanted to know if it's possible to modify the type out-of-scope, that's all. I guess a_guest's answer says that the best practice is to do exactly what you said. That's a fine answer for me. I did want to know if it's possible to modify type out-of-scope but will implement the best-practice approach. – kilgoretrout Sep 16 '15 at 15:11
  • 1
    `'0.5'` is a string. Strings are immutable in Python. You can't modify an immutable object. Related: [In Python, why can a function modify some arguments as perceived by the caller, but not others?](http://stackoverflow.com/q/575196/4279) – jfs Sep 16 '15 at 17:53

3 Answers3

2

It is good style for Python to return modified arguments from the function as tuple. This is more verbose and also allows you to achieve what you want to do. Unlike for e.g. C or C++ you can return more than one value from a function using the built-in type tuple so you don't have to modify arguments by their reference.

So this does the trick (without coverage of the ValueError):

def change_type(my_var):
    if float(my_var) > 0:
        return float(my_var)
    else:
        return my_var

my_var = '0.5'
my_var = change_type(my_var)
a_guest
  • 34,165
  • 12
  • 64
  • 118
  • thanks for clarifying the best practice, it's good to know and this is what I'll do. I accepted rob's answer though because this question was originally stated as wondering if/how one can modify type out-of-scope. I did think of returning as an option in practice, my question was more just curiosity. Thanks! – kilgoretrout Sep 16 '15 at 15:18
  • No problem, if you wan't to dive deeper into python (related to "how to change variables' values out of scope") you can take a look here: https://www.reddit.com/r/Python/comments/2441cv/can_you_change_the_value_of_1/. This uses the underlying implementation of Python (well, CPython, to be precise) wrapped in the module `ctypes`. But: Handle with care! ;-) – a_guest Sep 16 '15 at 15:37
  • @river_jones: Changing type depending on the sign looks like a bad idea. Why can't you write `value = float('0.5')`? – jfs Sep 16 '15 at 18:01
1

You could pass in a mutable container, like so:

def change_type(my_var):
    try:
        if float(my_var[0]) > 0:
            my_var[0] = float(my_var[0])
    except ValueError:
        pass


some_var = ['0.5']
change_type(some_var)
print(type(some_var[0]))

There is a documented way to affect the binding of a local variable in another frame: the inspect module. This might work for you:

import inspect
def change_type(my_var):
    try:
        if float(my_var) > 0:
            my_var = float(my_var)
            inspect.stack()[1][0].f_locals['some_var'] = my_var
    except ValueError:
        pass


some_var = '0.5'
change_type(some_var)
print(type(some_var))

I hope it is obvious that using inspect in this way is a bad idea.

Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Thanks for the answer. As I mentioned above, I did want to know if/how it would be possible to modify type out-of-scope (without returning a value; I should have said that explicitly), so seems like this is the only way. In the end I will do what @a_guest said because it's best practice, but thanks for feeding my curiosity. – kilgoretrout Sep 16 '15 at 15:16
  • To satisfy your curiosity, consider using `inspect`. See my recent edit. – Robᵩ Sep 16 '15 at 15:29
0

Yes, you can change a float in Python, but is it very bad idea since they are supposed to be immutable.

import sys, struct, ctypes

def set_float(obj, value):
    assert isinstance(obj, float), 'Object must be a float!'
    assert isinstance(value, float), 'Value must be a float!'
    stop = sys.getsizeof(obj)
    start = stop - struct.calcsize('d')
    array = ctypes.cast(id(obj), ctypes.POINTER(ctypes.c_ubyte))
    for args in zip(range(start, stop), struct.pack('d', value)):
        array.__setitem__(*args)

The code is from the How to Mutate a Float recipe in the Python Cookbook. Here is an example of how to use the set_float function.

>>> a = 1.2
>>> set_float(a, 3.4)
>>> a
3.4

Please note that this solution was designed to work with CPython and will probably not work with other variations of the language runtime.

Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117