python - شهادة - منتديات بايثون




كيفية جعل سلسلة من وظيفة الديكور؟ (11)

كيف يمكنني صنع اثنين من الديكور في بيثون من شأنه أن يفعل ما يلي؟

@makebold
@makeitalic
def say():
   return "Hello"

... والتي يجب أن تعود:

"<b><i>Hello</i></b>"

أنا لا أحاول إنشاء HTML بهذه الطريقة في تطبيق حقيقي - مجرد محاولة لفهم كيفية عمل سلاسل الديكور والديكور.


كيف يمكنني صنع اثنين من الديكور في بيثون من شأنه أن يفعل ما يلي؟

تريد الوظيفة التالية ، عندما تسمى:

@makebold
@makeitalic
def say():
    return "Hello"

لكي ترجع:

<b><i>Hello</i></b>

حل بسيط

لمعظم ببساطة القيام بذلك ، وجعل الديكور الذي يعيد lambdas (وظائف مجهولة الهوية) التي تغلق على وظيفة (إغلاق) وتسمى:

def makeitalic(fn):
    return lambda: '<i>' + fn() + '</i>'

def makebold(fn):
    return lambda: '<b>' + fn() + '</b>'

استخدمها الآن حسب الرغبة:

@makebold
@makeitalic
def say():
    return 'Hello'

و الأن:

>>> say()
'<b><i>Hello</i></b>'

مشاكل مع الحل البسيط

ولكن يبدو أننا فقدنا الوظيفة الأصلية تقريبًا.

>>> say
<function <lambda> at 0x4ACFA070>

للعثور عليه ، سوف نحتاج إلى حفر إغلاق كل lambda ، واحد منها مدفون في الآخر:

>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>

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

الحل الكامل المميز - التغلب على معظم هذه المشاكل

لدينا الديكور wrapsمن functoolsالوحدة في المكتبة القياسية!

from functools import wraps

def makeitalic(fn):
    # must assign/update attributes from wrapped function to wrapper
    # __module__, __name__, __doc__, and __dict__ by default
    @wraps(fn) # explicitly give function whose attributes it is applying
    def wrapped(*args, **kwargs):
        return '<i>' + fn(*args, **kwargs) + '</i>'
    return wrapped

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return '<b>' + fn(*args, **kwargs) + '</b>'
    return wrapped

من المؤسف أنه لا يزال هناك بعض النمطي ، ولكن هذا الأمر بسيط بقدر ما يمكننا تحقيقه.

في Python 3 ، يمكنك أيضًا الحصول __qualname__عليها __annotations__وتعيينها افتراضيًا.

و الآن:

@makebold
@makeitalic
def say():
    """This function returns a bolded, italicized 'hello'"""
    return 'Hello'

و الأن:

>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:

say(*args, **kwargs)
    This function returns a bolded, italicized 'hello'

استنتاج

لذلك نرى أن wrapsوظيفة الالتفاف تقوم بكل شيء تقريباً باستثناء أن تخبرنا بالضبط ما هي الوظيفة التي تأخذها كحجج.

هناك وحدات أخرى قد تحاول معالجة المشكلة ، ولكن الحل ليس بعد في المكتبة القياسية.


تزيين الدوال مع عدد مختلف من الحجج:

def frame_tests(fn):
    def wrapper(*args):
        print "\nStart: %s" %(fn.__name__)
        fn(*args)
        print "End: %s\n" %(fn.__name__)
    return wrapper

@frame_tests
def test_fn1():
    print "This is only a test!"

@frame_tests
def test_fn2(s1):
    print "This is only a test! %s" %(s1)

@frame_tests
def test_fn3(s1, s2):
    print "This is only a test! %s %s" %(s1, s2)

if __name__ == "__main__":
    test_fn1()
    test_fn2('OK!')
    test_fn3('OK!', 'Just a test!')

نتيجة:

Start: test_fn1  
This is only a test!  
End: test_fn1  


