1

it seems that strings and dicts behave fundamentally differently in python. when i pass a string to a function it gets modified in the local function's scope only, but when i do the same with a dict, it gets modified in the scope beyond the function:

def change_str(s):
    s += " qwe"

def change_arr(a):
    a[1] = "qwe"

ss = "asd"
change_str(ss)
print ss
# prints:
# asd

aa = {0:"asd"}
change_arr(aa)
print aa
# prints:
# {0: 'asd', 1: 'qwe'}

is this behavior intentional, and if so then why?

mulllhausen
  • 4,225
  • 7
  • 49
  • 71
  • possible duplicate of [Python function argument scope (Dictionaries v. Strings)](http://stackoverflow.com/questions/2951112/python-function-argument-scope-dictionaries-v-strings) – devnull Apr 28 '14 at 06:04
  • @devnull: Although the questions are similar, I think this question has independent interest, because both `+=` and `[]=` are operations that *could* be done in place on some types, just not these particular types. That other question compares item assignment to bare-name assignment, which can never mutate. – BrenBarn Apr 28 '14 at 06:07

3 Answers3

5

It is intentional behavior. Strings are immutable in python, so essentially all string operations return a new string and as your functions do not return anything, you cannot see the new string asd qwe. You can change the contents of mutable containers outside of local scope without declaring them global.

You can read more about mutable types in the official documentation of pythons data model.

msvalkon
  • 11,887
  • 2
  • 42
  • 38
  • @mulllhausen If you did that, you couldn't add anything to it. Is that what you want? – SethMMorton Apr 28 '14 at 06:07
  • @mullhausen Not really, because that sort of defeats the purpose of having a dictionary. You could not add items to it. – msvalkon Apr 28 '14 at 06:08
  • @SethMMorton i guess not. i'd like to be able to modify in the local scope without affecting the scope outside the function. i know i can do `a_new = a.copy()` then modify `a_new`, but just wondering if there is another way – mulllhausen Apr 28 '14 at 06:08
  • 1
    @mullhausen if you have mutable containers within mutable containers, like nested dictionaries or lists as values to your keys, `dict.copy()` will not help you, because only the reference is copied. You'll have to use [copy.deepcopy](https://docs.python.org/2/library/copy.html#copy.deepcopy). However you should consider dropping the local scope all together, and design your code so you won't have to worry about such problems. – msvalkon Apr 28 '14 at 06:10
1

Yes, it's intentional. Each type determines how operators work on it. The dict type is set up so that a[1] = "qwe" modifies the dict object. Such changes will be seen in any piece of code that references that object. The string type is set up so that s += "qwe" does not modify the object, but returns a new object. So other code that was referencing the original object will see no changes.

The shorthand way of saying that is that strings are immutable and dicts are mutable. However, it's worth noting that "dicts are mutable" isn't the whole reason why the behavior happens. The reason is that item assignment (someDict[item] = val) is an operation that actaully mutates a dict.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
1

Don't let the 'assignment' operator fool you. This is what is really going on in each of these functions:

def change_str(s):
    # operation has been split into 2 steps for clarity
    t = s.__iadd__("qwe") # creates a new string object
    s = t # as you know, without the `global` keyword, this `s` is local.

def change_arr(a):
    a.__setitem__(1, "qwe")

As you can see, only one of these functions actually has an assignment operation. The []= is shorthand for (or equivalent to) .__setitem__().

Joel Cornett
  • 24,192
  • 9
  • 66
  • 88