2013-07-05 9 views
9

Być może jako pozostałość po moich czasach z mocno napisanym językiem (Java), często znajduję sobie pisanie funkcji, a następnie wymuszanie kontroli typów. Na przykład:Czy muszę wymuszać sprawdzanie typu Pythona?

def orSearch(d, query): 
    assert (type(d) == dict) 
    assert (type(query) == list) 

Czy powinienem to robić dalej? jakie są zalety robienia/nie robienia tego?

+5

Nadal pracujesz z językiem o silnym typie ... dynamicznym! = Słabym – Brian

+2

[Mówiąc o "mocno wpisanym" i "słabo wpisanym" ...] (http : //stackoverflow.com/a/9929697/395760) – delnan

+0

Pytanie w tytule to idealne pytanie na SO i czy jest to efektywny/najlepszy sposób, sam masz odpowiedź. Jednak pytanie uzupełniające na końcu, które tam masz, jest bardziej http://programmers.stackexchange.com/ pytanie. – woozyking

Odpowiedz

6

Przestań to robić.

Punktem, w którym używa się języka "dynamicznego" (który jest silnie typowany do wartości *, untyped jak do zmiennych i późnej granicy) jest to, że twoje funkcje mogą być właściwie polimorficzne, ponieważ radzą sobie z dowolnym obiektem, który obsługuje interfejs, na którym opiera się twoja funkcja ("pisanie kaczkami").

Python definiuje wiele typowych protokołów (np. Iterowalnych), które różne typy obiektów mogą implementować bez wzajemnego powiązania. Protokoły nie są jako jedna z funkcji językowych (w przeciwieństwie do interfejsu java).

Praktyczny wynik tego jest taki, że ogólnie rzecz biorąc, dopóki rozumiesz typy w swoim języku i odpowiednio komentujesz (w tym za pomocą docstrukcji, aby inni ludzie również rozumieli typy w twoim programie), możesz na ogół pisać mniej kodu, ponieważ nie musisz kodować wokół systemu typu. Nie skończysz pisać tego samego kodu dla różnych typów, tylko z różnymi deklaracjami typów (nawet jeśli klasy są w rozłącznych hierarchiach) i nie będziesz musiał wymyślać, które rzuty są bezpieczne, a które nie, jeśli chcesz spróbować napisać tylko jeden kawałek kodu.

Istnieją inne języki, które teoretycznie oferują to samo: wpisz wnioskowane języki. Najpopularniejsze są C++ (przy użyciu szablonów) i Haskell. W teorii (i prawdopodobnie w praktyce) możesz napisać jeszcze mniej kodu, ponieważ typy są rozwiązywane statycznie, więc nie będziesz musiał pisać procedur obsługi wyjątków, aby poradzić sobie z błędnym typem. Uważam, że nadal wymagają od ciebie programowania w systemie typu, a nie w stosunku do rzeczywistych typów w twoim programie (ich systemy typu są dowodami twierdzącymi, i że są podatne, nie analizują całego twojego programu). Jeśli brzmi to dla ciebie dobrze, rozważ użycie jednego z tych języków zamiast Pythona (lub ruby, smalltalk lub dowolnego wariantu seplenienia).

Zamiast testowania typów, w pythonie (lub innym podobnym języku dynamicznym) należy używać wyjątków do przechwytywania, gdy obiekt nie obsługuje konkretnej metody. W takim przypadku pozwól, aby podniósł stos lub złap go i podnieś wyjątek dotyczący niewłaściwego typu. Ten typ "lepiej prosić o przebaczenie niż pozwolenie" jest idiomatycznym pythonem i znacznie przyczynia się do uproszczenia kodu.

W praktyce. Zmiany klas są możliwe w Pythonie i Smalltalk, ale rzadko. To nie to samo, co rzucanie w języku niskiego poziomu.

2

To jest nie-idiomatyczny sposób robienia rzeczy. Zazwyczaj w Pythonie używałeś testów try/except.

def orSearch(d, query): 
    try: 
     d.get(something) 
    except TypeError: 
     print("oops") 
    try: 
     foo = query[:2] 
    except TypeError: 
     print("durn") 
+1

Zapewniam wszystkich, że mam znacznie lepsze komunikaty o błędach w moim własnym kodzie. –

2

Osobiście mam awersję do twierdzi, wydaje się, że programista widział problemy nadchodzi, ale nie może być jedno, aby pomyśleć o tym, jak sobie z nimi radzić, drugi problemem jest to, że przykładem będzie dochodzić, jeśli którykolwiek z parametrów jest klasa wywodząca się z tych, których się spodziewasz, chociaż takie klasy powinny działać! - w powyższym przykładzie chciałbym pójść na coś takiego:

def orSearch(d, query): 
    """ Description of what your function does INCLUDING parameter types and descriptions """ 
    result = None 
    if not isinstance(d, dict) or not isinstance(query, list): 
     print "An Error Message" 
     return result 
    ... 

typ Uwaga pasuje tylko jeśli typ jest dokładnie tak, jak oczekiwano, isinstance pracuje dla klas pochodnych, jak również. np .:

>>> class dd(dict): 
... def __init__(self): 
...  pass 
... 
>>> d1 = dict() 
>>> d2 = dd() 
>>> type(d1) 
<type 'dict'> 
>>> type(d2) 
<class '__main__.dd'> 
>>> type (d1) == dict 
True 
>>> type (d2) == dict 
False 
>>> isinstance(d1, dict) 
True 
>>> isinstance(d2, dict) 
True 
>>> 

Można rozważyć odrzucenie niestandardowego wyjątku zamiast asercji. Możesz nawet generalizować jeszcze więcej, sprawdzając, czy parametry mają odpowiednie metody.

BTWMoże być wybredny o mnie, ale zawsze staram się unikać dochodzić w C/C++ na podstawie tego, czy pozostaje on w kodzie wtedy ktoś w ciągu kilku lat będzie wprowadzić zmiany, które powinny być przechwycony przez niego, nie przetestuj go wystarczająco dobrze w debugowaniu, aby to się stało (lub nawet nie przetestuj go w ogóle), skompiluj jako dostarczalny, tryb wydania, - który usuwa wszystkie potwierdzenia, tj. wszystkie sprawdzenia błędów, które zostały wykonane w ten sposób, a teraz mamy niewiarygodny kod i poważny ból głowy, aby znaleźć problemy.

+0

Twój komentarz ma sens. Zgadzam się również, że szczególnie w językach takich jak C, gdzie łatwo jest zdmuchnąć stopę, pomysł użycia asercji jako substytutu dla sprawdzania błędów jest całkiem nie na miejscu. Ale jaki jest sens twierdzeń? Czy to jest po prostu używać w testach jednostkowych? – franklin

+1

C/C++ zapewnia tylko coś w kodzie zbudowanym jako debugowanie i generalnie uważa się, że dostarczanie kodu debugowania jest złym pomysłem, w branżach, w których pracowałem, zabrania się także testowania kodu jednostkowego, który różni się od dostarczanego - dla oczywiste powody, dla których osobiście nie widzę sensu w przypadku C/C++ - python zapewnia sens dla testów jednostkowych _w narzędziach testowych_ i dla podania IDE podpowiedzi, jaki typ powinna być zmienna dla autouzupełniania itp., po prostu nie rób tego ___rely___ na zapewnia. –

1

Zgadzam się z podejściem Steve'a, gdy trzeba wykonać sprawdzenie typu. Nieczęsto znajduję potrzebę sprawdzania typów w Pythonie, ale jest co najmniej jedna sytuacja, w której to robię. To tam nie sprawdzanie typu może zwrócić nieprawidłową odpowiedź, która spowoduje błąd w późniejszym obliczeniu. Tego rodzaju błędy mogą być trudne do wyśledzenia, a wiele razy spotkałem się z nimi w Pythonie. Tak jak ty, najpierw nauczyłem się Java i nie musiałem się z nimi często borykać.

Załóżmy, że masz prostą funkcję, która oczekuje tablicy i zwraca pierwszy element.

def func(arr): return arr[0] 

jeśli wywołasz go z tablicą, otrzymasz pierwszy element tablicy.

>>> func([1,2,3]) 
1 

Będziesz także otrzymać odpowiedź, jeśli nazywają go sznurkiem lub obiektu dowolnej klasy, która implementuje magiczną metodę getitem.

>>> func("123") 
'1' 

To da odpowiedź, ale w tym przypadku jest niewłaściwego typu. Może się to zdarzyć w przypadku obiektów o tym samym numerze: method signature. Możesz nie odkryć błędu aż do dużo późniejszych obliczeń. Jeśli wystąpi to w twoim własnym kodzie, zwykle oznacza to, że wystąpił błąd w poprzednich obliczeniach, ale posiadanie sprawdzenia spowodowałoby to wcześniej. Jednakże, jeśli piszesz pakiet python dla innych, prawdopodobnie jest to coś, co powinieneś rozważyć niezależnie.

Nie powinieneś ponosić dużych nakładów za kontrolę, ale utrudni to czytanie twojego kodu, co jest wielką rzeczą w świecie Python.

0

Dwie rzeczy.

Po pierwsze, jeśli chcesz wydać ~ 200 USD, możesz uzyskać całkiem niezły identyfikator IDE dla Pythona. Używam PyCharm i jestem pod wrażeniem. (To przez tych samych ludzi, którzy tworzą program ReSharper dla C#.) Analizuje on twój kod podczas pisania i szuka miejsc, w których zmienne są niewłaściwego typu (pośród innych rzeczy).

drugie:

Przed użyłem pycharm, wpadłem do tego samego problemu - mianowicie, zapomniałbym o konkretnych sygnatur funkcji pisałem. Być może znalazłem to gdzieś, ale może napisałem (nie pamiętam teraz). Ale w każdym razie jest to dekorator, którego można używać wokół definicji funkcji, które sprawdzają dla ciebie typ.

nazwać to jak ten

@require_type('paramA', str) 
@require_type('paramB', list) 
@require_type('paramC', collections.Counter) 
def my_func(paramA, paramB, paramC): 
    paramB.append(paramC[paramA].most_common()) 
    return paramB 

W każdym razie, oto kod dekoratora.

def require_type(my_arg, *valid_types): 
    ''' 
     A simple decorator that performs type checking. 

     @param my_arg: string indicating argument name 
     @param valid_types: *list of valid types 
    ''' 
    def make_wrapper(func): 
     if hasattr(func, 'wrapped_args'): 
      wrapped = getattr(func, 'wrapped_args') 
     else: 
      body = func.func_code 
      wrapped = list(body.co_varnames[:body.co_argcount]) 

     try: 
      idx = wrapped.index(my_arg) 
     except ValueError: 
      raise(NameError, my_arg) 

     def wrapper(*args, **kwargs): 

      def fail(): 
       all_types = ', '.join(str(typ) for typ in valid_types) 
       raise(TypeError, '\'%s\' was type %s, expected to be in following list: %s' % (my_arg, all_types, type(arg))) 

      if len(args) > idx: 
       arg = args[idx] 
       if not isinstance(arg, valid_types): 
        fail() 
      else: 
       if my_arg in kwargs: 
        arg = kwargs[my_arg] 
        if not isinstance(arg, valid_types): 
         fail() 

      return func(*args, **kwargs) 

     wrapper.wrapped_args = wrapped 
     return wrapper 
    return make_wrapper 
+1

Po prostu trzeba pamiętać, że nie musisz wydawać 200 $ na IDE Pythona. Wydanie społeczności PyCharm jest bezpłatne, podobnie jak większość innych przyzwoitych IDE. – swalladge

3

Jeśli nalegać na dodanie typu sprawdzanie do kodu, można zajrzeć do annotations i jak mogą uprościć co masz napisać. Jeden z questions na StackOverflow wprowadził małą, zaciemnioną weryfikację typu, korzystając z adnotacji. Oto przykład oparty na twoim pytaniu:

>>> def statictypes(a): 
    def b(a, b, c): 
     if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c))) 
     return c 
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)]))) 

