Pertanyaan Bagaimana cara membuat rantai dekorator fungsi?


Bagaimana saya bisa membuat dua dekorator dengan Python yang akan melakukan hal berikut?

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

... yang harus kembali:

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

Saya tidak mencoba membuatnya HTML cara ini dalam aplikasi nyata - hanya mencoba untuk memahami cara kerja penghias dan penghias chaining.


2377
2018-04-11 07:05


asal


Jawaban:


Periksa dokumentasi untuk melihat cara kerja dekorator. Inilah yang Anda minta:

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>"

2654
2018-04-11 07:16



Jika Anda tidak ingin penjelasan panjang, lihat Jawaban Paolo Bergantino.

Dasar-Dasar Dekorator

Fungsi Python adalah objek

Untuk memahami dekorator, Anda harus terlebih dahulu memahami bahwa fungsi adalah objek dengan Python. Ini memiliki konsekuensi penting. Mari kita lihat mengapa dengan contoh sederhana:

def shout(word="yes"):
    return word.capitalize()+"!"

print(shout())
# outputs : 'Yes!'

# As an object, you can assign the function to a variable like any other object 
scream = shout

# Notice we don't use parentheses: we are not calling the function,
# we are putting the function "shout" into the variable "scream".
# It means you can then call "shout" from "scream":

print(scream())
# outputs : 'Yes!'

# More than that, it means you can remove the old name 'shout',
# and the function will still be accessible from 'scream'

del shout
try:
    print(shout())
except NameError, e:
    print(e)
    #outputs: "name 'shout' is not defined"

print(scream())
# outputs: 'Yes!'

Ingatlah ini. Kami akan segera memutarnya kembali.

Properti lain yang menarik dari fungsi Python adalah mereka dapat didefinisikan di dalam fungsi lain!

def talk():

    # You can define a function on the fly in "talk" ...
    def whisper(word="yes"):
        return word.lower()+"..."

    # ... and use it right away!
    print(whisper())

# You call "talk", that defines "whisper" EVERY TIME you call it, then
# "whisper" is called in "talk". 
talk()
# outputs: 
# "yes..."

# But "whisper" DOES NOT EXIST outside "talk":

try:
    print(whisper())
except NameError, e:
    print(e)
    #outputs : "name 'whisper' is not defined"*
    #Python's functions are objects

Referensi fungsi

Oke, masih di sini? Sekarang bagian yang menyenangkan ...

Anda telah melihat bahwa fungsi adalah objek. Karena itu, fungsi:

  • dapat ditugaskan ke variabel
  • dapat didefinisikan dalam fungsi lain

Itu artinya itu fungsi bisa return fungsi lain.

def getTalk(kind="shout"):

    # We define functions on the fly
    def shout(word="yes"):
        return word.capitalize()+"!"

    def whisper(word="yes") :
        return word.lower()+"...";

    # Then we return one of them
    if kind == "shout":
        # We don't use "()", we are not calling the function,
        # we are returning the function object
        return shout  
    else:
        return whisper

# How do you use this strange beast?

# Get the function and assign it to a variable
talk = getTalk()      

# You can see that "talk" is here a function object:
print(talk)
#outputs : <function shout at 0xb7ea817c>

# The object is the one returned by the function:
print(talk())
#outputs : Yes!

# And you can even use it directly if you feel wild:
print(getTalk("whisper")())
#outputs : yes...

Masih ada lagi!

Jika kamu bisa return sebuah fungsi, Anda dapat melewatkan satu sebagai parameter:

def doSomethingBefore(func): 
    print("I do something before then I call the function you gave me")
    print(func())

doSomethingBefore(scream)
#outputs: 
#I do something before then I call the function you gave me
#Yes!

Nah, Anda hanya memiliki semua yang diperlukan untuk memahami dekorator. Anda lihat, dekorator adalah "pembungkus", yang berarti itu mereka membiarkan Anda mengeksekusi kode sebelum dan sesudah fungsi yang mereka hiasi tanpa memodifikasi fungsi itu sendiri.

