2012-02-23 9 views
5

Istnieje wiele modułów Pythona do analizowania i koordynowania opcji wiersza poleceń (argparse, getopt, blargs itp.). A Python jest wyposażony w dobre wbudowane funkcje/idiomy do obsługi różnych argumentów funkcji (np. Wartości domyślne, * varargs, ** keyword_args). Ale kiedy czytam kod różnych projektów dla funkcji najwyższego poziomu, widzę wyraźnie mniejszą dyscyplinę i standaryzację argumentów funkcji niż argumenty wiersza poleceń.Zalecane moduły Pythona do obsługi argumentów funkcji?

Dla prostych funkcji nie stanowi to problemu; wbudowane funkcje argumentów działają świetnie i są więcej niż wystarczające. Ale istnieje wiele funkcjonalnie bogatych modułów, których funkcje najwyższego poziomu zapewniają wiele różnych argumentów i opcji (niektóre uzupełniające lub wyłączne), różne tryby działania, ustawienia domyślne, nadpisywanie itp. - to znaczy, że mają złożoność argumentów zbliżając się do argumentów linii poleceń. I wydaje się, że w dużej mierze radzą sobie z ich argumentami w ad hoc sposobów.

Biorąc pod uwagę liczbę modułów przetwarzania wiersza poleceń, a także to, jak wyrafinowane stały się z biegiem czasu, spodziewałbym się przynajmniej kilku modułów upraszczających spory skomplikowanych argumentów funkcji. Ale przeszukałem PyPi, Stackoverflow i Google bez powodzenia. A więc ... czy są jakieś polecane moduły obsługi argumentów funkcji (nie wiersza poleceń!)?

--- aktualizacja z przykładu ---

Trudno podać konkretny przykład naprawdę proste, ponieważ przypadek użycia nie pojawi się dopóki masz do czynienia z wyrafinowanym modułu. Oto przykład wyjaśnienia problemu w kodzie: moduł formatyzatora z wartościami domyślnymi, które można zastąpić w instancji formatera lub po wywołaniu funkcji/metody. Aby mieć tylko kilka opcji, jest już strasznie dużo verbiage obsługi opcji, a nazwy opcji są powtarzane ad nauseam.

defaults = { 'indent':  4, 
       'prefix': None, 
       'suffix': None, 
       'name':  'aFormatter', 
       'reverse': False, 
       'show_name': False 
      } 

class Formatter(object): 

    def __init__(self, **kwargs): 
     self.name = kwargs.get('name', defaults['name']) 
     self.indent = kwargs.get('indent', defaults['indent']) 
     self.prefix = kwargs.get('prefix', defaults['prefix']) 
     self.suffix = kwargs.get('suffix', defaults['suffix']) 
     self.reverse = kwargs.get('reverse', defaults['reverse']) 
     self.show_name = kwargs.get('show_name', defaults['show_name']) 

    def show_lower(self, *args, **kwargs): 
     indent = kwargs.get('indent', self.indent) or 0 
     prefix = kwargs.get('prefix', self.prefix) 
     suffix = kwargs.get('suffix', self.suffix) 
     reverse = kwargs.get('reverse', self.reverse) 
     show_name = kwargs.get('show_name', self.show_name) 

     strings = [] 
     if show_name: 
      strings.append(self.name + ": ") 
     if indent: 
      strings.append(" " * indent) 
     if prefix: 
      strings.append(prefix) 
     for a in args: 
      strings.append(a.upper() if reverse else a.lower()) 
     if suffix: 
      strings.append(suffix) 
     print ''.join(strings) 

if __name__ == '__main__': 
    fmt = Formatter() 
    fmt.show_lower("THIS IS GOOD") 
    fmt.show_lower("THIS", "IS", "GOOD") 
    fmt.show_lower('this IS good', reverse=True) 
    fmt.show_lower("something!", show_name=True) 

    upper = Formatter(reverse=True) 
    upper.show_lower("this is good!") 
    upper.show_lower("and so is this!", reverse=False) 
+0

Mówisz o def somefunc (requiredValue, optionalValue = None, * args, ** kwargs)?Jeśli tak, to kod do swojej funkcji, tak jak byś obsługiwał argumenty linii poleceń pochodzące z terminala (co jest samą funkcją). – platinummonkey

+3

Czy możesz podać przykład funkcji ze złożonym interfejsem i pomysł jakich uproszczeń można oczekiwać od biblioteki? (O ile jest to warte, wydaje mi się, że istnieją pewne moduły Pythona z dość okropnymi interfejsami funkcyjnymi. Zaleciłbym, aby traktować je jako odstraszające przykłady, zamiast próbować zrobić coś takiego samemu.) –

