ตกแต่งด้วยพารามิเตอร์?


401

ฉันมีปัญหากับการถ่ายโอนตัวแปร 'insurance_mode' โดยมัณฑนากร ฉันจะทำมันโดยคำสั่งมัณฑนากรต่อไปนี้:

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

แต่น่าเสียดายที่ข้อความนี้ใช้ไม่ได้ บางทีอาจจะมีวิธีที่ดีกว่าในการแก้ปัญหานี้

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function

3
ตัวอย่างของคุณไม่ถูกต้องทางไวยากรณ์ execute_complete_reservationรับพารามิเตอร์สองตัว แต่คุณจะผ่านมันไป นักตกแต่งเป็นเพียงน้ำตาลซินแทคติกสำหรับห่อฟังก์ชั่นภายในฟังก์ชั่นอื่น ๆ ดูdocs.python.org/reference/compound_stmts.html#functionสำหรับเอกสารฉบับสมบูรณ์
Brian Clapper

คำตอบ:


687

ไวยากรณ์สำหรับมัณฑนากรที่มีอาร์กิวเมนต์แตกต่างกันเล็กน้อยมัณฑนากรที่มีอาร์กิวเมนต์ควรส่งคืนฟังก์ชันที่จะใช้ฟังก์ชันและส่งคืนฟังก์ชันอื่น ดังนั้นมันจึงควรกลับมาเป็นมัณฑนากรปกติ สับสนเล็กน้อยใช่มั้ย ที่ฉันหมายถึงคือ:

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

ที่นี่คุณสามารถอ่านเพิ่มเติมเกี่ยวกับหัวเรื่อง - นอกจากนี้ยังเป็นไปได้ที่จะนำสิ่งนี้ไปใช้โดยใช้ออบเจ็กต์ callable และนั่นก็อธิบายไว้ที่นั่นด้วย


56
ฉันสงสัยว่าทำไม GVR จึงไม่สามารถใช้งานได้โดยส่งผ่านพารามิเตอร์เป็นอาร์กิวเมนต์มัณฑนากรที่ตามมาหลังจาก 'ฟังก์ชัน' 'Yo dawg ฉันได้ยินมาว่าคุณชอบการปิด ... '
Michel Müller

3
> ฟังก์ชันจะเป็นอาร์กิวเมนต์แรกหรือสุดท้ายหรือไม่ ชัดเจนก่อนเนื่องจากพารามิเตอร์เป็นรายการพารามิเตอร์ของความยาวตัวแปร > เป็นเรื่องแปลกที่คุณจะ "เรียก" ฟังก์ชั่นที่มีลายเซ็นต์ต่างจากที่ระบุไว้ในคำนิยาม ในขณะที่คุณชี้ให้เห็นว่ามันจะเข้ากันได้ดีจริง ๆ - คล้ายกับวิธีการเรียนที่เรียกว่า เพื่อให้ชัดเจนยิ่งขึ้นคุณสามารถมีการประชุมแบบมัณฑนากร (self_func, param1, ... ) แต่หมายเหตุ: ฉันไม่สนับสนุนการเปลี่ยนแปลงใด ๆ ที่นี่ Python อยู่ไกลเกินกว่าที่จะทำสิ่งนั้นได้และเราจะเห็นว่าการเปลี่ยนแปลงที่เกิดขึ้นนั้นประสบผลสำเร็จได้อย่างไร
Michel Müller

21
คุณลืม Virtue Function ที่มีประโยชน์มาก ๆ สำหรับ wrapper ตกแต่ง :)
socketpair

10
คุณลืมเรื่องการส่งคืนเมื่อมีการเรียกใช้ฟังก์ชันเช่น return function(*args, **kwargs)
formiaczek

36
อาจจะชัดเจน แต่ในกรณี: คุณต้องใช้มัณฑนากรนี้เป็น@decorator()และไม่ใช่แค่@decoratorแม้ว่าคุณจะมีอาร์กิวเมนต์ที่เป็นตัวเลือกเท่านั้น
Patrick Mevzek

325

แก้ไข : เพื่อความเข้าใจในเชิงลึกเกี่ยวกับรูปแบบจิตของนักตกแต่งให้ดูที่นี้น่ากลัว PyCon พูดคุย คุ้มค่ากับ 30 นาที