Dekorator buatan tangan

Bagaimana Anda melakukannya secara manual:

# A decorator is a function that expects ANOTHER function as parameter
def my_shiny_new_decorator(a_function_to_decorate):

    # Inside, the decorator defines a function on the fly: the wrapper.
    # This function is going to be wrapped around the original function
    # so it can execute code before and after it.
    def the_wrapper_around_the_original_function():

        # Put here the code you want to be executed BEFORE the original function is called
        print("Before the function runs")

        # Call the function here (using parentheses)
        a_function_to_decorate()

        # Put here the code you want to be executed AFTER the original function is called
        print("After the function runs")

    # At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED.
    # We return the wrapper function we have just created.
    # The wrapper contains the function and the code to execute before and after. It’s ready to use!
    return the_wrapper_around_the_original_function

# Now imagine you create a function you don't want to ever touch again.
def a_stand_alone_function():
    print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function() 
#outputs: I am a stand alone function, don't you dare modify me

# Well, you can decorate it to extend its behavior.
# Just pass it to the decorator, it will wrap it dynamically in 
# any code you want and return you a new function ready to be used:

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

Sekarang, Anda mungkin menginginkan itu setiap kali Anda menelepon a_stand_alone_function, a_stand_alone_function_decorated disebut sebagai gantinya. Itu mudah, timpa saja a_stand_alone_function dengan fungsi yang dikembalikan oleh my_shiny_new_decorator:

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

# That’s EXACTLY what decorators do!

Dekorator didemistifikasi

Contoh sebelumnya, menggunakan sintaks dekorator:

@my_shiny_new_decorator
def another_stand_alone_function():
    print("Leave me alone")

another_stand_alone_function()  
#outputs:  
#Before the function runs
#Leave me alone
#After the function runs

Ya, itu saja, sesederhana itu. @decorator hanyalah jalan pintas untuk:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

Dekorator hanyalah varian pythonic dari pola desain dekorator. Ada beberapa pola desain klasik yang tertanam dalam Python untuk memudahkan pengembangan (seperti iterator).

Tentu saja, Anda dapat mengakumulasi dekorator:

def bread(func):
    def wrapper():
        print("</''''''\>")
        func()
        print("<\______/>")
    return wrapper

def ingredients(func):
    def wrapper():
        print("#tomatoes#")
        func()
        print("~salad~")
    return wrapper

def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

Menggunakan sintaksis Python dekorator:

@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

Urutan Anda mengatur dekorator HAL-HAL PIKIR:

@ingredients
@bread
def strange_sandwich(food="--ham--"):
    print(food)

strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~

Sekarang: untuk menjawab pertanyaan ...

Sebagai kesimpulan, Anda dapat dengan mudah melihat bagaimana menjawab pertanyaan:

# The decorator to make it bold
def makebold(fn):
    # The new function the decorator returns
    def wrapper():
        # Insertion of some code before and after
        return "<b>" + fn() + "</b>"
    return wrapper

# The decorator to make it italic
def makeitalic(fn):
    # The new function the decorator returns
    def wrapper():
        # Insertion of some code before and after
        return "<i>" + fn() + "</i>"
    return wrapper

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

print(say())
#outputs: <b><i>hello</i></b>

# This is the exact equivalent to 
def say():
    return "hello"
say = makebold(makeitalic(say))

print(say())
#outputs: <b><i>hello</i></b>

Sekarang Anda bisa merasa bahagia, atau membakar otak Anda sedikit lebih banyak dan melihat penggunaan dekorator tingkat lanjut.


Mengambil dekorator ke tingkat berikutnya

Melewati argumen ke fungsi yang dihias

# It’s not black magic, you just have to let the wrapper 
# pass the argument:

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print("I got args! Look: {0}, {1}".format(arg1, arg2))
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

# Since when you are calling the function returned by the decorator, you are
# calling the wrapper, passing arguments to the wrapper will let it pass them to 
# the decorated function

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print("My name is {0} {1}".format(first_name, last_name))

print_full_name("Peter", "Venkman")
# outputs:
#I got args! Look: Peter Venkman
#My name is Peter Venkman

Metode dekorasi

Satu hal bagus tentang Python adalah metode dan fungsinya benar-benar sama. Satu-satunya perbedaan adalah bahwa metode mengharapkan bahwa argumen pertama mereka adalah referensi ke objek saat ini (self).

Itu berarti Anda dapat membangun dekorator untuk metode dengan cara yang sama! Ingat saja untuk mengambil self mempertimbangkan:

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # very friendly, decrease age even more :-)
        return method_to_decorate(self, lie)
    return wrapper


class Lucy(object):

    def __init__(self):
        self.age = 32

    @method_friendly_decorator
    def sayYourAge(self, lie):
        print("I am {0}, what did you think?".format(self.age + lie))

l = Lucy()
l.sayYourAge(-3)
#outputs: I am 26, what did you think?

Jika Anda membuat dekorator tujuan umum - yang akan Anda terapkan ke fungsi atau metode apa pun, terlepas dari argumennya - gunakan saja *args, **kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # The wrapper accepts any arguments
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print("Do I have args?:")
        print(args)
        print(kwargs)
        # Then you unpack the arguments, here *args, **kwargs
        # If you are not familiar with unpacking, check:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("Python is cool, no argument here.")

function_with_no_argument()
#outputs
#Do I have args?:
#()
#{}
#Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1,2,3)
#outputs
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3 

@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
    print("Do {0}, {1} and {2} like platypus? {3}".format(a, b, c, platypus))

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#outputs
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):

    def __init__(self):
        self.age = 31

    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # You can now add a default value
        print("I am {0}, what did you think?".format(self.age + lie))

m = Mary()
m.sayYourAge()
#outputs
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

Melewati argumen ke dekorator

Hebat, sekarang apa yang akan Anda katakan tentang memberikan argumen kepada dekorator itu sendiri?

Ini bisa agak bengkok, karena dekorator harus menerima fungsi sebagai argumen. Oleh karena itu, Anda tidak dapat meneruskan argumen fungsi yang dihias langsung ke dekorator.

Sebelum bergegas ke solusinya, mari kita tuliskan sedikit pengingat:

# Decorators are ORDINARY functions
def my_decorator(func):
    print("I am an ordinary function")
    def wrapper():
        print("I am function returned by the decorator")
        func()
    return wrapper

# Therefore, you can call it without any "@"

def lazy_function():
    print("zzzzzzzz")

decorated_function = my_decorator(lazy_function)
#outputs: I am an ordinary function

# It outputs "I am an ordinary function", because that’s just what you do:
# calling a function. Nothing magic.

@my_decorator
def lazy_function():
    print("zzzzzzzz")

#outputs: I am an ordinary function

Itu persis sama. "my_decorator"Disebut. Jadi ketika Anda @my_decorator, Anda mengatakan Python untuk memanggil fungsi 'diberi label oleh variabel "my_decorator"'.

Ini penting! Label yang Anda berikan dapat mengarah langsung ke dekorator—atau tidak.

Mari kita menjadi jahat.

def decorator_maker():

    print("I make decorators! I am executed only once: "
          "when you make me create a decorator.")

    def my_decorator(func):

        print("I am a decorator! I am executed only when you decorate a function.")

        def wrapped():
            print("I am the wrapper around the decorated function. "
                  "I am called when you call the decorated function. "
                  "As the wrapper, I return the RESULT of the decorated function.")
            return func()

        print("As the decorator, I return the wrapped function.")

        return wrapped

    print("As a decorator maker, I return a decorator")
    return my_decorator