+0

@SvenMarnach Naprawdę są bardzo ostrożne , przykłady "unikaj za wszelką cenę"! Ale gdy funkcja (powiedzmy funkcja fabryczna) jest punktem wejścia dla wyrafinowanej możliwości, to, że jest złożona, niekoniecznie jest błędem. Może po prostu odzwierciedlać złożoność/zmienność danego zadania. –

Odpowiedz

1

Jeśli API jest tak skomplikowane myślisz byłoby łatwiejsze w użyciu jakiś moduł do przetwarzania opcje, które zostały przekazane wam, istnieje duża szansa, że ​​rzeczywiste rozwiązanie ma uprościć API. Fakt, że niektóre moduły mają bardzo złożone sposoby wywoływania rzeczy, to wstyd, a nie funkcja.

3

Kiedy po raz pierwszy przeczytałem twoje pytanie, pomyślałem sobie, że prosisz o moduł pomocy zespołowej, i że on nie istnieje, ponieważ nikt nie chce napisać modułu, który umożliwia utrzymanie złego projektu.

Ale zdałem sobie sprawę, że sytuacja jest bardziej skomplikowana. Celem stworzenia takiego modułu, jak ten, który opisujesz, jest stworzenie kodu wielokrotnego użytku, ogólnego przypadku. Może się zdarzyć, że istnieją pewne interfejsy, które są uzasadnione. Jednak interfejsy te są właśnie interfejsami, których prawdopodobnie nie da się łatwo obsłużyć kodem ogólnego zastosowania. Są złożone, ponieważ zajmują się problematyczną domeną z wieloma specjalnymi przypadkami.

Innymi słowy, jeśli interfejs naprawdę nie może być refaktoryzowany, to prawdopodobnie wymaga dużej ilości niestandardowego, specjalnego kodu, który nie jest dostatecznie przewidywalny, aby być wartym uogólnienia w module. I odwrotnie, jeśli interfejs można łatwo załatać za pomocą modułu opisywanego rodzaju, to prawdopodobnie można go również refaktoryzować - w takim przypadku powinno być.

+0

Nie zgadzam się. Podążając za tym argumentem, nie ma sensu uogólniać przetwarzania wiersza poleceń na argparse, getopt itp. Ale tak jest. To, o co pytam, to analog dla modułów i funkcji: kod, który uogólnia i upraszcza złożone funkcje - w razie potrzeby, a czasami tak jest! - w ten sam sposób argparse pomaga dla poleceń. Moje czytanie kodu na PyPi sugeruje uproszczenie tego użycia byłoby pomocne. I nie jest to niemożliwe - mam dobre rozwiązanie robocze. Po prostu chce sprawdzić, czy istnieją bardziej akceptowane drogi. Przy tak wielu "tylko refaktorze!" odpowiedzi, zgaduję, że nie ma. Jeszcze. –

+0

@ JonathanEunice, cóż, nigdy nie powiedziałem, że to niemożliwe. Powiedziałem tylko, że prawdopodobnie zwiększy, a nie zmniejszy złożoność uzasadnionych i złożonych baz kodów. Mówiąc bardziej ogólnie, uważam, że analogia między interfejsami wiersza poleceń a interfejsami API jest fałszywa. Oni naprawdę nie są tacy podobni. – senderle

+0

IMO różnica stopnia, nie rodzaju. Wiersze poleceń i funkcje wywołują niektóre funkcje, z określonym zestawem opcji, instrukcji i danych. Na szczęście funkcje są często prostsze i łatwiejsze do powtórzenia. Ale kod, który widziałem z PyPi sugeruje, że nadal istnieje potrzeba uproszczenia funkcji. YMMV. –

0

jej w ręce dewelopera, ale jeśli robisz to biblioteka, która może być użyteczna dla innych projektów lub zostanie opublikowany na innych użytkowników, to myślę, że najpierw trzeba zidentyfikować problem i analizować je,

Dobrze dokumentuj swoje funkcje, dobrze jest zminimalizować liczbę argumentów, podaje wartości domyślne dla argumentów funkcjonalnych, w których użytkownicy mogą mieć problem z określeniem, co dokładnie trzeba przekazać.

i dla niektórych złożonych wymagań można zapewnić specjalne classmethods, które mogą być przesłonięte dla zaawansowanego programowania lub przez zaawansowanych użytkowników, którzy faktycznie chcą osiągnąć to, co grają z biblioteką, dziedziczenie jest zawsze obecne.