Start: test_fn2  
This is only a test! OK!  
End: test_fn2  


Start: test_fn3  
This is only a test! OK! Just a test!  
End: test_fn3  

بدلاً من ذلك ، يمكنك كتابة دالة مصنع تقوم بإرجاع الديكور الذي يلف قيمة الإرجاع للوظيفة المزينة في علامة تم تمريرها إلى وظيفة المصنع. فمثلا:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator():
            return '<%(tag)s>%(rv)s</%(tag)s>' % (
                {'tag': tag, 'rv': func()})
        return decorator
    return factory

يمكّنك هذا من الكتابة:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
    return 'hello'

أو

makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')

@makebold
@makeitalic
def say():
    return 'hello'

أنا شخصياً كنت سأكتب الديكور بطريقة مختلفة نوعًا ما:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator(val):
            return func('<%(tag)s>%(val)s</%(tag)s>' %
                        {'tag': tag, 'val': val})
        return decorator
    return factory

التي من شأنها أن تعطي:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
    return val
say('hello')

لا تنس البناء الذي يكون بناء الجملة الخاص بالديكور هو اختصار:

say = wrap_in_tag('b')(wrap_in_tag('i')(say)))

تحقق من الوثائق لمعرفة كيفية عمل الديكور. إليك ما طلبت:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

print hello() ## returns "<b><i>hello world</i></b>"

يبدو أن الأشخاص الآخرين أخبروك عن كيفية حل المشكلة. آمل أن يساعدك هذا على فهم ما هو المصممون.

المصممون مجرد سكر صناعي.

هذه

@decorator
def func():
    ...

يتوسع ل

def func():
    ...
func = decorator(func)

يضيف ديكور الثعبان وظائف إضافية لوظيفة أخرى

يمكن أن يكون مثل الديكور المائل

def makeitalic(fn):
    def newFunc():
        return "<i>" + fn() + "</i>"
    return newFunc

لاحظ أنه يتم تعريف وظيفة داخل وظيفة. ما يقوم به أساسا هو استبدال وظيفة مع واحدة محددة حديثا. على سبيل المثال ، لدي هذا الفصل

class foo:
    def bar(self):
        print "hi"
    def foobar(self):
        print "hi again"

الآن أقول ، أريد كلا الوظيفتين لطباعة "---" بعد وقبل أن تنتهي. يمكنني إضافة "---" قبل وبعد كل بيان طباعة. ولكن لأنني لا أحب تكرار نفسي ، سأقوم بعمل ديكور

def addDashes(fn): # notice it takes a function as an argument
    def newFunction(self): # define a new function
        print "---"
        fn(self) # call the original function
        print "---"
    return newFunction
    # Return the newly defined function - it will "replace" the original

حتى الآن يمكنني تغيير صفي إلى

class foo:
    @addDashes
    def bar(self):
        print "hi"

    @addDashes
    def foobar(self):
        print "hi again"

لمعرفة المزيد عن الديكور ، تحقق من http://www.ibm.com/developerworks/linux/library/l-cpdecor.html


عند التحدث عن مثال العداد - كما هو موضح أعلاه ، سيتم مشاركة العداد بين جميع الوظائف التي تستخدم الديكور:

def counter(func):
    def wrapped(*args, **kws):
        print 'Called #%i' % wrapped.count
        wrapped.count += 1
        return func(*args, **kws)
    wrapped.count = 0
    return wrapped

بهذه الطريقة ، يمكن إعادة استخدام الديكور الخاص بك لوظائف مختلفة (أو استخدامها لتزيين نفس الوظيفة عدة مرات:) func_counter1 = counter(func); func_counter2 = counter(func)، وسيظل المتغير المضاد خاص لكل منها.


لشرح الديكور بطريقة أبسط:

مع:

@decor1
@decor2
def func(*args, **kwargs):
    pass

عندما تفعل:

func(*args, **kwargs)

انت فعلا فعلتها:

decor1(decor2(func))(*args, **kwargs)

