ตกแต่งงูหลามในชั้นเรียน


140

หนึ่งสามารถเขียนสิ่งที่ชอบ:

class Test(object):
    def _decorator(self, foo):
        foo()

    @self._decorator
    def bar(self):
        pass

สิ่งนี้ล้มเหลว: ไม่รู้จักตนเองใน @self

ฉันก็ลอง:

@Test._decorator(self)

ซึ่งยังล้มเหลว: ทดสอบที่ไม่รู้จัก

ฉันต้องการเปลี่ยนตัวแปรอินสแตนซ์ในมัณฑนากรชั่วคราวแล้วเรียกใช้วิธีการตกแต่งก่อนที่จะเปลี่ยนกลับ

คำตอบ:


268

สิ่งนี้จะเป็นสิ่งที่คุณต้องการหรือไม่?

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

test = Test()

test.bar()

วิธีนี้จะหลีกเลี่ยงการเรียกตัวเองเพื่อเข้าถึงมัณฑนากรและปล่อยให้มันซ่อนอยู่ในเนมสเปซของคลาสเป็นวิธีปกติ

>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>> 

แก้ไขเพื่อตอบคำถามในความคิดเห็น:

วิธีใช้ decorator ที่ซ่อนอยู่ในคลาสอื่น

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

    _decorator = staticmethod( _decorator )

class TestB( Test ):
    @Test._decorator
    def bar( self ):
        print "override bar in"
        super( TestB, self ).bar()
        print "override bar out"

print "Normal:"
test = Test()
test.bar()
print

print "Inherited:"
b = TestB()
b.bar()
print

เอาท์พุท:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic

7
มัณฑนากรหรือฟังก์ชั่นการตกแต่ง? โปรดทราบว่าฟังก์ชั่น "เวทมนต์" ที่ส่งคืนที่แรปบาร์กำลังรับตัวแปรตนเองด้านบนเมื่อเรียกว่า "บาร์" ในอินสแตนซ์และสามารถทำสิ่งใดก็ได้กับตัวแปรอินสแตนซ์ที่ต้องการก่อนและหลัง (หรือไม่ก็ตาม) . ไม่มีสิ่งเช่นตัวแปรอินสแตนซ์เมื่อประกาศคลาส คุณต้องการที่จะทำอะไรบางอย่างกับชั้นเรียนจากภายในมัณฑนากรหรือไม่? ฉันไม่คิดว่าเป็นการใช้สำนวน
Michael Speer

1
ขอบคุณไมเคิลตอนนี้เท่านั้นที่เห็นว่านี่คือสิ่งที่ฉันต้องการ
hcvst

2
ฉันพบว่าวิธีนี้ดีกว่าคำตอบที่ยอมรับเพราะช่วยให้การใช้ไวยากรณ์ @ decorator ที่จุดของคำนิยาม ถ้าฉันต้องเรียกมัณฑนากรเมื่อจบชั้นเรียนแล้วมันก็ไม่ชัดเจนว่าฟังก์ชั่นนั้นกำลังได้รับการตกแต่ง มันค่อนข้างแปลกที่คุณไม่สามารถใช้ @staticmethod บนตัวตกแต่งได้ แต่อย่างน้อยก็ใช้งานได้
mgiuca

1
ฉันไม่คิดว่ามันจะทำงานถ้าฉันสร้างคลาสที่สืบทอดของ Test.For: class TestB (Test): @_decorator def foobar (ตัวเอง): พิมพ์ "adsf" มีวิธีแก้ปัญหาหรือไม่?
extraeee

1
@extraeee: ตรวจสอบการแก้ไขที่ฉันทำ คุณต้องมีคุณสมบัติมัณฑนากรที่ได้รับเป็นแบบคงที่ แต่หลังจากคุณใช้งานเสร็จแล้ว (หรือการกำหนดรุ่นแบบคงที่วิธีเป็นชื่ออื่น)
Michael Speer

58

สิ่งที่คุณต้องการทำไม่สามารถทำได้ ยกตัวอย่างเช่นไม่ว่าโค้ดด้านล่างจะมีผลหรือไม่:

class Test(object):

    def _decorator(self, foo):
        foo()

    def bar(self):
        pass
    bar = self._decorator(bar)

