40

I need to convert an arbitrary string to a string that is a valid variable name in Python.

Here's a very basic example:

s1 = 'name/with/slashes'
s2 = 'name '

def clean(s):
    s = s.replace('/', '')
    s = s.strip()

    return s

# the _ is there so I can see the end of the string
print clean(s1) + '_'

That is a very naive approach. I need to check if the string contains invalid variable name characters and replace them with ''

What would be a pythonic way to do this?

5
  • 2
    What problem are you trying to solve using this method? There may be a better way. Commented Jul 21, 2010 at 20:06
  • 2
    print repr("a string ") shows the string in quotes - neater than appending _ to it. Commented Jul 21, 2010 at 20:38
  • 2
    I'm traversing a scene graph from cinema 4d and need to re-create it in blender. To keep things easy for me to understand I want to use the actual names of the cinema4d objects as variable names for Blender Python, so I need to adjust those first Commented Jul 21, 2010 at 20:40
  • 1
    BTW, the general conversion is called "slugifying" and searching stacoverflow or the internet for "python slugify" will find many solutions. Commented Jul 25, 2023 at 15:30
  • Thanks @samwyse ! if I only new this 13 years ago :))) Never too late to learn something new though. Commented Jul 25, 2023 at 21:03

5 Answers 5

79

Well, I'd like to best Triptych's solution with ... a one-liner!

>>> def clean(varStr): return re.sub('\W|^(?=\d)','_', varStr)
...

>>> clean('32v2 g #Gmw845h$W b53wi ')
'_32v2_g__Gmw845h_W_b53wi_'

This substitution replaces any non-variable appropriate character with underscore and inserts underscore in front if the string starts with a digit. IMO, 'name/with/slashes' looks better as variable name name_with_slashes than as namewithslashes.

Sign up to request clarification or add additional context in comments.
40

According to Python, an identifier is a letter or underscore, followed by an unlimited string of letters, numbers, and underscores:

import re

def clean(s):

   # Remove invalid characters
   s = re.sub('[^0-9a-zA-Z_]', '', s)

   # Remove leading characters until we find a letter or underscore
   s = re.sub('^[^a-zA-Z_]+', '', s)

   return s

Use like this:

>>> clean(' 32v2 g #Gmw845h$W b53wi ')
'v2gGmw845hWb53wi'
5

You can use the built in func:str.isidentifier() in combination with filter(). This requires no imports such as re and works by iterating over each character and returning it if its an identifier. Then you just do a ''.join to convert the array to a string again.

s1 = 'name/with/slashes'
s2 = 'name '

def clean(s):
    s = ''.join(filter(str.isidentifier, s))
    return s

print f'{clean(s1)}_' #the _ is there so I can see the end of the string

EDIT:

If, like Hans Bouwmeester in the replies, want numeric values to be included as well, you can create a lambda which uses both the isIdentifier and the isdecimal functions to check the characters. Obviously this can be expanded as far as you want to take it. Code:

s1 = 'name/with/slashes'
s2 = 'name i2, i3    '
s3 = 'epng2 0-2g [ q4o 2-=2 t1  l32!@#$%*(vqv[r 0-34 2]] '

def clean(s):
    s = ''.join(filter( 
        lambda c: str.isidentifier(c) or str.isdecimal(c), s))
    return s
#the _ is there so I can see the end of the string
print(f'{ clean(s1) }_')
print(f'{ clean(s2) }_')
print(f'{ clean(s3) }_')

Gives :

namewithslashes_
namei2i3_
epng202gq4o22t1l32vqvr0342_
3

You should build a regex that's a whitelist of permissible characters and replace everything that is not in that character class.

0

I wanted a solution that didn't import re and yet collapsed multiple underscores and any leading/trailing underscores. Combining most of the non-regex answers here with a quirk of the str.split() function when given no arguments (from this answer to another question), I came up with this "one-liner" which I think works well (and fast, but not really tested):

def clean(s):
   return '_'.join(''.join(map(
       lambda c: c if str.isidentifier(c) or str.isdecimal(c) else ' ',
       s
   )).split()).lstrip('0123456789')

This uses the lambda approach with map() instead of filter(), and replaces non-identifier characters with spaces... which is odd until the split()+'_'.join() trick comes in, because split() without arguments collapses multiple sequential whitespaces--which then get "replaced" with single underscores. The only thing novel here is the slightly kludgy .lstrip('01234567890') call which strips any leading numbers.

So, for...

>>> s3 = ' 42epng2 0-2g [ q4o 2-=2 t1  l32!@#$%*(vqv[r 0-34 2]] '
>>> clean(s3)
'epng2_0_2g_q4o_2_2_t1_l32_vqv_r_0_34_2'

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.