>>> @statictypes 
def orSearch(d: dict, query: dict) -> type(None): 
    pass 

>>> orSearch({}, {}) 
>>> orSearch([], {}) 
Traceback (most recent call last): 
    File "<pyshell#162>", line 1, in <module> 
    orSearch([], {}) 
    File "<pyshell#155>", line 5, in <lambda> 
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)]))) 
    File "<pyshell#155>", line 5, in <listcomp> 
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)]))) 
    File "<pyshell#155>", line 3, in b 
    if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c))) 
TypeError: d should be <class 'dict'>, not <class 'list'> 
>>> orSearch({}, []) 
Traceback (most recent call last): 
    File "<pyshell#163>", line 1, in <module> 
    orSearch({}, []) 
    File "<pyshell#155>", line 5, in <lambda> 
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)]))) 
    File "<pyshell#155>", line 5, in <listcomp> 
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)]))) 
    File "<pyshell#155>", line 3, in b 
    if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c))) 
TypeError: query should be <class 'dict'>, not <class 'list'> 
>>> 

Możesz popatrzeć na sprawdzającego typ i zastanawiać się: "Co, u licha, to robi?" Postanowiłem sam się przekonać i przekształcić go w czytelny kod. Drugi szkic wyeliminował funkcję b (można ją nazwać verify). Trzeci i ostatni projekt wykonał kilka ulepszeń i jest pokazany na dole do użytku:

