मैं '' स्व '' x = x से कैसे बचूँ; स्व.य = य; __init__ में self.z = z ”पैटर्न


170

मुझे पैटर्न पसंद हैं

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...

अक्सर, बहुत अधिक मापदंडों के साथ। क्या इस प्रकार की थकाऊ दोहराव से बचने का एक अच्छा तरीका है? क्या वर्ग को namedtupleइसके बजाय विरासत में मिलना चाहिए ?


31
सभी ग्रहणशीलता खराब नहीं है। ध्यान रखें कि पायथन के क्लास मॉडल में उदाहरण विशेषताओं की स्पष्ट परिभाषा शामिल नहीं है, इसलिए ये असाइनमेंट स्व-दस्तावेजीकरण समकक्ष हैं।
चेपनर

4
@chepner: खैर, स्पष्ट परिभाषा की आवश्यकता नहीं है । आप __slots__इस उद्देश्य के लिए उपयोग कर सकते हैं ; यह सौम्य रूप से unpythonic (मेमोरी सेविंग पाने के लिए अधिक क्रिया) है, लेकिन मुझे यह पसंद है कि अगर मैं नाम लिखूं तो ऑटो-वाइविंग को एक पूरी नई विशेषता के जोखिम से बचना चाहिए।
शैडो रेंजर

2
किसी भी अच्छे संपादक के पास टेम्पलेट होंगे। आप टाइप करें ini <shortcut> x, y, z): <shortcut>और आप कर रहे हैं।
गेरनुक

3
यदि आप कोई अपरिवर्तनीय मान ऑब्जेक्ट चाहते हैं , तो नामांकित शीर्ष भयानक हैं । यदि आप एक नियमित, परिवर्तनशील वर्ग चाहते हैं, तो आप उनका उपयोग नहीं कर सकते।
रेमोगर्लिच

4
"मत करो एक अच्छा विकल्प है, जो भी विकल्प उपलब्ध है वह विधि हस्ताक्षर (और इस प्रकार संभवतः पूरे इंटरफ़ेस) को मार देगा।" इसके अलावा, यदि आपकी कक्षाओं में आरंभ करने के लिए खेतों की असहनीय मात्रा है, तो आप उन्हें विभाजित करने पर विचार कर सकते हैं।
क्रॉल्टन

जवाबों:


87

संपादित करें: यदि आपके पास 3.7+ python है तो बस उपयोग करें डेटाकेल्स का करें

एक डेकोरेटर समाधान जो हस्ताक्षर रखता है:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

2
अच्छा जवाब, लेकिन python2.7 के साथ काम नहीं करेगा: नहींsignature
MaxB

3
@alexis "डेकोरेटर.डेकोरेटर" डेकोरेटर स्वचालित रूप से फ़ंक्शन को लपेटता है
सिफोर

4
मैं इस बारे में काफी फटा हुआ हूं कि क्या इसे प्यार करना या नफरत करना। मैं हस्ताक्षर को संरक्षित करने की सराहना करता हूं।
काइल स्ट्रैंड

14
"... स्पष्ट निहितार्थ से बेहतर है। सरल जटिल से बेहतर है ..."
जैक स्टाउट

9
-1 काफी स्पष्ट रूप से यह भयानक है। मुझे नहीं पता कि यह कोड एक नज़र में क्या कर रहा है, और यह शाब्दिक रूप से कोड की मात्रा का दस गुना है। होशियार होने के नाते शांत और सभी महसूस करता है, लेकिन यह आपकी स्पष्ट चतुराई का दुरुपयोग है।
इयान न्यूजन

108

अस्वीकरण: ऐसा लगता है कि कई लोग इस समाधान को प्रस्तुत करने के बारे में चिंतित हैं, इसलिए मैं बहुत स्पष्ट अस्वीकरण प्रदान करूंगा। आपको इस घोल का उपयोग नहीं करना चाहिए। मैं इसे केवल जानकारी के रूप में प्रदान करता हूं, इसलिए आप जानते हैं कि भाषा इसके लिए सक्षम है। बाकी का जवाब सिर्फ भाषा क्षमताओं को दिखा रहा है, इस तरह से उनका उपयोग करने का समर्थन नहीं करता है।


