2012-04-24 10 views
33

जैसा कि मैंने समझा है कि एक वर्ग के __call__ या किसी सजावट के रूप में फ़ंक्शन को परिभाषित करने और कॉल करने के लिए पाइथन सजावट करने के दो तरीके हैं। इन तरीकों के फायदे/नुकसान क्या हैं? क्या कोई पसंदीदा तरीका है?पाइथन सजावट सर्वोत्तम अभ्यास, एक वर्ग बनाम एक फ़ंक्शन

उदाहरण 1

class dec1(object): 
    def __init__(self, f): 
     self.f = f 
    def __call__(self): 
     print "Decorating", self.f.__name__ 
     self.f() 

@dec1 
def func1(): 
    print "inside func1()" 

func1() 

# Decorating func1 
# inside func1() 

उदाहरण 2

def dec2(f): 
    def new_f(): 
     print "Decorating", f.__name__ 
     f() 
    return new_f 

@dec2 
def func2(): 
    print "inside func2()" 

func2() 

# Decorating func2 
# inside func2() 
+5

एक महत्वपूर्ण बात: अपने वास्तविक आवरण कार्यों मूल 'f' फ़ंक्शन को कॉल लेकिन कॉल प्राप्त करने वाला के लिए अपने दिए गए मान नहीं लौटते: इस सबसे अधिक संभावना एक गलत व्यवहार करने के लिए नेतृत्व करेंगे। – jsbueno

+0

