2008-11-20 27 views
54

मैं करने के लिए एक डेकोरेटर लिखने के लिए कोशिश कर रहा हूँ के अंतर्गत आता है लॉगिंग बनाताअजगर डेकोरेटर समारोह भूल जाते हैं कि यह एक वर्ग

Entering C.f 

लेकिन इसके बजाय मैं यह त्रुटि संदेश:

AttributeError: 'function' object has no attribute 'im_class' 

मुमकिन है इस 'myFunc' अंदर 'लकड़हारा' के दायरे के साथ कुछ है, लेकिन मैं नहीं, मैं है डीए क्या

+0

एक जवाब है, लेकिन पाया इस लेख गहराई में http://bit.ly चीजों को कवर करने/1NsBLmx – apcelent

उत्तर

41

क्लाउडियो का जवाब सही है, लेकिन आप self तर्क से कक्षा का नाम प्राप्त करके भी धोखा दे सकते हैं। यह विरासत के मामलों में भ्रामक लॉग स्टेटमेंट देगा, लेकिन आपको उस ऑब्जेक्ट की कक्षा बताएगा जिसकी विधि कहलाई जा रही है। उदाहरण के लिए:

from functools import wraps # use this to preserve function signatures and docstrings 
def logger(func): 
    @wraps(func) 
    def with_logging(*args, **kwargs): 
     print "Entering %s.%s" % (args[0].__class__.__name__, func.__name__) 
     return func(*args, **kwargs) 
    return with_logging 

class C(object): 
    @logger 
    def f(self): 
     pass 

C().f() 

जैसा कि मैंने कहा था, यह उन मामलों में ठीक से काम नहीं करेगा जहां आपने अभिभावक वर्ग से एक समारोह प्राप्त किया है; इस मामले में आप कह सकते हैं

class B(C): 
    pass 

b = B() 
b.f() 

और जहाँ आप वास्तव में के बाद से है कि सही वर्ग है संदेश Entering C.f प्राप्त करना चाहते हैं संदेश Entering B.f मिलता है। दूसरी तरफ, यह स्वीकार्य हो सकता है, इस मामले में मैं क्लाउडियो के सुझाव पर इस दृष्टिकोण की सिफारिश करता हूं।

+1

टाइपो: आप लॉगर फ़ंक्शन में 'with_logging' वापस भूल गए हैं। –

+2

वैसे, functools.wraps im_ * विशेषताओं को संरक्षित नहीं करता है। क्या आपको लगता है कि इस ओमिशन को बग माना जा सकता है? –

+1

मैं नाटक नहीं कर सकता कि मैं पूरी तरह से समझता हूं कि @wraps के साथ क्या चल रहा है, लेकिन यह निश्चित रूप से मेरी समस्या को हल करता है। बहुत बहुत धन्यवाद। –

7

ऐसा लगता है कि कक्षा बनाई जा रही है, पाइथन नियमित कार्य वस्तुओं को बनाता है। वे बाद में केवल अनबाउंड विधि वस्तुओं में बदल जाते हैं।

def logger(myFunc): 
    def new(*args, **keyargs): 
     print 'Entering %s.%s' % (myFunc.im_class.__name__, myFunc.__name__) 
     return myFunc(*args, **keyargs) 

    return new 

class C(object): 
    def f(self): 
     pass 
C.f = logger(C.f) 
C().f() 

यह वांछित परिणाम आउटपुट: कि जानने के बाद, यह एक ही तरीका है मैं तुम क्या चाहते करने के लिए मिल सकता है है।

आप एक कक्षा में सभी तरीकों रैप करने के लिए चाहते हैं, तो आप शायद एक wrapClass समारोह, जिसे फिर आप इस तरह इस्तेमाल कर सकते हैं बनाना चाहते हैं:

C = wrapClass(C) 
+0

स्थाई विधि के कारण wrapclass सावधान रहना चाहिए। –

+0

यह वर्ग सजावट के लिए एक अच्छा उपयोग केस जैसा दिखता है (पायथन 2.6 में नया)। वे फ़ंक्शन सजावट के समान ही काम करते हैं। – babbageclunk

6

कक्षा कार्यों हमेशा अपने पहले के रूप में स्वयं लेना चाहिए तर्क, तो आप im_class के बजाय इसका उपयोग कर सकते हैं।

def logger(myFunc): 
    def new(self, *args, **keyargs): 
     print 'Entering %s.%s' % (self.__class__.__name__, myFunc.__name__) 
     return myFunc(self, *args, **keyargs) 

    return new 

class C(object): 
    @logger 
    def f(self): 
     pass 
C().f() 

पहली बार में मैं self.__name__ उपयोग करना चाहता था, लेकिन क्योंकि उदाहरण कोई नाम नहीं है कि काम नहीं करता। कक्षा का नाम प्राप्त करने के लिए आपको self.__class__.__name__ का उपयोग करना होगा।

