Последняя тема этой лекции - декораторы. Само слово декоратор подсказывает, что речь идет о каких-то дополнительных деталях. Есть спектакль и декорации к нему. Полагается, что декорации могут способствовать пониманию смысла спектакля, суть которого передает текст. Само слово "decoration" в переводе с английского означает "украшать, награждать кого-либо орденом".
Декорирование достаточно широко применяется в программировании. В ООП используются паттерны (образцы) программирования, позволяющие для решения типовой проблемы строить код, следуя образец.
В языке Python декорирование применяется при работе с функциями и классами. Многие встроенные функции являются декорированными. Зачастую, целесообразно строить декоратор и для собственных функций. Попробуем разобраться, что означает декорирование и как строятся декораторы. Как обычно я постараюсь привести примеры, проясняющие суть дела.
Рассмотрим следующую ситуацию. Пусть в созданном нами модуле или классе есть несколько функций $$f_1, f_2, \dots f_n$$. В ходе эксплуатации или проектирования выяснилось, что этим функциям нужно добавить некоторую общую функциональность. Как справиться с этой задачей? Простое решение - добавить соответствующий код в каждую из функций. Такое решение нарушает два важных принципа программирования:
Если у нас есть работающая функция f и мы хотим добавить функциональность или изменить ее интерфейс, то один из способов - это построение новой функции g, представляющей обертку функции f:
$$g = wrapper(f)$$Приведу простой уже встречавшийся пример построения обертки. Для сортировки массива строился рекурсивный метод QSort(a, start, finish), имеющий три аргумента -сортируемый массив и два аргумента, задающие интервал сортировки. Исходно требуется сортировать массив, поэтому целесообразно построить нерекурсивную обертку рекурсивного метода:
def QuickSort(a):
QSort(a, 0, len(a))
У обертки в данном случае проще интерфейс - один аргумент, а не три. Все, что делает обертка, - это вызов рекурсивной функции.
Конечно, когда требуется добавить функциональность, обертка может быть сложнее и не сводится к простому вызову декорируемnой функции.
Какое же решение предлагает язык Python для решения возникшей у нас проблемы - добавления новой функциональности функциям $$f_1, f_2, \dots f_n$$? Python позволяет построить декоратор, который добавляет новую функциональность.
Давайте рассмотрим, как строится декоратор. Декоратор Python - это функция, аргументом которой является декорируемая функция. Заголовок декоратора, следовательно имеет вид:
def decor(func):
""" Декоратор """
Здесь d?cor - имя декоратора, func - имя декорируемой функции.
В теле декоратора строится функция-обертка декорируемой функции, которая и возвращается в качестве результата работы декоратора. Так что шаблон декоратора имеет вид:
def decor(func):
""" Декоратор """
def wrapper(*args, **kwargs):
""" функция обертка """
… код обертки …
return wrapper
Следуя этому шаблону, можно построить конкретный декоратор, задав соответствующий код обертки декорируемой функции. Заметьте, при построении функции-обертки ее аргументы совпадают с аргументами декорируемой функции.
Два важных момента, связанных с декораторами:
Декоратор - это функция, но декоратор не вызывается обычным способом. Вместо вызова декоратора используется декорирование функции. Синтаксически это выглядит так:
@decor def Expances10(goal = 'health'):
Заголовку декорируемой функции предшествует строка с именем декоратора, начинающаяся специальным символом @.
Перейдем теперь к примерам построения декораторов и их применения. Начнем с простого модельного примера. Пусть мы строим проект управления финансами некоторой семьи, планирующей свой бюджет (такие семьи теоретически существуют). В модуле планирования бюджета есть метод income, позволяющий пополнять бюджет, и три метода Expances, осуществляющих расходы на различные цели.
Начнем строить код нашего модуля:
""" Модуль: Планирование бюджета семьи """ sum = 100 #Выделенная сумма
Здесь sum - атрибут, задающий бюджет, объявлен на глобальном уровне и доступен всем методам модуля. Приведу теперь методы, ведающие расходами и доходами бюджета:
def Income(inc):
global sum
sum += inc
@decor
def Expances10(goal = 'health'):
global sum
res = 0.1 * sum
sum -= res
#сумма res используется для укрепления здоровья
@decor
def Expances30(goal = 'education'):
global sum
res = 0.3 * sum
sum -= res
#сумма res используется на образование
@decor
def Expances50(goal = 'family'):
global sum
res = 0.5 * sum
sum -= res
#сумма res используется для удовлетворения потребностей членов семьи
Расходные методы декорированы. Но пока будем полагать, что декорирование отсутствует. Тогда каждый вызов расходного метода будет тратить некоторую часть бюджета на свои цели, каким бы скудным не был бюджет. Представьте, что для семьи наступили трудные времена, например, из-за короновируса. Решили добавить, возможно временно, новую функциональность, ограничивающую возможность расходовать средства, если свободный бюджет ниже заданной границы limit.
В текст модуля добавили глобальный аргумент:
limit = 75 #ограничитель расходов.
Для добавления функциональности построили декоратор d?cor и декорировали им расходные функции. Вот код декоратора d?cor:
def decor(func):
""" Декоратор процедур
вызывает декорируемую процедуру,
если таможня дает добро
"""
def wrapper(*args, **kwargs):
if sum <= limit:
print ("Превышен лимит")
else: func(*args, **kwargs)
return wrapper
Декоратор построен в полном соответствии с приведенным выше шаблоном. Обертка в данном случае достаточно проста. Она не разрешает вызов метода, если выполняется ограничение на текущий размер бюджета.
Обратите внимание на то, как обеспечивается эквивалентность списков аргументов функции обертки и декорируемой функции. При вызове декорируемой функции используется форма вызова аргументов со звездочками:
Построим тест, проверяющий работу декорируемых функций:
def test1():
sum1 = sum
Expances10()
if sum1 > sum:
print("потрачено на здоровье - ", sum1 - sum )
sum1 = sum
Expances30()
if sum1 > sum:
print("потрачено на образование - ", sum1 - sum )
sum1 = sum
Expances50()
if sum1 > sum:
print("потрачено на семью - ", sum1 - sum )
При sum = 100 и limit = 75 этот тест дает следующие результаты:
На лекарства и образование денег хватило, а на покупки по запросам членов семьи денег в бюджете недостаточно.
В нашем примере расходные методы реализованы как процедуры, изменяющие значение глобального атрибута sum. Как изменится декоратор, если декорируемый метод является функцией, возвращающей значение. Давайте построим такой вариант расходных методов:
@decor1
def Expan10(goal = 'health'):
global sum
res = 0.1 * sum
sum -= res
return res
@decor1
def Expan30(goal = 'education'):
global sum
res = 0.3 * sum
sum -= res
return res
@decor1
def Expan50(goal = 'family'):
global sum
res = 0.5 * sum
sum -= res
return res
Эти функции декорированы другим декоратором decor1. Вот его текст:
def decor1(func):
""" Декоратор функций:
возвращает результат вызова декорируемой функцию,
если таможня дает добро,
иначе возвращает ноль
"""
def wrapper(*args, **kwargs):
if sum <= limit:
return 0
else: return func(*args, **kwargs)
return wrapper
Отличия от предыдущего варианта минимальны. По сути, добавлены операторы return, возвращающие значение функции, как для декорируемой функции, так и для обертки. Такой вариант предпочтительнее, поскольку позволяет отказаться от вывода сообщения о возникшей ситуации непосредственно в теле функции обертки. Функция, как и положено, возвращает некоторое значение, анализом которого занимается метод, вызывающий функцию.
Приведу тест, выполняющий проверку корректности работы новых декорированных функций:
def test2():
res =Expan50()
if res > 0:
print("потрачено на семью - ", res )
else:
print("Превышен лимит")
res = Expan10()
if res > 0:
print("потрачено на здоровье - ", res )
else:
print("Превышен лимит")
res = Expan30()
if res > 0:
print("потрачено на образование - ", res )
else:
print("Превышен лимит")
В этом тесте запросы членов семьи были удовлетворены, но на здоровье и образование денег уже не хватило.
Часто возникает потребность в построении декоратора с параметрами, что позволяет управлять декорированием в зависимости от декорируемой функции. Прямое добавление параметров декоратору невозможно. По правилам у декоратора может быть один и только один параметр - декорируемая функция. Однако существует простое решение этой проблемы. Достаточно построить обертку для декоратора. Обертывающая функция, не являясь декоратором, может иметь параметры, эти параметры доступны декоратору, встроенному в обертывающую функцию. Обертывающая функция возвращает в качестве результата встроенный в нее декоратор.
Давайте в нашем примере параметр limit зададим как параметр декоратора. Для этого построим обертку для декоратора decor1. Вот соответствующий код:
def decor2(limit = 75):
""" Обертка декоратора """
def decor1(func):
def wrapper(*args, **kwargs):
if sum <= limit:
return 0
else: return func(*args, **kwargs)
return wrapper
return decor1
Заметьте, теперь limit в обертке wrapper - это не глобальная переменная, а параметр функции decor2. Сама функция decor2 декоратором не является, но ее результат является декоратором. Поэтому при декорировании функции можно указывать имя этой функции в качестве декоратора и задавать параметр limit в момент декорирования.
Вот как выглядят теперь заголовки декорируемых функций в нашем примере:
@decor2(limit = 40) def Expan10(goal = 'health'): @decor2(limit = 50) def Expan30(goal = 'education'): @decor2() def Expan50(goal = 'family'):
Если запустить теперь тест 2, то при таком декорировании результаты будут следующими:
Иногда полезно при построении декоратора внутреннюю обертку wrapper декорировать встроенным декоратором wraps:
from functools import wraps @wraps def wrapper(*args, **kwargs):
Это не скажется на результатах работы, но может помочь при отладке, поскольку в этом случае функция-обертка содержит всю информацию о декорируемой функции.
Практически полезным декоратором, применимым к любой функции, является декоратор timer, позволяющий измерить время выполнения функции. Учитывая, что время измеряется с некоторой ошибкой и может быть сравнимо с величиной ошибки, полезно ввести параметр декоратора, позволяющий измерить время n -кратного выполнения функции. Приведу код такого декоратора, включая функцию обертки декоратора:
def timer(repeat = 1):
def decor_timer(func):
"""измеряет время работы функции
при вызове функции repeat раз"""
from time import time
def wrapper(*args, **kwargs):
start = time()
for i in range(repeat):
res = func(*args, **kwargs)
fin = time()
L = []
L.append(fin - start)
L.append(res)
return L
return wrapper
return decor_timer
Используем этот декоратор для выяснения того, насколько генератор работает быстрее классического кода. Построим два метода создания списка - с классическим кодом и с генератором:
@timer( repeat = 100)
def CreateList(n):
L = []
for num in range(n):
if (num % 3 == 0 and num % 5 == 0
and num % 13 == 0 and num % 31 == 0 and num % 2 != 0) :
L.append(num * num)
return L
@timer(repeat = 100)
def Gen_CreateList(n):
L = [num * num for num in range(n) if (num % 3 == 0 and num % 5 == 0
and num % 13 == 0 and num % 31 == 0 and num % 2 != 0)]
return L
Вот тест, запускающий эти методы на выполнение:
def test3():
res = CreateList(1000000)
print (res)
print()
print()
res = Gen_CreateList(1000000)
print (res)
Приведу результаты работы теста:
Что показывают эти результаты? Стократное создание списка с исходным списком в миллион элементов оба метода выполняют за время меньшее 6 секунд. Более важно, - рекламируемая эффективность генераторов в сравнении с классическим кодом не подтверждается результатами измерений.
Давайте проверим эффективность функций-генераторов. На прошлой лекции мы построили три варианта сложения матриц. Приведу код этих методов с небольшой вариацией:
@timer(repeat = 1)
def SumMatr(A, B):
"""Сложение квадратных матриц """
n = len(A)
LL = []
for row in range(n):
L = []
for col in range(n):
L.append(A[row][col] + B[row][col])
LL.append(L)
return LL
@timer(repeat = 1)
def GenSumMatr(A, B):
"""Сложение квадратных матриц.Генераторы списков"""
n = len(A)
L = [[A[row][col] + B[row][col]
for col in range(n)] for row in range(n)]
return L
def FunGenSumMatr(A, B):
"""
Сложение квадратных матриц. Функция-генератор
При каждом вызове возвращается
очередная строка суммарной матрицы.
Возвращаемое значение является генератором списка
"""
n = len(A)
for row in range(n):
yield [A[row][col] + B[row][col] for col in range(n)]
@timer(repeat = 1)
def ExprSumMatr(A, B):
"""Сложение квадратных матриц. Функция-генератор"""
n = len(A)
L = []
for value in FunGenSumMatr(A, B)
L.append(value)
return L
Первые два метода возвращают в качестве результата список, представляющий сумму матриц. Функция-генератор FunGenSumMatr выдает список, представляющий строку суммарной матрицы. Метод ExprSumMatr, создает конечный список по мере поступления строк.
Приведу тест, позволяющий проводить замеры времени работы этих трех методов:
def test4():
A = CreateMatr(1000)
B = CreateMatr(1000)
res = SumMatr(A, B)
print("Сложение матриц. Классический вариант: n = ", len(A),
' время выполнения = ', res[0])
res = GenSumMatr(A, B)
print("Сложение матриц. Генератор: n = ", len(A),
' время выполнения = ', res[0])
res = ExprSumMatr(A, B)
print("Сложение матриц. Функция-генератор: n = ", len(A),
' время выполнения = ', res[0])
Вот результаты работы этого теста:
Как видите, лучший вариант показывает вариант с построением генератора. Он на двадцать процентов эффективнее по времени классического варианта. Вариант с функцией-генератором работает немного медленнее простого генератора. Следует сказать, что преимущества функции генератора сказываются тогда, когда очередной элемент коллекции обрабатывается немедленно после его получения. Если же элементы собирать в список, как это делается в нашем примере, то тогда предпочтительнее обычный генератор.
В этом разделе анализ эффективности различных методов - это побочная цель. Главная цель - это демонстрация эффективности декорирования функций и практическая полезность декоратора timer. Как видите, декоратор, хотя и не награждает декорируемую функцию орденом, но придает ей дополнительную функциональность, не изменяя самой функции.
Декорировать можно не только функции. В языке Python есть возможность декорировать классы, добавляя к ним новые методы. Классы Python - это такие же объекты, как и прочие объекты, следовательно могут изменяться динамически. Язык Python позволяет работать с метаклассами, использующими декорирование классов. Эти инструменты используются в специальных сложных проектах и их рассмотрение выходит за пределы основ программирования.
Последняя тема этой лекции - декораторы. Само слово декоратор подсказывает, что речь идет о каких-то дополнительных деталях. Есть спектакль и декорации к нему. Полагается, что декорации могут способствовать пониманию смысла спектакля, суть которого передает текст. Само слово "decoration" в переводе с английского означает "украшать, награждать кого-либо орденом".
Декорирование достаточно широко применяется в программировании. В ООП используются паттерны (образцы) программирования, позволяющие для решения типовой проблемы строить код, следуя образец.
В языке Python декорирование применяется при работе с функциями и классами. Многие встроенные функции являются декорированными. Зачастую, целесообразно строить декоратор и для собственных функций. Попробуем разобраться, что означает декорирование и как строятся декораторы. Как обычно я постараюсь привести примеры, проясняющие суть дела.
Рассмотрим следующую ситуацию. Пусть в созданном нами модуле или классе есть несколько функций $$f_1, f_2, \dots f_n$$. В ходе эксплуатации или проектирования выяснилось, что этим функциям нужно добавить некоторую общую функциональность. Как справиться с этой задачей? Простое решение - добавить соответствующий код в каждую из функций. Такое решение нарушает два важных принципа программирования:
Если у нас есть работающая функция f и мы хотим добавить функциональность или изменить ее интерфейс, то один из способов - это построение новой функции g, представляющей обертку функции f:
$$g = wrapper(f)$$Приведу простой уже встречавшийся пример построения обертки. Для сортировки массива строился рекурсивный метод QSort(a, start, finish), имеющий три аргумента -сортируемый массив и два аргумента, задающие интервал сортировки. Исходно требуется сортировать массив, поэтому целесообразно построить нерекурсивную обертку рекурсивного метода:
def QuickSort(a):
QSort(a, 0, len(a))
У обертки в данном случае проще интерфейс - один аргумент, а не три. Все, что делает обертка, - это вызов рекурсивной функции.
Конечно, когда требуется добавить функциональность, обертка может быть сложнее и не сводится к простому вызову декорируемnой функции.
Какое же решение предлагает язык Python для решения возникшей у нас проблемы - добавления новой функциональности функциям $$f_1, f_2, \dots f_n$$? Python позволяет построить декоратор, который добавляет новую функциональность.
Давайте рассмотрим, как строится декоратор. Декоратор Python - это функция, аргументом которой является декорируемая функция. Заголовок декоратора, следовательно имеет вид:
def decor(func):
""" Декоратор """
Здесь d?cor - имя декоратора, func - имя декорируемой функции.
В теле декоратора строится функция-обертка декорируемой функции, которая и возвращается в качестве результата работы декоратора. Так что шаблон декоратора имеет вид:
def decor(func):
""" Декоратор """
def wrapper(*args, **kwargs):
""" функция обертка """
… код обертки …
return wrapper
Следуя этому шаблону, можно построить конкретный декоратор, задав соответствующий код обертки декорируемой функции. Заметьте, при построении функции-обертки ее аргументы совпадают с аргументами декорируемой функции.
Два важных момента, связанных с декораторами:
Декоратор - это функция, но декоратор не вызывается обычным способом. Вместо вызова декоратора используется декорирование функции. Синтаксически это выглядит так:
@decor def Expances10(goal = 'health'):
Заголовку декорируемой функции предшествует строка с именем декоратора, начинающаяся специальным символом @.
Перейдем теперь к примерам построения декораторов и их применения. Начнем с простого модельного примера. Пусть мы строим проект управления финансами некоторой семьи, планирующей свой бюджет (такие семьи теоретически существуют). В модуле планирования бюджета есть метод income, позволяющий пополнять бюджет, и три метода Expances, осуществляющих расходы на различные цели.
Начнем строить код нашего модуля:
""" Модуль: Планирование бюджета семьи """ sum = 100 #Выделенная сумма
Здесь sum - атрибут, задающий бюджет, объявлен на глобальном уровне и доступен всем методам модуля. Приведу теперь методы, ведающие расходами и доходами бюджета:
def Income(inc):
global sum
sum += inc
@decor
def Expances10(goal = 'health'):
global sum
res = 0.1 * sum
sum -= res
#сумма res используется для укрепления здоровья
@decor
def Expances30(goal = 'education'):
global sum
res = 0.3 * sum
sum -= res
#сумма res используется на образование
@decor
def Expances50(goal = 'family'):
global sum
res = 0.5 * sum
sum -= res
#сумма res используется для удовлетворения потребностей членов семьи
Расходные методы декорированы. Но пока будем полагать, что декорирование отсутствует. Тогда каждый вызов расходного метода будет тратить некоторую часть бюджета на свои цели, каким бы скудным не был бюджет. Представьте, что для семьи наступили трудные времена, например, из-за короновируса. Решили добавить, возможно временно, новую функциональность, ограничивающую возможность расходовать средства, если свободный бюджет ниже заданной границы limit.
В текст модуля добавили глобальный аргумент:
limit = 75 #ограничитель расходов.
Для добавления функциональности построили декоратор d?cor и декорировали им расходные функции. Вот код декоратора d?cor:
def decor(func):
""" Декоратор процедур
вызывает декорируемую процедуру,
если таможня дает добро
"""
def wrapper(*args, **kwargs):
if sum <= limit:
print ("Превышен лимит")
else: func(*args, **kwargs)
return wrapper
Декоратор построен в полном соответствии с приведенным выше шаблоном. Обертка в данном случае достаточно проста. Она не разрешает вызов метода, если выполняется ограничение на текущий размер бюджета.
Обратите внимание на то, как обеспечивается эквивалентность списков аргументов функции обертки и декорируемой функции. При вызове декорируемой функции используется форма вызова аргументов со звездочками:
Построим тест, проверяющий работу декорируемых функций:
def test1():
sum1 = sum
Expances10()
if sum1 > sum:
print("потрачено на здоровье - ", sum1 - sum )
sum1 = sum
Expances30()
if sum1 > sum:
print("потрачено на образование - ", sum1 - sum )
sum1 = sum
Expances50()
if sum1 > sum:
print("потрачено на семью - ", sum1 - sum )
При sum = 100 и limit = 75 этот тест дает следующие результаты:
На лекарства и образование денег хватило, а на покупки по запросам членов семьи денег в бюджете недостаточно.
В нашем примере расходные методы реализованы как процедуры, изменяющие значение глобального атрибута sum. Как изменится декоратор, если декорируемый метод является функцией, возвращающей значение. Давайте построим такой вариант расходных методов:
@decor1
def Expan10(goal = 'health'):
global sum
res = 0.1 * sum
sum -= res
return res
@decor1
def Expan30(goal = 'education'):
global sum
res = 0.3 * sum
sum -= res
return res
@decor1
def Expan50(goal = 'family'):
global sum
res = 0.5 * sum
sum -= res
return res
Эти функции декорированы другим декоратором decor1. Вот его текст:
def decor1(func):
""" Декоратор функций:
возвращает результат вызова декорируемой функцию,
если таможня дает добро,
иначе возвращает ноль
"""
def wrapper(*args, **kwargs):
if sum <= limit:
return 0
else: return func(*args, **kwargs)
return wrapper
Отличия от предыдущего варианта минимальны. По сути, добавлены операторы return, возвращающие значение функции, как для декорируемой функции, так и для обертки. Такой вариант предпочтительнее, поскольку позволяет отказаться от вывода сообщения о возникшей ситуации непосредственно в теле функции обертки. Функция, как и положено, возвращает некоторое значение, анализом которого занимается метод, вызывающий функцию.
Приведу тест, выполняющий проверку корректности работы новых декорированных функций:
def test2():
res =Expan50()
if res > 0:
print("потрачено на семью - ", res )
else:
print("Превышен лимит")
res = Expan10()
if res > 0:
print("потрачено на здоровье - ", res )
else:
print("Превышен лимит")
res = Expan30()
if res > 0:
print("потрачено на образование - ", res )
else:
print("Превышен лимит")
В этом тесте запросы членов семьи были удовлетворены, но на здоровье и образование денег уже не хватило.
Часто возникает потребность в построении декоратора с параметрами, что позволяет управлять декорированием в зависимости от декорируемой функции. Прямое добавление параметров декоратору невозможно. По правилам у декоратора может быть один и только один параметр - декорируемая функция. Однако существует простое решение этой проблемы. Достаточно построить обертку для декоратора. Обертывающая функция, не являясь декоратором, может иметь параметры, эти параметры доступны декоратору, встроенному в обертывающую функцию. Обертывающая функция возвращает в качестве результата встроенный в нее декоратор.
Давайте в нашем примере параметр limit зададим как параметр декоратора. Для этого построим обертку для декоратора decor1. Вот соответствующий код:
def decor2(limit = 75):
""" Обертка декоратора """
def decor1(func):
def wrapper(*args, **kwargs):
if sum <= limit:
return 0
else: return func(*args, **kwargs)
return wrapper
return decor1
Заметьте, теперь limit в обертке wrapper - это не глобальная переменная, а параметр функции decor2. Сама функция decor2 декоратором не является, но ее результат является декоратором. Поэтому при декорировании функции можно указывать имя этой функции в качестве декоратора и задавать параметр limit в момент декорирования.
Вот как выглядят теперь заголовки декорируемых функций в нашем примере:
@decor2(limit = 40) def Expan10(goal = 'health'): @decor2(limit = 50) def Expan30(goal = 'education'): @decor2() def Expan50(goal = 'family'):
Если запустить теперь тест 2, то при таком декорировании результаты будут следующими:
Иногда полезно при построении декоратора внутреннюю обертку wrapper декорировать встроенным декоратором wraps:
from functools import wraps @wraps def wrapper(*args, **kwargs):
Это не скажется на результатах работы, но может помочь при отладке, поскольку в этом случае функция-обертка содержит всю информацию о декорируемой функции.
Практически полезным декоратором, применимым к любой функции, является декоратор timer, позволяющий измерить время выполнения функции. Учитывая, что время измеряется с некоторой ошибкой и может быть сравнимо с величиной ошибки, полезно ввести параметр декоратора, позволяющий измерить время n -кратного выполнения функции. Приведу код такого декоратора, включая функцию обертки декоратора:
def timer(repeat = 1):
def decor_timer(func):
"""измеряет время работы функции
при вызове функции repeat раз"""
from time import time
def wrapper(*args, **kwargs):
start = time()
for i in range(repeat):
res = func(*args, **kwargs)
fin = time()
L = []
L.append(fin - start)
L.append(res)
return L
return wrapper
return decor_timer
Используем этот декоратор для выяснения того, насколько генератор работает быстрее классического кода. Построим два метода создания списка - с классическим кодом и с генератором:
@timer( repeat = 100)
def CreateList(n):
L = []
for num in range(n):
if (num % 3 == 0 and num % 5 == 0
and num % 13 == 0 and num % 31 == 0 and num % 2 != 0) :
L.append(num * num)
return L
@timer(repeat = 100)
def Gen_CreateList(n):
L = [num * num for num in range(n) if (num % 3 == 0 and num % 5 == 0
and num % 13 == 0 and num % 31 == 0 and num % 2 != 0)]
return L
Вот тест, запускающий эти методы на выполнение:
def test3():
res = CreateList(1000000)
print (res)
print()
print()
res = Gen_CreateList(1000000)
print (res)
Приведу результаты работы теста:
Что показывают эти результаты? Стократное создание списка с исходным списком в миллион элементов оба метода выполняют за время меньшее 6 секунд. Более важно, - рекламируемая эффективность генераторов в сравнении с классическим кодом не подтверждается результатами измерений.
Давайте проверим эффективность функций-генераторов. На прошлой лекции мы построили три варианта сложения матриц. Приведу код этих методов с небольшой вариацией:
@timer(repeat = 1)
def SumMatr(A, B):
"""Сложение квадратных матриц """
n = len(A)
LL = []
for row in range(n):
L = []
for col in range(n):
L.append(A[row][col] + B[row][col])
LL.append(L)
return LL
@timer(repeat = 1)
def GenSumMatr(A, B):
"""Сложение квадратных матриц.Генераторы списков"""
n = len(A)
L = [[A[row][col] + B[row][col]
for col in range(n)] for row in range(n)]
return L
def FunGenSumMatr(A, B):
"""
Сложение квадратных матриц. Функция-генератор
При каждом вызове возвращается
очередная строка суммарной матрицы.
Возвращаемое значение является генератором списка
"""
n = len(A)
for row in range(n):
yield [A[row][col] + B[row][col] for col in range(n)]
@timer(repeat = 1)
def ExprSumMatr(A, B):
"""Сложение квадратных матриц. Функция-генератор"""
n = len(A)
L = []
for value in FunGenSumMatr(A, B)
L.append(value)
return L
Первые два метода возвращают в качестве результата список, представляющий сумму матриц. Функция-генератор FunGenSumMatr выдает список, представляющий строку суммарной матрицы. Метод ExprSumMatr, создает конечный список по мере поступления строк.
Приведу тест, позволяющий проводить замеры времени работы этих трех методов:
def test4():
A = CreateMatr(1000)
B = CreateMatr(1000)
res = SumMatr(A, B)
print("Сложение матриц. Классический вариант: n = ", len(A),
' время выполнения = ', res[0])
res = GenSumMatr(A, B)
print("Сложение матриц. Генератор: n = ", len(A),
' время выполнения = ', res[0])
res = ExprSumMatr(A, B)
print("Сложение матриц. Функция-генератор: n = ", len(A),
' время выполнения = ', res[0])
Вот результаты работы этого теста:
Как видите, лучший вариант показывает вариант с построением генератора. Он на двадцать процентов эффективнее по времени классического варианта. Вариант с функцией-генератором работает немного медленнее простого генератора. Следует сказать, что преимущества функции генератора сказываются тогда, когда очередной элемент коллекции обрабатывается немедленно после его получения. Если же элементы собирать в список, как это делается в нашем примере, то тогда предпочтительнее обычный генератор.
В этом разделе анализ эффективности различных методов - это побочная цель. Главная цель - это демонстрация эффективности декорирования функций и практическая полезность декоратора timer. Как видите, декоратор, хотя и не награждает декорируемую функцию орденом, но придает ей дополнительную функциональность, не изменяя самой функции.
Декорировать можно не только функции. В языке Python есть возможность декорировать классы, добавляя к ним новые методы. Классы Python - это такие же объекты, как и прочие объекты, следовательно могут изменяться динамически. Язык Python позволяет работать с метаклассами, использующими декорирование классов. Эти инструменты используются в специальных сложных проектах и их рассмотрение выходит за пределы основ программирования.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.