विशेषताओं में स्पष्ट रूप से नकल मापदंडों के साथ वास्तव में कुछ भी गलत नहीं है। यदि आपके पास ctor में बहुत अधिक पैरामीटर हैं, तो इसे कभी-कभी एक कोड गंध माना जाता है और हो सकता है कि आपको इन params को कम वस्तुओं में समूहित करना चाहिए। दूसरी बार, यह आवश्यक है और इसके साथ कुछ भी गलत नहीं है। वैसे भी, यह स्पष्ट रूप से करने का तरीका है।

हालाँकि, जब से आप पूछ रहे हैं कि यह कैसे किया जा सकता है (और यह नहीं किया जाना चाहिए), तो एक समाधान यह है:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2

16
अच्छा उत्तर +1 ... हालाँकि self.__dict__.update(kwargs)थोड़ा और अधिक pythonic हो सकता है
Joran Beasley

44
इस दृष्टिकोण के साथ समस्या यह है कि A.__init__वास्तव में क्या तर्क की अपेक्षा है, इसका कोई रिकॉर्ड नहीं है, और गलत तर्क नामों के लिए कोई त्रुटि जांच नहीं है।
3

7
@JoranBeasley kwargsएक SQL इंजेक्शन हमले के बराबर के लिए खुले पत्तों के साथ उदाहरण शब्दकोश को आँख बंद करके अद्यतन करना । यदि आपके ऑब्जेक्ट में नाम की एक विधि है my_methodऔर आप my_methodकंस्ट्रक्टर के नाम पर एक तर्क पास करते हैं, तो update()डिक्शनरी, आप केवल विधि को ओवरवोट करते हैं।
पेड्रो

3
जैसा कि दूसरों ने कहा, सुझाव वास्तव में खराब प्रोग्रामिंग शैली है। यह महत्वपूर्ण जानकारी को छुपाता है। आप इसे दिखा सकते हैं, लेकिन आपको ओपी को इसका इस्तेमाल करने से रोकना चाहिए।
गेरनुक

3
@Proro क्या ग्रीज़ज़ी और जोरानबस्ले के वाक्य-विन्यास के बीच कोई अर्थ-भेद है?
गेरिट

29

जैसा कि दूसरों ने उल्लेख किया है, पुनरावृत्ति खराब नहीं है, लेकिन कुछ मामलों में इस प्रकार के मुद्दे के लिए एक नामांकित व्यक्ति एक महान फिट हो सकता है। यह स्थानीय लोगों () या kwargs का उपयोग करने से बचता है, जो आमतौर पर एक बुरा विचार है।

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

मैंने इसके लिए सीमित उपयोग पाया है, लेकिन आप किसी भी अन्य ऑब्जेक्ट (उदाहरण के लिए जारी) के साथ एक नेपथ्य को वारिस कर सकते हैं:

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

5
ये हैं तो अपने, tuples propertiesपद्धति के रूप में सिर्फ लिखा जा सकता है return tuple(self)जो अधिक maintainable है अगर भविष्य में और अधिक क्षेत्रों namedtuple परिभाषा में जुड़ जाते हैं।
पॉलएमजीजी

1
इसके अलावा, आपके नामांकित घोषणा स्ट्रिंग को फील्डनाम के बीच अल्पविराम की आवश्यकता नहीं होती है, XYZ = namedtuple("XYZ", "x y z")साथ ही साथ काम करता है।
पॉलएमसीजी

साभार @PaulMcGuire मैं वास्तव में एक साधारण ऐड-ऑन के बारे में सोचने की कोशिश कर रहा था जो विरासत और उस तरह के स्थान को दिखाने के लिए था। आप 100% सही हैं और यह अन्य विरासत में मिली वस्तुओं के साथ भी एक बड़ा आश्रय है! मैं उल्लेख करता हूं कि क्षेत्र के नाम अल्पविराम या अंतरिक्ष से अलग हो सकते हैं - मैं आदत से सीएसवी पसंद करता हूं
एक छोटी सी शेल स्क्रिप्ट