संभावित डुप्लिकेट [सजावटी वर्गों और सजावटी कार्यों के बीच अंतर] (http://stackoverflow.com/questions/4650333/difference-between-decorator-classes-and-decorator- क्रियाएं) –

उत्तर

29

यह वहाँ प्रत्येक विधि के लिए "लाभ" कर रहे हैं कि क्या कहने के लिए नहीं बल्कि व्यक्तिपरक है।

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

एक सजावटी (फ़ंक्शन सजावट के बारे में बात करना), केवल एक कॉल करने योग्य ऑब्जेक्ट है जो एक इनपुट पैरामीटर के रूप में कार्य करता है। पायथन के पास इसके बजाय दिलचस्प डिजाइन है जो को अन्य प्रकार के कॉल करने योग्य ऑब्जेक्ट्स बनाने के लिए फ़ंक्शन के अलावा अनुमति देता है - और कोई भी अवसर पर अधिक रखरखाव या छोटा कोड बनाने के लिए का उपयोग करने के लिए रख सकता है।

डेकोरेटर के लिए

def a(x): 
    ... 

a = my_decorator(a) 

कि इसके अलावा एक "वाक्यात्मक शॉर्टकट 'के रूप में अजगर 2.3 में वापस जोड़ा गया था, हम आम तौर सज्जाकार फोन कुछ" callables "है कि नहीं बल्कि" डेकोरेटर कारखानों "हो सकता है - जब हम इस तरह का उपयोग करें :

@my_decorator(param1, param2) 
def my_func(...): 
    ... 

कॉल param1 और param2 के साथ "my_decorator" करने के लिए किया जाता है - यह तो एक उद्देश्य यह है कि फिर से बुलाया जाएगा, इस समय एक पैरामीटर के रूप "my_func" होने देता है। इसलिए, इस मामले में, तकनीकी रूप से "सजावट" जो भी "my_decorator" द्वारा लौटाया जाता है, इसे "सजावटी कारखाना" बना देता है।

अब, सजाए गए या सजावटी कारखानों को आमतौर पर कुछ आंतरिक स्थिति रखना है। पहले मामले में, केवल एक चीज जो रखती है वह मूल फ़ंक्शन का संदर्भ है (आपके उदाहरणों में f नामक चर)। एक "सजावटी कारखाना" ऊपर दिए गए उदाहरण में अतिरिक्त राज्य चर ("param1" और "param2" पंजीकृत करना चाहता है)।

यह अतिरिक्त स्थिति, कार्यों के रूप में लिखे गए सजावटी के मामले में संलग्न कार्यों के भीतर चर में रखा जाता है, और वास्तविक रैपर फ़ंक्शन द्वारा "nonlocal" चर के रूप में उपयोग किया जाता है। यदि कोई उचित वर्ग लिखता है, तो उन्हें सजावटी फ़ंक्शन में उदाहरण चर के रूप में रखा जा सकता है (जिसे "कॉल करने योग्य ऑब्जेक्ट" के रूप में देखा जाएगा, न कि "फ़ंक्शन") - और उन तक पहुंच अधिक स्पष्ट और अधिक पठनीय है।

तो, ज्यादातर मामलों में यह पठनीयता की बात है कि आप एक दृष्टिकोण या दूसरे को पसंद करेंगे: छोटे, सरल सजावटी के लिए, कार्यात्मक दृष्टिकोण अक्सर कक्षा के रूप में लिखे गए एक से अधिक पठनीय होता है - जबकि कभी-कभी एक और विस्तृत एक - विशेष रूप से एक "सजावटी कारखाना" "पाइथन कोडिंग" के बारे में सलाह "फ्लैट नेस्टेड से बेहतर है" का पूरा फायदा उठाएगा।

पर विचार करें:

class MyDecorator(object): 
    """Decorator example mixing class and function definitions.""" 
    def __init__(self, func, param1, param2): 
     self.func = func 
     self.param1, self.param2 = param1, param2 

    def __call__(self, *args, **kwargs): 
     ... 
     #use self.param1 
     result = self.func(*args, **kwargs) 
     #use self.param2 
     return result 

def my_dec_factory(param1, param2): 
    def decorator(func): 
     return MyDecorator(func, param1, param2) 
    return decorator 

अद्यतन:

def my_dec_factory(param1, param2): 
    ... 
    ... 
    def real_decorator(func): 
     ... 
     def wraper_func(*args, **kwargs): 
      ... 
      #use param1 
      result = func(*args, **kwargs) 
      #use param2 
      return result 
     return wraper_func 
    return real_decorator 
इस "संकर" समाधान के खिलाफ

मिसिंग यू "शुद्ध वर्ग" सज्जाकार

अब के रूपों, ध्यान दें "संकर" विधि सबसे छोटी और अधिक पठनीय कोड रखने की कोशिश कर रहे "दोनों दुनिया के सर्वश्रेष्ठ" लेती है। एक पूर्ण "डेकोरेटर कारखाने" वर्गों के साथ विशेष रूप से परिभाषित या तो पता करने के लिए दो वर्गों, या एक "मोड" विशेषता की आवश्यकता होगी अगर यह सजाया समारोह रजिस्टर करने के लिए बुलाया गया था या वास्तव में अंतिम समारोह कॉल करने के लिए:

class MyDecorator(object): 
    """Decorator example defined entirely as class.""" 
    def __init__(self, p1, p2): 
     self.p1 = p1 
     ... 
     self.mode = "decorating" 

    def __call__(self, *args, **kw): 
     if self.mode == "decorating": 
      self.func = args[0] 
      self.mode = "calling" 
      return self 
     # code to run prior to function call 
     result = self.func(*args, **kw) 
     # code to run after function call 
     return result 

@MyDecorator(p1, ...) 
def myfunc(): 
    ... 

और अंत में एक शुद्ध, "सफेद colar" डेकोरेटर दो वर्गों के साथ परिभाषित - हो सकता है और अधिक अलग बातें रखते हुए, लेकिन एक बात करने के लिए अतिरेक में वृद्धि एक नहीं कह सकता कि यह अधिक पोषणीय है:

class Stage2Decorator(object): 
    def __init__(self, func, p1, p2, ...): 
     self.func = func 
     self.p1 = p1 
     ... 
    def __call__(self, *args, **kw): 
     # code to run prior to function call 
     ... 
     result = self.func(*args, **kw) 
     # code to run after function call 
     ... 
     return result 

class Stage1Decorator(object): 
    """Decorator example defined as two classes. 

    No "hacks" on the object model, most bureacratic. 
    """ 
    def __init__(self, p1, p2): 
     self.p1 = p1 
     ... 
     self.mode = "decorating" 

    def __call__(self, func): 
     return Stage2Decorator(func, self.p1, self.p2, ...) 


@Stage1Decorator(p1, p2, ...) 
def myfunc(): 
    ... 
+0

क्षमा करें कि मुझे जवाब देने में इतना लंबा लगा, मैं दूर था। वैसे भी, यह एक अच्छा जवाब था, धन्यवाद :) – olofom