يأخذ الديكور طابع الوظيفة ويخلق وظيفة جديدة تقوم بتنفيذ هذه الوظيفة وتحول النتيجة.

@deco
def do():
    ...

هو eqivarent إلى:

do = deco(do)

مثال:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

هذه

@deco
def do(number):
    return chr(number)  # number to letter

هو eqivalent إلى do2 def هذا (رقم): chr (رقم)

do2 = deco(do2)

65 <=> "أ"

print(do(65))
print(do2(65))
>>> B
>>> B

لفهم الديكور ، من المهم أن نلاحظ ، أن الديكور خلق وظيفة جديدة تفعل الداخلية التي تنفذ func وتحويل النتيجة.


هل يمكن جعل اثنين الديكور منفصلة أن تفعل ما تريد كما هو موضح أدناه مباشرة. لاحظ استخدام *args, **kwargsإعلان wrapped()الدالة الذي يدعم الوظيفة المزودة بخصائص متعددة (وهي ليست ضرورية فعلاً say()للوظيفة المثالية ، ولكنها متضمنة للعمومية).

لأسباب مماثلة ، functools.wrapsيتم استخدام الديكور لتغيير السمات الفوقية للوظيفة الملفوفة لتكون تلك الموجودة في الزينة. هذا يجعل رسائل الخطأ ووثائق الوظائف المدمجة ( func.__doc__) هي تلك الخاصة بالوظيفة المزخرفة بدلاً من wrapped()s.

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

التحسينات

كما ترى ، هناك الكثير من الشفرات المكررة في هذين المصممين. بالنظر إلى هذا التشابه ، سيكون من الأفضل لك بدلاً من ذلك أن تصنع نوعًا عامًا كان في الواقع مصنعًا للديكور - وبعبارة أخرى ، مصممًا يزيّن الديكورات الأخرى. بهذه الطريقة سيكون هناك تكرار أقل للكود - ويسمح بمتابعة مبدأ DRY .

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

لجعل الشفرة أكثر قابلية للقراءة ، يمكنك تعيين اسم وصفي أكثر للديكور الذي تولده المصنع:

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

أو حتى الجمع بينهما مثل هذا:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

نجاعة

في حين أن الأمثلة المذكورة أعلاه تعمل بكاملها ، فإن الشفرة التي تم إنشاؤها تنطوي على قدر كبير من النفقات العامة في شكل مكالمات دالة غريبة عندما يتم تطبيق ديكور متعدد في وقت واحد. هذا قد لا يهم ، اعتمادا على الاستخدام الدقيق (الذي قد يكون I / O- ملزمة ، على سبيل المثال).

إذا كانت سرعة الوظيفة المزخرفة مهمة ، فيمكن الحفاظ على الحمل العلوي لمكالمة واحدة إضافية عن طريق كتابة وظيفة مصنع ديكور مختلفة بعض الشيء والتي تنفذ إضافة كل العلامات في وقت واحد ، حتى تتمكن من توليد كود يتجنب المكالمات الدالة الإضافية التي تكبدتها باستخدام ديكورات منفصلة لكل علامة.

وهذا يتطلب المزيد من التعليمات البرمجية في الديكور نفسه ، ولكن هذا لا يعمل إلا عندما يتم تطبيقه على التعريفات الوظيفية ، وليس لاحقًا عندما يتم استدعاؤها. ينطبق هذا أيضًا عند إنشاء أسماء أكثر قابلية للقراءة باستخدام lambdaوظائف كما هو موضح مسبقًا. عينة:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

#decorator.py
def makeHtmlTag(tag, *args, **kwds):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwds["css_class"]) \
                                 if "css_class" in kwds else ""
        def wrapped(*args, **kwds):
            return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
        return wrapped
    # return decorator dont call it
    return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"

print hello()

يمكنك أيضا كتابة الديكور في الفصل

#class.py
class makeHtmlTagClass(object):
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class != "" else ""

    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped

@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)

print hello("Your name")




python-decorators