Επεξήγηση (με παραδείγματα και περιπτώσεις χρήσης)

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

Προαπαιτούμενη γνώση

Το θέμα των διακοσμητών στην Python απαιτεί κάποιες βασικές γνώσεις. Παρακάτω, έχω παραθέσει ορισμένες έννοιες με τις οποίες θα πρέπει να γνωρίζετε ήδη για να κατανοήσετε αυτό το σεμινάριο. Έχω επίσης συνδέσει πόρους όπου μπορείτε να αναλύσετε τις έννοιες αν χρειαστεί.

Βασική Python

Αυτό το θέμα είναι ένα πιο ενδιάμεσο/προχωρημένο θέμα. Ως αποτέλεσμα, πριν επιχειρήσετε να μάθετε, θα πρέπει να είστε ήδη εξοικειωμένοι με τα βασικά της Python, όπως τύπους δεδομένων, συναρτήσεις, αντικείμενα και κλάσεις.

Θα πρέπει επίσης να κατανοήσετε ορισμένες αντικειμενοστρεφείς έννοιες όπως λήπτες, ρυθμιστές και κατασκευαστές. Εάν δεν είστε εξοικειωμένοι με τη γλώσσα προγραμματισμού Python, ακολουθούν ορισμένοι πόροι για να ξεκινήσετε.

Οι λειτουργίες είναι πολίτες πρώτης κατηγορίας

Εκτός από τη βασική Python, θα πρέπει επίσης να γνωρίζετε αυτήν την πιο προηγμένη έννοια στην Python. Οι συναρτήσεις, και σχεδόν οτιδήποτε άλλο στην Python, είναι αντικείμενα όπως το int ή η συμβολοσειρά. Επειδή είναι αντικείμενα, μπορείτε να κάνετε μερικά πράγματα με αυτά, συγκεκριμένα:

  • Μπορείτε να μεταβιβάσετε μια συνάρτηση ως όρισμα σε μια άλλη συνάρτηση με τον ίδιο τρόπο που μεταφέρετε μια συμβολοσειρά ή int ως όρισμα συνάρτησης.
  • Οι συναρτήσεις μπορούν επίσης να επιστραφούν από άλλες συναρτήσεις όπως θα επέστρεφες άλλες τιμές συμβολοσειράς ή int.
  • Οι συναρτήσεις μπορούν να αποθηκευτούν σε μεταβλητές

Στην πραγματικότητα, η μόνη διαφορά μεταξύ λειτουργικών αντικειμένων και άλλων αντικειμένων είναι ότι τα λειτουργικά αντικείμενα περιέχουν τη μαγική μέθοδο __call__().

Ας ελπίσουμε, σε αυτό το σημείο, να είστε άνετοι με την προαπαιτούμενη γνώση. Μπορούμε να αρχίσουμε να συζητάμε το κύριο θέμα.

Τι είναι το Python Decorator;

Ένας διακοσμητής Python είναι απλώς μια συνάρτηση που λαμβάνει μια συνάρτηση ως όρισμα και επιστρέφει μια τροποποιημένη έκδοση της συνάρτησης που μεταβιβάστηκε. Με άλλα λόγια, η συνάρτηση foo είναι διακοσμητής εάν λαμβάνει, ως όρισμα, τη γραμμή συνάρτησης και επιστρέφει μια άλλη συνάρτηση baz.

Η συνάρτηση baz είναι μια τροποποίηση της γραμμής με την έννοια ότι μέσα στο σώμα της baz, υπάρχει μια κλήση στη γραμμή συνάρτησης. Ωστόσο, πριν και μετά το call to bar, ο baz μπορεί να κάνει τα πάντα. Αυτό ήταν μια μπουκιά. εδώ είναι κάποιος κώδικας για να απεικονίσει την κατάσταση:

# Foo is a decorator, it takes in another function, bar as an argument
def foo(bar):

    # Here we create baz, a modified version of bar
    # baz will call bar but can do anything before and after the function call
    def baz():

        # Before calling bar, we print something
        print("Something")

        # Then we run bar by making a function call
        bar()

        # Then we print something else after running bar
        print("Something else")

    # Lastly, foo returns baz, a modified version of bar
    return baz

Πώς να δημιουργήσετε έναν διακοσμητή στην Python;

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

  Τι είναι καλύτερο μεταξύ Samsung και iPhone;

