वर्ग के एक उदाहरण की विशेषताओं को मान्य करने के लिए सही दृष्टिकोण


84

इस तरह एक साधारण पायथन वर्ग होना:

class Spam(object):
    __init__(self, description, value):
        self.description = description
        self.value = value

मैं निम्नलिखित बाधाओं की जांच करना चाहता हूं:

  • "वर्णन खाली नहीं हो सकता"
  • "मान शून्य से अधिक होना चाहिए"

क्या मुझे:
1. स्पैम ऑब्जेक्ट बनाने से पहले डेटा को मान्य करना चाहिए ?
2. __init__विधि पर डेटा की जाँच करें ?
3. is_validस्पैम कक्षा पर एक विधि बनाएं और उसे स्पैम.विलेज () के साथ कॉल करें?
4. is_validस्पैम कक्षा पर एक स्थिर विधि बनाएं और इसे स्पैम के साथ कॉल करें। अमान्य (विवरण, मूल्य)?
5. सेटर्स घोषणा पर डेटा की जाँच करें?
6. आदि।

क्या आप एक अच्छी तरह से डिज़ाइन किए गए / पायथोनिक / वर्बोज़ (कई विशेषताओं के साथ वर्ग पर) / सुरुचिपूर्ण दृष्टिकोण की सिफारिश कर सकते हैं?

जवाबों:


106

आप प्रत्येक क्षेत्र में अलग से नियम लागू करने के लिए पायथन गुण का उपयोग कर सकते हैं , और क्लाइंट कोड फ़ील्ड को बदलने का प्रयास करने पर भी उन्हें लागू कर सकते हैं:

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, d):
        if not d: raise Exception("description cannot be empty")
        self._description = d

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v

नियमों के उल्लंघन के किसी भी प्रयास पर एक अपवाद को फेंक दिया जाएगा, यहां तक ​​कि अंदर भी __init__ फ़ंक्शन में, जिस स्थिति में ऑब्जेक्ट निर्माण विफल हो जाएगा।

अद्यतन: कुछ समय के बीच 2010 और अब, मैं के बारे में सीखा operator.attrgetter:

import operator

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

    description = property(operator.attrgetter('_description'))

    @description.setter
    def description(self, d):
        if not d: raise Exception("description cannot be empty")
        self._description = d

    value = property(operator.attrgetter('_value'))

    @value.setter
    def value(self, v):
        if not (v > 0): raise Exception("value must be greater than zero")
        self._value = v

1
+1 सुरुचिपूर्ण समाधान धन्यवाद, क्या आपको नहीं लगता कि इस तरह के एक छोटे वर्ग के लिए एक छोटी सी क्रिया है?
— systempuntoout

2
सहमत, यह सबसे सुंदर समाधान नहीं है। पायथन मुक्त श्रेणी के वर्गों (मुर्गियों को लगता है) को पसंद करता है, और पहुंच को नियंत्रित करने वाले गुणों का विचार थोड़ा सोचा गया था। यह कहने के बाद, यह किसी भी अन्य भाषा में अधिक संक्षिप्त नहीं होगा, जिसके बारे में मैं सोच सकता हूं।
— मार्सेलो कैंटोस

2
@MarceloCantos मुझे एहसास है कि यह एक पुराना सवाल है, लेकिन प्रलेखन (पायथन 3 के लिए यद्यपि) के आधार पर , self.description = descriptionएक अंडरस्कोर का उपयोग करना चाहिए , अर्थात self._description = description, या इससे कोई फर्क नहीं पड़ता? क्या यह आवश्यक या केवल "निजी" चर के पायथन संस्करण के समान है?
— जॉन बेन्सिन

12
@ जॉनबेंसिन: हाँ और नहीं। self.description = …संपत्ति के माध्यम से असाइन करता है, जबकि self._description = …अंतर्निहित क्षेत्र पर सीधे असाइन करता है। निर्माण के दौरान कौन सा उपयोग करना है यह एक डिजाइन विकल्प है, लेकिन यह आमतौर पर संपत्ति के माध्यम से आवंटित करने के लिए सुरक्षित है। उदाहरण के लिए, यदि आप कॉल करते हैं तो उपरोक्त कोड एक अपवाद बढ़ाएगा Spam('', 1)।
— मार्सेलो कैंटोस

1
मुझे नहीं लगता कि यह बहुत अधिक क्रिया है। अमान्य मानों को मान सेट करने के लिए विकल्प संभव है।
— टोनी एननिस

10

यदि आप ऑब्जेक्ट बनाते समय केवल मानों को मान्य करना चाहते हैं और अमान्य मानों को पास करना एक प्रोग्रामिंग त्रुटि माना जाता है, तो मैं मुखरता का उपयोग करूंगा:

class Spam(object):
    def __init__(self, description, value):
        assert description != ""
        assert value > 0
        self.description = description
        self.value = value

यह संक्षिप्त रूप में जैसा कि आप प्राप्त करने जा रहे हैं, और स्पष्ट रूप से दस्तावेज़ हैं कि ये ऑब्जेक्ट बनाने के लिए पूर्व शर्त हैं।


धन्यवाद डेव, मुखर का उपयोग करते हुए, मैं उस वर्ग के ग्राहक को कैसे निर्दिष्ट करूं जो गलत हो गया (विवरण या मूल्य)? क्या आपको नहीं लगता कि दावे का इस्तेमाल सिर्फ उन परिस्थितियों का परीक्षण करने के लिए किया जाना चाहिए जो कभी नहीं होनी चाहिए?
— systempuntoout

