views:

1290

answers:

6

Example:

>>> convert('CamelCase')
camel_case
+2  A: 

Not in the standard library, but I found this script that appears to contain the functionality you need.

Stefano Borini
+1  A: 

For the fun of it:

>>> def un_camel(input):
...     output = [input[0].lower()]
...     for c in input[1:]:
...             if c in ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
...                     output.append('_')
...                     output.append(c.lower())
...             else:
...                     output.append(c)
...     return str.join('', output)
...
>>> un_camel("camel_case")
'camel_case'
>>> un_camel("CamelCase")
'camel_case'

Or, more for the fun of it:

>>> un_camel = lambda i: i[0].lower() + str.join('', ("_" + c.lower() if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" else c for c in i[1:]))
>>> un_camel("camel_case")
'camel_case'
>>> un_camel("CamelCase")
'camel_case'
gahooa
+1, but what about un_camel("getHTTPResponseCode") ? :)
Stefano Borini
c.isupper() rather than c in ABCEF...Z
Jimmy
Python doesn't have regexes? A quick 's/[a-z]\K([A-Z][a-z])/_\L$1/g; lc $_' in Perl does the job (although it does not handle getHTTPResponseCode well; but that's expected, that should be named getHttpResponseCode)
jrockway
`str.join` has been deprecated for _ages_. Use `''.join(..)` instead.
John Fouhy
jrockway: It does have regular expressions, via the "re" module. It shouldn't be too difficult to make this work using regex rather than the approaches posted here.
Matthew Iselin
Python noob here, but why return str.join('', output)? Just to create a copy?
Tarks
Tarks, `output` is a list of characters, not a string. So running it through str.join with an empty string as the first parameter will compress the list into a string.
Evan Fosmark
@John: Why do you say that is deprecated? str.join("", []) is exactly the same as calling "".join([]). Bound vs. unbound. Same as calling ParentClass.__init__(self). However, I find the first more readable. Can you point to any docs on the depreciation?
gahooa
A: 
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
Jimmy
A: 

A horrendous example using regular expressions (you could easily clean this up :) ):

def f(s):
    return s.group(1).lower() + "_" + s.group(2).lower()

p = re.compile("([A-Z]+[a-z]+)([A-Z]?)")
print p.sub(f, "CamelCase")
print p.sub(f, "getHTTPResponseCode")

Works for getHTTPResponseCode though!

Alternatively, using lambda:

p = re.compile("([A-Z]+[a-z]+)([A-Z]?)")
print p.sub(lambda x: x.group(1).lower() + "_" + x.group(2).lower(), "CamelCase")
print p.sub(lambda x: x.group(1).lower() + "_" + x.group(2).lower(), "getHTTPResponseCode")

EDIT: It should also be pretty easy to see that there's room for improvement for cases like "Test", because the underscore is unconditionally inserted.

Matthew Iselin
+3  A: 

Here's my solution:

def un_camel(text):
    """ Converts a CamelCase name into an under_score name. 

        >>> un_camel('CamelCase')
        'camel_case'
        >>> un_camel('getHTTPResponseCode')
        'get_http_response_code'
    """
    result = []
    pos = 0
    while pos < len(text):
        if text[pos].isupper():
            if pos-1 > 0 and text[pos-1].islower() or pos-1 > 0 and \
            pos+1 < len(text) and text[pos+1].islower():
                result.append("_%s" % text[pos].lower())
            else:
                result.append(text[pos].lower())
        else:
            result.append(text[pos])
        pos += 1
    return "".join(result)

It supports those corner cases discussed in the comments. For instance, it'll convert getHTTPResponseCode to get_http_response_code like it should.

Evan Fosmark
-1 because this is very complicated compared to using regexps.
EOL
EOL, I'm sure plenty of non-regexp people would think otherwise.
Evan Fosmark
+15  A: 

This is pretty thorough:

def convert(name):
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

Works with all these (and doesn't harm already-un-cameled versions):

>>> convert('CamelCase')
'camel_case'
>>> convert('CamelCamelCase')
'camel_camel_case'
>>> convert('Camel2Camel2Case')
'camel2_camel2_case'
>>> convert('getHTTPResponseCode')
'get_http_response_code'
>>> convert('get2HTTPResponseCode')
'get2_http_response_code'
>>> convert('HTTPResponseCode')
'http_response_code'
>>> convert('HTTPResponseCodeXYZ')
'http_response_code_xyz'

Or if you're going to call it a zillion times, you can pre-compile the regexes:

first_cap_re = re.compile('(.)([A-Z][a-z]+)')
all_cap_re = re.compile('([a-z0-9])([A-Z])')
def convert(name):
    s1 = first_cap_re.sub(r'\1_\2', name)
    return all_cap_re.sub(r'\1_\2', s1).lower()
epost