سؤال

لنفترض أنني كتبت مصمم ديكور يقوم بشيء عام جدًا.على سبيل المثال، قد يقوم بتحويل جميع الوسائط إلى نوع معين، وإجراء التسجيل، وتنفيذ الحفظ، وما إلى ذلك.

هنا مثال:

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" عند إنشاء الوظيفة المزخرفة؟كيف أستخرج تلك المعلومات؟كيف يمكنني إنشاء الوظيفة المزخرفة - باستخدام exec؟

هل هناك أي طرق أخرى؟

هل كانت مفيدة؟

المحلول

  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() من ستدليب يحتفظ بالتوقيعات منذ Python 3.4:

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.

نصائح أخرى

يتم حل هذه المشكلة باستخدام مكتبة بايثون القياسية functools وعلى وجه التحديد functools.wraps الوظيفة، والتي تم تصميمها ل"قم بتحديث وظيفة مجمّع لتبدو مثل الوظيفة الملتفة".يعتمد سلوكه على إصدار Python، كما هو موضح أدناه.وبالتطبيق على المثال من السؤال، سيبدو الكود كما يلي:

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

عند تنفيذه في Python 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

عيبه الوحيد هو أنه في Python 2، لا يقوم بتحديث قائمة وسيطات الوظيفة.عند تنفيذها في بايثون 2، سوف تنتج:

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

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

هناك وحدة الديكور مع 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

يحرر:ج.F.وأشار سيباستيان إلى أنني لم أعدل args_as_ints الوظيفة - تم إصلاحها الآن.

نلقي نظرة على مصمم ديكور الوحدة النمطية - على وجه التحديد مصمم ديكور الديكور الذي يحل هذه المشكلة.

الخيار الثاني:

  1. تثبيت وحدة التفاف:

ملف $ easy_install

لدى Wrapt مكافأة، ويحافظ على توقيع الفصل.


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

كما علق أعلاه في إجابة 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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top