this recipe contains a function, "rename_members", which takes an object, and two strings describing variable naming conventions (e.g. "allcamel", "underscores", etc). it translates attribute names on the given object from the first naming convention to the second (it doesn't delete the original attributes). the function also takes an optional acceptance function, which will be passed each attribute. the default acceptance function is "callable".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
# this is not entirely serious import re _patterns = dict((k, re.compile('_*' + v)) for (k, v) in dict(allcamel=r'(?:[A-Z]+[a-z0-9]*)+$', trailingcamel=r'[a-z]+(?:[A-Z0-9]*[a-z0-9]*)+$', underscores=r'(?:[a-z]+_*)+[a-z0-9]+$').iteritems()) _caseTransition = re.compile('([A-Z][a-z]+)') def translate(name, _from, to): leading_underscores = str() while name == '_': leading_underscores += '_' name = name[1:] if _from in ('allcamel', 'trailingcamel'): words = _caseTransition.split(name) else: words = name.split('_') words = list(w for w in words if w is not None and 0 < len(w)) camelize = lambda words: ''.join(w.upper() + w[1:] for w in words) v = dict(smushed=lambda: ''.join(words).lower(), allcamel=lambda: camelize(words), trailingcamel=lambda: words.lower() + camelize(words[1:]), underscores=lambda: '_'.join(words).lower())[to]() return leading_underscores + v def rename_members(obj, _from, to, explode_for_non_matches=True, acceptance_function=callable, debug=False): assert _from in _patterns if to != 'smushed': assert to in _patterns for name in dir(obj): if name.startswith('__') and name.endswith('__'): continue thing = getattr(obj, name) if not acceptance_function(thing): continue if _patterns[_from].match(name): newname = translate(name, _from, to) if hasattr(obj, newname): raise ValueError('%r already has a %r attribute' % (obj, newname)) setattr(obj, newname, thing) if debug: print obj, ':', name, '->', newname else: if explode_for_non_matches: raise ValueError('attribute %r of %r is not well formed %r' % (name, obj, _from)) return obj # examples class Foo: def thisIsAMethod(self): pass rename_members(Foo, 'trailingcamel', 'underscores') assert hasattr(Foo, 'this_is_a_method') # you could of course pass a module or an instance or anything else class Foo: def this_is_a_method(self): pass rename_members(Foo, 'underscores', 'allcamel') assert hasattr(Foo, 'ThisIsAMethod')
possible values of the "_from" parameter are: "allcamel", "trailingcamel", "underscores".
possible values of the "to" parameter are "allcamel", "trailingcamel", "underscores", "smushed".
here are some examples of what the function thinks each name corresponds to:
* ThisIsAllCamel * thisIsTrailingCamel * this_is_underscores * thisissmushed
_from obviously cannot have the value "smushed" because it's unclear where the word boundaries are in strings that are written like that.
if explode_for_non_matches is True, then the function will raise a ValueError if it finds an attribute that passes the acceptance function, but whose name does not match the "from" scheme.
fiddle with the regexes if it makes you happy; here are some of the decisions i made:
* an attribute called 'item' (or anything all lower case) is valid for "trailingcamel" and "underscores", but not valid "allcamel" * an attribute called 'Item' is valid "allcamel" but not valid anything else.
so converting 'Item' to anything else will result in 'item'. converting 'item' to allcamel will result in 'Item'. the camel case modes think that case transitions are word boundaries, so 'ThisIsAnABCOk' will become 'this_is_an_abc_ok' (trailingcamel -> underscores), but bastardized casings like urllib's 'FancyURLopener' will get mistranslated as 'fancy_ur_lopener' (garbage in, garbage out). the only way to resolve something like that is on a case by case basis (pun not intended).
__special__ attributes are ignored, but leading underscores are legal for all modes.
i'm sure there are some bugs, also, but cursory testing didn't reveal anything.