Διαδρομές
/
Python
Python
/
Ύλη
/
Διακοσμητές
Δι

Διακοσμητές σε Python

{one: "1 άσκηση", other: "%{count} ασκήσεις"}

Σχετικά με την έννοια Διακοσμητές

Οι decorators είναι συναρτήσεις που παίρνουν μια άλλη συνάρτηση ως όρισμα με σκοπό να επεκτείνουν ή να αντικαταστήσουν τη συμπεριφορά της συνάρτησης που δέχονται. Αν η συνάρτηση A είναι ένας decorator και η συνάρτηση B είναι το όρισμά του, τότε η συνάρτηση A τροποποιεί, επεκτείνει ή αντικαθιστά τη συμπεριφορά της συνάρτησης B χωρίς να τροποποιεί τον κώδικα της συνάρτησης B. Λέμε ότι η συνάρτηση decorator A τυλίγει τη συνάρτηση B. Αν και μιλάμε για "τροποποίηση" της συμπεριφοράς, η συνάρτηση που τυλίγεται δεν αλλάζει στην πραγματικότητα. Η συμπεριφορά είτε προστίθεται γύρω από τη συνάρτηση που τυλίγεται (και γύρω από αυτό που επιστρέφει), είτε η συμπεριφορά της συνάρτησης που τυλίγεται υποκαθίσταται από κάποια άλλη συμπεριφορά.

Ένας decorator είναι συνάρτηση ανώτερης τάξης

Μια συνάρτηση ανώτερης τάξης είναι μια συνάρτηση που δέχεται μία ή περισσότερες συναρτήσεις ως ορίσματα ή/και επιστρέφει μία ή περισσότερες συναρτήσεις. Μια συνάρτηση που χρησιμοποιείται ως όρισμα ή επιστρέφεται από άλλη συνάρτηση είναι συνάρτηση πρώτης κλάσης. Μια συνάρτηση Python, ως αντικείμενο που μπορεί να κληθεί, είναι συνάρτηση πρώτης κλάσης που μπορεί να αποθηκευτεί σε μια μεταβλητή ή να χρησιμοποιηθεί ως όρισμα, όπως ακριβώς οποιαδήποτε άλλη τιμή ή αντικείμενο. Οι συναρτήσεις ανώτερης τάξης και οι συναρτήσεις πρώτης κλάσης συνεργάζονται για να κάνουν τους decorators εφικτούς.

Πώς μοιάζει η χρήση ενός decorator

Το σύμβολο @ μπαίνει μπροστά από το όνομα της συνάρτησης decorator και τοποθετείται ακριβώς πάνω από τη συνάρτηση που θέλουμε να διακοσμήσουμε, ως εξής:

@decorator
def decorated_function():
    pass

Κάποιοι decorators δέχονται ορίσματα:

@decorator_with_arg(name="Bob")
def decorated_function2():
    pass

Αν ένας decorator έχει προεπιλεγμένα ορίσματα, πρέπει να χρησιμοποιήσεις παρενθέσεις στην κλήση @decorator() για να λειτουργήσει, όπως ακριβώς θα έκανες καλώντας οποιαδήποτε συνάρτηση:

@decorator_with_default_arg()
def decorated_function3():
    pass

Αν ένας decorator παίρνει ορίσματα θέσης, το να μην τα δώσεις θα προκαλέσει ένα σφάλμα που θα μοιάζει κάπως έτσι:

TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'

Η σύνταξη @decorator είναι συντακτική ζάχαρη, ή αλλιώς μια συντομογραφία, για την κλήση της συνάρτησης που διακοσμεί και τη μεταβίβαση της συνάρτησης που διακοσμείται σε αυτήν ως όρισμα. Ακολουθούν παραδείγματα εναλλακτικών τρόπων για να καλέσεις έναν decorator:

def function():
    pass

function = decorator(function)


def function2():
    pass

function2 = decorator_with_arg(name="Bob")(function2)


def function3():
    pass

function3 = decorator_with_default_arg()(function3)

Γράφοντας έναν απλό decorator

