क्लास कैसे सजाएं?


129

पायथन 2.5 में, क्या एक डेकोरेटर बनाने का एक तरीका है जो एक वर्ग को सजाता है? विशेष रूप से, मैं एक सदस्य को एक वर्ग में जोड़ने के लिए डेकोरेटर का उपयोग करना चाहता हूं और उस सदस्य के लिए मान लेने के लिए कंस्ट्रक्टर को बदल सकता हूं।

निम्नलिखित की तरह कुछ खोज रहे हैं (जिसमें 'वर्ग फू:' पर ​​एक वाक्यविन्यास त्रुटि है:

def getId(self): return self.__id

class addID(original_class):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        original_class.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

if __name__ == '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

मुझे लगता है कि मैं वास्तव में क्या कर रहा हूँ पायथन में सी # इंटरफ़ेस की तरह कुछ करने का एक तरीका है। मुझे लगता है कि मुझे अपने प्रतिमान को बदलने की आवश्यकता है।

जवाबों:


80

मैं इस धारणा को दूसरा मानूंगा कि आप जिस दृष्टिकोण को रेखांकित कर रहे हैं उसके बजाय एक उपवर्ग पर विचार करना चाह सकते हैं। हालाँकि, आपके विशिष्ट परिदृश्य को नहीं जानकर, YMMV :-)

आप जो सोच रहे हैं वह एक मेटाक्लस है। __new__एक metaclass में समारोह वर्ग है, जो यह तो पहले वर्ग बनाई गई है पुनर्लेखन कर सकते हैं का पूरा प्रस्तावित परिभाषा पारित कर दिया है। आप उस समय, नए के लिए कंस्ट्रक्टर को उप कर सकते हैं।

उदाहरण:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

निर्माता को बदलना शायद थोड़ा नाटकीय है, लेकिन भाषा इस तरह के गहन आत्मनिरीक्षण और गतिशील संशोधन के लिए समर्थन प्रदान करती है।


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

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

203

इस सवाल के अलावा कि क्या क्लास डेकोरेटर्स आपकी समस्या का सही समाधान हैं:

पायथन 2.6 और उच्चतर में, @ -syntax के साथ वर्ग सज्जाकार हैं, इसलिए आप लिख सकते हैं:

@addID
class Foo:
    pass

पुराने संस्करणों में, आप इसे दूसरे तरीके से कर सकते हैं:

class Foo:
    pass

Foo = addID(Foo)

ध्यान दें कि यह फ़ंक्शन डेकोरेटर्स के लिए समान है, और यह कि डेकोरेटर को नए (या संशोधित मूल) वर्ग को वापस करना चाहिए, जो कि आप उदाहरण में नहीं कर रहे हैं। AddID डेकोरेटर इस तरह दिखेगा:

def addID(original_class):
    orig_init = original_class.__init__
    # Make copy of original __init__, so we can call it without recursion

    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        orig_init(self, *args, **kws) # Call the original __init__

    original_class.__init__ = __init__ # Set the class' __init__ to the new one
    return original_class

फिर आप अपने पायथन संस्करण के लिए उपयुक्त वाक्यविन्यास का उपयोग कर सकते हैं जैसा कि ऊपर वर्णित है।

लेकिन मैं दूसरों से सहमत हूं कि यदि आप ओवरराइड करना चाहते हैं तो वंशानुक्रम बेहतर अनुकूल है __init__


2
... हालांकि प्रश्न में विशेष रूप से पायथन 2.5 :-) का उल्लेख है
जरेट हार्डी