+0

@jiamo - क्षमा करें, आपका संपादन सही था - मेरे पास वापस आने के लिए कोई रास्ता नहीं है क्योंकि मैंने गलती से 'अस्वीकार' दबाया था। आपके द्वारा देखी गई बग अब तय की गई है। – jsbueno

+0

बहुत बढ़िया जवाब, धन्यवाद। "Self.mode" से बचने के लिए आप __call__ के अंदर एक फ़ंक्शन भी वापस कर सकते हैं। जैसे def __call __ (self, func): def wrapper (* args, ** kwargs): वापसी func (* args, ** kwargs) रिटर्न रैपर – Josejulio

1

दो अलग डेकोरेटर कार्यान्वयन कर रहे हैं। इनमें से एक सजावट के रूप में एक वर्ग का उपयोग करता है और दूसरा एक सजावट के रूप में एक समारोह का उपयोग करता है। आपको अपनी जरूरतों के लिए पसंदीदा कार्यान्वयन का चयन करना होगा।

उदाहरण के लिए, यदि आपके डेकोरेटर बहुत काम करता है तो आप वर्ग एक डेकोरेटर के रूप में, इस तरह उपयोग कर सकते हैं:

import logging 
import time 
import pymongo 
import hashlib 
import random 

DEBUG_MODE = True 

class logger(object): 

     def __new__(cls, *args, **kwargs): 
       if DEBUG_MODE: 
         return object.__new__(cls, *args, **kwargs) 
       else: 
         return args[0] 

     def __init__(self, foo): 
       self.foo = foo 
       logging.basicConfig(filename='exceptions.log', format='%(levelname)s % (asctime)s: %(message)s') 
       self.log = logging.getLogger(__name__) 

     def __call__(self, *args, **kwargs): 
       def _log(): 
         try: 
           t = time.time() 
           func_hash = self._make_hash(t) 
           col = self._make_db_connection() 
           log_record = {'func_name':self.foo.__name__, 'start_time':t, 'func_hash':func_hash} 
           col.insert(log_record) 
           res = self.foo(*args, **kwargs) 
           log_record = {'func_name':self.foo.__name__, 'exc_time':round(time.time() - t,4), 'end_time':time.time(),'func_hash':func_hash} 
           col.insert(log_record) 
           return res 
         except Exception as e: 
           self.log.error(e) 
       return _log() 

     def _make_db_connection(self): 
       connection = pymongo.Connection() 
       db = connection.logger 
       collection = db.log 
       return collection 

     def _make_hash(self, t): 
       m = hashlib.md5() 
       m.update(str(t)+str(random.randrange(1,10))) 
       return m.hexdigest() 
+0

हाँ, लेकिन मुझे अभी भी फायदे/ऐसा करने के दो तरीकों और क्या उपयोग करने के लिए नुकसान के लिए नुकसान। ठीक है, कक्षा सजावट थोड़ा और अधिक उन्नत हो सकता है मुझे लगता है? कोई नुकसान? – olofom

+0

ठीक है, सजावटी इसका एक कार्य या वर्ग है जो इनपुट पर फ़ंक्शन या कक्षा लेता है। और अब आप सोच सकते हैं कि किस मामले में फ़ंक्शन का उपयोग करना सुविधाजनक है या जब आप कक्षा का उपयोग करना चाहते हैं। – Denis

+1

यह उत्तर अवधारणात्मक रूप से गलत है: यह "वर्ग सजावट" नहीं है - एक "वर्ग सजावट" एक वर्ग को सजाने के लिए, और इसे गीलेर के साथ भ्रमित नहीं किया जाता है, इसका कार्यान्वयन एक वर्ग या कार्य होता है। – jsbueno

9

मैं ज्यादातर jsbueno से सहमत: वहाँ कोई भी सही तरीका नहीं है। यह स्थिति पर निर्भर करता है। लेकिन मुझे लगता है कि अधिकांश मामलों में डीफ़ शायद बेहतर है, क्योंकि यदि आप कक्षा के साथ जाते हैं, तो अधिकांश "असली" काम __call__ में किया जा रहा है। साथ ही, कॉलबेल जो फ़ंक्शंस नहीं हैं वे बहुत दुर्लभ हैं (कक्षा को तुरंत चालू करने के उल्लेखनीय अपवाद के साथ), और आम तौर पर लोग इसकी अपेक्षा नहीं करते हैं। साथ ही, लोगों के लिए बनाम आवृत्ति चर का ट्रैक रखने के लिए स्थानीय चर आमतौर पर आसान होते हैं, क्योंकि उनके पास अधिक सीमित गुंजाइश है, हालांकि इस मामले में, आवृत्ति चर शायद __call__ (__init__ के साथ केवल तर्कों से कॉपी करने के साथ) में उपयोग की जाती हैं।

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

