Објашњено (са примерима и случајевима коришћења)

Питхон декоратери су невероватно корисна конструкција у Питхону. Користећи декораторе у Питхон-у, можемо да изменимо понашање функције тако што ћемо је умотати у другу функцију. Декоратери нам омогућавају да пишемо чистији код и делимо функционалност. Овај чланак је водич не само о томе како користити декоратере већ и како их креирати.

Предуслов знања

Тема декоратера у Питхон-у захтева неко основно знање. У наставку сам навео неке концепте са којима би већ требало да будете упознати да бисте разумели овај водич. Такође сам повезао ресурсе на којима можете да дочарате концепте ако је потребно.

Основни Питхон

Ова тема је средња/напреднија тема. Као резултат тога, пре него што покушате да научите, требало би да сте већ упознати са основама Питхон-а, као што су типови података, функције, објекти и класе.

Такође би требало да разумете неке објектно оријентисане концепте као што су геттери, сеттери и конструктори. Ако нисте упознати са програмским језиком Питхон, ево неких ресурса за почетак.

Функције су грађани првог реда

Поред основног Питхона, требало би да будете свесни и овог напреднијег концепта у Питхону. Функције, и скоро све остало у Питхон-у, су објекти попут инт или стринг. Пошто су они објекти, можете да урадите неколико ствари са њима, наиме:

  • Можете проследити функцију као аргумент другој функцији на исти начин на који проследите стринг или инт као аргумент функције.
  • Функције такође могу да врате друге функције као што бисте вратили друге стринг или инт вредности.
  • Функције се могу чувати у променљивим

У ствари, једина разлика између функционалних објеката и других објеката је што функционални објекти садрже магични метод __цалл__().

Надамо се да сте у овом тренутку задовољни предусловним знањем. Можемо да почнемо да разговарамо о главној теми.

Шта је Питхон декоратер?

Питхон декоратор је једноставно функција која узима функцију као аргумент и враћа модификовану верзију функције која је прослеђена. Другим речима, функција фоо је декоратер ако као аргумент узима функцијску траку и враћа другу функцију баз.

Функција баз је модификација бара у смислу да унутар тела баз постоји позив функцијске траке. Међутим, пре и после позива у бар, баз може све. То је била пуна уста; ево неког кода да илуструје ситуацију:

# 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

Како направити декоратер у Питхон-у?

Да бих илустровао како се декоратори креирају и користе у Питхон-у, илустроваћу ово једноставним примером. У овом примеру, креираћемо функцију декоратера дневника која ће евидентирати име функције коју украшава сваки пут када се та функција покрене.

  Како пронаћи некога на ЛинкедИн-у по адреси е-поште

За почетак, креирали смо функцију декоратера. Декоратер узима функцију као аргумент. фунц је функција коју украшавамо.

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

Унутар функције декоратера, креираћемо нашу модификовану функцију која ће евидентирати име фунц пре покретања фунц.

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

Затим, функција цреате_логгер ће вратити измењену функцију. Као резултат, цела наша функција цреате_логгер ће изгледати овако:

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

    return modified_function

Завршили смо са креирањем декоратера. Функција цреате_логгер је једноставан пример функције декоратера. Узима фунц, што је функција коју украшавамо, и враћа другу функцију, модифиед_фунц. модифиед_фунц прво евидентира име фунц, пре него што покрене фунц.

Како користити декоратере у Питхон-у

Да бисмо користили наш декоратер, користимо @ синтаксу на следећи начин:

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

Сада можемо позвати саи_хелло() у нашој скрипти, а излаз би требао бити следећи текст:

Calling:  say_hello
"Hello, World"

Али шта ради @цреате_логгер? Па, то је примена декоратора на нашу саи_хелло функцију. Да бисмо боље разумели шта се ради, код одмах испод овог пасуса би постигао исти резултат као стављање @цреате_логгер испред саи_хелло.

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

say_hello = create_logger(say_hello)

Другим речима, један од начина да се користе декоратори у Питхон-у је да експлицитно позовете декоратор који пролази у функцији као што смо урадили у коду изнад. Други и сажетији начин је употреба @ синтаксе.

У овом одељку смо покрили како да креирамо Питхон декоратере.

Мало компликованији примери

Горњи пример је био једноставан случај. Постоје мало сложенији примери као што је када функција коју украшавамо узима аргументе. Друга сложенија ситуација је када желите да украсите цео разред. Овде ћу покрити обе ове ситуације.

Када функција узме аргументе

Када функција коју украшавате узме аргументе, измењена функција треба да прими аргументе и да их проследи када на крају упути позив неизмењеној функцији. Ако то звучи збуњујуће, дозволите ми да објасним у фоо-бар терминима.

Подсетимо се да је фоо функција декорације, бар је функција коју украшавамо, а баз је украшена трака. У том случају, бар ће узети аргументе и проследити их баз током позива на баз. Ево примера кода за учвршћивање концепта:

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