import functools 

def statictypes(func): 
    template = '{} should be {}, not {}' 
    @functools.wraps(func) 
    def wrapper(*args): 
     for name, arg in zip(func.__code__.co_varnames, args): 
      klass = func.__annotations__.get(name, object) 
      if not isinstance(arg, klass): 
       raise TypeError(template.format(name, klass, type(arg))) 
     result = func(*args) 
     klass = func.__annotations__.get('return', object) 
     if not isinstance(result, klass): 
      raise TypeError(template.format('return', klass, type(result))) 
     return result 
    return wrapper 

Edit:

Minęło ponad cztery lata, ponieważ odpowiedź została napisana, a wiele się zmieniło w Pythonie od tego czasu. W wyniku tych zmian i rozwoju osobistego w języku, wydaje się korzystne ponowne przejrzenie kodu sprawdzającego typ i przepisanie go w celu skorzystania z nowych funkcji i udoskonalonej techniki kodowania. Dlatego wprowadzono następującą rewizję, która wprowadza kilka marginalnych ulepszeń do dekoratora funkcji statictypes (obecnie zmienionej na nazwę static_types).

#! /usr/bin/env python3 
import functools 
import inspect 


def static_types(wrapped): 
    def replace(obj, old, new): 
     return new if obj is old else obj 

    signature = inspect.signature(wrapped) 
    parameter_values = signature.parameters.values() 
    parameter_names = tuple(parameter.name for parameter in parameter_values) 
    parameter_types = tuple(
     replace(parameter.annotation, parameter.empty, object) 
     for parameter in parameter_values 
    ) 
    return_type = replace(signature.return_annotation, signature.empty, object) 

    @functools.wraps(wrapped) 
    def wrapper(*arguments): 
     for argument, parameter_type, parameter_name in zip(
      arguments, parameter_types, parameter_names 
     ): 
      if not isinstance(argument, parameter_type): 
       raise TypeError(f'{parameter_name} should be of type ' 
           f'{parameter_type.__name__}, not ' 
           f'{type(argument).__name__}') 
     result = wrapped(*arguments) 
     if not isinstance(result, return_type): 
      raise TypeError(f'return should be of type ' 
          f'{return_type.__name__}, not ' 
          f'{type(result).__name__}') 
     return result 
    return wrapper 
8

W większości przypadków mogłoby to kolidować z pisaniem kaczek i dziedziczeniem.

  • Dziedziczenie: pewno zamierzał napisać coś z efektu

    assert isinstance(d, dict) 
    

    aby upewnić się, że kod działa poprawnie również z podklas dict. Jest to podobne do użycia w Javie. Ale Python ma coś, że Java nie ma, a mianowicie

  • Duck wpisując: najbardziej wbudowane funkcje nie wymagają, aby obiekt należy do określonej klasy, tylko że ma pewne funkcje składowe, które zachowują się w odpowiedni sposób . Pętla for, na przykład, wymaga tylko, aby zmienna pętli była iterowana, co oznacza, że ​​ma ona funkcje składowe i zachowuje się poprawnie.

Dlatego, jeśli nie chcesz zamykać drzwi na pełną moc Pythona, nie sprawdzaj konkretnych typów w kodzie produkcyjnym. (Mimo to może być przydatny do debugowania.)

Powiązane problemy