แน่นอนมันไม่ถูกต้องเนื่องจากselfไม่ได้กำหนดไว้ ณ จุดนั้น สิ่งเดียวกันจะเกิดขึ้นTestเนื่องจากมันจะไม่ถูกกำหนดจนกว่าจะมีการกำหนดคลาสเอง (ซึ่งอยู่ในกระบวนการ) ฉันกำลังแสดงตัวอย่างรหัสนี้ให้คุณเพราะนี่คือสิ่งที่ตัวอย่างมัณฑนากรของคุณเปลี่ยนเป็น

ดังนั้นอย่างที่คุณเห็นการเข้าถึงอินสแตนซ์ในมัณฑนากรแบบนี้เป็นไปไม่ได้เพราะมัณฑนากรถูกนำไปใช้ในระหว่างการนิยามฟังก์ชั่น / วิธีการใด ๆ ที่พวกเขาเชื่อมต่ออยู่

หากคุณต้องการการเข้าถึงระดับชั้นเรียนลองสิ่งนี้:

class Test(object):

    @classmethod
    def _decorator(cls, foo):
        foo()

    def bar(self):
        pass
Test.bar = Test._decorator(Test.bar)

5
ควรอัปเดตเพื่ออ้างอิงคำตอบที่ถูกต้องมากขึ้นด้านล่าง
Nathan Buesgens

1
ดี ร้อยแก้วของคุณบอกว่าเป็นไปไม่ได้ แต่รหัสของคุณแสดงให้เห็นว่าต้องทำอย่างไร
นักฟิสิกส์บ้า

22
import functools


class Example:

    def wrapper(func):
        @functools.wraps(func)
        def wrap(self, *args, **kwargs):
            print("inside wrap")
            return func(self, *args, **kwargs)
        return wrap

    @wrapper
    def method(self):
        print("METHOD")

    wrapper = staticmethod(wrapper)


e = Example()
e.method()

1
TypeError: วัตถุ 'staticmethod' ไม่สามารถเรียกได้
wyx

@wyx ไม่เรียกว่ามัณฑนากร ตัวอย่างเช่นควร@fooไม่ใช่@foo()
docyoda

อาร์กิวเมนต์แรกควรwrapperเป็นselfหรือไม่
CpILL

7

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

class myclass(object):
    def __init__(self):
        self.property = "HELLO"

    @adecorator(property="GOODBYE")
    def method(self):
        print self.property

นี่คือรหัสมัณฑนากร

class adecorator (object):
    def __init__ (self, *args, **kwargs):
        # store arguments passed to the decorator
        self.args = args
        self.kwargs = kwargs

    def __call__(self, func):
        def newf(*args, **kwargs):

            #the 'self' for a method function is passed as args[0]
            slf = args[0]

            # replace and store the attributes
            saved = {}
            for k,v in self.kwargs.items():
                if hasattr(slf, k):
                    saved[k] = getattr(slf,k)
                    setattr(slf, k, v)

            # call the method
            ret = func(*args, **kwargs)

            #put things back
            for k,v in saved.items():
                setattr(slf, k, v)

            return ret
        newf.__doc__ = func.__doc__
        return newf 

หมายเหตุ: เนื่องจากฉันเคยใช้ตัวตกแต่งคลาสคุณจะต้องใช้ @adecorator () พร้อมกับเครื่องหมายวงเล็บเพื่อตกแต่งฟังก์ชั่น


7

นี่เป็นวิธีหนึ่งในการเข้าถึง (และใช้) selfจากภายในdecoratorคลาสที่กำหนดภายใน:

class Thing(object):
    def __init__(self, name):
        self.name = name

    def debug_name(function):
        def debug_wrapper(*args):
            self = args[0]
            print 'self.name = ' + self.name
            print 'running function {}()'.format(function.__name__)
            function(*args)
            print 'self.name = ' + self.name
        return debug_wrapper

    @debug_name
    def set_name(self, new_name):
        self.name = new_name

เอาท์พุท (ทดสอบบนPython 2.7.10):

>>> a = Thing('A')
>>> a.name
'A'
>>> a.set_name('B')
self.name = A
running function set_name()
self.name = B
>>> a.name
'B'

ตัวอย่างข้างต้นโง่ แต่ได้ผล


4

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

class OutputAnalysis(object):
    "analyze the output of diagnostic commands"
    def handler_for(name):
        "decorator to associate a function with a command"
        def wrapper(func):
            func.handler_for = name
            return func
        return wrapper
    # associate mount_p with 'mount_-p.txt'
    @handler_for('mount -p')
    def mount_p(self, slurped):
        pass

