Transform letters in a string I am learning python and want to improve my skills in it. So could someone advice of how to improve the code below (this is a simple programming problem taken from one of the web sites). I feel like my code is not really Pythonic.

Problem: Using the Python language, have the function LetterChanges(str) take the str parameter being passed and modify it using the following algorithm. Replace every letter in the string with the letter following it in the alphabet (ie. c becomes d, z becomes a). Then capitalize every vowel in this new string (a, e, i, o, u) and finally return this modified string. 

from sets import Set

def LetterChanges(str): 
   vowels = Set(['a', 'e', 'i', 'o', 'u'])
   new_str = ''
   for i in str:
      cur_char = i.lower()
      if cur_char == 'z':
         new_str += 'A'
      elif ord(cur_char) &gt;= 97 and ord(cur_char) &lt; 122:
         new_char = chr(ord(i) + 1)
         new_str += (new_char if new_char not in vowels else new_char.upper())
      else:
         new_str += i
   return new_str

if __name__=='__main__': 
   print(LetterChanges(raw_input()))
Your code defines a function with a clear input and a clear output. Also, you've put the call to it behind the if __name__ == '__main__':. You've taken good habits on this points. Nonetheless, a few things can still be improved.

Python version

If you are learning Python, there is no good reason to start with Python 2 which is reaching its end of life. You should focus on learning Python 3, going to be supported for a while and with various warts corrected (and obviously more features).

In any case, the sets module is not required since Python 2.6 since we have the set builtin.

Style

There is an official standard Python style guide called PEP 8. This is highly recommended reading. It gives guidelines to help writing code that is both readable and consistent. The Python community tries to follow these guidelines, more or less strictly (a key aspect of PEP 8 is that it provides guidelines and not strict rules to follow blindly).

It deals with various aspects of the code style: naming conventions, indentation convention, etc.

You'll find various tools to try to check whether your code is PEP 8 compliant and if is it not, to try and fix this:


pycodestyle package (formerly known as pep8) to check you code
pep8online to check your code with an online tool
autopep8 package to fix your code automatically
Also, this is also checked by various linters: pylint, pyflakes, flake8, etc.


In your case, the major points I would fix are:


indentation should be 4 space
function names should snake_cased
you could try to take into account the advice to use join instead of "CPython's efficient implementation of in-place string concatenation".


Naming

On top of the snake-case point mentioned above, a few things could be improved in the naming. Naming is a difficult part of programming so what I suggest are just personal, far from perfect, suggestions (from a non-native English speaker). In your case, I'd call the function tranform_string so that it conveys the fact that:


it is an action (a noun would correspond to an object/class name)
it applies to string (and not to single character even if the distinction is fuzzy in Python compared to other programming languages)


The name str is a bit of a problem because it hides the str builtin. I do not find any alternative better than str_ which is still pretty poor :-(.

Edit: I don't know why I didn't think about "s" in the first place but after reading it in another answer, it looks like a much better option indeed. I am too lazy to edit my code everywhere.

Finally, i is a good name for an (integer) index. For a single character, c seems easier to understand (char if you feel verbose).

Chained comparison

In Python, you can chain comparison. If your case, you can write:

elif 97 &lt;= ord(cur_char) &lt; 122:


More beautiful character check

From the Zen of Python:


  Beautiful is better than ugly.
  Explicit is better than implicit.
  Simple is better than complex.


In your case, you could avoid the call the ord and get rid of the magic number by writing the simple:

elif 'a' &lt;= cur_char &lt; 'z':


Rewriting the logic

You could have the logic adding a character to new_str in a single place.

    cur_char = c.lower()
    if cur_char == 'z':
        new_char = 'A'
    elif 'a' &lt;= cur_char &lt; 'z':
        new_char = chr(ord(c) + 1)
        if new_char in vowels:
            new_char = new_char.upper()
    else:
        new_char = c
    new_str += new_char


Then, you could try to makes the logic as close as possible to the way it is given in the problem for instance by making "z" a special case only for the operation where you take the following letter.

    low_char = c.lower()
    if 'a' &lt;= low_char &lt;= 'z':
        new_char = 'a' if low_char == 'z' else chr(ord(c) + 1)
        if new_char in vowels:
            new_char = new_char.upper()
    else:
        new_char = c
    new_str += new_char


Shorter way to define vowels

Because strings are iterable and set takes any kind of iterable, you could write:

vowels = set('aeiou')


Better ascii check

You could reuse values from the string module to make your test more beautiful:

   if low_char in string.ascii_lowercase:


Removing string concatenation

As mentionned earlier, it is advised not to use string concatenation and use join instead, when relevant.

At this stage, the code (with added tests) looks like:

import string

def transform_string(s):
    vowels = set('aeiou')
    lst = []
    for c in s:
        if c in string.ascii_letters:
            new_c = 'a' if c.lower() == 'z' else chr(ord(c) + 1)
            if new_c in vowels:
                new_c = new_c.upper()
        else:
            new_c = c
        lst.append(new_c)
    return "".join(lst)

if __name__=='__main__':
    assert transform_string("")            == ""
    assert transform_string("qwertyuiop")  == "rxfsUzvjpq"
    assert transform_string("asdfghjkl")   == "btEghIklm"
    assert transform_string("zxcvbnm")     == "AydwcOn"
    assert transform_string("1234567890")  == "1234567890"
    assert transform_string("QWERTYUIOP")  == "RXFSUZVJPQ"
    assert transform_string("ASDFGHJKL")   == "BTEGHIKLM"
    assert transform_string("ZXCVBNM")     == "AYDWCON"
    assert transform_string("(){}&lt;&gt;+_-=")  == "(){}&lt;&gt;+_-="