วิธีคิดอย่างหนึ่งของนักตกแต่งที่มีข้อโต้แย้งคือ

@decorator
def foo(*args, **kwargs):
    pass

แปลเป็น

foo = decorator(foo)

ดังนั้นหากมัณฑนากรมีข้อโต้แย้ง

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

แปลเป็น

foo = decorator_with_args(arg)(foo)

decorator_with_args เป็นฟังก์ชันที่ยอมรับอาร์กิวเมนต์ที่กำหนดเองและส่งคืน decorator จริง (ที่จะใช้กับฟังก์ชันที่ตกแต่ง)

ฉันใช้กลลวงแบบง่ายๆพร้อม Partials เพื่อทำให้การตกแต่งของฉันง่ายขึ้น

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

ปรับปรุง:

ด้านบนfooกลายเป็นreal_decorator(foo)

หนึ่งในผลกระทบของการตกแต่งฟังก์ชั่นคือชื่อfooจะถูกแทนที่ด้วยการประกาศของมัณฑนากร fooคือ "แทนที่" real_decoratorโดยสิ่งที่ถูกส่งกลับโดย ในกรณีนี้วัตถุฟังก์ชั่นใหม่

ทั้งหมดของfooเมตาดาต้า 's แทนที่สะดุดตา docstring และชื่อฟังก์ชั่น

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

functools.wrapsทำให้เรามีวิธีที่สะดวกในการ "ยก" docstring และชื่อให้กับฟังก์ชันที่ส่งคืน

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

4
คำตอบของคุณอธิบายความสมบูรณ์ของฉากภายในของผู้ตกแต่งอย่างสมบูรณ์แบบขอขอบคุณ
zsf222

คุณสามารถเพิ่ม@functools.wraps?
Mr_and_Mrs_D

1
@Mr_and_Mrs_D functool.wrapsผมได้อัปเดตโพสต์ด้วยเช่นกับ การเพิ่มในตัวอย่างอาจทำให้ผู้อ่านสับสนมากขึ้น
srj

7
argนี่อะไร!
displayname

1
คุณจะส่งผ่านการโต้แย้งไปbarยังการโต้แย้งได้real_decoratorอย่างไร
Chang Zhao

85

ฉันต้องการแสดงความคิดที่ IMHO ค่อนข้างสง่างาม วิธีแก้ปัญหาที่เสนอโดย t.dubrownik แสดงรูปแบบที่เหมือนกันเสมอ: คุณต้องใช้ wrapper สามชั้นโดยไม่คำนึงถึงสิ่งที่มัณฑนากรทำ

ดังนั้นฉันคิดว่านี่เป็นงานสำหรับนักตกแต่ง meta นั่นคือมัณฑนากรสำหรับนักตกแต่ง ในฐานะที่เป็นมัณฑนากรเป็นฟังก์ชั่นมันใช้งานได้จริงเป็นมัณฑนากรปกติที่มีข้อโต้แย้ง:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

สิ่งนี้สามารถนำไปใช้กับมัณฑนากรปกติเพื่อเพิ่มพารามิเตอร์ ตัวอย่างเช่นสมมติว่าเรามีผู้ตกแต่งภายในซึ่งเพิ่มผลลัพธ์ของฟังก์ชันเป็นสองเท่า:

def double(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

ด้วย@parametrizedเราสามารถสร้าง@multiplyมัณฑนากรทั่วไปที่มีพารามิเตอร์

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

ตามอัตภาพพารามิเตอร์แรกของparametrizedมัณฑนากรเป็นฟังก์ชันในขณะที่อาร์กิวเมนต์ที่เหลือจะสอดคล้องกับพารามิเตอร์ของมัณฑนากร parametrized

ตัวอย่างการใช้งานที่น่าสนใจอาจเป็นมัคคุเทศก์ประเภทที่ปลอดภัย:

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

หมายเหตุสุดท้าย: ที่นี่ฉันไม่ได้ใช้functools.wrapsฟังก์ชั่นเสื้อคลุม แต่ฉันอยากจะแนะนำให้ใช้มันตลอดเวลา


3
ไม่ได้ใช้สิ่งนี้อย่างแน่นอน แต่ช่วยให้ฉันเข้าใจแนวคิด :) ขอบคุณ!
mouckatron