i można również przeczytać PEP8 również, co może być pomocne, ale ostatecznym celem jest określenie minimalnej liczby argumentów, ograniczenie użytkowników do wprowadzenia wymaganych argumentów, dobrze jest podać domyślne wartości opcjonalnych argumentów - w sposób, w jaki twój library/code jest również łatwo zrozumiałe dla zwykłych programistów ...

0

Możesz napisać bardziej ogólny kod dla domyślnego.

Jeśli myślisz o niewywiązywaniu się z zobowiązań, przechodząc przez domyślne ustawienia i zastępując słowa kluczowe, jeśli nie istnieją.

defaults = { 'indent':  4, 
      'prefix': None, 
      'suffix': None, 
      'name':  'aFormatter', 
      'reverse': False, 
      'show_name': False 
     } 

class Formatter(object): 

    def __init__(self, **kwargs): 
     for d,dv in defaults.iteritems(): 
     kwargs[d] = kwargs.get(d, dv) 

marginesie: Polecam przy użyciu słów kluczowych args w definicji __init__ metodę z domyślnych. Pozwala to na określenie function naprawdę stać kontrakt do innych twórców i użytkowników swojej klasie (Formatter)

def __init__(self, indent=4, reverse=False .....etc.....): 
+0

'kwargs [d] = kwargs.get (d, dv)' można zapisać jako 'kwargs.setdefault (d, dv)'. Dlatego nazywa się 'setdefault()' - to ustawienie jest domyślną wartością, jeśli nie ustawiono jeszcze żadnej wartości. –

2
  1. Nie sądzę linii poleceń analizowania i przetwarzania argumentem funkcji mają wiele wspólnego. Głównym problemem z wierszem poleceń jest to, że jedyną dostępną strukturą danych jest płaska lista łańcuchów, a nie masz dostępnego instrumentu, takiego jak nagłówek funkcji, który definiuje znaczenie każdego ciągu. W nagłówku funkcji Pythona możesz nadać nazwy każdemu z parametrów, możesz zaakceptować kontenery jako parametry, możesz zdefiniować domyślne wartości argumentów itp. Co robi biblioteka parsowania wiersza poleceń, faktycznie udostępnia linię poleceń niektóre z oferuje funkcje Pythona do wywoływania funkcji: nadaj nazwy parametrom, przypisz wartości domyślne, przekonwertuj na żądane typy itp. W Pythonie wszystkie te funkcje są wbudowane, więc nie potrzebujesz biblioteki, aby osiągnąć ten poziom wygody.

  2. Jeśli chodzi o Twój przykład, istnieje wiele sposobów ulepszenia tego projektu dzięki funkcjom oferowanym w języku. Możesz użyć domyślnych wartości argumentów zamiast ze słownika defaults, możesz enkapsulować wszystkie flagi w klasie FormatterConfig i przekazywać tylko jeden argument zamiast wszystkich tych argumentów. Ale załóżmy, że chcesz dokładnie interfejs, który podałeś w przykładowym kodzie. Jednym ze sposobów osiągnięcia tego jest następujący kod:

    class Config(dict): 
        def __init__(self, config): 
         dict.__init__(self, config) 
         self.__dict__ = self 
    
    def get_config(kwargs, defaults): 
        config = defaults.copy() 
        config.update(kwargs) 
        return Config(config) 
    
    class Formatter(object): 
    
        def __init__(self, **kwargs): 
         self.config = get_config(kwargs, defaults) 
    
        def show_lower(self, *args, **kwargs): 
         config = get_config(kwargs, self.config) 
    
         strings = [] 
         if config.show_name: 
          strings.append(config.name + ": ") 
         strings.append(" " * config.indent) 
         if config.prefix: 
          strings.append(config.prefix) 
         for a in args: 
          strings.append(a.upper() if config.reverse else a.lower()) 
         if config.suffix: 
          strings.append(config.suffix) 
         print "".join(strings) 
    

    Python oferuje wiele narzędzi do obsługi tego rodzaju argumentów. Więc nawet jeśli zdecydujemy się nie używać niektórych z nich (jak domyślne argumenty), nadal możemy uniknąć powtarzania się za bardzo.

+0

Punkt 1: Tak! Dobrze powiedziane. Funkcje Pythona zaczynają być znacznie bogatsze od płaskich łańcuchów argumentów wiersza poleceń. Punkt 2: Concur. Jeśli Config/get_config znajdowałby się w module, byłby to dobry początek dokładnie o to, o co prosiłem ... wyczyść natychmiastową obsługę konfiguracji domyślnych, nadpisań itp., Która rozszerza argumenty Pythona, by pasowały/obsługuje wysoce opcjonalne funkcje. Zaskoczony, nie ma już na tym miejscu wypolerowanego, dobrze znanego modułu PyPi. –