Python'da bir fonksiyonun parametrelerini kontrol edin

Örneğin, yordamsal gürültü üreten bir işleve sahibim.

def procedural_noise(width, height, seed):
   ...

Bu fonksiyonun tüm parametreleri pozitif olmalıdır. Sanırım, eğer parametreler üzerinde pozitif değilse bunu kontrol etmem ve istisna atmam gerekiyor. İyi (pitonik yol) bir yaklaşım mı?

Haklı olduğumu varsayalım. Parametreleri kontrol etmenin en iyi yolu hangisidir?


Parametrelerin her biri için dama yazabilirim:

def procedural_noise(width, height, seed):
    if width <= 0:
        raise ValueError("Width should be positive")
    if height <= 0:
        raise ValueError("Height should be positive")
    if seed <= 0:
        raise ValueError("Seed should be positive")
    ...

Programcı için istisna olacağı, düzeltmesi gerekenleri açık bir şekilde ifade etmesi gerekir, ancak bence iyi gözükmüyor.

Aşağıdaki kod daha kolay, ancak ideal olmaktan çok uzak:

def procedural_noise(width, height, seed):
    if width <= 0 or height <= 0 or seed <= 0:
        raise ValueError("All of the parameters should be positive")
    ...

The last question: which is the best way to write tests with unittest framework, that checks types of parameters and their values?

Aşağıdaki işlevi bir test sınıfında yazabilirim:

def test_positive(self):
    self.assertRaises(ValueError, main.procedural_noise, -10, -10, 187)

Doğru bir çözüm mü?


UPD: Tüm yanıtları geri aldım, çünkü her biri benim için yararlı bir bilgiye sahip, ancak en iyi yanıtlarını seçemiyorum (sanırım yarın en çok sorulan soruyu seçmenin adil olduğunu düşünüyorum)

4
Burada bir şeyler yanlış - sıfır pozitif değil, ancak hiçbir hata ortaya çıkmayacak :)
katma yazar Messa, kaynak
Afedersiniz. Ülkemde gece vardı :)
katma yazar Queue Overflow, kaynak

6 cevap

Ayrıca bu, fonksiyon açıklamaları için güzel bir kullanım örneği olabilir (Python 3'te). Örnek:

import inspect
from functools import wraps    

def positive(name, value):
    if value < 0:
        raise ValueError(name + " must be positive")

def check_params(f):
    signature = inspect.signature(f)
    @wraps(f)
    def wrapper(*args, **kwargs):
        bound_arguments = signature.bind(*args, **kwargs)
        for name, value in bound_arguments.arguments.items():
            annotation = signature.parameters[name].annotation
            if annotation is inspect.Signature.empty:
                continue
            annotation(name, value)
        return f(*args, **kwargs)
    return wrapper

@check_params
def procedural_noise(width: positive, height: positive, seed: positive):
    pass # ...

check_params dekoratöründe biraz teftiş-fu ( github.com/ceronman/typeannotations ), ancak işlev argümanlarını kontrol etmek için oldukça güzel ve esnek bir yol sağlar - s ise s, için > veya başka bir gürültü kodu.

6
katma
Çok teşekkürler, çok ilginç bir çözüm. Özellikle, daha önce fonksiyon açıklamalarının bu kadar güzel bir uygulamasıyla karşılaşmamıştım.
katma yazar Queue Overflow, kaynak

Bu şekilde yapardım:

def procedural_noise(width, height, seed):
    check_positive(width, "width")
    check_positive(height, "height")
    check_positive(seed, "seed")

def check_positive(value, name):
    if value < 0:
        raise ValueError(name + " must be positive")

Başka bir fikir - küçük bir "kesmek":

def procedural_noise(width, height, seed):
    check_positive(width=width)
    check_positive(height=height)
    check_positive(seed=seed)

def check_positive(**kwargs):
    for name, value in kwargs.items():
        if value < 0:
            raise ValueError(name + " must be positive")

Bu da şöyle söylenebilir:

def procedural_noise(width, height, seed):
    check_positive(width=width, height=height, seed=seed)

Bu, neredeyse diğer cevaplardakilerle aynıdır, ancak bu şekilde prosedürural_noise orijinal işlevi, çok temel bilgiler dışında herhangi bir argüman işlemesinden oldukça temiz tutulur. Daha fazla anlamsal :)