ฉันพยายามนี้และมีบางประเด็น
Jeff

@ เจฟฟ์ช่วยเล่าปัญหาที่คุณมีให้เราหน่อยได้ไหม?
Dacav

ฉันเชื่อมโยงกับคำถามของฉันและฉันคิดออก ... ฉันต้องโทรหา@wrapsฉันสำหรับกรณีเฉพาะของฉัน
Jeff

4
โอ้ฉันหายไปทั้งวันเลย โชคดีที่ฉันได้รับคำตอบนี้ (ซึ่งอาจเป็นคำตอบที่ดีที่สุดที่เคยสร้างบนอินเทอร์เน็ตทั้งหมด) พวกเขาก็ใช้@parametrizedเคล็ดลับของคุณเช่นกัน ปัญหาที่ฉันมีคือฉันลืม@ไวยากรณ์เท่ากับการโทรจริง (อย่างใดฉันรู้ว่าและไม่ทราบว่าในเวลาเดียวกับที่คุณสามารถรวบรวมจากคำถามของฉัน) ดังนั้นหากคุณต้องการแปล@ไวยากรณ์เป็นการโทรทั่วโลกเพื่อตรวจสอบว่ามันทำงานอย่างไรคุณควรแสดงความคิดเห็นชั่วคราวก่อนหรือจบท้ายการโทรสองครั้งและรับผลลัพธ์
mumbojumbo

79

นี่คือรุ่นที่ปรับเปลี่ยนเล็กน้อยจากคำตอบของ t.dubrownik ทำไม?

  1. ในฐานะแม่แบบทั่วไปคุณควรคืนค่าส่งคืนจากฟังก์ชั่นดั้งเดิม
  2. สิ่งนี้จะเปลี่ยนชื่อของฟังก์ชั่นซึ่งอาจส่งผลกระทบต่อมัณฑนากร / รหัสอื่น ๆ

ดังนั้นใช้@functools.wraps():

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator

37

ฉันคิดว่าปัญหาของคุณกำลังส่งผ่านข้อโต้แย้งไปยังมัณฑนากรของคุณ นี่เป็นเรื่องยุ่งยากเล็กน้อยและไม่ตรงไปตรงมา

นี่คือตัวอย่างของวิธีการทำสิ่งนี้:

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

พิมพ์:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

ดูบทความของ Bruce Eckel สำหรับรายละเอียดเพิ่มเติม


20
ระวังคลาสมัณฑนากร มันไม่ทำงานกับวิธีการยกเว้นว่าคุณจะสร้างตรรกะของอินสแตนซ์วิธีอธิบาย

9
delnan สนใจที่จะทำอย่างละเอียด? ฉันต้องใช้รูปแบบนี้เพียงครั้งเดียวดังนั้นฉันยังไม่ได้เจอข้อผิดพลาดใด ๆ
Ross Rogers

2
@RossRogers การเดาของฉันคือ @delnan หมายถึงสิ่งต่าง ๆ เช่น__name__คลาสมัณฑนากรที่ไม่มี
jamesc

9
@jamesc นั่นก็เช่นกันแม้ว่ามันจะค่อนข้างง่ายในการแก้ กรณีเฉพาะที่ฉันอ้างถึงclass Foo: @MyDec(...) def method(self, ...): blahซึ่งไม่ได้ผลเพราะFoo().methodจะไม่เป็นวิธีที่ถูกผูกไว้และจะไม่ผ่านselfโดยอัตโนมัติ สิ่งนี้สามารถแก้ไขได้โดยการสร้างMyDecdescriptor และสร้างวิธีการเชื่อมโยง__get__แต่ก็มีส่วนเกี่ยวข้องและชัดเจนน้อยลง ในที่สุดชั้นเรียนมัณฑนากรไม่สะดวกอย่างที่คิด

2
@delnan ฉันต้องการที่จะเห็นข้อแม้นี้โดดเด่นมากขึ้น ฉันกดปุ่มมันและสนใจที่จะเห็นวิธีการแก้ปัญหาที่ไม่ทำงาน (เกี่ยวข้องมากขึ้นชัดเจนน้อยลงแม้ว่ามันอาจจะเป็น)
HaPsantran

