views:

165

answers:

12

In Python, strings are immutable.

What is the standard idiom to walk through a string character-by-character and modify it?

The only methods I can think of are some genuinely stanky hacks related to joining against a result string.

--

In C:

for(int i = 0; i < strlen(s); i++)
{
   s[i] = F(s[i]);
}

This is super expressive and says exactly what I am doing. That is what I am looking for.

+1  A: 

Strings are iterable and can be walked through like lists. Strings also have a number of basic methods such as .replace() that might be what you're looking for. All string methods return a new string. So instead of modifying the string in place you can simply replace its existing value.

>>> mystring = 'robot drama'
>>> mystring = mystring.replace('r', 'g')
>>> mystring
'gobot dgama'
jathanism
Strings are *immutable* and cannot be assigned to by member.
Paul Nathan
Correct. All string operations return a copy of the input string. The variable name is NOT immutable, however, so by re-assigning a string operation to the same variable name, you are effectively "mutating" the string.
jathanism
+1  A: 
>>> mystring = "Th1s 1s my str1ng"
>>> mystring.replace("1", "i")
'This is my string'

If you want to store this new string you'll have to mystring = mystring.replace("1", "i"). This is because in Python strings are immutable.

Ashish
1: Don't use "string" as a variable name. 2: That doesn't modify the variable.
bstpierre
That doesn't select by index and modify it.
Paul Nathan
@bstpierre: There you go. :)
Ashish
@Paul Nathan: Where was "by index" a requirement?
S.Lott
+1  A: 

string.translate is probably the closest function to what you're after.

Tim McNamara
Interesting function - what I'm looking for, however, is to be able to adjust at a given index (or return a string that has that given index modified).
Paul Nathan
@Tim, no need to import from string, `str` objects have the `translate` method. It's usually handy to use `maketrans` from the `string` module though
gnibbler
+3  A: 

Don't use a string, use something mutable like bytearray:

#!/usr/bin/python

s = bytearray("my dog has fleas")
for n in xrange(len(s)):
    s[n] = chr(s[n]).upper()
print s

Results in:

MY DOG HAS FLEAS

Edit:

Since this is a bytearray, you aren't (necessarily) working with characters. You're working with bytes. So this works too:

s = bytearray("\x81\x82\x83")
for n in xrange(len(s)):
    s[n] = s[n] + 1
print repr(s)

gives:

bytearray(b'\x82\x83\x84')

If you want to modify characters in a Unicode string, you'd maybe want to work with memoryview, though that doesn't support Unicode directly.

bstpierre
+1 I like this, I didn't know about `bytearray`.
David Zaslavsky
They aren't Characters, however, just bytes. Works only for ASCII, not Unicode.
S.Lott
Works for valid *bytes*, not just ASCII.
bstpierre
A: 
def modifyIdx(s, idx, newchar):
    return s[:idx] + newchar + s[idx+1:]
Joe Koberg
+1  A: 

Assigning a particular character to a particular index in a string is not a particularly common operation, so if you find yourself needing to do it, think about whether there may be a better way to accomplish the task. But if you do need to, probably the most standard way would be to convert the string to a list, make your modifications, and then convert it back to a string.

s = 'abcdefgh'
l = list(s)
l[3] = 'r'
s2 = ''.join(l)

EDIT: As posted in bstpierre's answer, bytearray is probably even better for this task than list, as long as you're not working with Unicode strings.

s = 'abcdefgh'
b = bytearray(s)
b[3] = 'r'
s2 = str(b)
David Zaslavsky
And if you are working with multibyte strings you better be careful not to chop an encoded character!
Joe Koberg
A: 

If I ever need to do something like that I just convert it to a mutable list

For example... (though it would be easier to use sort (see second example) )

>>> s = "abcdfe"
>>> s = list(s)
>>> s[4] = "e"
>>> s[5] = "f"
>>> s = ''.join(s)
>>> print s
abcdef
>>>
# second example
>>> s.sort()
>>> s = ''.join(s)
Zimm3r
A: 

You can use StringIO or cStringIO classes to receive file-like mutable interface of string.

Odomontois
+5  A: 

you can use the UserString module:

 >>> import UserString
... s = UserString.MutableString('Python')
... print s
Python
>>> s[0] = 'c'
>>> print s
cython
killown
+1 thanks for referring to `UserString`
Satoru.Logic
A: 

Here is an example using translate to switch "-" with "." and uppercase "a"s

>>> from string import maketrans
>>> trans_table = maketrans(".-a","-.A")
>>> "foo-bar.".translate(trans_table)
'foo.bAr-'

This is much more efficient that flipping to byte array and back if you just need to do single char replacements

gnibbler
+5  A: 

The Python analog of your C:

for(int i = 0; i < strlen(s); i++)
{
   s[i] = F(s[i]);
}

would be:

s = "".join(F(c) for c in s)

which is also very expressive. It says exactly what is happening, but in a functional style rather than a procedural style.

Ned Batchelder
Interesting; I sort of like it, except for the null string. Probably as good as I'm getting though.
Paul Nathan
A significant part of the C to me is that it works in-place.
Joe Koberg
@Joe Koberg: A significant part of the Python is that it's shorter and more clear. "in place" is limiting because you can't make the string longer.
S.Lott
I understand that, but the OP seemed to be focusing on it. And rejected many standard python string transformation methods. That it didn't work in-place was the only thing left.
Joe Koberg
Isn't `map()` the same as the list comprehension and join?
Javier Badia
@javier: map() applies a function to a list of values, and returns the list of results.
Ned Batchelder
+2  A: 

I'd say the most Pythonic way is to use map():

s = map(func, s) # func has been applied to every character in s

This is the equivalent of writing:

s = "".join(func(c) for c in s)
Javier Badia