3
katma
@JuniorCompressor ilginç bir fikir, cevap güncellendi.
katma yazar Messa, kaynak
Belki check_positive için check_positive (** kwargs)
katma yazar JuniorCompressor, kaynak
Ben zaten upvoted;) Ben check_positive (area = width * height) gibi başka yararlı kullanımları olduğunu düşünüyorum. Ayrıca, eksiksiz olması için test için bir şey söyleyebilirsiniz.
katma yazar JuniorCompressor, kaynak
Teşekkürler! Bu çözüm bana daha tanıdık geliyor. Bir kez bu işlevi kullanacaksam, kontrol işlevindeki check_positive yerleştirme işlevi hakkında ne düşünüyorsunuz?
katma yazar Queue Overflow, kaynak

İlk soruya gelince, inceleyin modülünü kullanın:

import inspect

def procedural_noise(width, height, seed):
    frame = inspect.currentframe()
    args, _, _, values = inspect.getargvalues(frame)

    for name in args:
        if values[name] < 0:
             raise ValueError(name + " should be positive")

procedural_noise(3, -66, 2)

Çıktı:

Traceback (most recent call last):
  File "C:\Users\Sam\Desktop\hack.py", line 10, in 
    procedural_noise(3, -6, 2)
  File "C:\Users\Sam\Desktop\hack.py", line 8, in procedural_noise
    raise ValueError(name + " should be positive")
ValueError: height should be positive

Aksi takdirde, bu şekilde sözlük paketlemeyi de kullanabilirsiniz:

def procedural_noise(**params):
    for name in params.keys():
        if params[name] < 0:
             raise ValueError(name + " should be positive")

procedural_noise(width=3, height=6, seed=-2)

Çıktı:

Traceback (most recent call last):
  File "...\hack.py", line 6, in 
    procedural_noise(width=3, height=6, seed=-2)
  File "...\hack.py", line 4, in procedural_noise
    raise ValueError(name + " should be positive")
ValueError: seed should be positive

2
katma
Ancak, bu işlev çağrıldığında adlandırılmış parametreler gerektirir - OP'nin istemediği bir şey.
katma yazar MattDMo, kaynak
Bu kesinlikle gitmenin bir yolu. İşte, bir oy ver.
katma yazar MattDMo, kaynak
Haklısın. Daha iyi bir çözüm düşünüyorum.
katma yazar Sam Bruns, kaynak
@MattDmo: Düzenlenmiş yayınımı görün;)
katma yazar Sam Bruns, kaynak
Yardım edebildiğime sevindim :) Haklısın. Sorununuz için en iyi çözüm bu değil
katma yazar Sam Bruns, kaynak
Çok teşekkür ederim. Çözümünüzü dictionary_packing ile son halini aldım, ancak tek bir dezavantajı var: bu işlevi kullanacak olan programcı, IDE'sindeki işlev parametreleriyle ilgili bir ipucu görmez.
katma yazar Queue Overflow, kaynak

Kendinizi bu kadar çok bulursanız, belki bir dekoratör kodunuzu daha okunaklı hale getirebilir:

def assert_positive(f):
    def wrapper(*args, **kwargs):
        for i, v in enumerate(args):
            if v < 0:
                raise ValueError('The parameter at position %d should be >= 0' % i)
        for k, v in kwargs.items():
            if v < 0:
                raise ValueError('The parameter %s should be >= 0' % k)
        return f(*args, **kwargs)
    return wrapper

Öyleyse, işlevinizi şu şekilde ilan edebilirsiniz:

@assert_positive
def procedural_noise(width, height, seed=0):
    ...

Bunun gibi istisnalar ortaya çıkarır:

>>> procedural_noise(0,-1,0)
Traceback (most recent call last):
  File "", line 1, in 
  File "argument_checking.py", line 5, in wrapper
    raise ValueError('The parameter at position %d should be >= 0' % i)