1
आप उदाहरण के लिए कथन में एक संदेश जोड़ सकते हैं assert value > 0, "value attribute to Spam must be greater than zero"। मुखबिर डेवलपर को वास्तव में संदेश हैं और उन्हें क्लाइंट कोड द्वारा नहीं पकड़ा जाना चाहिए, क्योंकि वे एक प्रोग्रामिंग त्रुटि का संकेत देते हैं। यदि आप चाहते हैं कि क्लाइंट त्रुटि को पकड़ ले और संभाल ले तो स्पष्ट रूप से एक अपवाद जैसे कि ValueError बढ़ाएं, जैसा कि अन्य उत्तरों में दिखाया गया है।
— डेव किर्बी

1
आपके दूसरे प्रश्न का उत्तर देने के लिए, हां एसेर्ट्स का उपयोग उन परिस्थितियों का परीक्षण करने के लिए किया जाना चाहिए जो कभी नहीं होनी चाहिए - यही कारण है कि मैंने कहा "यदि अवैध मूल्यों में गुजरना एक प्रोग्रामिंग त्रुटि माना जाता है ..."। अगर ऐसा नहीं है तो जोर-आजमाइश न करें।
— डेव किर्बी

def से पहले डाला जाना चाहिए__init__
— डेटापुग

1
धन्यवाद @datapug, ने टाइपो को ठीक किया।
— डेव किर्बी

7

जब तक आप अपने खुद के रोल करने में जहन्नुम नहीं हैं , तब तक आप बस फॉर्मेंकोड का उपयोग कर सकते हैं । यह वास्तव में कई विशेषताओं और स्कीमा (केवल उपवर्ग स्कीमा) के साथ चमकता है और इसमें बहुत सारे उपयोगी सत्यापनकर्ता अंतर्निहित हैं। जैसा कि आप देख सकते हैं कि यह "स्पैम ऑब्जेक्ट बनाने से पहले मान्य डेटा" दृष्टिकोण है।

from formencode import Schema, validators

class SpamSchema(Schema):
    description = validators.String(not_empty=True)
    value = validators.Int(min=0)

class Spam(object):
    def __init__(self, description, value):
        self.description = description
        self.value = value

## how you actually validate depends on your application
def validate_input( cls, schema, **input):
    data = schema.to_python(input) # validate `input` dict with the schema
    return cls(**data) # it validated here, else there was an exception

# returns a Spam object
validate_input( Spam, SpamSchema, description='this works', value=5) 

# raises an exception with all the invalid fields
validate_input( Spam, SpamSchema, description='', value=-1) 

आप चेक के दौरान __init__भी कर सकते हैं (और उन्हें वर्णनकर्ताओं के साथ पूरी तरह से पारदर्शी बना सकते हैं। सज्जाकार | मेटाक्लास), लेकिन मैं इसका बहुत बड़ा प्रशंसक नहीं हूं। मुझे उपयोगकर्ता इनपुट और आंतरिक वस्तुओं के बीच एक स्वच्छ अवरोध पसंद है।


6

यदि आप केवल उन मानों को वैध बनाना चाहते हैं, जो आप निर्माता को दे सकते हैं, तो आप ऐसा कर सकते हैं:

class Spam(object):
    def __init__(self, description, value):
        if not description or value <=0:
            raise ValueError
        self.description = description
        self.value = value

यह निश्चित रूप से किसी को भी ऐसा करने से नहीं रोकेगा:

>>> s = Spam('s', 5)
>>> s.value = 0
>>> s.value
0

इसलिए, सही दृष्टिकोण इस बात पर निर्भर करता है कि आप क्या हासिल करना चाहते हैं।


यह मेरा वास्तविक दृष्टिकोण है; लेकिन मुझे यह पसंद नहीं है जब विशेषताएँ संख्या बढ़ाएँ और \ या बाधाओं की जाँच अधिक परिष्कृत हो। यह init विधि को बहुत अधिक अव्यवस्थित करने के लिए लगता है।
— systempuntoout

1
@ सिस्टम: आप वैधता की जाँच अपने तरीके से कर सकते हैं: इस स्थिति के बारे में कठिन और तेज़ नियम नहीं हैं।
— साइलेंटगॉस्ट

1

आप कोशिश कर सकते हैं pyfields:

from pyfields import field

class Spam(object):
    description = field(validators={"description can not be empty": lambda s: len(s) > 0})
    value = field(validators={"value must be greater than zero": lambda x: x > 0})

s = Spam()
s.description = "hello"
s.description = ""  # <-- raises error, see below

यह पैदावार देता है

ValidationError[ValueError]: Error validating [<...>.Spam.description=''].
  InvalidValue: description can not be empty. 
  Function [<lambda>] returned [False] for value ''.

यह अजगर 2 और 3.5 के विपरीत है (जैसा कि विरोध किया गया है pydantic), और सत्यापन तब होता है जब मूल्य बदल जाता है (केवल पहली बार नहीं, जैसा कि विरोध किया जाता हैattrs )। यह आपके लिए कंस्ट्रक्टर बना सकता है, लेकिन डिफ़ॉल्ट रूप से ऐसा नहीं करता जैसा कि ऊपर दिखाया गया है।

ध्यान दें कि आप वैकल्पिक रूप से उपयोग करना चाह सकते हैं mini-lambda सादे पुराने लंबो कार्यों के बजाय करने की यदि आप त्रुटि संदेशों को और अधिक सीधा होना चाहते हैं (वे असफल अभिव्यक्ति प्रदर्शित करेंगे)।

देखें pyfieldsप्रलेखन जानकारी के लिए (मैं जिस तरह से लेखक हूँ;))

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.