5
लेनिस्प मेस के लिए खेद है, लेकिन कोड के नमूने टिप्पणियों में सख्ती से महान नहीं हैं ...: ऐड एडिड (ओरिजिनल_क्लास): डी__क्लास। ओरिजिन_इनिट_ = ओरिजिनल_क्लास। init__ डिस इनिट __ (स्व, * आर्ग्स , ** केव्स): प्रिंट "डेकोरेटर"। self.id = 9 original_class .__ Orig__init __ (स्वयं, * args, ** kws ) ____ass___ init = init return original_class @addID वर्ग फू: डीप __init__ (स्व): प्रिंट "फू" a = फू () प्रिंट a.id
जेराल्ड सेनक्लेरेन्स डे ग्रैनसी

2
@Steven, @Gerald सही है, यदि आप अपना उत्तर अपडेट कर सकते हैं, तो उसका कोड पढ़ना बहुत आसान होगा;)
दिन

3
@ दिन: अनुस्मारक के लिए धन्यवाद, मैंने पहले टिप्पणियों पर ध्यान नहीं दिया था। हालाँकि: मुझे गेराल्ड्स कोड के साथ-साथ अधिकतम पुनरावृत्ति की गहराई भी मिलती है। मैं कोशिश करूँगा और एक कामकाजी संस्करण बनाऊ ;-)
स्टीवन

1
@Day और @Gerald: क्षमा करें, समस्या जेराल्ड के कोड के साथ नहीं थी, मैंने टिप्पणी में मांगे गए कोड के कारण गड़बड़ कर दी; ;-)
स्टीवन

20

किसी ने समझाया नहीं कि आप कक्षाओं को गतिशील रूप से परिभाषित कर सकते हैं। तो आपके पास एक डेकोरेटर हो सकता है जो एक उपवर्ग को परिभाषित करता है (और रिटर्न):

def addId(cls):

    class AddId(cls):

        def __init__(self, id, *args, **kargs):
            super(AddId, self).__init__(*args, **kargs)
            self.__id = id

        def getId(self):
            return self.__id

    return AddId