ValueError: The parameter at position 1 should be >= 0
>>> procedural_noise(0,0,seed=-1)
Traceback (most recent call last):
  File "", line 1, in 
  File "argument_checking.py", line 8, in wrapper
    raise ValueError('The parameter %s should be >= 0' % k)
ValueError: The parameter seed should be >= 0

Pitonik yol genellikle, argümanlarınızı çok fazla kontrol etmemektir, ancak karşı örnekler vardır. Tamamen farklı şeyler yapan yapılara örnekler:

  • range(-1) - Looks funny, but is fine, returns []
  • time.sleep(-1) - Crashes with an IOError: [Errno 22] Invalid argument which I think is just Python's way of saying that a system call returned an error. Maybe I'm lucky that system calls do argument checking...
  • chr(-1) - ValueError: chr() arg not in range(256)
1
katma
Teşekkürler. Python'daki argümanların kontrolü hakkında bir şeyler okumanızı tavsiye eder misiniz? Çok ilginç bir tema, çünkü tip kontrolünün onaylama yaklaşımı olmadığını biliyorum, ancak yanlış tipte bir değişken kullanmanın fonksiyonun çalışmasına neden olacağı birçok durum var.
katma yazar Queue Overflow, kaynak

Gibi bir şey dene

def procedural_noise(width, height, seed):
    for key,val in locals().items():
        if val < 0:
             raise ValueError(key + " should be positive")
1
katma
Teşekkürler! Ülkemizde fiil var: “kısalık yeteneklerin kız kardeşi” :)
katma yazar Queue Overflow, kaynak

Yerleşik tümü 'yi kullanabilirsiniz - tümü yinelenebilir olanı alır ve yinelenebilir öğedeki öğelerin tümü gerçeğe uygun değilse, Doğru değerini döndürür. Bu nedenle, örneğin, tüm pozitif sayılar gerçeğe uygun olduğundan, yazabilirsiniz:

def procedural_noise(width, height, seed):
  if all((width, height, seed)):
    # Do stuff
  else:
    raise ValueError("You biffed it.")

Çifte parenlere dikkat edin - bunun nedeni (genişlik, yükseklik, tohum) demetini tümü bölümüne geçirdiğimden - sadece hepsini yazamıyorum ( width, height, seed) , çünkü all , bir tuple gibi yinelenebilir bir argüman bekler.


Test sorusu ile ilgili olarak, prosedürü_noise 'un tanımlandığı modülün adının foo olarak adlandırıldığını varsayalım, sonra test dosyanızda assertRaises , ile anahtar kelimeyle aşağıdaki gibi:

import foo
import unittest

class Test(unittest.TestCase):
  def test_positive(self):
    with self.assertRaises(ValueError):
      foo.procedural_noise(-10, -10, 187)

Bununla ilgili daha fazla bilgi için en eski dokümanlarda okuyun.

0
katma
Bu sadece yanlış. Kodunuz yalnızca üç girişin de 0 olması durumunda bir Hata oluşturacaktır. Aksi halde, OP'nin aradığı şey bu değildir, açıkça özel istisna mesajları vermek istediğini belirtir.
katma yazar mu 無, kaynak
Haklısınız, örneği orjinal olarak olarak yazdım (eğer hepsi ((args))): şeyler yap . Düzelttiğime inanıyorum - kodu bir koşullu olarak yerleştirirseniz beklenen davranışa sahip olması gerekir.
katma yazar Dan, kaynak
Evet, bir hatanın oluştuğunu kontrol etmek için assertRaises kullanmak doğru. Kodunuzun tüm vakaları yakaladığından emin olmak için testte her bir argümanı tek tek kontrol etmeyi denemek isteyebilirsiniz; bu, birkaç assertRaises ifadesi içerir.
katma yazar Dan, kaynak
Bu benim için pek uygun değil, ama yine de cevap için teşekkürler. Test sorusu gelince, fonksiyonumun bir test sınıfında olduğunu yazdım, bu yüzden kodunuza benzeyen bir kodum var. Benim durumumda assertRaises ve ValueError kullanmak doğru mu?
katma yazar Queue Overflow, kaynak
Cevabınız için teşekkürler Dan!
katma yazar Queue Overflow, kaynak