12
def decorator(argument):
    def real_decorator(function):
        def wrapper(*args):
            for arg in args:
                assert type(arg)==int,f'{arg} is not an interger'
            result = function(*args)
            result = result*argument
            return result
        return wrapper
    return real_decorator

การใช้งานของมัณฑนากร

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

จากนั้น

adder(2,3)

ผลิต

10

แต่

adder('hi',3)

ผลิต

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)

<ipython-input-140-d3420c248ebd> in wrapper(*args)
      3         def wrapper(*args):
      4             for arg in args:
----> 5                 assert type(arg)==int,f'{arg} is not an interger'
      6             result = function(*args)
      7             result = result*argument

AssertionError: hi is not an interger

8

นี่คือเทมเพลตสำหรับมัณฑนากรฟังก์ชั่นที่ไม่ต้องการ()หากไม่มีการกำหนดพารามิเตอร์:

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator

ตัวอย่างนี้ได้รับด้านล่าง:

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

ยังทราบว่าfactor_or_func(หรือพารามิเตอร์อื่น ๆ ) ไม่ควรได้รับพระราชเสาวนีย์wrapper()ใน
norok2

ทำไมคุณต้องตรวจสอบในlocals()?
Shital Shah

@ShitalShah ()ที่ครอบคลุมกรณีที่มัณฑนากรถูกใช้โดยไม่
norok2

4

ในตัวอย่างของฉันฉันตัดสินใจที่จะแก้ปัญหานี้ผ่านแลมบ์ดาแบบบรรทัดเดียวเพื่อสร้างฟังก์ชันมัณฑนากรใหม่:

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

เมื่อดำเนินการแล้วสิ่งนี้จะพิมพ์ออกมา:

Finished!
All Done!

อาจจะไม่สามารถขยายได้เหมือนกับโซลูชันอื่น ๆ แต่ใช้งานได้สำหรับฉัน


วิธีนี้ใช้ได้ผล แม้ว่าจะใช่มันทำให้ยากที่จะตั้งค่าเป็น decorator
Arindam Roychowdhury

3

การเขียนมัณฑนากรที่ทำงานด้วยและไม่มีพารามิเตอร์เป็นสิ่งที่ท้าทายเพราะ Python คาดหวังว่าพฤติกรรมที่แตกต่างอย่างสิ้นเชิงในสองกรณีนี้! มีคำตอบมากมายที่พยายามหลีกเลี่ยงปัญหานี้และด้านล่างคือการปรับปรุงคำตอบโดย @ norok2 โดยเฉพาะรูปแบบนี้จะช่วยลดการใช้locals()โดยเฉพาะรูปแบบนี้จะช่วยลดการใช้งานของ

ทำตามตัวอย่างเดียวกันตามที่กำหนดโดย @ norok2:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

เล่นกับรหัสนี้นี้

การดักจับคือผู้ใช้ต้องระบุคีย์คู่ค่าพารามิเตอร์แทนพารามิเตอร์ตำแหน่งและพารามิเตอร์แรกถูกสงวนไว้


2

เป็นที่ทราบกันดีว่าโค้ดสองชิ้นต่อไปนี้เกือบเท่ากัน:

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

ข้อผิดพลาดทั่วไปคือการคิดว่า@ซ่อนอาร์กิวเมนต์ที่เหลือไว้

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

การเขียนมัณฑนากรจะง่ายกว่านี้ถ้าการ@ทำงานเป็นอย่างไร น่าเสียดายที่นั่นไม่ใช่สิ่งที่ทำ


พิจารณามัณฑนากรWaitซึ่งหลอกหลอนการทำงานของโปรแกรมสักสองสามวินาที หากคุณไม่ผ่านในช่วงเวลารอค่าเริ่มต้นคือ 1 วินาที กรณีใช้งานแสดงอยู่ด้านล่าง

##################################################
@Wait
def print_something(something):
    print(something)