जिसका उपयोग पायथन 2 में किया जा सकता है (ब्लाकनाथ की टिप्पणी जो बताती है कि आपको 2.6+ में ऐसा क्यों करना चाहिए:

class Foo:
    pass

FooId = addId(Foo)

और इस तरह पायथन 3 में (लेकिन super()अपनी कक्षाओं में उपयोग करने के लिए सावधान रहें ):

@addId
class Foo:
    pass

तो आप अपना केक ले सकते हैं और इसे खा सकते हैं - विरासत और सज्जाकार!


4
पाइथन 2.6+ में डेकोरेटर में उपवर्ग खतरनाक है, क्योंकि यह superमूल कक्षा में कॉल को तोड़ता है । यदि Fooएक विधि नाम दिया fooहै कि कहा जाता है super(Foo, self).foo(), यह असीम recurse होगा क्योंकि नाम Fooउपवर्ग डेकोरेटर द्वारा वापस करने के लिए बाध्य किया जाता है, नहीं मूल वर्ग (जो किसी भी नाम से सुलभ नहीं है)। पायथन 3 का super()तर्कहीन इस मुद्दे से बचता है (मैं उसी संकलक जादू के माध्यम से मानता हूं जो इसे बिल्कुल काम करने की अनुमति देता है)। आप अलग-अलग नाम के तहत कक्षा को मैन्युअल रूप से सजाने के द्वारा समस्या के आसपास भी काम कर सकते हैं (जैसा कि आपने पायथन 2.5 उदाहरण में किया था)।
22

1
हुह। धन्यवाद, मुझे कोई पता नहीं था (मैं अजगर 3 का उपयोग करता हूं)। एक टिप्पणी जोड़ देंगे।
andrew cooke

13

यह एक अच्छा अभ्यास नहीं है और ऐसा करने के लिए कोई तंत्र नहीं है। जो आप चाहते हैं उसे हासिल करने का सही तरीका विरासत है।

कक्षा के प्रलेखन पर एक नज़र डालें ।

एक छोटा सा उदाहरण:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

इस तरह से बॉस के पास सब कुछ Employeeहै, अपनी __init__पद्धति और अपने सदस्यों के साथ भी ।


3
मुझे लगता है कि जो मैं चाहता था वह उस वर्ग का बॉस अज्ञेय था जिसमें यह शामिल है। यही है, दर्जनों अलग-अलग वर्ग हो सकते हैं जिन्हें मैं बॉस सुविधाओं को लागू करना चाहता हूं। क्या मैंने बॉस से विरासत में मिली इन दर्जन भर कक्षाओं को छोड़ दिया है?
रॉबर्ट गौलैंड

5
@ रॉबर्ट गोलैंड: यही कारण है कि पायथन में कई विरासत हैं। हां, आपको विभिन्न मूल वर्गों से विभिन्न पहलुओं को प्राप्त करना चाहिए।
एस.लॉट

7
@ एस.लॉट: सामान्य रूप से कई विरासत एक बुरा विचार है, यहां तक ​​कि विरासत का बहुत अधिक स्तर भी बुरा है। मैं आपको कई उत्तराधिकार से दूर रहने की सलाह दूंगा।
मैपरसन

5
mpeterson: अजगर में कई विरासत इस दृष्टिकोण से भी बदतर है? अजगर की कई विरासतों में क्या गलत है?
अराफंगियन

2
@ प्रारूप: बहु विरासत को आमतौर पर मुसीबत का चेतावनी संकेत माना जाता है। यह जटिल पदानुक्रम और कठिन-से-पालन संबंधों के लिए बनाता है। यदि आपका समस्या डोमेन मल्टी इनहेरिटेंस के लिए अच्छी तरह से उधार देता है, (क्या इसे hierarchycally मॉडलिंग किया जा सकता है?) यह कई लोगों के लिए एक स्वाभाविक पसंद है। यह उन सभी भाषाओं पर लागू होता है जो बहु-विरासत की अनुमति देती हैं।
मोर्टन जेन्सेन

6

मैं सहमत हूँ कि विरासत में मिली समस्या के लिए एक बेहतर फिट है।

मुझे यह प्रश्न वास्तव में आसान लगा, हालांकि सजाने वाली कक्षाओं पर, सभी का धन्यवाद।

अन्य उत्तरों के आधार पर, उदाहरणों का एक और युगल है, जिसमें शामिल है कि पायथन 2.7 में चीजें कैसे प्रभावित करती हैं, (और @wraps , जो मूल फ़ंक्शन के डॉकस्ट्रिंग, आदि को बनाए रखता है):

def dec(klass):
    old_foo = klass.foo
    @wraps(klass.foo)
    def decorated_foo(self, *args ,**kwargs):
        print('@decorator pre %s' % msg)
        old_foo(self, *args, **kwargs)
        print('@decorator post %s' % msg)
    klass.foo = decorated_foo
    return klass

@dec  # No parentheses
class Foo...

अक्सर आप अपने डेकोरेटर में पैरामीटर जोड़ना चाहते हैं:

from functools import wraps

def dec(msg='default'):
    def decorator(klass):
        old_foo = klass.foo
        @wraps(klass.foo)
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
    return decorator

@dec('foo decorator')  # You must add parentheses now, even if they're empty
class Foo(object):
    def foo(self, *args, **kwargs):
        print('foo.foo()')

@dec('subfoo decorator')
class SubFoo(Foo):
    def foo(self, *args, **kwargs):
        print('subfoo.foo() pre')
        super(SubFoo, self).foo(*args, **kwargs)
        print('subfoo.foo() post')

@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
    def foo(self, *args, **kwargs):
        print('subsubfoo.foo() pre')
        super(SubSubFoo, self).foo(*args, **kwargs)
        print('subsubfoo.foo() post')

SubSubFoo().foo()

आउटपुट:

@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator

मैंने एक फ़ंक्शन डेकोरेटर का उपयोग किया है, क्योंकि मैं उन्हें अधिक संक्षिप्त पाता हूं। यहाँ एक वर्ग को सजाने के लिए एक वर्ग है:

class Dec(object):

    def __init__(self, msg):
        self.msg = msg

    def __call__(self, klass):
        old_foo = klass.foo
        msg = self.msg
        def decorated_foo(self, *args, **kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass

एक और अधिक मजबूत संस्करण जो उन कोष्ठकों के लिए जाँच करता है, और काम करता है अगर विधियाँ सजाया वर्ग पर मौजूद नहीं हैं:

from inspect import isclass

def decorate_if(condition, decorator):
    return decorator if condition else lambda x: x

def dec(msg):
    # Only use if your decorator's first parameter is never a class
    assert not isclass(msg)

    def decorator(klass):
        old_foo = getattr(klass, 'foo', None)

        @decorate_if(old_foo, wraps(klass.foo))
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            if callable(old_foo):
                old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)

        klass.foo = decorated_foo
        return klass

    return decorator

assertजांच करता है कि डेकोरेटर कोष्ठकों के बिना नहीं किया गया है। यदि यह है, तो सजाया जा रहा वर्ग msgडेकोरेटर के पैरामीटर को पारित किया जाता है , जो एक उठाता हैAssertionError

@decorate_ifकेवल decoratorअगर लागू होता है conditionमूल्यांकन करने के लिएTrue

getattr, callableपरीक्षण, और @decorate_ifइसलिए उपयोग किया जाता है कि डेकोरेटर यदि नहीं तोड़ता है foo()विधि वर्ग सजाया जा रहा है पर मौजूद नहीं है।


4

वास्तव में यहाँ एक वर्ग सज्जाकार का एक बहुत अच्छा कार्यान्वयन है:

https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

मुझे वास्तव में लगता है कि यह एक बहुत ही रोचक कार्यान्वयन है। क्योंकि यह जिस कक्षा को सजाता है, उसे उपवर्गित करता है, यह बिल्कुल इस वर्ग को isinstanceचेक जैसी चीजों में व्यवहार करेगा ।

इसका एक अतिरिक्त लाभ है: __init__एक कस्टम django फ़ॉर्म में बयान के लिए यह असामान्य नहीं है कि इसमें संशोधन या परिवर्धन किया self.fieldsजाए ताकि परिवर्तनों के बाद सभी के लिए यह बेहतर self.fieldsहो ।__init__ क्लास में लिए प्रश्न के ।

बहुत चालाक।

हालांकि, आपकी कक्षा में आप वास्तव में सजावट को कंस्ट्रक्टर को बदलना चाहते हैं, जो मुझे नहीं लगता कि क्लास डेकोरेटर के लिए एक अच्छा उपयोग मामला है।


0

यहां एक उदाहरण है जो एक वर्ग के मापदंडों को वापस करने के सवाल का जवाब देता है। इसके अलावा, यह अभी भी वंशानुक्रम की श्रृंखला का सम्मान करता है, अर्थात केवल कक्षा के मापदंडों को ही लौटाया जाता है। फ़ंक्शन get_paramsको एक सरल उदाहरण के रूप में जोड़ा जाता है, लेकिन निरीक्षण कार्यात्मक मॉड्यूल के लिए अन्य कार्यात्मकता को जोड़ा जा सकता है।

import inspect 

class Parent:
    @classmethod
    def get_params(my_class):
        return list(inspect.signature(my_class).parameters.keys())

class OtherParent:
    def __init__(self, a, b, c):
        pass

class Child(Parent, OtherParent):
    def __init__(self, x, y, z):
        pass

print(Child.get_params())
>>['x', 'y', 'z']

0

Django के पास method_decoratorएक डेकोरेटर है जो किसी भी डेकोरेटर को मेथड डेकोरेटर में बदल देता है, आप देख सकते हैं कि इसे किस तरह से लागू किया जाता है django.utils.decorators:

https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53

https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class

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