स्पर्शरेखा: के लिए कि क्या आप वर्ग या समारोह के साथ जाने के बावजूद, आप functools.wraps है, जो अपने आप में एक डेकोरेटर के रूप में इस्तेमाल किया जा करने के लिए है का उपयोग करना चाहिए (हम गहराई में जाने चाहिए!) तो जैसे:

import functools 

def require_authorization(f): 
    @functools.wraps(f) 
    def decorated(user, *args, **kwargs): 
     if not is_authorized(user): 
      raise UserIsNotAuthorized 
     return f(user, *args, **kwargs) 
    return decorated 

@require_authorization 
def check_email(user, etc): 
    # etc. 

यह बनाता है decoratedcheck_email जैसे देखें इसे func_name विशेषता बदलकर।

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

def require_authorization(action): 
    def decorate(f): 
     @functools.wraps(f): 
     def decorated(user, *args, **kwargs): 
      if not is_allowed_to(user, action): 
       raise UserIsNotAuthorized(action, user) 
      return f(user, *args, **kwargs) 
     return decorated 
    return decorate 

वैसे, मैं भी सज्जाकार के अत्यधिक उपयोग के खिलाफ गार्ड पर हो सकता है,: उस मामले में, मैं सिर्फ डीईएफ़ का एक और स्तर जोड़ने।

भयानक ढेर के निशान के प्रबंधन के लिए एक दृष्टिकोण है कि सजावट के व्यवहार को काफी हद तक बदलने की नीति न हो। जैसे

def log_call(f): 
    @functools.wraps(f) 
    def decorated(*args, **kwargs): 
     logging.debug('call being made: %s(*%r, **%r)', 
         f.func_name, args, kwargs) 
     return f(*args, **kwargs) 
    return decorated 

अपने ढेर रखने के लिए एक अधिक चरम दृष्टिकोण समझदार निशान डेकोरेटर के लिए है decoratee असंशोधित वापस जाने के लिए है, तो जैसे:

import threading 

DEPRECATED_LOCK = threading.Lock() 
DEPRECATED = set() 

def deprecated(f): 
    with DEPRECATED_LOCK: 
     DEPRECATED.add(f) 
    return f 

@deprecated 
def old_hack(): 
    # etc. 

समारोह एक रूपरेखा है कि जानता है के भीतर भी कहा जाता है, तो यह उपयोगी है deprecated सजावट के बारे में। जैसे

class MyLamerFramework(object): 
    def register_handler(self, maybe_deprecated): 
     if not self.allow_deprecated and is_deprecated(f): 
      raise ValueError(
       'Attempted to register deprecated function %s as a handler.' 
       % f.func_name) 
     self._handlers.add(maybe_deprecated) 
+0

जिसे मैं "हाइब्रिड दृष्टिकोण" कहता हूं, वह सिर्फ '__call__' विधि को कार्य करने जैसा है एक सजावटी खुद - एक पैरामीटर के रूप में सजाया समारोह हो रही है। क्या आप कक्षा सजावट का उदाहरण देते हैं जो ऐसा नहीं करेगा? (इस प्रकार, '__call__' को ऑब्जेक्ट को किसी अन्य ऑब्जेक्ट में अभी भी किसी अन्य ऑब्जेक्ट में फ़ीड करना होगा, जो कसकर पहले के साथ मिलकर - "-" राज्य को "पता" करने के लिए दबाएगा यदि यह पहले से ही किसी फ़ंक्शन से जुड़ा हुआ है या नहीं - '__call__' सजाए गए फ़ंक्शन को स्वयं लपेटने की तुलना में मुझे और अधिक भ्रमित लगता है। – jsbueno

+0

उत्कृष्ट उत्तर। –

संबंधित मुद्दे