सजाए गए कार्यों के हस्ताक्षर को संरक्षित करना


111

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

यहाँ एक उदाहरण है:

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

>>> funny_function("3", 4.0, z="5")
22

अभी तक सब कुछ ठीक है। हालाँकि, एक समस्या है। सजाया गया फ़ंक्शन मूल फ़ंक्शन के दस्तावेज़ को बनाए नहीं रखता है:

>>> help(funny_function)
Help on function g in module __main__:

g(*args, **kwargs)

सौभाग्य से, वहाँ एक समाधान है:

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

इस बार, फ़ंक्शन नाम और दस्तावेज़ सही हैं:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

लेकिन अभी भी एक समस्या है: फ़ंक्शन हस्ताक्षर गलत है। सूचना "* args, ** kwargs" बेकार के बगल में है।

क्या करें? मैं दो सरल लेकिन त्रुटिपूर्ण वर्कअराउंड के बारे में सोच सकता हूं:

1 - डॉकस्ट्रिंग में सही हस्ताक्षर शामिल करें:

def funny_function(x, y, z=3):
    """funny_function(x, y, z=3) -- computes x*y + 2*z"""
    return x*y + 2*z

नकल की वजह से यह खराब है। हस्ताक्षर अभी भी स्वचालित रूप से उत्पन्न प्रलेखन में ठीक से नहीं दिखाए जाएंगे। फ़ंक्शन को अपडेट करना और डॉकस्ट्रिंग को बदलना या टाइपो बनाने के बारे में भूलना आसान है। [ और हाँ, मैं इस तथ्य से अवगत हूं कि डॉकस्ट्रिंग पहले से ही फ़ंक्शन बॉडी को डुप्लिकेट करता है। कृपया इसे अनदेखा करें; funny_function सिर्फ एक यादृच्छिक उदाहरण है। ]

2 - एक डेकोरेटर का उपयोग न करें, या हर विशिष्ट हस्ताक्षर के लिए एक विशेष उद्देश्य वाले डेकोरेटर का उपयोग करें:

def funny_functions_decorator(f):
    def g(x, y, z=3):
        return f(int(x), int(y), z=int(z))
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

यह ऐसे कार्यों के सेट के लिए ठीक काम करता है जिनमें समान हस्ताक्षर होते हैं, लेकिन यह सामान्य रूप से बेकार है। जैसा कि मैंने शुरुआत में कहा था, मैं पूरी तरह से उदारतापूर्वक सज्जाकारों का उपयोग करने में सक्षम होना चाहता हूं।

मैं एक ऐसे समाधान की तलाश में हूं जो पूरी तरह से सामान्य और स्वचालित हो।

तो सवाल यह है: क्या यह बनाए जाने के बाद सजाए गए फ़ंक्शन हस्ताक्षर को संपादित करने का एक तरीका है?

अन्यथा, क्या मैं एक डेकोरेटर लिख सकता हूं जो फ़ंक्शन हस्ताक्षर को निकालता है और सजाए गए फ़ंक्शन का निर्माण करते समय "* kwargs, ** kwargs" के बजाय उस जानकारी का उपयोग करता है? मैं उस जानकारी को कैसे निकालूं? मुझे सजाए गए फ़ंक्शन का निर्माण कैसे करना चाहिए - निष्पादन के साथ?

कोई अन्य दृष्टिकोण?


1
"आउट ऑफ डेट" कभी नहीं कहा। मैं कम या ज्यादा सोच रहा था कि inspect.Signatureसजाए गए कार्यों से क्या जोड़ा जाए।
नाइटशाडेक्यूएन

जवाबों:


79
  1. डेकोरेटर मॉड्यूल स्थापित करें :

    $ pip install decorator
  2. अनुकूली परिभाषा args_as_ints():

    import decorator
    
    @decorator.decorator
    def args_as_ints(f, *args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    
    @args_as_ints
    def funny_function(x, y, z=3):
        """Computes x*y + 2*z"""
        return x*y + 2*z
    
    print funny_function("3", 4.0, z="5")
    # 22
    help(funny_function)
    # Help on function funny_function in module __main__:
    # 
    # funny_function(x, y, z=3)
    #     Computes x*y + 2*z
    

पायथन 3.4+

functools.wraps()पार्थिव 3.4 के बाद से stdlib हस्ताक्षरों को संरक्षित करता है:

import functools


def args_as_ints(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

functools.wraps()पायथन 2.5 के बाद से कम से कम उपलब्ध है , लेकिन यह वहां हस्ताक्षर को संरक्षित नहीं करता है:

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(*args, **kwargs)
#    Computes x*y + 2*z

सूचना: के *args, **kwargsबजाय x, y, z=3


तुम्हारा पहला जवाब नहीं था, लेकिन अब तक का सबसे व्यापक है :-) मैं वास्तव में एक समाधान को शामिल करना चाहूंगा जिसमें कोई तृतीय पक्ष मॉड्यूल शामिल नहीं है, लेकिन डेकोरेटर मॉड्यूल के लिए स्रोत को देखना, यह काफी सरल है कि मैं सक्षम हो जाऊंगा बस इसे कॉपी करें।
फ्रेड्रिक जोहानसन

1
@MarkLodato: functools.wraps()पहले से ही Python 3.4+ में हस्ताक्षर हस्ताक्षरित करता है (जैसा कि उत्तर में कहा गया है)। क्या आपका मतलब है कि सेटिंग wrapper.__signature__पहले के संस्करणों पर मदद करती है? (आपने किन संस्करणों का परीक्षण किया है?)
jfs

1
@MarkLodato: help()पायथन 3.4 पर सही हस्ताक्षर दिखाता है। आपको क्यों लगता functools.wraps()है कि टूटी हुई है और आईपीथॉन नहीं है?
५१६

1
@MarkLodato: अगर इसे ठीक करने के लिए हमें कोड लिखना है तो यह टूट गया है। यह देखते हुए कि help()सही परिणाम पैदा करता है, सवाल यह है कि सॉफ्टवेयर का कौन सा टुकड़ा तय किया जाना चाहिए: functools.wraps()या आईपीथॉन? किसी भी मामले में, मैन्युअल रूप से असाइन __signature__करना सबसे अच्छा एक समाधान है - यह दीर्घकालिक समाधान नहीं है।
jfs

1
ऐसा लगता है कि inspect.getfullargspec()अभी भी functools.wrapsअजगर 3.4 के लिए उचित हस्ताक्षर नहीं लौटाता है और inspect.signature()इसके बजाय आपको इसका उपयोग करना चाहिए ।
तुक्का मुस्टोनन

16

यह पायथन के मानक पुस्तकालय functoolsऔर विशेष रूप से functools.wrapsफ़ंक्शन के साथ हल किया गया है, जिसे " लिपटे फ़ंक्शन की तरह दिखने के लिए एक आवरण फ़ंक्शन को अपडेट करने" के लिए डिज़ाइन किया गया है । यह व्यवहार पायथन संस्करण पर निर्भर करता है, हालांकि, जैसा कि नीचे दिखाया गया है। प्रश्न से उदाहरण के लिए लागू, कोड की तरह दिखेगा:

from functools import wraps

def args_as_ints(f):
    @wraps(f) 
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

पायथन 3 में निष्पादित होने पर, यह निम्नलिखित उत्पादन करेगा:

>>> funny_function("3", 4.0, z="5")
22
>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

इसका एकमात्र दोष यह है कि अजगर 2 में हालांकि, यह फ़ंक्शन की तर्क सूची को अपडेट नहीं करता है। पायथन 2 में निष्पादित होने पर, यह उत्पादन करेगा:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

यकीन नहीं है कि अगर यह स्फिंक्स है, लेकिन यह काम नहीं करता है जब लिपटे फ़ंक्शन एक वर्ग की एक विधि है। स्फिंक्स डेकोरेटर के कॉल हस्ताक्षर की रिपोर्ट करना जारी रखता है।
वर्णमाला

9

डेकोरेटर के साथ एक डेकोरेटर मॉड्यूल है जिसका decoratorआप उपयोग कर सकते हैं:

@decorator
def args_as_ints(f, *args, **kwargs):
    args = [int(x) for x in args]
    kwargs = dict((k, int(v)) for k, v in kwargs.items())
    return f(*args, **kwargs)

तब हस्ताक्षर और विधि की मदद संरक्षित है:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

संपादित करें: JF सेबस्टियन ने बताया कि मैंने args_as_intsफ़ंक्शन को संशोधित नहीं किया है - यह अब तय हो गया है।



6

दूसरा विकल्प:

  1. लपेटें मॉड्यूल स्थापित करें:

$ easy_install लिपटे

रैपप्ट में एक बोनस है, वर्ग हस्ताक्षर को संरक्षित करें।


import wrapt
import inspect

@wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z

2

जैसा कि jfs के उत्तर में ऊपर टिप्पणी की गई है ; यदि आप उपस्थिति ( helpऔर inspect.signature) के संदर्भ में हस्ताक्षर से चिंतित हैं , तो उपयोग functools.wrapsकरना पूरी तरह से ठीक है।

यदि आप व्यवहार के संदर्भ में हस्ताक्षर से चिंतित हैं (विशेष रूप TypeErrorसे तर्कों के मामले में), functools.wrapsइसे संरक्षित नहीं करता है। आपको इसके decoratorलिए, या इसके मूल इंजन के मेरे सामान्यीकरण के लिए उपयोग करना चाहिए , जिसका नाम है makefun

from makefun import wraps

def args_as_ints(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("wrapper executes")
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# wrapper executes
# 22

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

funny_function(0)  
# observe: no "wrapper executes" is printed! (with functools it would)
# TypeError: funny_function() takes at least 2 arguments (1 given)

इस पोस्ट के बारे मेंfunctools.wraps भी देखें ।


1
साथ ही, inspect.getfullargspecकॉल करने से परिणाम नहीं रखा जाता है functools.wraps
laike9m

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