7

Załóżmy, że mam funkcję o nazwie generator, która zwraca 4-tkę z losowo wybranymi wartościami w określonych wstępnie określonych zakresach. Powiedzmy, że krotka jest postaci (age, sex, location, marital_status):Dopasowywanie wzorców dla parametrów funkcji w języku Python

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"} 

Z drugiej strony, powiedzmy, że zdefiniowano 20 różnych funkcji, z definicji tak:

def p1 (age, sex, location, marital_status) 
def p2 (age, sex, location, marital_status) 
. 
. 

gdzie p1 ma otrzymać parametry z wartości następującej postaci:

`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 

i wyobrazić sobie inny zestaw wartości dla p2 aż do p20.

Jaki jest pragmatyczny sposób określania, który zestaw wygenerowanych wartości jest zgodny z daną funkcją?

W tym przypadku wszystkie definicje gdzie dokładnie taka sama, ale mogę sobie wyobrazić przypadki, gdzie mogą znajdować się niewielkie różnice w definicjach, na przykład p18 mogłyby być def p1 (age, location) ze szczególnych ograniczeń w zakresie możliwości age i location.

P.S. Wzorce nie muszą się wykluczać, co oznacza, że ​​zestaw wygenerowanych wartości może równie dobrze pasować do więcej niż jednej funkcji.

+0

Ponieważ jesteś na Pythonie 3, * Adnotacje funkcji * mogą być najlepszym sposobem: https://www.python.org/dev/peps/pep-3107/. – cdarke

+0

Czy wzorce wzajemnie się wykluczają, czy też możliwe jest, że więcej wzorów pasuje? – dlask

+0

@dlask: Dobra uwaga. Istnieje niewielkie prawdopodobieństwo, że wzór pasuje do więcej niż jednej funkcji. –

Odpowiedz

1

Jako pythonic sposób w Pythona 3.X (ale nie 2.X), możesz dołączyć annotation information (dowolnie zdefiniowane przez użytkownika dane o argumentach funkcji i wyniku) do obiektu funkcji. Tutaj możesz użyć tej funkcji w dekoratorze, aby zawinąć swoją funkcję, aby sprawdzić zakres argumentów.

Na przykład można użyć następujących funkcji testowej Zakres:

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=8) 
invalid arg range 

Jest tu jakiś punkt.Po pierwsze, ponieważ informacje o adnotacjach znajdują się w słowniku (python zwraca je jako słownik), a słowniki nie mają określonej kolejności, musisz użyć argumentów słów kluczowych w swojej funkcji, aby uzyskać względny zakres w adnotacji słownik informacji.

Również tutaj użyłem tylko zakresu liczbowego, ale możesz użyć niektórych niestandardowych zakresów, takich jak lista słów, takich jak to, co pokazałeś w pytaniu. Wewnątrz jednak musisz sprawdzić jego typ, a następnie na podstawie jego typu użyć właściwej operacji:

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()) 
1
# 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"}) 

Kilka komentarzy:

  • Testy mogą być zawarte w samych funkcji.
  • Funkcje testowe muszą również radzić sobie z brakującymi parametrami.
  • Możliwe jest również bezpośrednie wykorzystanie parametrów funkcji.

Istnieje kilka odmian rzeczywiście możliwe, ale główna zasada jest taka sama:
Znajdź funkcję dopasowania i nazwać.

1

Jeśli są chętni, aby dodać sformatowane ciągi doc do swoich funkcji (zamiast typu sprawdzanie każdy argument), wtedy można rozważyć robi coś takiego:

# 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"])