0

आप फ़ंक्शन से उदाहरण विधि (या तो बाध्य या अनबाउंड) बनाने के लिए new.instancemethod() का भी उपयोग कर सकते हैं।

25

कार्य केवल रनटाइम पर विधियां बन जाते हैं। यही है, जब आपको C.f मिलता है तो आपको एक बाध्य फ़ंक्शन मिलता है (और C.f.im_class is C)। उस समय आपके कार्य को परिभाषित किया गया है, यह केवल एक सादा कार्य है, यह किसी भी वर्ग से बंधे नहीं है। यह अनबाउंड और अलग-अलग फ़ंक्शन लॉगर द्वारा सजाया गया है।

self.__class__.__name__ आपको कक्षा का नाम देगा, लेकिन आप इसे कुछ और सामान्य तरीके से पूरा करने के लिए वर्णनकर्ताओं का भी उपयोग कर सकते हैं।यह पैटर्न in a blog post on Decorators and Descriptors वर्णन किया गया है, और विशेष रूप से अपने लकड़हारा डेकोरेटर के एक कार्यान्वयन दिखाई देगा:

class logger(object): 
    def __init__(self, func): 
     self.func = func 
    def __get__(self, obj, type=None): 
     return self.__class__(self.func.__get__(obj, type)) 
    def __call__(self, *args, **kw): 
     print 'Entering %s' % self.func 
     return self.func(*args, **kw) 

class C(object): 
    @logger 
    def f(self, x, y): 
     return x+y 

C().f(1, 2) 
# => Entering <bound method C.f of <__main__.C object at 0x...>> 

जाहिर उत्पादन सुधार किया जा सकता (का उपयोग कर, उदाहरण के लिए, getattr(self.func, 'im_class', None) द्वारा), लेकिन यह सामान्य पद्धति दोनों के लिए काम करेंगे विधियों और कार्यों। हालांकि यह पुरानी शैली कक्षाओं के लिए नहीं काम (लेकिन सिर्फ उन का उपयोग न करें;) होगा

+0

किसी और के लिए 'TypeError: foo' बिल्कुल एक्स तर्क 'या' विशेषता गायब 'लेता है और महसूस किया जाता है कि' स्वयं 'तर्क आपके सजाए गए फ़ंक्शन पर नहीं जा रहा है, यह समाधान है, धन्यवाद @ianb – theannouncer

+0

इस दृष्टिकोण को बाध्य विधि, अनबाउंड विधि और फ़ंक्शन के लिए एक अलग लॉगर की आवश्यकता है लॉगिंग। – KeatsKelleher

6

मैं inspect लाइब्रेरी का उपयोग कर एक बहुत ही इसी तरह की समस्या के लिए एक समाधान मिल गया। जब सजावटी को बुलाया जाता है, भले ही फ़ंक्शन अभी तक कक्षा से बंधे न हो, फिर भी आप ढेर का निरीक्षण कर सकते हैं और यह पता लगा सकते हैं कि कौन सा वर्ग सजावटी को बुला रहा है। आप कम से कम कक्षा के स्ट्रिंग नाम को प्राप्त कर सकते हैं, अगर आपको इसकी आवश्यकता है (संभवतः इसे तब तक संदर्भित नहीं किया जा सकता है जब से इसे बनाया जा रहा है)। तब कक्षा के निर्माण के बाद आपको कुछ भी कॉल करने की आवश्यकता नहीं है।

import inspect 

def logger(myFunc): 
    classname = inspect.getouterframes(inspect.currentframe())[1][3] 
    def new(*args, **keyargs): 
     print 'Entering %s.%s' % (classname, myFunc.__name__) 
     return myFunc(*args, **keyargs) 
    return new 

class C(object): 
    @logger 
    def f(self): 
     pass 

C().f() 

हालांकि यह जरूरी नहीं कि दूसरों से बेहतर है, यह केवल तरह से मैं डेकोरेटर करने के लिए कॉल के दौरान भविष्य विधि के वर्ग के नाम की खोज के लिए यह पता लगाने कर सकते हैं। inspect लाइब्रेरी दस्तावेज़ों में फ़्रेम के संदर्भों को संदर्भ न रखने का ध्यान रखें।

+2

यह वही है जो मैं चाहता हूं - विधि और कक्षा के बारे में जानकारी यह _before_ के लिए बाध्य होगी, इसे पहली बार बुलाया जाएगा। – 203

16

विचार यहाँ प्रस्तावित उत्कृष्ट रहे हैं, लेकिन कुछ नुकसान:

  1. inspect.getouterframes और args[0].__class__.__name__ सादा कार्यों और स्थिर-तरीकों के लिए उपयुक्त नहीं हैं।
  2. __get__ एक कक्षा में होना चाहिए, जिसे @wraps द्वारा अस्वीकार कर दिया गया है।
  3. @wraps स्वयं को निशान छिपाने के लिए बेहतर होना चाहिए।