##################################################
@Wait(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Wait(delay=3)
def print_something_else(something_else):
    print(something_else)

เมื่อWaitมีข้อโต้แย้งเช่น@Wait(3)จากนั้นสายWait(3) จะถูกดำเนินการก่อนสิ่งอื่นใดเกิดขึ้น

นั่นคือโค้ดสองชิ้นต่อไปนี้เทียบเท่ากัน

@Wait(3)
def print_something_else(something_else):
    print(something_else)

###############################################
return_value = Wait(3)
@return_value
def print_something_else(something_else):
    print(something_else)

นี่คือปัญหา.

if `Wait` has no arguments:
    `Wait` is the decorator.
else: # `Wait` receives arguments
    `Wait` is not the decorator itself.
    Instead, `Wait` ***returns*** the decorator

วิธีแก้ไขปัญหาหนึ่งแสดงอยู่ด้านล่าง:

ให้เราเริ่มต้นด้วยการสร้างคลาสต่อไปนี้DelayedDecorator:

class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Delayed Decorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Delayed Decorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "If only one input, input must be callable",
                    "Instead, received:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

ตอนนี้เราสามารถเขียนสิ่งต่าง ๆ เช่น:

 dec = DelayedDecorator(Wait, delay=4)
 @dec
 def delayed_print(something):
    print(something)

โปรดทราบว่า:

  • dec ไม่ยอมรับข้อโต้แย้งหลายข้อ
  • dec ยอมรับฟังก์ชั่นที่จะถูกห่อเท่านั้น

    อิมพอร์ตตรวจสอบคลาส PolyArgDecoratorMeta (ชนิด): การเรียก def (Wait, * args, ** kwargs): ลอง: arg_count = len (args) ถ้า (arg_count == 1): ถ้า callable (args [0]): SuperClass = ตรวจสอบ getmro (PolyArgDecoratorMeta) [1] r = SuperClass โทร (รอ args [0]) อื่น: r = DelayedDecorator (รอ * args, ** kwargs) อื่น: r = DelayedDecorator (รอ args * args, ** kwargs) ในที่สุด: pass return r

    อิมพอร์ตคลาสเวลารอ (metaclass = PolyArgDecoratorMeta): def init (i, func, delay = 2): i._func = func i._delay = ความล่าช้า

    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 

โค้ดสองชิ้นต่อไปนี้เทียบเท่ากัน:

@Wait
def print_something(something):
     print (something)

##################################################

def print_something(something):
    print(something)
print_something = Wait(print_something)

เราสามารถพิมพ์"something"ไปยังคอนโซลได้ช้ามากดังนี้:

print_something("something")

#################################################
@Wait(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Wait, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("something")

หมายเหตุสุดท้าย

มันอาจดูเหมือนโค้ดจำนวนมาก แต่คุณไม่จำเป็นต้องเขียนคลาสDelayedDecoratorและPolyArgDecoratorMetaทุกครั้ง รหัสเดียวที่คุณต้องเขียนด้วยตนเองดังนี้: ซึ่งค่อนข้างสั้น:

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Wait(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r

1

กำหนด "ฟังก์ชั่น decoratorize" เพื่อสร้างฟังก์ชั่นที่กำหนดเองมัณฑนากร:

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

ใช้วิธีนี้:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...

1

คำตอบที่ดีข้างต้น อันนี้ยังแสดงให้เห็น@wrapsซึ่งใช้สตริง doc และชื่อฟังก์ชั่นจากฟังก์ชั่นเดิมและนำไปใช้กับรุ่นที่ห่อใหม่:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

พิมพ์:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello

0

ในกรณีที่ทั้งฟังก์ชั่นและมัณฑนากรต้องใช้อาร์กิวเมนต์คุณสามารถทำตามวิธีการด้านล่าง

ตัวอย่างเช่นมีชื่อมัณฑนากรdecorator1ซึ่งใช้อาร์กิวเมนต์

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

ตอนนี้ถ้าdecorator1อาร์กิวเมนต์ต้องเป็นแบบไดนามิกหรือผ่านในขณะที่เรียกใช้ฟังก์ชัน

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

ในรหัสด้านบน

  • seconds เป็นอาร์กิวเมนต์สำหรับ decorator1
  • a, b เป็นข้อโต้แย้งของ func1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.