Reputation: 2319
Suppose I have a function named generator
that returns a 4-tuple with randomly selected values in certain pre-specified ranges. Let's say the tuple is of the form (age, sex, location, marital_status)
:
age is in range(5, 85)
sex is a member of the set {"m", "f"}
location is a member of the set of all the cities in California
marital_status is a member of {"married", "single", "separated"}
On the other hand, let's say I have defined 20 different functions with definitions like this:
def p1 (age, sex, location, marital_status)
def p2 (age, sex, location, marital_status)
.
.
where p1
is supposed to receive parameters with values of the following form:
`age` must be in the range 20 to 45
`sex` must be male
`location` could be any city in Southern California
`marital_status` could be either single or married
and imagine a different set of values for p2
all the way to p20
.
What is a pragmatic way to determine which set of generated values match which function?
In this case all the definitions where exactly the same, but I can imagine instances where there might be slight differences in the definitions, for example p18
could be def p1 (age, location)
with specific limitations on the range of possibilities for age
and location
.
P.S. The patters are not necessarily mutually exclusive, meaning a set of generated values might as well match more than one function.
Upvotes: 8
Views: 7730
Reputation: 107287
As a Pythonic way in Python 3.X (but not 2.X), you can attach annotation information (arbitrary user-defined data about a function’s arguments and result) to a function object. Here you can use this feature in a decorator to wrap your function to check the range of your arguments.
For example you can use the following range test function :
def rangetest(func):
def onCall(**kargs):
argchecks = func.__annotations__
if all(val in range(*argchecks.get(arg)) for arg,val in kargs.items()):
return func(**kargs)
else :
print ("invalid arg range")
return onCall
@rangetest
def func(a:(1, 5), b:(4,7), c:(0, 10)):
print(a + b + c)
Demo :
func(a=2, b=6, c=8)
16
func(a=2, b=6, c=15)
invalid arg range
There is some point here. The first is that, since the annotation information is within a dictionary (python returns it as a dictionary) and dictionaries don't have a specific order, you need to use keyword arguments in your function to be able to get its relative range in annotation information dictionary.
Also here I just used numeric range but you can use some custom ranges like list of words like what you show in your question.But inside the all
you need to check its type then based on its type use a proper operation :
all(kwargs.get(arg) in range(*arg_range) if is instance (arg_range,tuple) else kwargs.get(arg) in arg_range for arg,arg_range in argchecks.items())
Upvotes: 2
Reputation: 1024
If you are willing to add formatted doc strings to your functions (instead of type checking each argument), then you can consider doing something like this:
# This function has a foratted doc string.
# :argument: Truth condition to be evaluated
# If any condition is False, function won't be called
def party_list(name, age, sex):
"""
:name:"{}" != "Dad"
:age:17< {} <25
:sex:True
"""
print("You're invited to my party, {}!".format(name))
# We will make some sample data
keys = ["name", "age", "sex"]
values = [["John", 24, "Male"],
["Sarah", 25, "Female"],
["Pikachu", 2, "Pokemon"],
["Pizza guy", 18, "Male"]]
# Turn it in to a dictionary
for key, value in enumerate(values):
values[key] = {t:p for t, p in zip(keys, value)}
# These conditions can be dynamically made for each function,
# because we have access to the doc string from the outside
conditions = list(filter(lambda c: ':' in c, party_list.__doc__.split('\n')))
for friend in values:
for case in conditions:
tag, code = case.split(':')[1:]
if not eval(code.format(friend[tag])):
break
else:
party_list(friend["name"], friend["age"], friend["sex"])
Upvotes: 1
Reputation: 8982
# define test t1 for function p1
def t1(params):
return params["age"] in range(5, 85) \
and params["sex"] in ["m", "f"] \
and cityof(params["location"], "California") \
and params["marital_status"] in ["married", "single", "separated"]
# and similarly for other p* functions
# define functions
def p1(params): ...
def p2(params): ...
# and so on
# bind tests and functions
RULES = {
(t1, p1),
(t2, p2),
...
}
# have the right functions called
def go(params):
for rule in RULES:
if rule[0](params):
rule[1](params)
# test it
go({"age": 6, "sex": "m", "location": "somewhere", "marital_status": "single"})
Just a few comments:
There are actually several variations possible but the main principle is the same:
Find the matching function and call it.
Upvotes: 1