ตอนนี้เราได้เชื่อมโยงข้อมูลกับวิธีการเรียนแต่ละวิธีแล้วเราต้องรวบรวมข้อมูลนั้นและเก็บไว้ในแอตทริบิวต์ class

OutputAnalysis.cmd_handler = {}
for value in OutputAnalysis.__dict__.itervalues():
    try:
        OutputAnalysis.cmd_handler[value.handler_for] = value
    except AttributeError:
        pass

4

ต่อไปนี้เป็นส่วนขยายของคำตอบของ Michael Speer เพื่อเพิ่มเติมอีกไม่กี่ก้าว

อินสแตนซ์มัณฑนากรเมธอดซึ่งรับอาร์กิวเมนต์และดำเนินการกับฟังก์ชันที่มีอาร์กิวเมนต์และค่าส่งคืน

class Test(object):
    "Prints if x == y. Throws an error otherwise."
    def __init__(self, x):
        self.x = x

    def _outer_decorator(y):
        def _decorator(foo):
            def magic(self, *args, **kwargs) :
                print("start magic")
                if self.x == y:
                    return foo(self, *args, **kwargs)
                else:
                    raise ValueError("x ({}) != y ({})".format(self.x, y))
                print("end magic")
            return magic

        return _decorator

    @_outer_decorator(y=3)
    def bar(self, *args, **kwargs) :
        print("normal call")
        print("args: {}".format(args))
        print("kwargs: {}".format(kwargs))

        return 27

และจากนั้น

In [2]:

    test = Test(3)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    normal call
    args: (13, 'Test')
    kwargs: {'q': 9, 'lollipop': [1, 2, 3]}
Out[2]:
    27
In [3]:

    test = Test(4)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-3-576146b3d37e> in <module>()
          4     'Test',
          5     q=9,
    ----> 6     lollipop=[1,2,3]
          7 )

    <ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs)
         11                     return foo(self, *args, **kwargs)
         12                 else:
    ---> 13                     raise ValueError("x ({}) != y ({})".format(self.x, y))
         14                 print("end magic")
         15             return magic

    ValueError: x (4) != y (3)

3

มัณฑนากรดูจะเหมาะสมกว่าในการปรับเปลี่ยนการทำงานของวัตถุทั้งหมด (รวมถึงวัตถุฟังก์ชัน) กับการทำงานของวิธีการของวัตถุซึ่งโดยทั่วไปจะขึ้นอยู่กับคุณลักษณะของอินสแตนซ์ ตัวอย่างเช่น:

def mod_bar(cls):
    # returns modified class

    def decorate(fcn):
        # returns decorated function

        def new_fcn(self):
            print self.start_str
            print fcn(self)
            print self.end_str

        return new_fcn

    cls.bar = decorate(cls.bar)
    return cls

@mod_bar
class Test(object):
    def __init__(self):
        self.start_str = "starting dec"
        self.end_str = "ending dec" 

    def bar(self):
        return "bar"

ผลลัพธ์คือ:

>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec

1

คุณสามารถตกแต่งมัณฑนากร:

import decorator

class Test(object):
    @decorator.decorator
    def _decorator(foo, self):
        foo(self)

    @_decorator
    def bar(self):
        pass

1

ฉันมีการใช้งานนักตกแต่งที่อาจช่วยได้

    import functools
    import datetime


    class Decorator(object):

        def __init__(self):
            pass


        def execution_time(func):

            @functools.wraps(func)
            def wrap(self, *args, **kwargs):

                """ Wrapper Function """

                start = datetime.datetime.now()
                Tem = func(self, *args, **kwargs)
                end = datetime.datetime.now()
                print("Exection Time:{}".format(end-start))
                return Tem

            return wrap


    class Test(Decorator):

        def __init__(self):
            self._MethodName = Test.funca.__name__

        @Decorator.execution_time
        def funca(self):
            print("Running Function : {}".format(self._MethodName))
            return True


    if __name__ == "__main__":
        obj = Test()
        data = obj.funca()
        print(data)

1

ประกาศในชั้นใน วิธีการแก้ปัญหานี้ค่อนข้างแข็งและแนะนำ

class Test(object):
    class Decorators(object):
    @staticmethod
    def decorator(foo):
        def magic(self, *args, **kwargs) :
            print("start magic")
            foo(self, *args, **kwargs)
            print("end magic")
        return magic

    @Decorators.decorator
    def bar( self ) :
        print("normal call")

test = Test()

test.bar()

ผลลัพธ์:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.