Οι περισσότεροι decorators έχουν σκοπό να επεκτείνουν ή να αντικαταστήσουν τη συμπεριφορά μιας άλλης συνάρτησης, αλλά κάποιοι μπορεί να μην κάνουν τίποτα άλλο από το να επιστρέφουν τις συναρτήσεις που τυλίγουν.

Οι decorators είναι συναρτήσεις που παίρνουν τουλάχιστον ένα όρισμα: τη συνάρτηση που τυλίγουν. Συνήθως επιστρέφουν είτε τη συνάρτηση που τυλίγουν είτε το αποτέλεσμα μιας έκφρασης που χρησιμοποιεί τη συνάρτηση που τυλίγουν.

Ένας απλός decorator, δηλαδή ένας που απλώς επιστρέφει τη συνάρτηση που τυλίγει, μπορεί να γραφτεί ως εξής:

>>> def do_nothing(func):
...     return func
...
... @do_nothing
... def function4():
...     return 4
...
>>> print(function4())
4

Ένας decorator μπορεί απλώς να προσθέτει παρενέργειες, όπως επιπλέον πληροφορίες που χρησιμοποιούνται για την καταγραφή:

>>> def my_logger(func):
...     print(f"Entering {func.__name__}")
...     return func
...
... @my_logger
... def my_func():
...     print("Hello")
...
>>> my_func()
Entering my_func
Hello

Ένας decorator δεν επιστρέφει τον εαυτό του. Μπορεί να επιστρέψει τα ορίσματα συναρτήσεων που δέχτηκε, μια άλλη συνάρτηση ή μία ή περισσότερες τιμές που αντικαθιστούν την τιμή επιστροφής της συνάρτησης που δέχτηκε ή που διακόσμησε. Αν ένας decorator επιστρέφει μια άλλη συνάρτηση, συνήθως θα επιστρέφει μια εσωτερική συνάρτηση.

Εσωτερικές συναρτήσεις

Μια συνάρτηση μπορεί να οριστεί μέσα σε μια άλλη συνάρτηση. Μια τέτοια εμφωλευμένη συνάρτηση ονομάζεται εσωτερική συνάρτηση. Ένας decorator μπορεί να χρησιμοποιήσει μια εσωτερική συνάρτηση για να τυλίξει το όρισμα συνάρτησης που δέχτηκε. Στη συνέχεια, ο decorator επιστρέφει την εσωτερική του συνάρτηση. Η εσωτερική συνάρτηση μπορεί μετά να επιστρέψει το αρχικό όρισμα συνάρτησης.

Ένας decorator επικύρωσης που χρησιμοποιεί εσωτερική συνάρτηση

Ακολουθεί ένα παράδειγμα ενός decorator που χρησιμοποιείται για επικύρωση:

def my_validator(func):
    def my_wrapper(world):
        print(f"Entering {func.__name__} with {world} argument")
        
        if world == "Pluto":
            print("Pluto is not a planet!")
        else:
            return func(world)
    return my_wrapper

@my_validator
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!

Στην πρώτη γραμμή έχουμε τον ορισμό του ορίσματος func του decorator. Στην επόμενη γραμμή βρίσκεται ο ορισμός της εσωτερικής συνάρτησης του decorator, η οποία τυλίγει το όρισμα func. Επειδή η εσωτερική συνάρτηση τυλίγει το όρισμα func του decorator, της περνάμε το ίδιο όρισμα που περνάμε και στο func. Πρόσεξε ότι ο wrapper δεν χρειάζεται να χρησιμοποιήσει το ίδιο όνομα για το όρισμα που ορίστηκε στο func. Η αρχική συνάρτηση χρησιμοποιεί το planet και ο decorator το world, και ο decorator δουλεύει κανονικά.

