6

I noticed that my code has many statements like this:

var = "some_string"
var = some_func(var)
var = another_func(var)
print(var)  # outputs "modified_string"

It's really annoying me, it just looks awful (in the opposite of whole Python). How to avoid using that and start using it in a way like this:

var = "some_string"
modify(var, some_func)
modify(var, another_func)
print(var) # outputs "modified_string"
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
impress_meh
  • 69
  • 1
  • 1
  • 2
  • 3
    In my experience, it is best to avoid side-effect like this. Instead, return the value (as in the first example). If there are multiple values, they might be wrapped compound type like a Tuple (and then decomposed by the caller). Alternatively, perhaps the problem is "just too many repeat assignments"? Consider: `print another_func(some_func("some_string"))` –  Jun 17 '11 at 23:36
  • 3
    How does `x = func(x)` look worse than `modify(x, func)`? I'm 100% clear on what the first example should do, and 0% clear on what the second ought to do. – Chris Lutz Jun 18 '11 at 00:09
  • @Chris Lutz, I'm not _certain_ this is a duplicate. The literal question is "how do I pass a variable by reference," but the real question is "how do I avoid repeatedly assigning a new value to the same variable name." That's actually an interesting and worthwhile question. – senderle Jun 18 '11 at 00:12
  • @senderle - I suppose something like `x = chain_funcs(func1, func2, ..., x)` would be kinda okay. It'd make the order of calling a bit ambiguous though. – Chris Lutz Jun 18 '11 at 00:22
  • I agree most wholeheartedly with pst - in general it's best to stick with functions that only do one thing, and do them well. If you're looking to often repeat the same sequences of functions and you'd rather not have duplicate code, consider wrapping your common sequences of functions in their own higher-level function, whose sole purpose is to call these lower-level functions. This practice will help you in the long run, I guarantee it. – machine yearning Jun 18 '11 at 01:25

7 Answers7

6

That might not be the most "pythonic" thing to do, but you could "wrap" your string in a list, since lists are mutable in Python. For example:

var = "string"
var_wrapper = [var]

Now you can pass that list to functions and access its only element. When changed, it will be visible outside of the function:

def change_str(lst):
    lst[0] = lst[0] + " changed!"

and you'll get:

>>> change_str(var_wrapper)
>>> var_wrapper[0]
"string changed!"

To make things a bit more readable, you could take it one step further and create a "wrapper" class:

class my_str:
    def __init__(self, my_string):
        self._str = my_string

    def change_str(self, new_str):
        self._str = new_str

    def __repr__(self):
        return self._str

Now let's run the same example:

>>> var = my_str("string")
>>> var
string
>>> var.change_str("new string!")
>>> var
new string!

* Thanks for @Error-SyntacticalRemorse for the remark of making a class.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
4

The problem is that str, int and float (long too, if you're in Py 2.x (True and False are really ints, so them too)) are what you call 'immutable types' in Python. That means that you can't modify their internal states: all manipulations of an str (or int or float) will result in a "new" instance of the str (or whatever) while the old value will remain in Python's cache until the next garbage collection cycle.

Basically, there's nothing you can do. Sorry.

cwallenpoole
  • 79,954
  • 26
  • 128
  • 166
3

In fact, there's been at least one attempt to add a compose function to functools. I guess I understand why they didn't... But hey, that doesn't mean we can't make one ourselves:

def compose(f1, f2):
    def composition(*args, **kwargs):
        return f1(f2(*args, **kwargs))
    return composition

def compose_many(*funcs):
    if len(funcs) == 1:
        return funcs[0]
    if len(funcs) == 2:
        return compose(funcs[0], funcs[1])
    else:
        return compose(funcs[0], compose_many(*funcs[1:]))

Tested:

>>> def append_foo(s):
...     return s + ' foo'
... 
>>> def append_bar(s):
...     return s + ' bar'
... 
>>> append_bar(append_foo('my'))
'my foo bar'
>>> compose(append_bar, append_foo)('my')
'my foo bar'
>>> def append_baz(s):
...     return s + ' baz'
... 
>>> compose_many(append_baz, append_bar, append_foo)('my')
'my foo bar baz'

Come to think of it, this probably isn't the best solution to your problem. But it was fun to write.

senderle
  • 145,869
  • 36
  • 209
  • 233
  • 1
    Wow! I really over-engineered that one. Apart from there being a much simpler recursive solution, there's even a one-liner! `lambda lst: reduce(lambda x, y: lambda *args, **kwargs: x(y(*args, **kwargs)), lst)`. It's not pretty, but it works; see [here](http://www.amk.ca/python/writing/functional#the-functional-module) for a lambda-free version using `functional`. – senderle Jun 18 '11 at 03:08
1

There is a way to modify an immutable variable, by rewriting it in the local symbol table, however, I think that it's not very nice and should be avoided as much as possible.

def updatevar(obj, value, callingLocals=locals()):
    callingLocals[next(k for k, o in callingLocals.items() if o is obj)] = value

Another way, even less pythonic, is to use exec with a formatted instruction. It gets the variable name as a string thanks to this solution:

def getname(obj, callingLocals=locals()):
    """
    a quick function to print the name of input and value.
    If not for the default-Valued callingLocals, the function would     always
    get the name as "obj", which is not what I want.
    """
    return next(k for k, v in callingLocals.items() if v is obj)

def updatevar2(k, v, callingLocals=locals()):
    n = getname(k, callingLocals)
    exec('global {};{}={}'.format(n, n, repr(v)))

The result is as expected:

var = "some_string"
updatevar(var, "modified_string")
print(var) # outputs "modified_string"
updatevar2(var, var + '2')
print(var) # outputs "modified_string2"
Cory86
  • 11
  • 1
1

the others already explained why that's not possible, but you could:

for modify in some_func, other_func, yet_another_func:
 var = modify(var)

or as pst said:

var = yet_another_func(other_func(some_func(var)))
jcomeau_ictx
  • 37,688
  • 6
  • 92
  • 107
0

Strings are immutable in python, so your second example can't work. In the first example you are binding the name var to a completely new object on each line.

Typically multiple assignments to a single name like that are a code smell. Perhaps if you posted a larger sample of code someone here could show you a better way?

Daenyth
  • 35,856
  • 13
  • 85
  • 124
-1

I'm just gonna put this right here (since none of the answers seem to have addressed it yet)

If you're commonly repeating the same sequences of functions, consider wrapping them in a higher level function:

def metafunc(var):
    var = somefunc(var)
    var = otherfunc(var)
    var = thirdfunc(var)
    return lastfunc(var)

Then when you call the function metafunc you know exactly what's happening to your var: nothing. All you get out of the function call is whatever metafunc returns. Additionally you can be certain that nothing is happening in parts of your program that you forgot about. This is really important especially in scripting languages where there's usually a lot going on behind the scenes that you don't know about/remember.

There are benefits and drawbacks to this, the theoretical discussion is under the category of pure functional programming. Some real-world interactions (such as i/o operations) require non-pure functions because they need real-world implications beyond the scope of your code's execution. The principle behind this is defined briefly here:

http://en.wikipedia.org/wiki/Functional_programming#Pure_functions

machine yearning
  • 9,889
  • 5
  • 38
  • 51