Decorators

Python types

  • int 
  • float
  • str
  • bool
  • None
  • complex
  1. Всяка стойност има тип. Дори функциите.
  2. Всяка стойност е обект от някакъв клас, включително функциите.
  3. Дори типовете са обекти и си имат тип!

Всичко има тип!

Функциите са обекти!

>>> class Panda: 
....    pass 
>>> pandarian = Panda()
>>> type(pandarian)
<class '__main__.Panda'>
>>> type(101)
<class 'int'>
>>> type("Programming 101")
<class 'str'>
>>> type(len)
<class 'builtin_function_or_method'>
>>> type(dir)
<class 'builtin_function_or_method'>

Тоест мога да си направя клас и неговите обекти да се държат като функции?

>>> class Panda:
...     def __call__(self):
...          return "You called e panda! Hello mister panda is here!"
... 
>>> pandarian = Panda()
>>> pandarian()
'You called e panda! Hello mister panda is here!'

__call__ различава функциите от нормалните обекти!

Функциите са обекти.

И обектите също могат да се държат като функции!

 

Дай да преговорим(1):

  1. Всичко е обект.
  2. Функциите са обекти.
  3. Функциите могат да връщат всякакви обекти.

Следователно...

Функциите могат да връщат други функции!

>>> def take_print():
...     print("I will return a print function!")
...     return print
... 
>>> my_print = take_print()
I will return a print function!
>>> type(my_print)
<class 'builtin_function_or_method'>
>>> my_print("Hello World!")
Hello World!

Област на видимост

  1. Всеки блок от код (функция) си има своя област на видимост, в която стоят локално дефинираните променливи!
     
  2. Ако една функция не може да намери дадена променлива в локалния си скоуп, търси в глобалния за променлива със същото име.

Примери:

>>> global_var = "Le me global"
>>> def much_function():
...     print(global_var)
... 
>>> much_function()
Le me global
>>> global_var = "Le me global"
>>> def much_function():
...     global_var = "Le me local"
...     print(global_var)
... 
>>> much_function()
Le me local

Влагане на функции

  1. Можем да дефинираме функция в функция

  2. Вложената функция вижда променливите в скоупа на обвиващата функция!
def get_kangaroo(size):
    print(size) # 50

    def get_baby_kangaroo():
        size = 10
        print(size) # 10

    get_baby_kangaroo()
    print(size) # 50

get_kangaroo(50)

Днес научихме (2):

  1. Функциите са обекти. Те могат да приемат за аргумент и да връщат други функции.
  2. Как работи скоупа на променливите.
  3. Можем да дефинираме функция в функция.

Така може да си правим Closures 

Closures:

Имаме closure, когато вложена функция достъпва променлива, дефинирана в обграждаща функция.
def get_quadratic_function(a, b, c):

    def quadratic_function(x):
        return a * x ** 2 + b * x + c

    return quadratic_function

func = get_quadratic_function(1, 2, 3)
print(func(1)) # 6
print(func(2)) # 11
print(func(3)) # 18

Проблем:

Ние сме огромен телеком и хората идват при нас за да си плащат телефона, интернета и ТВ.

def pay_net(months, user_id):
    print("{} payed Internet for {} months".format(user_id, months))
    save_to_database(months, user_id)


def pay_tv(months, user_id):
    print("{} payed TV for {} months".format(user_id, months))
    save_to_database(months, user_id)


def pay_phone(months, user_id):
    print("{} payed Phone for {} months".format(user_id, months))
    save_to_database(months, user_id)


pay_net(1, "ivaylo@hackbulgaria.com")
pay_phone(2, "radorado@hackbulgaria.com")
pay_tv(1, "toni@hackbulgaria.com")
Сещаме се, че трябва да поддържаме промоции. Плати X месеца получаваш 1 безплатен.

Решение: слагаме още 1 параметър!

def pay_net(months, user_id, promotion_months):
    if months >= promotion_months:
        months += 1

    print("{} payed Internet for {} months".format(user_id, months))
    save_to_database(months, user_id)


def pay_tv(months, user_id, promotion_months):
    if months >= promotion_months:
        months += 1

    print("{} payed TV for {} months".format(user_id, months))
    save_to_database(months, user_id)


def pay_phone(months, user_id, promotion_months):
    if months >= promotion_months:
        months += 1

    print("{} payed Phone for {} months".format(user_id, months))
    save_to_database(months, user_id)

pay_net(1, "ivaylo@hackbulgaria.com", 3)
pay_phone(2, "radorado@hackbulgaria.com", 2)
pay_tv(1, "toni@hackbulgaria.com", 2)

Проблеми:

  1. Всеки път ще трябва да се сещаме каква беше промоцията за конкретната услуга.
  2. Ако предлагахме 100 услуги във всички функции ли щяхме да бъркаме?

Използваме новите знания, за да "декорираме" функцията!

def get_promotion(func, promotion_months):
    def promotion(months, user_id):
        if months >= promotion_months:
            months += 1
        return func(months, user_id)
    return promotion


def pay_net(months, user_id):
    print("{} payed Internet for {} months".format(user_id, months))
    save_to_database(months, user_id)


def pay_tv(months, user_id):
    print("{} payed TV for {} months".format(user_id, months))
    save_to_database(months, user_id)


def pay_phone(months, user_id):
    print("{} payed Phone for {} months".format(user_id, months))
    save_to_database(months, user_id)

call_pay_net = get_promotion(pay_net, 1)
call_pay_phone = get_promotion(pay_phone, 3)
call_pay_tv = get_promotion(pay_tv, 2)

call_pay_net(1, "ivaylo@hackbulgaria.com")
call_pay_phone(2, "radorado@hackbulgaria.com")
call_pay_tv(1, "toni@hackbulgaria.com")
  • Малко странно се създават нашите функции
  • Ами ако някой забрави да ги декорира, къде отиват промоциите?
call_pay_net = get_promotion(pay_net, 1)
call_pay_phone = get_promotion(pay_phone, 3)
call_pay_tv = get_promotion(pay_tv, 2)

Python има синтаксис за това!

def get_promotion(func):
    def promotion(months, user_id):
        if months >= 3: # 3 is hardcoded
            months += 1
        return func(months, user_id)
    return promotion

@get_promotion
def pay_net(months, user_id):
    print("{} payed Internet for {} months".format(user_id, months))
    save_to_database(months, user_id)

pay_net(5, "radorado@hackbulgaria.com")

Можем и да подаваме аргументи на декораторите!

def get_promotion(promotion_months):
    def accepter(func):
        def decorated(months, user_id):
            if months >= promotion_months:
                months += 1
            return func(months, user_id)
        return decorated
    return accepter

@get_promotion(3)
def pay_net(months, user_id):
    print("{} payed Internet for {} months".format(user_id, months))
    save_to_database(months, user_id)

pay_net(5, "radorado@hackbulgaria.com")

Декоратори

  • изпълняват се преди декорираната функция
  • променят поведението на фунцията, без да се променя тя вътрешно
  • събират обща функционалност за различни функции

Можем и повече от един декоратор!

Първо се извиква най-вътрешния декоратор!

@notify_via_email
@get_promotion(3)
def pay_net(months, user_id):
    print("{} payed Internet for {} months".format(user_id, months))
    save_to_database(months, user_id)

Декоратори в нашия живот!

Decorators 101 v5

By Hack Bulgaria

Decorators 101 v5

  • 1,461