Η εσωτερική συνάρτηση είτε επιστρέφει το func είτε, αν world == "Pluto", τυπώνει ότι ο Πλούτωνας δεν είναι πλανήτης. Θα μπορούσε αντ' αυτού να είναι γραμμένη ώστε να πετάει ένα ValueError. Έτσι, η εσωτερική συνάρτηση τυλίγει το func και είτε επιστρέφει το func είτε κάνει κάτι που υποκαθιστά αυτό που θα έκανε το func. Ο decorator επιστρέφει την εσωτερική του συνάρτηση. Η εσωτερική συνάρτηση μπορεί να επιστρέψει ή να μην επιστρέψει την αρχική συνάρτηση που δέχτηκε. Ανάλογα με το ποιος κώδικας εκτελείται υπό συνθήκη στη συνάρτηση wrapper ή στην εσωτερική συνάρτηση, μπορεί να επιστραφεί το func, να πεταχτεί ένα σφάλμα ή να επιστραφεί μια τιμή του τύπου επιστροφής του func.

Διακοσμώντας μια συνάρτηση που δέχεται οποιονδήποτε αριθμό ορισμάτων

Μπορούμε να γράψουμε decorators για συναρτήσεις που δέχονται οποιονδήποτε αριθμό ορισμάτων, χρησιμοποιώντας τη σύνταξη *args και **kwargs.

Ακολουθεί ένα παράδειγμα ενός decorator για μια συνάρτηση που δέχεται οποιονδήποτε αριθμό ορισμάτων:

def double(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) * 2
    return wrapper

@double
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 18

print(add(2, 3, 4, 5, 6))
#-> 40

Αυτό λειτουργεί για τον διπλασιασμό της τιμής επιστροφής της συνάρτησης-ορίσματος. Αν θέλουμε να τριπλασιάσουμε, να τετραπλασιάσουμε κ.λπ. την τιμή επιστροφής, μπορούμε να προσθέσουμε μια παράμετρο στον ίδιο τον decorator, όπως δείχνουμε στην επόμενη ενότητα.

Decorators που έχουν τις δικές τους παραμέτρους

Ακολουθεί ένα παράδειγμα ενός decorator που μπορεί να ρυθμιστεί ώστε να πολλαπλασιάζει την τιμή επιστροφής της διακοσμημένης συνάρτησης με έναν οποιονδήποτε αριθμό:

def multi(factor=1):
     if factor == 0:
        raise ValueError("factor must not be 0")

     def outer_wrapper(func):
         def inner_wrapper(*args, **kwargs):
             return func(*args, **kwargs) * factor
         return inner_wrapper
     return outer_wrapper

@multi(factor=3)
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 27

print(add(2, 3, 4, 5, 6))
#-> 60

Οι πρώτες γραμμές επικυρώνουν ότι το factor δεν είναι 0. Στη συνέχεια ορίζεται ο εξωτερικός wrapper. Αυτός έχει την ίδια υπογραφή που περιμένουμε για έναν decorator χωρίς παραμέτρους. Ο εξωτερικός wrapper έχει μια εσωτερική συνάρτηση με την ίδια υπογραφή με την αρχική συνάρτηση. Ο εσωτερικός wrapper κάνει τη δουλειά του πολλαπλασιασμού της τιμής που επιστρέφει η αρχική συνάρτηση με το όρισμα που περνάμε στον decorator. Ο εξωτερικός wrapper επιστρέφει τον εσωτερικό wrapper και ο decorator επιστρέφει τον εξωτερικό wrapper.

Ακολουθεί ένα παράδειγμα ενός decorator με παραμέτρους που ελέγχει αν θα επικυρώσει το όρισμα που περνάμε στην αρχική συνάρτηση:

def check_for_pluto(check=True):
    def my_validator(func):
        def my_wrapper(world):
            print(f"Entering {func.__name__} with {world} argument")
            if check and world == "Pluto":
                print("Pluto is not a planet!")
            else:
                return func(world)
        
        return my_wrapper
    return my_validator

@check_for_pluto(check=False)
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!

Αυτό επιτρέπει να εναλλάσσεις εύκολα μεταξύ του ελέγχου για τον Pluto ή όχι, και γίνεται χωρίς να χρειάζεται να τροποποιήσεις τη my_func.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα