[newbie] A question about lists and strings

M

Mok-Kong Shen

In an earlier question about lists, I was told about the issue of
creation of local names in a function. However, I still can't
understand why the program below outputs:

[999] sss
[999]

and not two identical lines of output. For both operators "+=" should
anyway work in similar manner in the function xx in my view.

Thanks for your help in advance.

M. K. Shen

----------------------------------------------------------

def xx(list,str):
list+=[999]
str+="sss"

lista=[]
stra=""
lista+=[999]
stra+="sss"
print(lista,stra)

listb=[]
strb=""
xx(listb,strb)
print(listb,strb)
 
R

Roman Vashkevich

10.08.2012, × 13:19, Mok-Kong Shen ÎÁÐÉÓÁÌ(Á):
In an earlier question about lists, I was told about the issue of
creation of local names in a function. However, I still can't
understand why the program below outputs:

[999] sss
[999]

and not two identical lines of output. For both operators "+=" should
anyway work in similar manner in the function xx in my view.

Thanks for your help in advance.

M. K. Shen

----------------------------------------------------------

def xx(list,str):
list+=[999]
str+="sss"

lista=[]
stra=""
lista+=[999]
stra+="sss"
print(lista,stra)

listb=[]
strb=""
xx(listb,strb)
print(listb,strb)

It seems like your xx() function doesn't return local str parameter and prints the global empty str, whereas it mutates the global list.

Roman
 
R

Roman Vashkevich

10.08.2012, × 13:28, Roman Vashkevich ÎÁÐÉÓÁÌ(Á):
10.08.2012, × 13:19, Mok-Kong Shen ÎÁÐÉÓÁÌ(Á):
In an earlier question about lists, I was told about the issue of
creation of local names in a function. However, I still can't
understand why the program below outputs:

[999] sss
[999]

and not two identical lines of output. For both operators "+=" should
anyway work in similar manner in the function xx in my view.

Thanks for your help in advance.

M. K. Shen

----------------------------------------------------------

def xx(list,str):
list+=[999]
str+="sss"

lista=[]
stra=""
lista+=[999]
stra+="sss"
print(lista,stra)

listb=[]
strb=""
xx(listb,strb)
print(listb,strb)

It seems like your xx() function doesn't return local str parameter and prints the global empty str, whereas it mutates the global list.

Roman

Excuse me for the mess I just did in my message:)
The function doesn't print anything of cause. It takes list by reference and creates a new local str.
When it's called with listb and strb arguments, listb is passed by reference and mutated. A string "sss" is concatenated with an empty local str. Nothing more happens. Since local str is not returned by xx(), it can not be expected to be printed out in the statement that follows. What is printed out in the print statement is the mutated listb and the global strb.

RV
 
P

Peter Otten

Mok-Kong Shen said:
In an earlier question about lists, I was told about the issue of
creation of local names in a function. However, I still can't
understand why the program below outputs:

[999] sss
[999]

and not two identical lines of output. For both operators "+=" should
anyway work in similar manner in the function xx in my view.

Thanks for your help in advance.

M. K. Shen

----------------------------------------------------------

def xx(list,str):
list+=[999]
str+="sss"

a += b

is internally translated into to

a = a.__iadd__(b)

If the 'list' class were implemented in Python it would look like

class list:
def __iadd__(self, other):
for item in other:
self.append(item)
return self

i. e. the original list is modified when you perform += and you'll see the
modification when you look at any name bound to that original list:

b = a = [1, 2]
a += [3, 4]
print a # [1, 2, 3, 4]
print b # [1, 2, 3, 4]

Strings on the other hand are "immutable" -- they cannot be altered after
the initial creation. The hypothetical __iadd__() implementation is

class str:
def __iadd__(self, other):
return self + other

So += rebinds a name to a new string:

b = a = "first"
a += "second"
print b # first
print a # firstsecond

Armed with this knowledge
lista=[]
stra=""
lista+=[999]

[999] is appended to lista and lista is rebound to itself.
stra+="sss"

A new string "" + "sss" is created and stra is bound to that new string.
print(lista,stra)

listb=[]
strb=""
xx(listb,strb)

Inside xx()

(1) 999 is appended to listb and the local variable list is rebound.
(2) A new string "" + "sss" is created and bound to the local variable str.
print(listb,strb)

If you have understood the above here's a little brain teaser:
Traceback (most recent call last):

What are the contents of a[0] now?
 
D

Dave Angel

In an earlier question about lists, I was told about the issue of
creation of local names in a function. However, I still can't
understand why the program below outputs:

[999] sss
[999]

and not two identical lines of output. For both operators "+=" should
anyway work in similar manner in the function xx in my view.