1
मैं अक्सर namedtupleइस सटीक उद्देश्य के लिए एस का उपयोग करता हूं , विशेष रूप से गणितीय कोड में जहां एक फ़ंक्शन अत्यधिक पैराट्राइज्ड हो सकता है और इसमें गुणांक का एक गुच्छा होता है जो केवल एक साथ समझ में आता है।
5

इसके साथ समस्या namedtupleयह है कि वे केवल पढ़ने के लिए हैं। आप ऐसा abc.x += 1कुछ नहीं कर सकते हैं या कर सकते हैं ।
हम्सटरज़ेन

29

स्पष्ट का तात्पर्य अंतर्निहित से बेहतर है ... इसलिए सुनिश्चित करें कि आप इसे और अधिक संक्षिप्त बना सकते हैं:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

बेहतर सवाल आपको होना चाहिए?

... यह कहा कि यदि आप एक नामित टपल चाहते हैं, तो मैं एक नामित का उपयोग करने की सिफारिश करूंगा (याद रखें कि टपल के पास कुछ शर्तें जुड़ी हुई हैं) ... शायद आप एक आदेश चाहते हैं या यहां तक ​​कि सिर्फ एक तानाशाही ...


तब ऑब्जेक्ट को चक्रीय कचरा संग्रह की आवश्यकता होगी क्योंकि यह एक विशेषता के रूप में है
जॉन ला रूय

3
@bernie (या यह bemie है?), कभी-कभी ke n ning मुश्किल है
बिल्ली

4
थोड़े अधिक कुशल परीक्षणों के if k != "self":लिए if v is not self:, स्ट्रिंग तुलना की तुलना में, सस्ते पहचान परीक्षण में परिवर्तित किया जा सकता है । मुझे लगता है कि तकनीकी रूप से __init__निर्माण के बाद दूसरी बार बुलाया जा सकता है और selfबाद के तर्क के रूप में पारित किया जा सकता है , लेकिन मैं वास्तव में यह नहीं सोचना चाहता कि राक्षस किस तरह का होगा। :-)
शैडो रेंजर

यही कारण है कि एक समारोह जिनमें से वापसी मान लेता है में बनाया जा सकता है locals: set_fields_from_locals(locals())। फिर यह अधिक जादुई डेकोरेटर आधारित समाधानों से अधिक नहीं है।
Lii

20

gruszczyS उत्तर पर विस्तार करने के लिए , मैंने एक पैटर्न का उपयोग किया है:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

मुझे यह तरीका पसंद है क्योंकि यह:

  • पुनरावृत्ति से बचा जाता है
  • ऑब्जेक्ट का निर्माण करते समय टाइपोस के खिलाफ प्रतिरोधी है
  • उपवर्ग के साथ अच्छी तरह से काम करता है (बस कर सकते हैं super().__init(...))
  • एक वर्ग-स्तर (जहाँ वे संबंधित हैं) के बजाय विशेषताओं के प्रलेखन के लिए अनुमति देता है X.__init__

पाइथन 3.6 से पहले, यह उस विशेषता पर कोई नियंत्रण नहीं देता है जिसमें विशेषताएँ सेट की जाती हैं, जो कि कुछ विशेषताओं के बसने वाले गुणों के साथ अन्य विशेषताओं का उपयोग करने पर एक समस्या हो सकती है।

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


10

आप भी कर सकते हैं:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

बेशक, आपको inspectमॉड्यूल आयात करना होगा ।


8

यह बिना किसी अतिरिक्त आयात के एक समाधान है।

हेल्पर फ़ंक्शन

एक छोटा सहायक कार्य इसे और अधिक सुविधाजनक और पुन: प्रयोज्य बनाता है:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

आवेदन

आपको इसे कॉल करने की आवश्यकता है locals():

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

परीक्षा

a = A(1, 2, 3)
print(a.__dict__)

आउटपुट:

{'y': 2, 'z': 3, 'x': 1}

बिना बदले locals()

यदि आप locals()इस संस्करण का उपयोग बदलना पसंद नहीं करते हैं :

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)

docs.python.org/2/library/functions.html#locals locals() को संशोधित नहीं किया जाना चाहिए (यह दुभाषिया को प्रभावित कर सकता है, आपके मामले में, selfकॉलिंग फ़ंक्शन के दायरे से हटाकर )
MaxB

