ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 26.10.2023
Просмотров: 57
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Также в конструкции try – except могут присутствовать необязательные секции:
Else – её тело будет выполнено, если код в секции try выполнился и не сгенерировал исключение.
Finally – её тело будет вызвано в любом случае, даже если выло вызвано и обработано исключение.
def divide(num, denom):
try:
result = num / denom
except ZeroDivisionError as e:
print(f"Ошибка деления на ноль: {e}")
result = None
except Exception as e:
print(f"Выполнение завершилось с ошибкой: {e}") # Может возникнуть, например, если попытаться поделить значения разных типов (строку на число и т.п.)
result = None
else:
print("Операция выполнена успешно")
finally:
return result
Вызов исключения, raise
Можно намеренно вызывать исключения. Иногда это может быт полезно. Например, если вы написали функцию, которая ожидает параметры строго определённого типа. И если кто-то вызвал вашу функцию передав значения иных типов, то может быть будет лучше вызвать исключение, чтобы предотвратить получение непредсказуемого результата.
class DifferentTypesException(Exception):
pass
def custom_sum(a, b):
if type(a) is not type(b):
raise DifferentTypesException
if a is None:
raise Exception("Нельзя складывать None")
return a + b
custom_sum(4, 4.0) # будет вызвано исключение, так как типы аргументов int и float
custom_sum(None, None) # будет вызвано исключение, несмотря на то что аргументы одного типа, но они None
custom_sum(4, 4) # вернёт корректный результат
Функции
Анонимные
# Функция, которая возводит число в квадрат и возвращает
def power_2(value):
return value ** 2
# Функция map() проходит по каждому числу из списка и передаёт его в функцию power_2, результат добавляет к новой последовательности
powered = map(power_2, [1, 2, 3])
# Аналогично предыдущему примеру, только в качестве обработчика используется не именованая функция, а лямбда-функция (функция без имени), составленная на лету
powered_too = map(lambda value: value ** 2, [1, 2, 3])
# Обе полученные последовательности приводим к списковому типу и сравниваем, в обоих списках получили одинаковый результат
assert(list(powered) == list(powered_too))
Именованные
Описание функции начинается с ключевого слова def. Если функция должна вернуть какой-то результат в точку вызова используется ключевое слово return.
Передавать аргументы функции можно позиционно (по порядку), либо явно указывая имя входного аргумента. Разрешается часть аргументов передавать позиционно, а часть по имени, но в этом случае сначала должны следовать все позиционные аргументы, а потом именованные (чередовать нельзя).
def is_in_range(a, b, c):
return a <= b <= c
is_in_range(1, 2, 10) # передача аргументов по позиции a=1, b=2, c=10
is_in_range(c=1, b=2, a=10) # передача аргументов по имени a=10, b=2, c=1
is_in_range(1, 2, c=10) # a и b переданы по позиции, c - по имени
#is_in_range(1, c=2, 10) # ошибка, нельзя передавать аргументы по позиции после передачи по имени
В функции могут быть определены значения аргументов по-умолчанию, тогда эти аргументы можно не передавать при вызове.
def is_in_range(a, b, c=10):
return a <= b <= c
is_in_range(1, 2)
Позиционные аргументы можно сворачивать в список (*args), а именованные в словарь (**kwargs). Такие аргументы называются вараргами. Имена вараргов могут быть произвольными, но обычно args и kwargs.
def foo(a, b, *args, **kwargs): # Необязательные аргументы можно сгруппировать в список используя * (позиционные аргументы), и в словарь используя ** (именованные аргументы)
print(a, b)
for arg in args:
print(f"arg {arg}")
for k, v in kwargs.items():
print(f"kwarg: {k} {v}")
foo(1, 22, 35, 14, var1=-2, var2=0) # 1, 22 - обязательные аргументы, 35, 14 - необязательные позиционные, var1=-2, var2=0 - необязательные именованные
unnamed = [3, 15, 10, 20]
named = {'key1': 105, 'key2': 106}
foo('8', '18', *unnamed, **named) # Используя * и ** с именем списка или словаря соответственно, можно передать их значения как отдельные позиционные и именованные аргументы
Декораторы
import time
def profile(function):
def wrapper():
init_time = time.time()
function()
finish_time = time.time()
print(f"Время выполнения функции {function}: {finish_time - init_time}")
return wrapper
@profile
def target():
time.sleep(2)
print("Готово")
target()
Функция profie – декоратор. Функция target продекорирована функцией profile.
При вызове функции target сначала вызывается функция profile, а функция target передаётся в качестве аргумента.
Функция profile определяет функцию wrapper, в которой вызывается полученная в аргументе функция и замеряется время её выполнения. Функция profile возвращает функцию (вызываемый объект) wrapper.
В результате в точке вызова функции target вызовется функция wrapper, которая определена в функции-декораторе profile, которая в свою очередь выполнит саму функцию target замерив время её выполнения.
Декораторы используются для расширения возможности функций и методов классов без необходимости их изменения.
Это лишь базовый пример. Можно использовать декораторы для обёртки функций, принимающих параметры, а также декораторы, которые сами принимаю параметры.
Классы
Описание класса и создание экземпляра
class CustomType:
pass
my_obj = CustomType() # my_obj - объект класса CustomType
Магические методы
Рассмотрим только некоторые
__init__(), __repr__()
class CustomType:
def __init__(self, a, b):
# Конструктор класса
self.a = a
self.b = b
def __repr__(self):
# Возвращает строковое представление экземпляра класса
return f"{self.a} : {self.b}"
my_obj = CustomType(4, 8)
print(my_obj)
__add__(), __sub__()… и другие арифметические операции
class CustomType:
def __init__(self, a, b):
# Конструктор класса
self.a = a
self.b = b
def __repr__(self):
# Возвращает строковое представление экземпляра класса
return f"{self.a} : {self.b}"
def __add__(self, other):
# Сумма экземпляров класса
# В данной реализации возвращает новый объект, атрибуты которого равны сумме атрибутов исходных объектов
return CustomType(self.a + other.a, self.b + other.b)
def __sub__(self, other):
# Разница экземпляров класса
# В данной реализации возвращает новый объект, атрибуты которого равны разнице атрибутов исходных объектов
return CustomType(self.a - other.a, self.b - other.b)
my_obj1 = CustomType(4, 8)
my_obj2 = CustomType(3, 6)
my_obj3 = my_obj1 + my_obj2
my_obj4 = my_obj1 - my_obj2
print(my_obj1) # 4 : 8
print(my_obj2) # 3 : 6
print(my_obj3) # 7 : 14
print(my_obj4) # 1 : 2
__enter__(), __exit__()
class CustomType:
def __init__(self, a, b):
# Конструктор класса
self.a = a
self.b = b
def __repr__(self):
# Возвращает строковое представление экземпляра класса
return f"{self.a} : {self.b}"
def __enter__(self):
# Выполняется при входе в контекст объекта через конструкцию with ...
print(f"Вход в контекст объекта {self}")
return self
def __exit__(self, *args):
# Выполняется при выходе из конструкции with ...
print(f"Выход из контекста объекта {self}")
my_obj1 = CustomType(4, 8)
with my_obj1 as o:
print(f"Код в контексте объекта {o}")
print("Завершение работы с менеджером контекста")
__iter__(), __next__()
class CustomType:
def __init__(self, a, b, c, d):
# Конструктор класса
self.a = a
self.b = b
self.c = c
self.d = d
def __iter__(self):
# Инициализация итератора
# Вызывается на старте цикла for или при передаче объекта в качестве аргумента функции iter()
self.attrs = [
self.a,
self.b,
self.c,
self.d
]
self.attrs_len = 4
self.current_attr = -1
return self
def __next__(self):
# Переход к следующей итерации
# Вызывается на каждой итерации цикла for или при передаче итератора в качестве аргумента функции next()
# Требует вызова исключения StopIteration для завершения
self.current_attr += 1
if self.current_attr < self.attrs_len:
return self.attrs[self.current_attr]
else:
raise StopIteration
my_obj1 = CustomType(4, 8, 3, 9)
for attr in my_obj1:
print(attr)
iterator = iter(my_obj1)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
__call__()
Определение данного метода позволяет создать класс вызываемых объектов (функторов), которые по своему поведению напоминают обычные функции.
class CustomType:
def __init__(self, a, b):
self.a = a
self.b = b
def __call__(self, param1, param2):
self.a += param1
self.b += param2
obj = CustomType(4, 5)
print(obj.a, obj.b) # a=4, b=5
obj(3, 4) # a=4+3 b=5+4
print(obj.a, obj.b) # a=7, b=9
В данном примере магический метод __call__ ожидает 2 аргумента, и первый будет добавлен к полю «a», а второй к полю «b». Поэтому после вызова объекта (функтора) obj значения его полей увеличатся на величину переданных аргументов.
Инкапсуляция, договорённости
Атрибуты класса, начинающиеся с нижнего подчёркивания _ считаются protected, с двойного подчёркивания __ private.
В случае protected атрибутов допускается обращение к ним в пределах самого класса или наследников. Однако это работает на уровне договорённости, то есть физически допускается обращение извне, но нежелательно.
В случае private атрибутов обращение извне ограничивается интерпретатором, однако и это ограничение можно обойти используя обращение вида «объект._Класс__атрибут» (опять же, использование нежелательно).
class CustomType:
def __init__(self, a):
self._a = a # protected атрибут
def __hello(self):
return "Hello"
my_object = CustomType(42)
print(my_object._a) # выведет значение защищённого атрибута, но такое обращение нежелательно
#print(my_object.__hello()) # выведет ошибку, если раскомментировать
print(my_object._CustomType__hello())
Полиморфизм, утиная типизация
Полиморфизм в Python основан на утиной типизации. Термин «утиная типизация» (также неявная типизация) произошёл от цитаты «Если это выглядит как утка, плавает как утка и крякает как утка, то это, возможно, и есть утка».
Если несколько классов содержат методы с одинаковыми именами, но реализуют их по-разному, эти классы являются полиморфными. Функция сможет оценить эти полиморфные методы, не зная, какой класс она вызывает.
class Chick:
def tweet(self):
print("Цип-цип")
class Сuckoo:
def tweet(self):
print("Ку-ку")
class Sparrow:
def tweet(self):
print("Чирик-чирик")
# список объектов абсолютно несвязанных классов, однако имеющих метод с одним и тем же именем
for bird in [Chick(), Сuckoo(), Sparrow()]:
bird.tweet()
Наследование, MRO, переопределение методов и вызов методов базовых классов
В Python 3 проблема ромбовидного наследования решается путём использования алгоритма поиска в ширину в процессе определения порядка разрешения методов (MRO – method resolution order)
class A:
def foo(self):
print("A")
class B(A):
pass
class C(A):
def foo(self):
print("C")
class D(B, C):
pass
d = D() # "C"
d.foo()
print(D.__mro__) # D, B, C, A
В Python 2, не найдя метод foo() в классе D и B, был бы вызван метод foo() класса A, а не C. Таким образом, в Python 2 использовался метод поиска в глубину.
Атрибут __mro__ позволяет точно определить порядок разрешения методов.
Так же при переопределении методов в классах-наследниках допускается использовать реализацию из базового класса, расширяя её.
import time
class Progression:
def calculate(self, start, end, step):
while start < end:
time.sleep(0.1)
start += step
return start
class ProfiledProgression(Progression):
def calculate(self, *args):
init_time = time.time()
# Вызов метода базового класса
result = super().calculate(*args)
return result, f"Время выполнения: {time.time() - init_time}"
p = Progression()
pp = ProfiledProgression()
print(p.calculate(1, 10, 2))
print(pp.calculate(1, 10, 2)) # выведет тот же результат, а также время выполнения
Генераторы
Генераторы – это функции, которые позволяют генерировать и возвращать значения на лету, не храня их в памяти (в виде списков и других). Особенностью этих функций является возможность возвращать значения при этом, чтобы функция не завершилась, а могла продолжить работу (resumable). Для этого используется ключевое слово yield вместо return.
def gen_even(n):
counter = 0
while counter < n:
yield counter # yield возвращает значение, однако функция не завершается как это было бы с return, а ожидает следующей итерации (вызова функции next())
counter += 2
for even_number in gen_even(11): # цикл for неявно вызывает iter(gen_even(11)) в начале, а затем next() на каждой итерации
print(even_number)
# Ручной проход по генератору
generator = gen_even(11)
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator)) # ошибка - больше нет значений для генерации, условие цикла перестало проходить, генератор "выдохся" (exhausted)
Генераторные выражения (generator expression)
Пример аналогичный предыдущему. Генератор, который генерирует чётные числа, только записанный не в виде функции, а в виде однострочного генераторного выражения
for even_number in (x for x in range(11) if x % 2 == 0):
print(even_number)
generator = (x for x in range(11) if x % 2 == 0)
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
Списковые выражения (list comprehension, dict comprehansion)
Пример аналогичный предыдущему, только краткая запись сразу формирует результат в памяти в виде списка. Запись похожа на генераторное выражение, только заключается в квадратные скобки:
even_numbers = [x for x in range(11) if x % 2 == 0]
print(even_numbers) # [0, 2, 4, 6, 8, 10]
Так же списковые выражения могут использоваться для формирования множеств и словарей. Выражение в этом случае заключается в фигурные скобки:
even_numbers = {x for x in range(11) if x % 2 == 0}
print(even_numbers) # {0, 2, 4, 6, 8, 10}
dict_with_prefixes = {"ключ_" + k : v for k, v in {"один": 1, "два": 2, "три": 3}.items()}