Recipe 1.9. Simplifying Usage of Strings' translate Method
Credit: Chris Perkins, Raymond Hettinger
Problem
You often want to use the fast code in
strings' TRanslate method, but
find it hard to remember in detail how that method and the function
string.maketrans work, so you want a handy
facade to simplify their use in typical cases.
Solution
The TRanslate method of strings is quite powerful
and flexible, as detailed in Recipe 1.10. However, exactly because of
that power and flexibility, it may be a nice idea to front it with a
"facade" that simplifies its
typical use. A little factory function,
returning a closure, can do wonders for this kind of
task:
import string
def translator(frm='', to='', delete='', keep=None):
if len(to) == 1:
to = to * len(frm)
trans = string.maketrans(frm, to)
if keep is not None:
allchars = string.maketrans('', '')
delete = allchars.translate(allchars, keep.translate(allchars, delete))
def translate(s):
return s.translate(trans, delete)
return translate
Discussion
I often find myself wanting to use strings'
translate method for any one of a few purposes,
but each time I have to stop and think about the details (see Recipe 1.10 for more information
about those details). So, I wrote myself a class (later remade into
the factory closure presented in this recipe's
Solution) to encapsulate various possibilities behind a
simpler-to-use facade. Now, when I want a function that keeps only
characters from a given set, I can easily build and use that
function:
>>> digits_only = translator(keep=string.digits)
>>> digits_only('Chris Perkins : 224-7992')
'2247992'
It's similarly simple when I want to
remove a set of characters:
>>> no_digits = translator(delete=string.digits)
>>> no_digits('Chris Perkins : 224-7992')
'Chris Perkins : -'
and when I want to replace a set of characters with a single
character:
>>> digits_to_hash = translator(from=string.digits, to='#')
>>> digits_to_hash('Chris Perkins : 224-7992')
'Chris Perkins : ###-####'
While the latter may appear to be a bit of a special case, it is a
task that keeps coming up for me every once in a while.
I had to make one arbitrary design decision in this
recipenamely, I decided that the delete
parameter "trumps" the
keep parameter if they overlap:
>>> trans = translator(delete='abcd', keep='cdef')
>>> trans('abcdefg')
'ef'
For your applications it might be preferable to ignore
delete if keep is specified, or,
perhaps better, to raise an exception if they are both specified,
since it may not make much sense to let them both be given in the
same call to translator, anyway. Also: as noted in
Recipe 1.8 and Recipe 1.10, the code in this
recipe works only for normal strings, not for
Unicode strings. See Recipe 1.10 to learn how to code this
kind of functionality for Unicode strings, whose
translate method is different from that of plain
(i.e., byte) strings.
|
A
closure is nothing terribly complicated: just
an "inner" function that refers to
names (variables) that are local to an
"outer" function containing it.
Canonical toy-level example:
def make_adder(addend):
def adder(augend): return augend+addend
return adder
Executing p = make_adder(23) makes a closure of
inner function adder internally referring to a name
addend that is bound to the value 23. Then,
q = make_adder(42) makes
another closure, for which, internally, name
addend is instead bound to the value 42. Making
q in no way interferes with
p, they can happily and independently
coexist. So we can now execute, say, print p(100),
q(100) and enjoy the output 123 142.
In practice, you may often see make_adder referred
to as a closure rather than by the pedantic,
ponderous periphrasis "a function that returns a
closure"fortunately, context often clarifies
the situation. Calling make_adder a
factory (or factory
function) is both accurate and concise; you may also say
it's a closure factory to
specify it builds and returns closures, rather than, say, classes or
class instances.
|
|