Dekorator Python di kelas


140

Bisakah seseorang menulis sesuatu seperti:

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

    @self._decorator
    def bar(self):
        pass

Ini gagal: self in @self tidak diketahui

Saya juga mencoba:

@Test._decorator(self)

yang juga gagal: Tes tidak diketahui

Saya ingin sementara mengubah beberapa variabel contoh di dekorator dan kemudian menjalankan metode yang dihiasi, sebelum mengubahnya kembali.

Jawaban:


268

Akankah sesuatu seperti ini melakukan apa yang Anda butuhkan?

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()

Ini menghindari panggilan untuk diri sendiri untuk mengakses dekorator dan membiarkannya tersembunyi di namespace kelas sebagai metode biasa.

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

diedit untuk menjawab pertanyaan dalam komentar:

Cara menggunakan dekorator tersembunyi di kelas lain

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

Keluaran:

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
Dekorator atau fungsi yang didekorasi? Perhatikan fungsi "ajaib" yang dikembalikan yang membungkus bilah menerima variabel mandiri di atas ketika "bilah" dipanggil pada instance dan dapat melakukan apa saja terhadap variabel instan yang diinginkan sebelum dan sesudah (atau bahkan apakah atau tidak) itu disebut "bar" . Tidak ada yang namanya variabel instan saat mendeklarasikan kelas. Apakah Anda ingin melakukan sesuatu ke kelas dari dalam dekorator? Saya tidak berpikir itu adalah penggunaan idiomatik.
Michael Speer

1
Terima kasih Michael, baru sekarang melihat bahwa inilah yang saya inginkan.
hcvst

2
Saya menemukan solusi ini jauh lebih bagus daripada jawaban yang diterima, karena memungkinkan penggunaan sintaksis @ dekorator pada titik definisi. Jika saya harus menelepon dekorator di akhir kelas, maka tidak jelas bahwa fungsinya sedang didekorasi. Agak aneh bahwa Anda tidak dapat menggunakan @staticmethod pada dekorator itu sendiri, tetapi setidaknya itu berfungsi.
mgiuca

1
Saya tidak berpikir itu berfungsi jika saya membuat kelas Test yang diwariskan. Misalnya: class TestB (Test): @_decorator def foobar (self): print "adsf" Apakah ada solusinya?
ekstraee

1
@extraeee: periksa edit yang saya buat. Anda harus memenuhi syarat dekorator yang diberikan sebagai metode statis, tetapi hanya setelah Anda selesai menggunakannya (atau menugaskan versi metode statis ke nama yang berbeda)
Michael Speer

58

Apa yang ingin Anda lakukan tidak mungkin. Ambil, misalnya, apakah kode di bawah ini terlihat valid atau tidak:

class Test(object):

    def _decorator(self, foo):
        foo()

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

Itu, tentu saja, tidak valid karena selftidak didefinisikan pada saat itu. Hal yang sama berlaku karena Testtidak akan didefinisikan sampai kelas itu sendiri didefinisikan (yang sedang dalam proses). Saya menunjukkan kepada Anda potongan kode ini karena ini yang diubah menjadi potongan dekorator Anda.

Jadi, seperti yang Anda lihat, mengakses instance dalam dekorator seperti itu tidak benar-benar mungkin karena dekorator diterapkan selama definisi fungsi / metode apa pun yang dilampirkan dan bukan selama instantiasi.

Jika Anda memerlukan akses tingkat kelas , coba ini:

class Test(object):

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

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

5
mungkin harus diperbarui untuk merujuk pada jawaban yang lebih akurat di bawah ini
Nathan Buesgens

1
Bagus. Prosa Anda mengatakan tidak mungkin, tetapi kode Anda cukup banyak menunjukkan bagaimana melakukannya.
Fisikawan Gila

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: objek 'staticmethod' tidak bisa dipanggil
wyx

@ Tidak, jangan panggil dekorator. Misalnya, seharusnya @foo, bukan@foo()
docyoda

Seharusnya tidak argumen pertama untuk wrappermenjadi self?
CpILL

7

Saya menggunakan jenis dekorator ini dalam beberapa situasi debugging, ini memungkinkan mengesampingkan properti kelas dengan dekorasi, tanpa harus menemukan fungsi panggilan.

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

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

Ini adalah kode dekorator

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 

Catatan: karena saya telah menggunakan dekorator kelas, Anda harus menggunakan @adecorator () dengan tanda kurung untuk menghias fungsi, bahkan jika Anda tidak memberikan argumen apa pun kepada konstruktor kelas dekorator.


7

Ini adalah salah satu cara untuk mengakses (dan telah menggunakan) selfdari dalam decoratordefinisi di dalam kelas yang sama:

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

Output (diuji pada 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'

Contoh di atas konyol, tetapi berhasil.


4

Saya menemukan pertanyaan ini ketika meneliti masalah yang sangat mirip. Solusi saya adalah untuk membagi masalah menjadi dua bagian. Pertama, Anda perlu menangkap data yang ingin Anda asosiasikan dengan metode kelas. Dalam hal ini, handler_for akan mengaitkan perintah Unix dengan handler untuk output perintah itu.

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

Sekarang kita telah mengaitkan beberapa data dengan setiap metode kelas, kita perlu mengumpulkan data itu dan menyimpannya dalam atribut kelas.

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

4

Berikut ini adalah perluasan jawaban Michael Speer untuk mengambil beberapa langkah lebih jauh:

Dekorator metode instan yang mengambil argumen dan bertindak pada fungsi dengan argumen dan nilai kembali.

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

Lalu

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

Dekorator tampaknya lebih cocok untuk memodifikasi fungsionalitas seluruh objek (termasuk objek fungsi) dibandingkan fungsionalitas metode objek yang secara umum akan bergantung pada atribut instance. Sebagai contoh:

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"

Outputnya adalah:

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

1

Anda dapat menghias dekorator:

import decorator

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

    @_decorator
    def bar(self):
        pass

1

Saya memiliki Implementasi Dekorator yang Mungkin Membantu

    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

Deklarasikan di kelas batin. Solusi ini cukup solid dan direkomendasikan.

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()

Hasil:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.