तो, मैं इस पृष्ठ, लिंक, डॉक्स और अपने खुद के सिर,
से कुछ विचारों को संयोजित कर दिया और अंत में एक समाधान है, सबसे बढ़कर तीन नुकसान का अभाव पाया।

नतीजतन, method_decorator:

  • वर्ग सजाया विधि के लिए बाध्य है जानता है।
  • functools.wraps() से अधिक सटीक रूप से सिस्टम विशेषताओं के उत्तर देकर सजावट के निशान छुपाता है।
  • यूनिट-परीक्षणों के साथ एक अनबाउंड इंस्टेंस-विधियों, कक्षा-विधियों, स्थिर-विधियों और सादे कार्यों को बाध्य करने के लिए कवर किया गया है।

उपयोग:

pip install method_decorator 
from method_decorator import method_decorator 

class my_decorator(method_decorator): 
    # ... 

full unit-tests for usage details देखें।

और यहाँ सिर्फ method_decorator वर्ग के कोड है:

class method_decorator(object): 

    def __init__(self, func, obj=None, cls=None, method_type='function'): 
     # These defaults are OK for plain functions 
     # and will be changed by __get__() for methods once a method is dot-referenced. 
     self.func, self.obj, self.cls, self.method_type = func, obj, cls, method_type 

    def __get__(self, obj=None, cls=None): 
     # It is executed when decorated func is referenced as a method: cls.func or obj.func. 

     if self.obj == obj and self.cls == cls: 
      return self # Use the same instance that is already processed by previous call to this __get__(). 

     method_type = (
      'staticmethod' if isinstance(self.func, staticmethod) else 
      'classmethod' if isinstance(self.func, classmethod) else 
      'instancemethod' 
      # No branch for plain function - correct method_type for it is already set in __init__() defaults. 
     ) 

     return object.__getattribute__(self, '__class__')(# Use specialized method_decorator (or descendant) instance, don't change current instance attributes - it leads to conflicts. 
      self.func.__get__(obj, cls), obj, cls, method_type) # Use bound or unbound method with this underlying func. 

    def __call__(self, *args, **kwargs): 
     return self.func(*args, **kwargs) 

    def __getattribute__(self, attr_name): # Hiding traces of decoration. 
     if attr_name in ('__init__', '__get__', '__call__', '__getattribute__', 'func', 'obj', 'cls', 'method_type'): # Our known names. '__class__' is not included because is used only with explicit object.__getattribute__(). 
      return object.__getattribute__(self, attr_name) # Stopping recursion. 
     # All other attr_names, including auto-defined by system in self, are searched in decorated self.func, e.g.: __module__, __class__, __name__, __doc__, im_*, func_*, etc. 
     return getattr(self.func, attr_name) # Raises correct AttributeError if name is not found in decorated self.func. 

    def __repr__(self): # Special case: __repr__ ignores __getattribute__. 
     return self.func.__repr__() 
+0

यह एकमात्र दृष्टिकोण था जो मेरे लिए काम करता था। मुझे इसके बजाय ऑब्जेक्ट इंस्टेंस संदर्भ की आवश्यकता है – Simon

0

के बजाय परिभाषा समय में सजाने कोड इंजेक्शन लगाने, जब समारोह पता नहीं है यह वर्ग,/कहा जाता देरी समारोह तक इस कोड चलाने पहुँचा रहा है ।डिस्क्रिप्टर वस्तु देर स्वयं के कोड इंजेक्शन लगाने, पहुँच/कॉल समय की सुविधा:

class decorated(object): 
    def __init__(self, func, type_=None): 
     self.func = func 
     self.type = type_ 

    def __get__(self, obj, type_=None): 
     return self.__class__(self.func.__get__(obj, type_), type_) 

    def __call__(self, *args, **kwargs): 
     name = '%s.%s' % (self.type.__name__, self.func.__name__) 
     print('called %s with args=%s kwargs=%s' % (name, args, kwargs)) 
     return self.func(*args, **kwargs) 

class Foo(object): 
    @decorated 
    def foo(self, a, b): 
     pass 

अब हम दोनों उपयोग समय (__get__) पर और कॉल समय (__call__) में कक्षा निरीक्षण कर सकते हैं। इस तंत्र सादे तरीकों के साथ-साथ स्थिर लिए काम करता है | वर्ग तरीके:

>>> Foo().foo(1, b=2) 
called Foo.foo with args=(1,) kwargs={'b': 2} 

पूर्ण उदाहरण: https://github.com/aurzenligl/study/blob/master/python-robotwrap/Example4.py

ऐसा नहीं है
संबंधित मुद्दे