Για να ξεκινήσετε, δημιουργήσαμε τη λειτουργία διακοσμητή. Ο διακοσμητής παίρνει τη λειτουργία ως επιχείρημα. func είναι η λειτουργία που διακοσμούμε.

def create_logger(func):
    # The function body goes here

Μέσα στη συνάρτηση διακοσμητή, πρόκειται να δημιουργήσουμε την τροποποιημένη συνάρτησή μας που θα καταγράφει το όνομα του func πριν από την εκτέλεση του func.

# Inside create_logger
def modified_func():
    print("Calling: ", func.__name__)
    func()

Στη συνέχεια, η συνάρτηση create_logger θα επιστρέψει την τροποποιημένη συνάρτηση. Ως αποτέλεσμα, ολόκληρη η συνάρτηση create_logger θα μοιάζει με αυτό:

def create_logger(func):
    def modified_func():
        print("Calling: ", func.__name__)
        func()

    return modified_function

Τελειώσαμε με τη δημιουργία του διακοσμητή. Η συνάρτηση create_logger είναι ένα απλό παράδειγμα συνάρτησης διακοσμητή. Παίρνει τη λειτουργία, που είναι η συνάρτηση που διακοσμούμε, και επιστρέφει μια άλλη συνάρτηση, την modified_func. Το modified_func καταγράφει πρώτα το όνομα του func, πριν εκτελέσει το func.

Πώς να χρησιμοποιήσετε διακοσμητές στην Python

Για να χρησιμοποιήσουμε τον διακοσμητή μας, χρησιμοποιούμε τη σύνταξη @ ως εξής:

@create_logger
def say_hello():
    print("Hello, World!")

Τώρα μπορούμε να καλέσουμε την say_hello() στο σενάριό μας και η έξοδος θα πρέπει να είναι το ακόλουθο κείμενο:

Calling:  say_hello
"Hello, World"

Τι κάνει όμως το @create_logger; Λοιπόν, εφαρμόζουμε το διακοσμητή στη συνάρτηση say_hello. Για να κατανοήσετε καλύτερα τι κάνει, ο κώδικας ακριβώς κάτω από αυτήν την παράγραφο θα είχε το ίδιο αποτέλεσμα με το να βάλετε το @create_logger πριν το say_hello.

def say_hello():
    print("Hello, World!")

say_hello = create_logger(say_hello)

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

Σε αυτήν την ενότητα, καλύψαμε πώς να δημιουργήσετε διακοσμητές Python.

Λίγο πιο περίπλοκα παραδείγματα

Το παραπάνω παράδειγμα ήταν μια απλή περίπτωση. Υπάρχουν ελαφρώς πιο περίπλοκα παραδείγματα, όπως όταν η συνάρτηση που διακοσμούμε δέχεται ορίσματα. Μια άλλη πιο περίπλοκη κατάσταση είναι όταν θέλετε να διακοσμήσετε μια ολόκληρη τάξη. Θα καλύψω και τις δύο αυτές καταστάσεις εδώ.

Όταν η συνάρτηση δέχεται ορίσματα

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

Θυμηθείτε ότι το foo είναι η λειτουργία διακοσμητή, το μπαρ είναι η λειτουργία που διακοσμούμε και το baz είναι το διακοσμημένο μπαρ. Σε αυτήν την περίπτωση, η μπάρα θα λάβει τα επιχειρήματα και θα τα μεταβιβάσει στο baz κατά τη διάρκεια της κλήσης στο baz. Ακολουθεί ένα παράδειγμα κώδικα για την παγίωση της έννοιας:

def foo(bar):
    def baz(*args, **kwargs):
        # You can do something here
        ___
        # Then we make the call to bar, passing in args and kwargs
        bar(*args, **kwargs)
        # You can also do something here
        ___

    return baz

Εάν τα *args και **kwargs φαίνονται άγνωστα. Είναι απλώς δείκτες στα επιχειρήματα θέσης και λέξης-κλειδιού, αντίστοιχα.

Είναι σημαντικό να σημειωθεί ότι το baz έχει πρόσβαση στα ορίσματα και επομένως μπορεί να εκτελέσει κάποια επικύρωση των ορισμάτων πριν καλέσει τη γραμμή.