Ако *аргс и **кваргс изгледају непознато; они су једноставно показивачи на аргументе позиције и кључне речи, респективно.

Важно је напоменути да баз има приступ аргументима и стога може да изврши проверу аргумената пре него што позове бар.

Пример би био да имамо функцију декоратера, осигуравају_стринг која би обезбедила да је аргумент који се прослеђује функцији коју украшава стринг; ми бисмо то имплементирали овако:

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

Могли бисмо да украсимо саи_хелло функцију овако:

@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

Као што се и очекивало, сценарио је успео да одштампа ‘Хелло Јохн’ јер је ‘Јохн’ стринг. Избацио је изузетак када је покушао да одштампа „Здраво 3“ јер „3“ није био стринг. Декоратор суре_стринг може се користити за валидацију аргумената било које функције која захтева стринг.

  Како да видите сопствене лајкове на Твитеру

Декорисање часа

Осим што само украшавамо функције, можемо украсити и часове. Када додате декоратор у класу, декорисани метод замењује метод конструктора/иницијатора класе(__инит__).

Да се ​​вратимо на фоо-бар, претпоставимо да је фоо наш декоратер, а Бар класа коју украшавамо, онда ће фоо украсити Бар.__инит__. Ово ће бити корисно ако желимо било шта да урадимо пре него што се инстанцирају објекти типа Бар.

То значи да следећи код

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

У ствари, инстанцирање објекта класе Бар, дефинисаног коришћењем било које од две методе, требало би да вам пружи исти излаз:

Doing some stuff before instantiation
In initiator

Примери декоратера у Питхон-у

Иако можете да дефинишете сопствене декоратере, постоје неки који су већ уграђени у Питхон. Ево неких од уобичајених декоратера на које можете наићи у Питхон-у:

@статицметход

Статичка метода се користи за класу да укаже да је метода коју украшава статичка метода. Статичке методе су методе које се могу покренути без потребе за инстанцирањем класе. У следећем примеру кода креирамо класу Дог са статичким методом барк.

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!

Као што видите, први метод је чистији и очигледније је да је функција статична функција пре него што сте уопште почели да читате код. Као резултат тога, за преостале примере, користићу први метод. Али само запамтите да је друга метода алтернатива.

@цлассметход

Овај декоратор се користи да означи да је метод који украшава метод класе. Методе класе су сличне статичким методама по томе што обе не захтевају да се класа инстанцира пре него што се могу позвати.

  Како омогућити или онемогућити Алваис он Дисплаи у иПхоне 14 Про

Међутим, главна разлика је у томе што методе класе имају приступ атрибутима класе док статичке методе немају. То је зато што Питхон аутоматски прослеђује класу као први аргумент методу класе кад год се она позове. Да бисмо креирали метод класе у Питхон-у, можемо да користимо декоратор цлассметход.

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

@проперти.сеттер

Декоратор проперти.сеттер се користи за креирање методе постављања за наша својства. Да бисте користили декоратор @проперти.сеттер, замените својство именом својства, за које креирате сеттер. На пример, ако креирате сеттер за методу за својство фоо, ваш декоратер ће бити @фоо.сеттер. Ево примера пса за илустрацију:

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

Значај декоратера у Питхон-у

Сада када смо покрили шта су декоратери и видели сте неке примере декоратера, можемо да разговарамо о томе зашто су декоратери важни у Питхон-у. Декоратери су важни из више разлога. Неке од њих, навео сам у наставку:

  • Они омогућавају поновну употребу кода: У горе наведеном примеру евидентирања, могли бисмо да користимо @цреате_логгер на било којој функцији коју желимо. Ово нам омогућава да додамо функционалност евидентирања свим нашим функцијама без ручног писања за сваку функцију.
  • Они вам омогућавају да пишете модуларни код: Опет, враћајући се на пример евидентирања, са декоратерима, можете одвојити основну функцију, у овом случају саи_хелло од друге функционалности која вам је потребна, у овом случају, евидентирања.
  • Они побољшавају оквире и библиотеке: Декоратори се у великој мери користе у Питхон оквирима и библиотекама како би обезбедили додатну функционалност. На пример, у веб оквирима као што су Фласк или Дјанго, декоратори се користе за дефинисање рута, руковање аутентификацијом или примену међувера на одређене погледе.

Завршне речи

Декоратери су невероватно корисни; можете их користити за проширење функција без промене њихове функционалности. Ово је корисно када желите да мерите перформансе функција, евидентирате сваки пут када се функција позове, потврдите аргументе пре позивања функције или проверите дозволе пре покретања функције. Једном када разумете декоратере, моћи ћете да пишете код на чистији начин.

Следеће, можда ћете желети да прочитате наше чланке о торкама и коришћењу цУРЛ-а у Питхон-у.