Chris
Chris

Reputation: 1672

How to pass a variable to a re.sub callback?

I am using a re.sub callback to replace substrings with random values, but I would like the random values to be the same across different strings. Since the re.sub callback does not allow arguments, I am not sure how to do this.

Here is a simplified version of what I'm doing:

def evaluate(match):
    mappings = {'A': 1, 'B': 2}
    return str(eval(match.group(0)[2:-1], mappings))

# variables = {'A':[1,2,3,4,5], 'B':[1,2,3,4,5]}    
# mappings2 = {k:v[random.randint(0,len(v)-1)] for k, v in variables.items()}
string_one: "#{A} + #{B}"
string_two: "#{A+B}"
newstring_one = sub(r'\#\{([^#]+)\}', evaluate, string_one)
newstring_two = sub(r'\#\{([^#]+)\}', evaluate, string_two)

Now, as it stands, the strings will be properly evaluated: newstring_one is "1 + 2" and newstring_two is "3". But I want to be able to pick the values randomly, and still have them replaced in both strings. This would involve deleting the 'mappings' line in 'evaluate', and using something like the two commented lines. How, though, can I get my randomly chosen mappings2 to be used when eval-ing both strings, if I cannot pass it as an argument in the re.sub callback function?

Many thanks.

Upvotes: 10

Views: 5984

Answers (4)

Armin Rigo
Armin Rigo

Reputation: 12980

The variant I would recommend:

mappings = {'A': 1, 'B': 2}  # Or whatever ...
def evaluate(match):
    return str(eval(match.group(0)[2:-1], mappings))
newstring = sub(r'\#\{([^#]+)\}', evaluate, string)

i.e. simply put the def evaluate(..) just before the sub(), as a local function.

Upvotes: 0

kennytm
kennytm

Reputation: 523544

You could create a closure.

def evaluator(mappings):
  def f(match):
    return str(eval(match.group(0)[2:-1], mappings))
  return f

evaluate = evaluator({'A': 1, 'B': 2})

Since f is just a single statement, you could simply use lambda:

def evaluator(mappings):
  return lambda match: str(eval(match.group(0)[2:-1], mappings))

Upvotes: 2

Ferdinand Beyer
Ferdinand Beyer

Reputation: 67197

The easiest way I guess is to make use of functools.partial, which allows you create a "partially evaluated" function:

from functools import partial

def evaluate(match, mappings):
    return str(eval(match.group(0)[2:-1], mappings))

mappings = {'A': 1, 'B': 2}  # Or whatever ...

newstring = sub(r'\#\{([^#]+)\}', partial(evaluate, mappings=mappings), string)

Upvotes: 16

kennytm
kennytm

Reputation: 523544

You could use a function object.

 class A(object):
  def __init__(self, mappings):
    self.mappings = mappings
  def __call__(self, match):
    return str(eval(match.group(0)[2:-1], self.mappings))

 evaluate = A({'A': 1, 'B': 2})

Upvotes: 1

Related Questions