patternpythonMinor
Eval is evil: Dynamic method calls from named regex groups in Python 3
Viewed 0 times
callsfromgroupsmethodnamedevilpythondynamicregexeval
Problem
I'm working on a simple dice roller for a Python IRC bot. The particular game this roller is for uses six different kinds of dice, which I've defined using a simple base class, and created six instances of it, passing a string array with the possible values of each dice.
An example of the instances:
Now for a user to actually access this function, they must write a textual dice expression in the IRC channel. An example usage:
So I need a way to quickly convert from the dice expression provided by the user, to call the
I'm using a regular expression to evaluate the dice expression, so named capture groups seem like the most straightforward way to handle this - at the possible cost of my immortal soul, since I'm eval-ing the group name to the proper instance of DiceBag.
Is there a better, or saner, or more pythonic, or perhaps less evil way I could be handling this use case?
class DiceBag(object):
def __init__(self, dice: List[str]):
self.bag = dice
def draw(self, count: int) -> str:
return [random.choice(self.bag) for _ in range(0, count)]An example of the instances:
proficiency_dice = DiceBag(['', 'S', 'S', 'SS', 'SS', 'A', 'AS', 'AS', 'AS', 'AA', 'AA', '!'])
boost_dice = DiceBag(['', '', 'AA', 'A', 'SA', 'S'])Now for a user to actually access this function, they must write a textual dice expression in the IRC channel. An example usage:
?roll 2p1b
Result: P(A, AA) B(S)So I need a way to quickly convert from the dice expression provided by the user, to call the
draw method on the appropriate class.I'm using a regular expression to evaluate the dice expression, so named capture groups seem like the most straightforward way to handle this - at the possible cost of my immortal soul, since I'm eval-ing the group name to the proper instance of DiceBag.
#User's input comes in as a string on the dice var
rex = (r'(?P\ds)?'
r'(?P\da)?'
r'(?P\dd)?'
r'(?P\dp)?')
to_roll = re.match(rex, dice).groups()
for group in to_roll:
if group: # We don't care about the Nones
dicetype = group.split('')[1]
dicecnt = group.split('')[0] # handle the extra rolls later
eval(dicetype + "_dice" + ".draw(" + dicecnt + ")")Is there a better, or saner, or more pythonic, or perhaps less evil way I could be handling this use case?
Solution
Since you're converting user input into your own inner representation before
But here, I'd go with simple dictionaries. It allows for automatically building the regexp and you don't really need the name of the capturing group since the dictionary already maps that information:
Note the use of
Also:
evaluating, this is lesser evil. And sometimes this is the right tool for the job (inspect and pdb make good use of it).But here, I'd go with simple dictionaries. It allows for automatically building the regexp and you don't really need the name of the capturing group since the dictionary already maps that information:
# Maps allowed characters to actual dice
DIE = {
's': setback_dice,
'd': difficulty_dice,
'a': ability_dice,
'p': proficiency_dice,
}
# Build the rules to parse user input
REX = ''.join(r'(\d{})?'.format(t) for t in DIE)
to_roll = re.match(REX, dice).groups()
for group in filter(None, to_roll):
count, type_ = group
DIE[type_].draw(int(count))Note the use of
filter to "not care about the Nones" and the uppercase variable names for constants.Also:
- you might want to wrap your code into functions to ease reusability and testing;
- you might want to use
re.compileto improve efficiency;
- you might want to come up with a better regexp as this one is very dependant on the order it is written.
Code Snippets
# Maps allowed characters to actual dice
DIE = {
's': setback_dice,
'd': difficulty_dice,
'a': ability_dice,
'p': proficiency_dice,
}
# Build the rules to parse user input
REX = ''.join(r'(\d{})?'.format(t) for t in DIE)
to_roll = re.match(REX, dice).groups()
for group in filter(None, to_roll):
count, type_ = group
DIE[type_].draw(int(count))Context
StackExchange Code Review Q#124643, answer score: 6
Revisions (0)
No revisions yet.