Thanks for your help in advance.

M. K. Shen

----------------------------------------------------------

def xx(list,str):
list+=[999]
str+="sss"

lista=[]
stra=""
lista+=[999]
stra+="sss"
print(lista,stra)

listb=[]
strb=""
xx(listb,strb)
print(listb,strb)

I'm rewriting your xx function so it doesn't overwrite reserved words,
list and str.

def xx(mylist, mystring):
mylist += [999]
mystring += "xxx"

There are a couple of effects you need to understand in order to see
what's happening.

First, some terminology. You don't assign values in Python, you bind a
name or another lvalue to an object. a=650 builds an object of type
int, and binds it to the name a. if you then say b=a, you bind b to the
*SAME* object, the previously created int object of value 650. It can
be useful to print the id() of an object, to convince yourself that
they're actually the same. So if you print id(a) and id(b), you'll get
the same value. But if you said c=651 and d=651, you'd have two
objects, and the two names would be bound to different objects, with
different ids.

The += operator is an example of an augmented assignment operator.
Others that behave the same way include *= -= and so on.

The += operator tries to modify the object referred to by the left hand
side, by modifying that object in place. If that fails, perhaps because
the object is immutable, then it builds a new object, and binds the left
side to that new object.

So, if you have a list, and you use += to append to it, you still have
the same list object, but it's longer now. If you have a string, or an
int, or ... that's immutable, then it builds a new one, and binds it to
the left side.

Now, put those two operations inside a function, and what do we have ?
Let's go through the function, looking at what happens.

Local parameter mylist gets bound to the list object bound to listb.
The object is not copied, it's now bound to new names, one in the global
space, one local. When the mylist += statement is executed, it
modifies that object, and instantly the global "variable" listb is modified.

Local parameter mystring gets bound to the string object bound to strb.
We still only have one (immutable) object. When the mystring +=
statement is executed, it creates a new string object by concatenating
the old one and the "xxx" string. At this point, we can print
id(mystring) and see that it changed. When the function returns, the
local symbols are unbound, and the new string object is discarded since
there are no longer any refs.

At this point, in top-level code, the listb object has been modified,
and the strb one has not; it still is bound to the old value.
 
M

Mok-Kong Shen

Am 10.08.2012 12:07, schrieb Dave Angel:
[snip]
At this point, in top-level code, the listb object has been modified,
and the strb one has not; it still is bound to the old value.

This means there is no way of modifying a string at the top level
via a function, excepting through returning a new value and assigning
that to the string name at the top level. Please again correct me, if
I am wrong.

M. K. Shen
 
C

Chris Angelico

Thanks for the explanation of the output obtained. But this means
nonetheless that parameters of types lists and strings are dealt with
in "inherently" (semantically) different ways by Python, right?

It's nothing to do with parameters, but yes, lists are mutable and
strings are immutable. A tuple will behave the same way a string does:
(1, 2, 3, 4)


By the way:

But if you said c=651 and d=651, you'd have two
objects, and the two names would be bound to different objects, with
different ids.

To be more accurate, you *may* have two different objects. It's
possible for things to be optimized (eg with small numbers, or with
constants compiled at the same time).

ChrisA
 
D

Dave Angel

Am 10.08.2012 11:48, schrieb Roman Vashkevich:
[snip]
The function .... It takes list by reference and creates a new local
str. When it's called with listb and strb arguments, listb is passed
by reference and mutated. A string "sss" is concatenated with an
empty local str. Nothing more happens. Since local str is not
returned by xx(), it can not be expected to be printed out in the
statement that follows. What is printed out in the print statement is
the mutated listb and the global strb.

Thanks for the explanation of the output obtained. But this means
nonetheless that parameters of types lists and strings are dealt with
in "inherently" (semantically) different ways by Python, right?

M. K. Shen

Nothing to do with parameters. Lists are mutable, and strings are
immutable, so += behaves differently.
 
C

Chris Angelico

This means there is no way of modifying a string at the top level
via a function, excepting through returning a new value and assigning
that to the string name at the top level. Please again correct me, if
I am wrong.

Yes, but you can (ab)use a one-element list as a pointer.
foo=["Hello"]
def mutate(ptr):
ptr[0]="World"
mutate(foo)
print(foo[0])
World

But it's probably worth thinking about exactly why you're wanting to
change that string, and what you're really looking to accomplish.
There may well be a better way.

Chris Angelico
 
M

Mok-Kong Shen

Am 10.08.2012 12:40, schrieb Chris Angelico:
But it's probably worth thinking about exactly why you're wanting to
change that string, and what you're really looking to accomplish.
There may well be a better way.