@MaxB डॉक्स से आप का हवाला देते हैं: ... परिवर्तन दुभाषिया द्वारा इस्तेमाल स्थानीय और मुक्त चर के मूल्यों को प्रभावित नहीं कर सकता है । selfमें अभी भी उपलब्ध है __init__
माइक मुलर

ठीक है, पाठक को उम्मीद है कि यह स्थानीय चर को प्रभावित करेगा, लेकिन कई परिस्थितियों के आधार पर यह हो सकता है या नहीं । मुद्दा यह है कि यह यूबी है।
अधिकतम डेब

उद्धरण: "इस शब्दकोष की सामग्री को संशोधित नहीं किया जाना चाहिए"
मैक्सबी

@MaxB मैंने एक ऐसा संस्करण जोड़ा है जो स्थानीय लोगों को नहीं बदलता है ()।
माइक मुलर

7

एक दिलचस्प पुस्तकालय जो इसे संभालता है (और बहुत सारे बॉयलरप्लेट से बचा जाता है) एट्रस है । उदाहरण के लिए, आपका उदाहरण इसे कम किया जा सकता है (मान लें कि वर्ग कहा जाता है MyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

आपको __init__अब किसी विधि की भी आवश्यकता नहीं है, जब तक कि यह अन्य सामान भी नहीं करता है। यहाँ ग्लिफ़ लेफकोविट्ज़ द्वारा एक अच्छा परिचय दिया गया है


किस हद तक attrनिरर्थक बना दिया गया है dataclasses?
गेरिट

1
@gerrit यह अटार्स पैकेज के प्रलेखन में चर्चा की गई है । Tbh, मतभेद अब और बड़े नहीं लगते।
इवो ​​मर्चिएर्स

5

मेरी 0.02 $। यह जोरन बेस्ली के जवाब के बहुत करीब है, लेकिन अधिक सुरुचिपूर्ण:

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

इसके अतिरिक्त, माइक मुलर के उत्तर (मेरे स्वाद के लिए सबसे अच्छा) को इस तकनीक से कम किया जा सकता है:

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

और सिर्फ auto_init(locals())अपने से बुलाओ__init__


1
docs.python.org/2/library/functions.html#locals locals() को संशोधित नहीं किया जाना चाहिए (अपरिभाषित व्यवहार)
MaxB

4

यह पायथन में चीजों को करने का एक स्वाभाविक तरीका है। कुछ और अधिक चतुर का आविष्कार करने की कोशिश मत करो, यह अत्यधिक चतुर कोड का नेतृत्व करेगा जो आपकी टीम में कोई भी नहीं समझेगा। अगर आप टीम के खिलाड़ी बनना चाहते हैं और फिर इसे इस तरह लिखते रहें।


4

पायथन 3.7 आगे

Python 3.7 में, आप (ab) का उपयोग कर सकते हैं dataclassdataclasses मॉड्यूल से उपलब्ध डेकोरेटर का । प्रलेखन से:

यह मॉड्यूल एक सज्जाकार प्रदान करता है और स्वचालित रूप से उत्पन्न विशेष विधियों जैसे __init__()और जोड़ने के लिए कार्य करता है__repr__() उपयोगकर्ता-निर्धारित वर्गों के लिए। यह मूल रूप से पीईपी 557 में वर्णित किया गया था।

इन उत्पन्न विधियों में उपयोग करने के लिए सदस्य चर को PEP 526 प्रकार के एनोटेशन का उपयोग करके परिभाषित किया गया है। उदाहरण के लिए यह कोड:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

अन्य बातों के अलावा, जोड़ देगा __init__() जो दिखता है:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

ध्यान दें कि इस विधि को स्वचालित रूप से कक्षा में जोड़ा जाता है: यह ऊपर दिखाए गए InventoryItem परिभाषा में सीधे निर्दिष्ट नहीं है।

यदि आपकी कक्षा बड़ी और जटिल है, तो इसका उपयोग करना अनुचित हो सकता है dataclass। मैं इसे पायथन 3.7.0 की रिलीज़ के दिन लिख रहा हूं, इसलिए उपयोग पैटर्न अभी तक अच्छी तरह से स्थापित नहीं हुए हैं।

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