Ένα παράδειγμα θα ήταν αν είχαμε μια συνάρτηση διακοσμητή, sure_string που θα διασφάλιζε ότι το όρισμα που μεταβιβάζεται σε μια συνάρτηση που διακοσμεί είναι συμβολοσειρά. θα το εφαρμόσουμε ως εξής:

def ensure_string(func):
    def decorated_func(text):
        if type(text) is not str:
             raise TypeError('argument to ' + func.__name__ + ' must be a string.')
        else:
             func(text)

    return decorated_func

Θα μπορούσαμε να διακοσμήσουμε τη συνάρτηση say_hello ως εξής:

@ensure_string
def say_hello(name):
    print('Hello', name)

Στη συνέχεια, θα μπορούσαμε να δοκιμάσουμε τον κώδικα χρησιμοποιώντας αυτό:

say_hello('John') # Should run just fine
say_hello(3) # Should throw an exception

Και θα πρέπει να παράγει την ακόλουθη έξοδο:

Hello John
Traceback (most recent call last):
   File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception
   File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.')
TypeError: argument to say hello must be a string. $0

Όπως ήταν αναμενόμενο, το σενάριο κατάφερε να τυπώσει το «Hello John» γιατί το «John» είναι χορδή. Έριξε μια εξαίρεση όταν προσπάθησε να εκτυπώσει το “Hello 3” επειδή το “3” δεν ήταν συμβολοσειρά. Ο διακοσμητής sure_string θα μπορούσε να χρησιμοποιηθεί για την επικύρωση των ορισμάτων οποιασδήποτε συνάρτησης που απαιτεί συμβολοσειρά.

  Πώς να χρησιμοποιήσετε το νέο Bing με τεχνητή νοημοσύνη στο Chromebook σας

Διακόσμηση τάξης

Εκτός από απλώς διακοσμητικές λειτουργίες, μπορούμε επίσης να διακοσμήσουμε μαθήματα. Όταν προσθέτετε έναν διακοσμητή σε μια κλάση, η διακοσμημένη μέθοδος αντικαθιστά τη μέθοδο κατασκευής/εκκινητή της κλάσης (__init__).

Επιστρέφοντας στο foo-bar, ας υποθέσουμε ότι ο foo είναι ο διακοσμητής μας και το Bar είναι η τάξη που διακοσμούμε, τότε ο foo θα διακοσμήσει το Bar.__init__. Αυτό θα είναι χρήσιμο εάν θέλουμε να κάνουμε οτιδήποτε προτού δημιουργηθούν αντικείμενα του τύπου Bar.

Αυτό σημαίνει ότι ο παρακάτω κώδικας

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

@foo
class Bar:
    def __init__(self):
        print("In initiator")

Είναι ισοδύναμο με

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

class Bar:
    def __init__(self):
        print("In initiator")


Bar.__init__ = foo(Bar.__init__)

Στην πραγματικότητα, η δημιουργία ενός αντικειμένου της κλάσης Bar, που ορίζεται χρησιμοποιώντας μία από τις δύο μεθόδους, θα πρέπει να σας δώσει την ίδια έξοδο:

Doing some stuff before instantiation
In initiator

Παραδείγματα διακοσμητών σε Python

Ενώ μπορείτε να ορίσετε τους δικούς σας διακοσμητές, υπάρχουν μερικοί που είναι ήδη ενσωματωμένοι στην Python. Ακολουθούν μερικοί από τους συνηθισμένους διακοσμητές που μπορεί να συναντήσετε στην Python:

@staticmethod

Η στατική μέθοδος χρησιμοποιείται σε μια κλάση για να υποδείξει ότι η μέθοδος που διακοσμεί είναι μια στατική μέθοδος. Οι στατικές μέθοδοι είναι μέθοδοι που μπορούν να εκτελεστούν χωρίς την ανάγκη δημιουργίας στιγμιότυπου της κλάσης. Στο παρακάτω παράδειγμα κώδικα, δημιουργούμε μια κλάση Dog με στατική μέθοδο bark.

class Dog:
    @staticmethod
    def bark():
        print('Woof, woof!')

Τώρα μπορείτε να προσπελάσετε τη μέθοδο φλοιού ως εξής:

Dog.bark()

Και η εκτέλεση του κώδικα θα παράγει την ακόλουθη έξοδο:

Woof, woof!

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

class Dog:
    def bark():
        print('Woof, woof!')

Dog.bark = staticmethod(Dog.bark)

Και μπορούμε ακόμα να χρησιμοποιήσουμε τη μέθοδο του φλοιού με τον ίδιο τρόπο

Dog.bark()

Και θα παρήγαγε την ίδια παραγωγή

Woof, woof!

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

@classmethod

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

  Επεξήγηση των τύπων σύνδεσης κάρτας βίντεο

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

class Dog:
    @classmethod
    def what_are_you(cls):
        print("I am a " + cls.__name__ + "!")

Για να εκτελέσουμε τον κώδικα, απλώς καλούμε τη μέθοδο χωρίς να δημιουργήσουμε την κλάση:

Dog.what_are_you()

Και η έξοδος είναι:

I am a Dog!

@ιδιοκτησία

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

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

Τώρα μπορούμε να έχουμε πρόσβαση στο όνομα του σκύλου σαν μια κανονική ιδιοκτησία,

# Creating an instance of the class
foo = Dog('foo')

# Accessing the name property
print("The dog's name is:", foo.name)

Και το αποτέλεσμα της εκτέλεσης του κώδικα θα ήταν

The dog's name is: foo

@property.setter

Ο διακοσμητής property.setter χρησιμοποιείται για τη δημιουργία μιας μεθόδου setter για τις ιδιοκτησίες μας. Για να χρησιμοποιήσετε τον διακοσμητή @property.setter, αντικαθιστάτε την ιδιότητα με το όνομα της ιδιοκτησίας, δημιουργείτε ένα ρυθμιστή για. Για παράδειγμα, εάν δημιουργείτε ένα σέτερ για τη μέθοδο για το ακίνητο foo, ο διακοσμητής σας θα είναι @foo.setter. Ακολουθεί ένα παράδειγμα σκύλου για επεξήγηση:

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

    # Creating a setter for our name property
    @name.setter
    def name(self, new_name):
        self.__name = new_name

Για να δοκιμάσουμε τον ρυθμιστή, μπορούμε να χρησιμοποιήσουμε τον ακόλουθο κώδικα:

# Creating a new dog
foo = Dog('foo')

# Changing the dog's name
foo.name="bar"

# Printing the dog's name to the screen
print("The dog's new name is:", foo.name)

Η εκτέλεση του κώδικα θα παράγει την ακόλουθη έξοδο:

The dogs's new name is: bar

Η σημασία των διακοσμητών στην Python

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

  • Επιτρέπουν την επαναχρησιμοποίηση κώδικα: Στο παράδειγμα καταγραφής που δίνεται παραπάνω, θα μπορούσαμε να χρησιμοποιήσουμε το @create_logger σε οποιαδήποτε συνάρτηση θέλουμε. Αυτό μας επιτρέπει να προσθέτουμε λειτουργικότητα καταγραφής σε όλες τις λειτουργίες μας χωρίς να την γράφουμε με μη αυτόματο τρόπο για κάθε λειτουργία.
  • Σας επιτρέπουν να γράψετε αρθρωτό κώδικα: Και πάλι, επιστρέφοντας στο παράδειγμα καταγραφής, με τους διακοσμητές, μπορείτε να διαχωρίσετε τη βασική συνάρτηση, σε αυτήν την περίπτωση say_hello από την άλλη λειτουργικότητα που χρειάζεστε, σε αυτήν την περίπτωση, την καταγραφή.
  • Βελτιώνουν τα πλαίσια και τις βιβλιοθήκες: Οι διακοσμητές χρησιμοποιούνται ευρέως σε πλαίσια και βιβλιοθήκες Python για να παρέχουν πρόσθετη λειτουργικότητα. Για παράδειγμα, σε πλαίσια ιστού όπως το Flask ή το Django, οι διακοσμητές χρησιμοποιούνται για τον καθορισμό διαδρομών, τον χειρισμό ελέγχου ταυτότητας ή την εφαρμογή ενδιάμεσου λογισμικού σε συγκεκριμένες προβολές.

Τελικές Λέξεις

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

Στη συνέχεια, μπορεί να θέλετε να διαβάσετε τα άρθρα μας σχετικά με τις πλειάδες και τη χρήση του cURL στην Python.