My problem is the following: I have at top level 3 lists and 3 strings:
lista, listb, listc and stra, strb, strc. I can with a single function
call change all 3 list values. How could I realize similar changes
of the 3 string values with a single function call?

Thanks in advance.

M. K. Shen
 
D

Dave Angel

Am 10.08.2012 12:07, schrieb Dave Angel:
[snip]
At this point, in top-level code, the listb object has been modified,
and the strb one has not; it still is bound to the old value.

This means there is no way of modifying a string at the top level
via a function, excepting through returning a new value and assigning
that to the string name at the top level. Please again correct me, if
I am wrong.

M. K. Shen

You're close. There are three ways I can think of. The "right" way is
to return a value, which the caller can use any way he wants, including
binding it to a global.

Second is to declare the name as global, rather than taking the object
as a formal parameter. In this case, you're taking on the
responsibility for managing that particular global, by its correct name.
def yy():
global strb
strb += "whatever"

Third is to hold the string in some more complex structure which is
mutable. (untested, may contain typos)
def zz(mydict):
mydict["key1"] += "text"

called as:
globaldict = {"key1": "initial ", "key2": "init"}
 
D

Dave Angel

It's nothing to do with parameters, but yes, lists are mutable and
strings are immutable. A tuple will behave the same way a string does:

(1, 2, 3, 4)


By the way:


To be more accurate, you *may* have two different objects. It's
possible for things to be optimized (eg with small numbers, or with
constants compiled at the same time).

ChrisA

You're right, of course. But I picked the value of 650+ deliberately,
as I believe CPython doesn't currently optimize ints over 256.
 
R

Roman Vashkevich

10.08.2012, × 14:12, Mok-Kong Shen ÎÁÐÉÓÁÌ(Á):
Am 10.08.2012 11:48, schrieb Roman Vashkevich:
[snip]
The function .... It takes list by reference and creates a new local
str. When it's called with listb and strb arguments, listb is passed
by reference and mutated. A string "sss" is concatenated with an
empty local str. Nothing more happens. Since local str is not
returned by xx(), it can not be expected to be printed out in the
statement that follows. What is printed out in the print statement is
the mutated listb and the global strb.

Thanks for the explanation of the output obtained. But this means
nonetheless that parameters of types lists and strings are dealt with
in "inherently" (semantically) different ways by Python, right?

M. K. Shen

I am not sure I understand your question. Can you rephrase it or make it more explicit?

RV
 
D

Dave Angel

Am 10.08.2012 12:40, schrieb Chris Angelico:


My problem is the following: I have at top level 3 lists and 3 strings:
lista, listb, listc and stra, strb, strc. I can with a single function
call change all 3 list values. How could I realize similar changes
of the 3 string values with a single function call?

Thanks in advance.

M. K. Shen
Make sure your function returns the values, rather than just trying to
mutate them in place.

def aa(mystring1, mystring2, mystring3):
mystring1 += "something"
mystring2 += "otherthing"
mystring3 += "nobody"
return mystring1, mystring2, mystring3

str1, str2, str3 = aa(str1, str2, str3)
 
C

Chris Angelico

You're right, of course. But I picked the value of 650+ deliberately,
as I believe CPython doesn't currently optimize ints over 256.

Yep. Also:
False

Same thing occurs (or at least, appears to) with constants in the same
module. Could potentially be a fairly hefty optimization, if you use
the same numbers all the time (bit flags, scale factors, modulo
divisors, etc, etc, etc).

Still doesn't alter your fundamental point of course.

ChrisA
 
R

Roman Vashkevich

10.08.2012, × 14:12, Mok-Kong Shen ÎÁÐÉÓÁÌ(Á):
Am 10.08.2012 11:48, schrieb Roman Vashkevich:
[snip]
The function .... It takes list by reference and creates a new local
str. When it's called with listb and strb arguments, listb is passed
by reference and mutated. A string "sss" is concatenated with an
empty local str. Nothing more happens. Since local str is not
returned by xx(), it can not be expected to be printed out in the
statement that follows. What is printed out in the print statement is
the mutated listb and the global strb.

Thanks for the explanation of the output obtained. But this means
nonetheless that parameters of types lists and strings are dealt with
in "inherently" (semantically) different ways by Python, right?

M. K. Shen

DaveA provided a very explicit explanation of how py handles list and string objects.
Parameters are handled "inherently" by functions...

RV
 
M

Mok-Kong Shen

Am 10.08.2012 12:56, schrieb Roman Vashkevich:
I am not sure I understand your question. Can you rephrase it or make it more explicit?

I have just detailed my problem and Dave Angel has shown how to solve
it properly.

M. K. Shen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top