# Let’s create a decorator. It’s just a new function after all.
new_decorator = decorator_maker()       
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

# Then we decorate the function

def decorated_function():
    print("I am the decorated function.")

decorated_function = new_decorator(decorated_function)
#outputs:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function

# Let’s call the function:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Tidak mengherankan di sini.

Mari lakukan PERSIS hal yang sama, tetapi lewati semua variabel perantara sial:

def decorated_function():
    print("I am the decorated function.")
decorated_function = decorator_maker()(decorated_function)
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# Finally:
decorated_function()    
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Ayo berhasil bahkan lebih pendek:

@decorator_maker()
def decorated_function():
    print("I am the decorated function.")
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

#Eventually: 
decorated_function()    
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Hei, apa kamu lihat itu? Kami menggunakan panggilan fungsi dengan "@"sintaks! :-)

Jadi, kembali ke dekorator dengan argumen. Jika kita dapat menggunakan fungsi untuk menghasilkan dekorator dengan cepat, kita dapat meneruskan argumen ke fungsi itu, bukan?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

    def my_decorator(func):
        # The ability to pass arguments here is a gift from closures.
        # If you are not comfortable with closures, you can assume it’s ok,
        # or read: https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
        print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

        # Don't confuse decorator arguments and function arguments!
        def wrapped(function_arg1, function_arg2) :
            print("I am the wrapper around the decorated function.\n"
                  "I can access all the variables\n"
                  "\t- from the decorator: {0} {1}\n"
                  "\t- from the function call: {2} {3}\n"
                  "Then I can pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)

        return wrapped

    return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print("I am the decorated function and only knows about my arguments: {0}"
           " {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments("Rajesh", "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Sheldon 
#   - from the function call: Rajesh Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

Ini dia: dekorator dengan argumen. Argumen dapat ditetapkan sebagai variabel:

c1 = "Penny"
c2 = "Leslie"

@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print("I am the decorated function and only knows about my arguments:"
           " {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Penny 
#   - from the function call: Leslie Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only know about my arguments: Leslie Howard

Seperti yang Anda lihat, Anda dapat memberikan argumen kepada dekorator seperti fungsi apa pun yang menggunakan trik ini. Anda bahkan dapat menggunakannya *args, **kwargs jika Anda ingin. Tapi ingat dekorator dipanggil hanya sekali. Tepat ketika Python mengimpor skrip. Anda tidak dapat secara dinamis mengatur argumen sesudahnya. Saat Anda melakukan "impor x", fungsinya sudah dihias, jadi kamu tidak bisa mengubah apa pun.


Mari berlatih: mendekorasi dekorator

Oke, sebagai bonus, saya akan memberikan Anda potongan untuk membuat dekorator menerima setiap argumen secara umum. Setelah semua, untuk menerima argumen, kami menciptakan dekorator kami menggunakan fungsi lain.

Kami membungkus dekorator.

Ada hal lain yang kita lihat baru-baru ini yang membungkus fungsi?

Oh ya, dekorator!

Mari bersenang-senang dan menulis dekorator untuk para dekorator:

def decorator_with_args(decorator_to_enhance):
    """ 
    This function is supposed to be used as a decorator.
    It must decorate an other function, that is intended to be used as a decorator.
    Take a cup of coffee.
    It will allow any decorator to accept an arbitrary number of arguments,
    saving you the headache to remember how to do that every time.
    """

    # We use the same trick we did to pass arguments
    def decorator_maker(*args, **kwargs):

        # We create on the fly a decorator that accepts only a function
        # but keeps the passed arguments from the maker.
        def decorator_wrapper(func):

            # We return the result of the original decorator, which, after all, 
            # IS JUST AN ORDINARY FUNCTION (which returns a function).
            # Only pitfall: the decorator must have this specific signature or it won't work:
            return decorator_to_enhance(func, *args, **kwargs)

        return decorator_wrapper

    return decorator_maker

Ini dapat digunakan sebagai berikut:

# You create the function you will use as a decorator. And stick a decorator on it :-)
# Don't forget, the signature is "decorator(func, *args, **kwargs)"
@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print("Decorated with {0} {1}".format(args, kwargs))
        return func(function_arg1, function_arg2)
    return wrapper

# Then you decorate the functions you wish with your brand new decorated decorator.

@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print("Hello {0} {1}".format(function_arg1, function_arg2))

decorated_function("Universe and", "everything")
#outputs:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

# Whoooot!

Saya tahu, terakhir kali Anda memiliki perasaan ini, itu setelah mendengarkan seseorang berkata: "sebelum memahami rekursi, Anda harus memahami rekursi terlebih dahulu". Tapi sekarang, bukankah Anda merasa nyaman dalam menguasai ini?


Praktik terbaik: dekorator

  • Dekorator diperkenalkan dengan Python 2.4, jadi pastikan kode Anda akan berjalan di> = 2.4.
  • Dekorator memperlambat panggilan fungsi. Ingat itu.
  • Anda tidak dapat menghapus fungsi. (Sana adalah peretasan untuk membuat dekorator yang dapat dihapus, tetapi tidak ada yang menggunakannya.) Jadi setelah fungsi didekorasi, itu dihiasi untuk semua kode.
  • Dekorator membungkus fungsi, yang dapat membuatnya sulit untuk didebug. (Ini menjadi lebih baik dari Python> = 2.5; lihat di bawah.)

Itu functools Modul diperkenalkan dengan Python 2.5. Ini termasuk fungsi functools.wraps(), yang menyalin nama, modul, dan dokumentasi fungsi yang dihias ke pembungkusnya.

(Fakta menarik: functools.wraps() adalah seorang dekorator! )

# For debugging, the stacktrace prints you the function __name__
def foo():
    print("foo")

print(foo.__name__)
#outputs: foo

# With a decorator, it gets messy    
def bar(func):
    def wrapper():
        print("bar")
        return func()
    return wrapper

@bar
def foo():
    print("foo")

print(foo.__name__)
#outputs: wrapper

# "functools" can help for that

import functools

def bar(func):
    # We say that "wrapper", is wrapping "func"
    # and the magic begins
    @functools.wraps(func)
    def wrapper():
        print("bar")
        return func()
    return wrapper

@bar
def foo():
    print("foo")

print(foo.__name__)
#outputs: foo

Bagaimana bisa para dekorator berguna?

Sekarang pertanyaan besar: Apa yang bisa saya gunakan dekorator untuk?

Tampak keren dan kuat, tetapi contoh praktisnya akan sangat bagus. Nah, ada 1000 kemungkinan. Penggunaan klasik memperluas perilaku fungsi dari lib eksternal (Anda tidak dapat memodifikasinya), atau untuk melakukan debug (Anda tidak ingin memodifikasinya karena sifatnya sementara).

Anda dapat menggunakannya untuk memperluas beberapa fungsi dengan cara KERING, seperti:

def benchmark(func):
    """
    A decorator that prints the time a function takes
    to execute.
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print("{0} {1}".format(func.__name__, time.clock()-t))
        return res
    return wrapper


def logging(func):
    """
    A decorator that logs the activity of the script.
    (it actually just prints it, but it could be logging!)
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print("{0} {1} {2}".format(func.__name__, args, kwargs))
        return res
    return wrapper


def counter(func):
    """
    A decorator that counts and prints the number of times a function has been executed
    """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print("{0} has been used: {1}x".format(func.__name__, wrapper.count))
        return res
    wrapper.count = 0
    return wrapper

@counter
@benchmark
@logging
def reverse_string(string):
    return str(reversed(string))

print(reverse_string("Able was I ere I saw Elba"))
print(reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!"))

#outputs:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x 
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

Tentu saja hal yang baik dengan dekorator adalah bahwa Anda dapat langsung menggunakannya pada hampir semua hal tanpa menulis ulang. KERING, saya berkata:

@counter
@benchmark
@logging
def get_random_futurama_quote():
    from urllib import urlopen
    result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read()
    try:
        value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0]
        return value.strip()
    except:
        return "No, I'm ... doesn't!"


print(get_random_futurama_quote())
print(get_random_futurama_quote())

#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

Python sendiri menyediakan beberapa dekorator: property, staticmethod, dll.

  • Django menggunakan dekorator untuk mengelola caching dan melihat izin.
  • Memutar untuk memalsukan panggilan fungsi asinkron.

Ini benar-benar taman bermain besar.


3805
2018-04-11 08:00



Atau, Anda bisa menulis fungsi pabrik yang mengembalikan dekorator yang membungkus nilai kembalian dari fungsi yang dihias dalam tag yang diteruskan ke fungsi pabrik. Sebagai contoh:

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

Ini memungkinkan Anda untuk menulis:

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

atau

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

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

Secara pribadi saya akan menulis dekorator dengan agak berbeda:

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

yang akan menghasilkan:

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

Jangan lupa konstruksi untuk sintaks dekorator adalah singkatan:

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

132
2017-10-25 06:18



Sepertinya orang lain sudah memberi tahu Anda cara menyelesaikan masalah. Saya harap ini akan membantu Anda memahami apa itu dekorator.

Dekorator hanyalah gula sintaksis.

Ini

@decorator
def func():
    ...

meluas ke

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

100
2018-04-11 07:19



Dan tentu saja Anda dapat mengembalikan lambdas juga dari fungsi dekorator:

def makebold(f): 
    return lambda: "<b>" + f() + "</b>"
def makeitalic(f): 
    return lambda: "<i>" + f() + "</i>"

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

print say()

59
2018-05-17 03:26



Dekorator Python menambahkan fungsi tambahan ke fungsi lain

Seorang dekorator huruf miring bisa seperti

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

Perhatikan bahwa fungsi didefinisikan di dalam fungsi. Apa yang pada dasarnya dilakukan adalah mengganti fungsi dengan yang baru didefinisikan. Sebagai contoh, saya memiliki kelas ini

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

Sekarang katakan, saya ingin kedua fungsi untuk mencetak "---" setelah dan sebelum selesai. Saya bisa menambahkan cetak "---" sebelum dan sesudah setiap pernyataan cetak. Tetapi karena saya tidak suka mengulang sendiri, saya akan membuat dekorator

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

Jadi sekarang saya bisa mengubah kelas saya menjadi

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

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

Untuk lebih lanjut tentang dekorator, periksa http://www.ibm.com/developerworks/linux/library/l-cpdecor.html


56
2017-12-26 06:13



Kamu bisa membuat dua dekorator terpisah yang melakukan apa yang Anda inginkan seperti yang digambarkan langsung di bawah ini. Perhatikan penggunaan *args, **kwargs dalam deklarasi wrapped() fungsi yang mendukung fungsi yang dihias memiliki beberapa argumen (yang tidak benar-benar diperlukan untuk contoh say() fungsi, tetapi termasuk untuk umum).

Untuk alasan yang sama, functools.wraps dekorator digunakan untuk mengubah atribut meta dari fungsi yang dibungkus menjadi orang-orang dari yang sedang didekorasi. Ini membuat pesan kesalahan dan dokumentasi fungsi tertanam (func.__doc__) menjadi orang-orang dari fungsi yang dihias, bukan 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>

Penyempitan

Seperti yang Anda lihat, ada banyak kode duplikat di dua dekorator ini. Mengingat kesamaan ini, akan lebih baik bagi Anda untuk membuat yang generik yang sebenarnya adalah a pabrik dekorator—dengan kata lain, dekorator yang membuat dekorator lain. Dengan cara itu akan ada pengulangan kode yang lebih sedikit — dan memungkinkan KERING prinsip yang harus diikuti.

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>

Untuk membuat kode lebih mudah dibaca, Anda dapat menetapkan nama yang lebih deskriptif ke dekorator yang dibuat oleh pabrik:

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>

atau bahkan menggabungkannya seperti ini:

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>

Efisiensi

Sementara contoh-contoh di atas melakukan semua pekerjaan, kode yang dihasilkan melibatkan jumlah overhead yang wajar dalam bentuk panggilan fungsi asing ketika beberapa dekorator diterapkan sekaligus. Ini mungkin tidak masalah, tergantung penggunaan yang tepat (yang mungkin I / O-terikat, misalnya).

Jika kecepatan fungsi yang dihias adalah penting, overhead dapat disimpan ke satu fungsi tambahan panggilan dengan menulis fungsi pabrik penghias yang sedikit berbeda yang mengimplementasikan menambahkan semua tag sekaligus, sehingga dapat menghasilkan kode yang menghindari panggilan fungsi tambahan yang terjadi dengan menggunakan dekorator terpisah untuk setiap tag.

Ini membutuhkan lebih banyak kode di dekorator itu sendiri, tetapi ini hanya berjalan ketika itu sedang di-appled ke definisi fungsi, bukan nanti ketika mereka sendiri dipanggil. Ini juga berlaku saat membuat nama yang lebih mudah dibaca dengan menggunakan lambda fungsi seperti yang diilustrasikan sebelumnya. Mencicipi:

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>

25
2017-12-03 18:09



Cara lain melakukan hal yang sama:

class bol(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<b>{}</b>".format(self.f())

class ita(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<i>{}</i>".format(self.f())

@bol
@ita
def sayhi():
  return 'hi'

Atau, lebih fleksibel:

class sty(object):
  def __init__(self, tag):
    self.tag = tag
  def __call__(self, f):
    def newf():
      return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
    return newf

@sty('b')
@sty('i')
def sayhi():
  return 'hi'

18
2017-07-26 16:11



Bagaimana saya bisa membuat dua dekorator dengan Python yang akan melakukan hal berikut?

Anda menginginkan fungsi berikut, ketika dipanggil:

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

Mengembalikan:

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

Solusi sederhana

Untuk melakukan hal ini, buatlah penghias yang mengembalikan lambdas (fungsi anonim) yang menutup fungsi (penutupan) dan menyebutnya:

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

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

Sekarang gunakan mereka seperti yang diinginkan:

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

dan sekarang:

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

Masalah dengan solusi sederhana

Tapi sepertinya kita hampir kehilangan fungsi aslinya.

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

Untuk menemukannya, kita perlu menggali ke dalam penutupan masing-masing lambda, salah satunya terkubur di sisi lain:

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

Jadi jika kita meletakkan dokumentasi pada fungsi ini, atau ingin dapat mendekorasi fungsi yang mengambil lebih dari satu argumen, atau kita hanya ingin tahu fungsi apa yang kita cari dalam sesi debugging, kita perlu melakukan sedikit lebih banyak dengan pembungkus.

Solusi berfitur lengkap - mengatasi sebagian besar masalah ini

Kami memiliki dekorator wraps dari functools modul di perpustakaan standar!

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

Sangat disayangkan masih ada beberapa boiler, tetapi ini sesederhana yang bisa kita lakukan.

Di Python 3, Anda juga mendapatkan __qualname__ dan __annotations__ ditugaskan secara default.

Jadi sekarang:

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

Dan sekarang:

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

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

Kesimpulan

Jadi kami melihatnya wraps membuat fungsi pembungkusan melakukan hampir semua hal kecuali memberi tahu kami apa fungsi yang diperlukan sebagai argumen.

Ada modul lain yang mungkin mencoba untuk mengatasi masalah, tetapi solusinya belum di perpustakaan standar.


15
2018-03-20 09:48