- Разница 2 и 3 версии python
- ООП
- SOLID
- Что такое классы?
- Что такое магические методы?
- Что такое миксин?
- Что такое наследование классов и как его использовать?
- Что такое полиморфизм и как его реализовать в python?
- Как работает инкапсуляция в python?
- Как можно реализовать инкапсуляцию в Python, используя только публичные методы?
- Что такое дескрипторы и как они используются в Python?
- Что такое абстрактные классы и зачем они нужны в Python?
- Что такое множественное наследование и какие проблемы могут возникнуть при его использовании в Python?
- Как можно реализовать агрегацию объектов в Python?
- Что такое примеси (mixins) и как они используются в Python?
- Что такое композиция объектов и как ее можно использовать в Python?
- Типы и структуры данных в python
- Какие типы и структуры данных бывают в python?
- Что такое мутабельные и иммутабельные типы данных?
- Что может быть в качестве ключа словаря?
- Что такое хеш-функция?
- В чём особенность словаря в python?
- Списки, кортежи и множества в чём их отличие?
- Как работает механизм генераторов списков в python?
- Как работает механизм словарей в python?
- Что такое namedtuple в python и как его использовать?
- Как работает механизм множеств в python?
- Что такое графы и как их можно реализовать в Python?
- Какие типы данных в Python поддерживаются многопоточностью, и почему?
- Какие типы данных в Python поддерживаются многопроцессорностью, и почему?
- Что такое рекурсия и как она работает в Python?
- Что такое динамическое программирование и как оно используется в Python?
- Какие структуры данных в Python можно использовать для реализации очередей и стеков, и как они работают?
- Какие структуры данных в Python можно использовать для реализации графов, и как они работают?
- Какие структуры данных в Python можно использовать для реализации деревьев, и как они работают?
- Какие структуры данных в Python можно использовать для реализации куч (heap), и как они работают?
- Что такое "Big O notation", и как она используется для анализа производительности алгоритмов и структур данных в Python?
- GIL
- GC
- Итераторы, декораторы и генераторы
- Алгоритмы
В Python 2 print был оператором: print "Hello, world"
В Python 3 print - функция: print ("Hello, world")
В Python 2 были две функции: range - возвращает список; xrange - возвращает итератор
В Python 3 есть только функция range, и она возвращает итератор
В Python 2 при делении целых чисел возвращает целоче число
В Python 3 при делении целых чисел возвращает вещественное число
Магические методы
- Так как в 3 Питоне различий между строкой и юникодом больше нет,
__unicode__
исчез, а появился__bytes__
(который ведёт себя так же как__str__
и__unicode__
в 2.7) для новых встроенных функций построения байтовых массивов. - Так как деление в 3 Питоне теперь по-умолчанию «правильное деление»,
__div__
больше нет. __coerce__
больше нет, из-за избыточности и странного поведения.__cmp__
больше нет, из-за избыточности.__nonzero__
было переименовано в__bool__
.next
у итераторов был переименован в__next__
.
- SOLID
- Что такое классы?
- Магические методы
- Что такое миксин?
- Что такое наследование классов и как его использовать?
- Что такое полиморфизм и как его реализовать в python?
- Как работает инкапсуляция в python?
- Как можно реализовать инкапсуляцию в Python, используя только публичные методы?
- Что такое дескрипторы и как они используются в Python?
- Что такое абстрактные классы и зачем они нужны в Python?
- Что такое множественное наследование и какие проблемы могут возникнуть при его использовании в Python?
- Как можно реализовать агрегацию объектов в Python?
- Что такое примеси (mixins) и как они используются в Python?
- Что такое композиция объектов и как ее можно использовать в Python?
(наверх)
S - Принцип единственной ответственности (single responsibility principle)
Для каждого класса должно быть определено единственное назначение. Не должно возникать God object, который занимается всем в программе.
O - Принцип открытости/закрытости (open–closed principle)
«программные сущности … должны быть открыты для расширения, но закрыты для модификации». Мы должны иметь возможность добавлять функциональность.
L - Принцип подстановки Барбары Лисков (Liskov substitution principle)
«объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения
правильности выполнения программы». У объекта есть тип, но сами классы выстраиваются в иерархию классов. Подтип типа всю старую функциональность должен выполнять.
I - Принцип разделения интерфейса (interface segregation principle)
«много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс
общего назначения». Интерфейс - способ провзаимодействовать с какой-то программной действующей функцией.
D - Принцип инверсии зависимостей (dependency inversion principle) «Зависимость на Абстракциях. Нет зависимости на что-то конкретное».
(наверх)
Класс — тип, описывающий устройство объектов. Объект — это экземпляр класса.
class C:
pass
имя_объекта = имя_класса()
У класса может не быть тела.
Простейший пример класса:
class Rectangle:
default_color = "green" # статический атрибут
def __init__(self, width, height): # конструктор класса
self.width = width # динамический атрибут
self.height = height # динамический атрибут
В python нет возможности сделать несколько конструкторов.
(наверх)
Если какой-то идентификатор начинается с двух подчёркиваний, дальше пишется что-либо, потом снова два подчёркивания, то это спец метод.
Какие магические методы и для чего используются?
class FileObject:
def __init__(self, filepath='~', filename='sample.txt'): # Обёртка для файлового объекта, чтобы быть уверенным в том, что файл будет закрыт при удалении.
self.file = open(join(filepath, filename), 'r+') # Открыть файл filename в filepath в режиме чтения и записи
def __del__(self):
self.file.close()
del self.file
Метод, который будет вызван при инициализации объекта.
Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __init__
. __new__
используется весьма редко, но иногда бывает полезен, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) или строка.
__new__(cls, [...])
Инициализатор класса. Самый базовый магический метод, __init__
. С его помощью мы можем инициализировать объект.
__init__(self, [...])
Деструктор объекта
__del__
Самый базовый из методов сравнения __cmp__(self, other)
. Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). __cmp__
должен вернуть отрицательное число, если self < other
, ноль, если self == other
, и положительное число в случае self > other
. Но, обычно, лучше определить каждое сравнение, которое вам нужно, чем определять их всех в __cmp__
. Но __cmp__
может быть хорошим способом избежать повторений и увеличить ясность, когда все необходимые сравнения оперируют одним критерием.
-
__eq__(self, other)
Определяет поведение оператора равенства, ==. -
__ne__(self, other)
Определяет поведение оператора неравенства, !=. -
__lt__(self, other)
Определяет поведение оператора меньше, <. -
__gt__(self, other)
Определяет поведение оператора больше, >. -
__le__(self, other)
Определяет поведение оператора меньше или равно, <=. -
__ge__(self, other)
Определяет поведение оператора больше или равно, >=.
Унарные операторы и функции
Унарные операторы и функции имеют только один операнд — отрицание, абсолютное значение, и так далее.
-
__pos__(self)
Определяет поведение для унарного плюса(+some_object)
-
__neg__(self)
Определяет поведение для отрицания(-some_object)
-
__abs__(self)
Определяет поведение для встроенной функцииabs()
. -
__invert__(self)
Определяет поведение для инвертирования оператором ~. Для объяснения что он делает смотри статью в Википедии о бинарных операторах. -
__round__(self, n)
Определяет поведение для встроенной функцииround()
. n это число знаков после запятой, до которого округлить. -
__floor__(self)
Определяет поведение дляmath.floor()
, то есть, округления до ближайшего меньшего целого. -
__ceil__(self)
Определяет поведение дляmath.ceil()
, то есть, округления до ближайшего большего целого. -
__trunc__(self)
Определяет поведение дляmath.trunc()
, то есть, обрезания до целого.
Обычные арифметические операторы
-
__add__(self, other)
Сложение. -
__sub__(self, other)
Вычитание. -
__mul__(self, other)
Умножение. -
__floordiv__(self, other)
Целочисленное деление, оператор //. -
__div__(self, other)
Деление, оператор /. -
__truediv__(self, other)
Правильное деление. Заметьте, что это работает только когда используетсяfrom __future__ import division
. -
__mod__(self, other)
Остаток от деления, оператор %. -
__divmod__(self, other)
Определяет поведение для встроенной функции divmod(). -
__pow__
Возведение в степень, оператор **. -
__lshift__(self, other)
Двоичный сдвиг влево, оператор <<. -
__rshift__(self, other)
Двоичный сдвиг вправо, оператор >>. -
__and__(self, other)
Двоичное И, оператор &. -
__or__(self, other)
Двоичное ИЛИ, оператор |. -
__xor__(self, other)
Двоичный xor, оператор ^.
Магические методы преобразования типов
В Питоне множество магических методов, предназначенных для определения поведения для встроенных функций преобразования типов.
-
__int__(self)
Преобразование типа в int. -
__long__(self)
Преобразование типа в long. -
__float__(self)
Преобразование типа в float. -
__complex__(self)
Преобразование типа в комплексное число. -
__oct__(self)
Преобразование типа в восьмеричное число. -
__hex__(self)
Преобразование типа в шестнадцатеричное число. -
__index__(self)
Преобразование типа к int, когда объект используется в срезах (выражения вида[start:stop:step]
). Если вы определяете свой числовой тип, который может использоваться как индекс списка, вы должны определить__index__
. -
__trunc__(self)
Вызывается приmath.trunc(self)
. Должен вернуть своё значение, обрезанное до целочисленного типа (обычно long). -
__coerce__(self, other)
Метод для реализации арифметики с операндами разных типов.__coerce__
должен вернуть None если преобразование типов невозможно. Если преобразование возможно, он должен вернуть пару (кортеж из 2-х элементов) из self и other, преобразованные к одному типу.
Представление своих классов
Часто бывает полезно представление класса в виде строки. В Питоне существует несколько методов, которые вы можете определить для настройки поведения встроенных функций при представлении вашего класса.
-
__str__(self)
Определяет поведение функцииstr()
, вызванной для экземпляра вашего класса. -
__repr__(self)
Определяет поведение функцииrepr()
, вызванной для экземпляра вашего класса. Главное отличие отstr()
в целевой аудитории.repr()
больше предназначен для машинно-ориентированного вывода (более того, это часто должен быть валидный код на Питоне), аstr()
предназначен для чтения людьми. -
__unicode__(self)
Определяет поведение функцииunicode()
, вызванной для экземпляра вашего класса.unicode()
похож наstr()
, но возвращает строку в юникоде. Если клиент вызываетstr()
на экземпляре вашего класса, а вы определили только__unicode__()
, то это не будет работать. Постарайтесь всегда определять__str__()
для случая, когда кто-то не имеет такой роскоши как юникод. -
__format__(self, formatstr)
Определяет поведение, когда экземпляр вашего класса используется в форматировании строк нового стиля. Например,"Hello, {0:abc}!".format(a)
приведёт к вызовуa.__format__("abc")
. Это может быть полезно для определения ваших собственных числовых или строковых типов, которым вы можете захотеть предоставить какие-нибудь специальные опции форматирования. -
__hash__(self)
Определяет поведение функцииhash()
, вызванной для экземпляра вашего класса. Метод должен возвращать целочисленное значение, которое будет использоваться для быстрого сравнения ключей в словарях. Заметьте, что в таком случае обычно нужно определять и__eq__
тоже. Руководствуйтесь следующим правилом:a == b
подразумеваетhash(a) == hash(b)
. -
__nonzero__(self)
Определяет поведение функцииbool()
, вызванной для экземпляра вашего класса. Должна вернутьTrue
илиFalse
, в зависимости от того, когда вы считаете экземпляр соответствующимTrue
илиFalse
. -
__dir__(self)
Определяет поведение функцииdir()
, вызванной на экземпляре вашего класса. Этот метод должен возвращать пользователю список атрибутов. Обычно, определение__dir__
не требуется, но может быть жизненно важно для интерактивного использования вашего класса, если вы переопределили__getattr__
или__getattribute__
. -
__sizeof__(self)
Определяет поведение функцииsys.getsizeof()
, вызванной на экземпляре вашего класса. Метод должен вернуть размер вашего объекта в байтах.
Магия контейнеров
Магические методы, используемые контейнерами.
-
__len__(self)
Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров. -
__getitem__(self, key)
Определяет поведение при доступе к элементу, используя синтаксисself[key]
. Тоже относится и к протоколу изменяемых и к протоколу неизменяемых контейнеров. Должен выбрасывать соответствующие исключения: TypeError если неправильный тип ключа и KeyError если ключу не соответствует никакого значения. -
__setitem__(self, key, value)
Определяет поведение при присваивании значения элементу, используя синтаксисself[nkey] = value
. Часть протокола изменяемого контейнера. Опять же, вы должны выбрасывать KeyError и TypeError в соответствующих случаях. -
__delitem__(self, key)
Определяет поведение при удалении элемента (то естьdel self[key]
). Это часть только протокола для изменяемого контейнера. Вы должны выбрасывать соответствующее исключение, если ключ некорректен. -
__iter__(self)
Должен вернуть итератор для контейнера. Итераторы возвращаются в множестве ситуаций, главным образом для встроенной функцииiter()
и в случае перебора элементов контейнера выражениемfor x in container:
. Итераторы сами по себе объекты и они тоже должны определять метод__iter__
, который возвращаетself
. -
__reversed__(self)
Вызывается чтобы определить поведения для встроенной функцииreversed()
. Должен вернуть обратную версию последовательности. Реализуйте метод только если класс упорядоченный, как список или кортеж. -
__contains__(self, item)
__contains__
предназначен для проверки принадлежности элемента с помощью in и not in. Вы спросите, почему же это не часть протокола последовательности? Потому что когда__contains__
не определён, Питон просто перебирает всю последовательность элемент за элементом и возвращает True если находит нужный. -
__missing__(self, key)
__missing__
используется при наследовании отdict
. Определяет поведение для для каждого случая, когда пытаются получить элемент по несуществующему ключу (так, например, если у меня есть словарьd
и я пишуd["george"]
когда "george" не является ключом в словаре, вызываетсяd.__missing__("george"))
.
Как вызывать магические методы
Магический метод | Когда он вызывается (пример) | Объяснение |
---|---|---|
__new__(cls [,...]) |
instance = MyClass(arg1, arg2) |
__new__ вызывается при создании экземпляра |
__init__(self [,...]) |
instance = MyClass(arg1, arg2) |
__init__ вызывается при создании экземпляра |
__cmp__(self, other) |
self == other, self > other, etc. |
Вызывается для любого сравнения |
__pos__(self) |
+self |
Унарный знак плюса |
__neg__(self) |
-self |
Унарный знак минуса |
__invert__(self) |
~self |
Побитовая инверсия |
__index__(self) |
x[self] |
Преобразование, когда объект используется как индекс |
__nonzero__(self) |
bool(self), if self: |
Булевое значение объекта |
__getattr__(self, name) |
self.name # name не определено |
Пытаются получить несуществующий атрибут |
__setattr__(self, name, val) |
self.name = val |
Присвоение любому атрибуту |
__delattr__(self, name) |
del self.name |
Удаление атрибута |
__getattribute__(self, name) |
self.name |
Получить любой атрибут |
__getitem__(self, key) |
self[key] |
Получение элемента через индекс |
__setitem__(self, key, val) |
self[key] = val |
Присвоение элементу через индекс |
__delitem__(self, key) |
del self[key] |
Удаление элемента через индекс |
__iter__(self) |
for x in self |
Итерация |
__contains__(self, value) |
value in self, value not in self |
Проверка принадлежности с помощью in |
__call__(self [,...]) |
self(args) |
«Вызов» экземпляра |
__enter__(self) |
with self as x: |
with оператор менеджеров контекста |
__exit__(self, exc, val, trace) |
with self as x: |
with оператор менеджеров контекста |
__getstate__(self) |
pickle.dump(pkl_file, self) |
Сериализация |
__setstate__(self) |
data = pickle.load(pkl_file) |
Сериализация |
(наверх)
Это класс, который реализует несколько методов, которые ты добавляешь к разным классам для того, чтобы они унаследовали и тоже получили какие-то методы.
(наверх)
Наследование классов в объектно-ориентированном программировании позволяет создавать новые классы на основе уже существующих классов. Это позволяет наследующему классу наследовать атрибуты (переменные и методы) от родительского класса, что способствует повторному использованию кода и созданию иерархии классов.
Для создания наследования классов в Python используется следующий синтаксис:
class РодительскийКласс:
# Атрибуты и методы родительского класса
class НаследующийКласс(РодительскийКласс):
# Атрибуты и методы наследующего класса
В примере выше класс НаследующийКласс
наследует атрибуты и методы от класса РодительскийКласс
. Это означает, что НаследующийКласс
может использовать все публичные атрибуты и методы, определенные в РодительскомКлассе
, а также добавлять свои собственные атрибуты и методы.
Пример кода:
class Animal:
def __init__(self, name):
self.name = name
def sound(self):
pass # Абстрактный метод, будет переопределен в дочерних классах
class Dog(Animal):
def sound(self):
return "Woof!"
class Cat(Animal):
def sound(self):
return "Meow!"
dog = Dog("Buddy")
print(dog.name) # Выводит "Buddy"
print(dog.sound()) # Выводит "Woof!"
cat = Cat("Kitty")
print(cat.name) # Выводит "Kitty"
print(cat.sound()) # Выводит "Meow!"
В этом примере Animal
является родительским классом, а Dog
и Cat
являются наследующими классами. У них есть общий атрибут name, наследуемый от Animal
, и переопределенный метод sound()
. Созданные объекты классов Dog
и Cat
могут использовать как унаследованный атрибут name
, так и метод sound()
, соответствующий каждому классу.
Таким образом, наследование классов позволяет эффективно организовывать иерархию классов и переиспользовать код, что является одной из ключевых концепций объектно-ориентированного программирования.
(наверх)
Полиморфизм в объектно-ориентированном программировании означает способность объектов разных классов использовать одно и то же имя метода, но с различной реализацией. Это позволяет обрабатывать различные типы данных с использованием общих интерфейсов.
В Python полиморфизм достигается с помощью наследования и переопределения методов, а также с помощью использования механизма динамической типизации языка.
Пример полиморфизма в Python:
class Animal:
def sound(self):
pass
class Dog(Animal):
def sound(self):
return "Woof!"
class Cat(Animal):
def sound(self):
return "Meow!"
class Cow(Animal):
def sound(self):
return "Moo!"
def make_sound(animal):
print(animal.sound())
dog = Dog()
cat = Cat()
cow = Cow()
make_sound(dog) # Выводит "Woof!"
make_sound(cat) # Выводит "Meow!"
make_sound(cow) # Выводит "Moo!"
(наверх)
Инкапсуляция в объектно-ориентированном программировании (ООП) - это механизм, который позволяет объединить данные (переменные) и методы, работающие с этими данными, в единый объект, скрывая внутреннюю реализацию от внешнего кода. Один из основных принципов ООП - скрытие деталей реализации и предоставление только интерфейса для взаимодействия с объектом.
В Python инкапсуляция достигается с помощью соглашений об использовании именования и специальных атрибутов (методов).
Примеры инкапсуляции в Python:
- Использование одиночного подчеркивания _ перед именем переменной или метода. Это соглашение указывает на то, что переменная или метод предназначены для внутреннего использования и не должны быть использованы вне класса или его наследников:
class MyClass:
def __init__(self):
self._internal_var = 42
def _internal_method(self):
return "This is an internal method."
obj = MyClass()
print(obj._internal_var) # Выводит 42
print(obj._internal_method()) # Выводит "This is an internal method."
В данном примере переменная _internal_var
и метод _internal_method()
считаются внутренними для класса MyClass
, и хотя они доступны извне, они не рекомендуется использовать в других частях программы.
- Использование двойного подчеркивания
__
перед именем переменной или метода. Это создает механизм именования, называемый "name mangling", который делает переменные и методы недоступными для прямого доступа извне класса:
class MyClass:
def __init__(self):
self.__private_var = 42
def __private_method(self):
return "This is a private method."
obj = MyClass()
print(obj.__private_var) # Вызывает ошибку AttributeError
print(obj.__private_method()) # Вызывает ошибку AttributeError
В данном примере переменная __private_var
и метод __private_method()
являются приватными для класса MyClass
и недоступны для прямого доступа извне класса.
Таким образом, инкапсуляция в Python достигается с помощью использования соглашений об именовании и специальных атрибутов. Однако, следует отметить, что в Python инкапсуляция является соглашением и не является полностью принудительной. Разработчики могут обойти эти соглашения и получить доступ к "скрытым" переменным и методам, но это считается плохой практикой, так как нарушает принципы ООП и может нарушить целостность класса.
(наверх)
Инкапсуляция в Python может быть реализована с использованием только публичных методов путем ограничения прямого доступа к внутренним переменным класса и предоставления контролируемого интерфейса для взаимодействия с ними.
Пример реализации инкапсуляции с использованием только публичных методов:
class MyClass:
def __init__(self):
self._private_var = 42
def get_private_var(self):
return self._private_var
def set_private_var(self, value):
# Дополнительные проверки или логика можно добавить здесь
self._private_var = value
obj = MyClass()
print(obj.get_private_var()) # Выводит 42
obj.set_private_var(10)
print(obj.get_private_var()) # Выводит 10
В этом примере переменная _private_var
считается внутренней для класса MyClass
и не доступна непосредственно извне. Вместо этого, класс предоставляет публичные методы get_private_var()
и set_private_var()
, которые позволяют получать значение и устанавливать новое значение внутренней переменной _private_var
.
Таким образом, внутреннее состояние объекта класса остается скрытым, а взаимодействие с этим состоянием происходит через публичные методы, которые обеспечивают контролируемый доступ к внутренним данным.
Важно отметить, что в Python соглашение о использовании одинарного подчеркивания перед именем переменной (_private_var
) указывает на то, что переменная предназначена для внутреннего использования и не рекомендуется использовать ее непосредственно извне класса. Однако, это всего лишь соглашение, а не полностью принудительное ограничение, и разработчики могут обойти это соглашение и получить доступ к переменной.
(наверх)
Дескрипторы в Python - это механизм, который позволяет контролировать доступ к атрибутам класса. Они определяются с помощью специальных методов, таких как __get__()
, __set__()
, и __delete__()
, которые определяют поведение при доступе, установке или удалении значения атрибута.
Дескрипторы используются для создания управляемых атрибутов, которые могут выполнять дополнительные операции, проверки и логику при доступе к значениям атрибутов.
Примеры использования дескрипторов в Python:
- Дескриптор для доступа к атрибуту:
class Descriptor:
def __get__(self, instance, owner):
return instance._value
def __set__(self, instance, value):
instance._value = value
class MyClass:
attribute = Descriptor()
obj = MyClass()
obj.attribute = 42
print(obj.attribute) # Выводит 42
В этом примере Descriptor
является дескриптором, определяющим доступ к атрибуту attribute
класса MyClass
. При установке значения атрибута (obj.attribute = 42
) вызывается метод __set__()
дескриптора, который устанавливает значение _value
в экземпляре класса. При чтении значения атрибута (print(obj.attribute)
) вызывается метод __get__()
, который возвращает значение _value
из экземпляра класса.
- Дескриптор для выполнения дополнительной логики при доступе к атрибуту:
class Descriptor:
def __get__(self, instance, owner):
print("Getting attribute")
return instance._value
def __set__(self, instance, value):
print("Setting attribute")
instance._value = value
def __delete__(self, instance):
print("Deleting attribute")
del instance._value
class MyClass:
attribute = Descriptor()
obj = MyClass()
obj.attribute = 42 # Выводит "Setting attribute"
print(obj.attribute) # Выводит "Getting attribute" и 42
del obj.attribute # Выводит "Deleting attribute"
В этом примере дескриптор Descriptor
выполняет дополнительные операции при доступе, установке или удалении значения атрибута. Когда значение устанавливается (obj.attribute = 42
), вызывается метод __set__()
, который выводит сообщение "Setting attribute". При чтении значения (print(obj.attribute)
) вызывается метод __get__()
, который выводит сообщение "Getting attribute" и возвращает значение _value
. При удалении атрибута (del obj.attribute
) вызывается метод __delete__()
, который выводит сообщение "Deleting attribute" и удаляет _value
.
Таким образом, дескрипторы позволяют контролировать доступ к атрибутам класса и добавлять дополнительную логику при их доступе, установке или удалении. Это помогает создавать более гибкие и управляемые классы.
(наверх)
Абстрактные классы в Python - это классы, которые не предназначены для создания экземпляров, а служат в качестве базовых классов для других классов. Они определяют интерфейс (абстрактные методы) и поведение, которое должны иметь классы-наследники, но не предоставляют конкретную реализацию для всех методов. Абстрактные классы используются для создания общей структуры и обязательств для классов-наследников.
Абстрактные классы в Python реализуются с использованием модуля abc
(Abstract Base Classes). Модуль abc
предоставляет базовый класс ABC
и декораторы для создания абстрактных методов и свойств.
Примеры использования абстрактных классов в Python:
- Создание абстрактного класса с абстрактным методом:
from abc import ABC, abstractmethod
class AbstractClass(ABC):
@abstractmethod
def abstract_method(self):
pass
class ConcreteClass(AbstractClass):
def abstract_method(self):
print("Implementation of abstract_method")
# obj = AbstractClass() # Вызывает ошибку TypeError, так как абстрактный класс не может быть инстанциирован
obj = ConcreteClass()
obj.abstract_method() # Выводит "Implementation of abstract_method"
В этом примере AbstractClass
является абстрактным классом, определяющим абстрактный метод abstract_method()
. Класс ConcreteClass
наследуется от AbstractClass
и обязан предоставить реализацию для абстрактного метода. Объект класса ConcreteClass
может быть создан и вызван абстрактный метод.
- Создание абстрактного класса с абстрактным свойством:
from abc import ABC, abstractproperty
class AbstractClass(ABC):
@abstractproperty
def abstract_property(self):
pass
class ConcreteClass(AbstractClass):
@property
def abstract_property(self):
return "Value of abstract_property"
# obj = AbstractClass() # Вызывает ошибку TypeError, так как абстрактный класс не может быть инстанциирован
obj = ConcreteClass()
print(obj.abstract_property) # Выводит "Value of abstract_property"
В этом примере AbstractClass
является абстрактным классом, определяющим абстрактное свойство abstract_property
. Класс ConcreteClass
наследуется от AbstractClass
и предоставляет реализацию для абстрактного свойства с помощью декоратора @property
. Объект класса ConcreteClass
может быть создан и доступен к абстрактному свойству.
Абстрактные классы в Python полезны, когда требуется определить общую структуру для классов-наследников, а также гарантировать, что определенные методы или свойства будут реализованы в этих классах. Они помогают создавать более чистый и структурированный код, а также обеспечивают проверку на соответствие интерфейсу при создании новых классов.
Что такое множественное наследование и какие проблемы могут возникнуть при его использовании в Python?
(наверх)
Множественное наследование в Python - это возможность класса наследовать свойства и методы одновременно от нескольких родительских классов. При множественном наследовании класс может наследовать функциональность от нескольких классов, что позволяет создавать более гибкие и переиспользуемые структуры классов.
Пример использования множественного наследования:
class BaseClass1:
def method1(self):
print("Method 1 from BaseClass1")
class BaseClass2:
def method2(self):
print("Method 2 from BaseClass2")
class DerivedClass(BaseClass1, BaseClass2):
pass
obj = DerivedClass()
obj.method1() # Выводит "Method 1 from BaseClass1"
obj.method2() # Выводит "Method 2 from BaseClass2"
В этом примере классы BaseClass1
и BaseClass2
являются родительскими классами, а класс DerivedClass
наследует от обоих родительских классов. Это позволяет объекту класса DerivedClass
иметь доступ как к методу method1()
из BaseClass1
, так и к методу method2()
из BaseClass2
.
Однако, при использовании множественного наследования могут возникать некоторые проблемы:
- Проблемы с именованием: Если несколько родительских классов имеют методы с одинаковыми именами, возникает неоднозначность при вызове метода из класса-наследника. Это называется "ромбовидным наследованием" или "проблемой алмаза". В таких случаях необходимо явно указывать, какой метод нужно вызывать.
class BaseClass:
def method(self):
print("Method from BaseClass")
class SubClass1(BaseClass):
def method(self):
print("Method from SubClass1")
class SubClass2(BaseClass):
def method(self):
print("Method from SubClass2")
class DerivedClass(SubClass1, SubClass2):
pass
obj = DerivedClass()
obj.method() # Выводит "Method from SubClass1"
В этом примере DerivedClass
наследует от SubClass1
и SubClass2
, которые оба переопределяют метод method()
. При вызове метода method()
у объекта DerivedClass
будет выполнена реализация из SubClass1
, так как SubClass1
указан первым в списке наследования.
- Проблемы с дублированием кода: Если несколько родительских классов реализуют одинаковую функциональность, то может возникнуть дублирование кода. Это может привести к сложностям в поддержке и изменении кода.
class BaseClass1:
def method(self):
print("Method from BaseClass1")
class BaseClass2:
def method(self):
print("Method from BaseClass2")
class DerivedClass(BaseClass1, BaseClass2):
pass
obj = DerivedClass()
obj.method() # Выводит "Method from BaseClass1"
В этом примере и BaseClass1
, и BaseClass2
имеют метод method()
, но при наследовании в DerivedClass
используется реализация из BaseClass1
. Это может быть неожиданным результатом и может потребовать явного решения о том, какой метод использовать.
- Сложность иерархии классов: Множественное наследование может привести к сложности в иерархии классов и усложнить понимание и структурирование кода. Если иерархия классов становится слишком сложной, может быть трудно отследить, откуда берется определенное свойство или метод.
В целом, множественное наследование в Python - мощный инструмент, позволяющий создавать более гибкие и переиспользуемые структуры классов. Однако, при его использовании следует быть внимательным и учитывать возможные проблемы, такие как конфликты имен, дублирование кода и сложность иерархии классов.
(наверх)
Агрегация объектов в Python — это процесс создания отношения, при котором один объект содержит ссылку на другой объект в качестве своего атрибута. Это позволяет создавать более сложные структуры данных, где объекты взаимодействуют и совместно работают.
Пример реализации агрегации объектов в Python:
class Student:
def __init__(self, name, id):
self.name = name
self.id = id
class Course:
def __init__(self, name, students):
self.name = name
self.students = students
# Создание объектов Student
student1 = Student("Alice", 1)
student2 = Student("Bob", 2)
student3 = Student("Charlie", 3)
# Создание объекта Course с агрегацией объектов Student
course = Course("Math", [student1, student2, student3])
# Доступ к агрегированным объектам
for student in course.students:
print(student.name, student.id)
В этом примере у нас есть классы Student
и Course
. Класс Student
представляет студента с его именем и идентификатором. Класс Course
представляет курс с именем и агрегацией объектов Student
, которые являются студентами этого курса.
Мы создаем несколько объектов класса Student
(student1
, student2
, student3
) и затем создаем объект класса Course
(course
) с агрегацией этих объектов.
Затем мы можем получить доступ к агрегированным объектам Student
через атрибут students
объекта Course
. В цикле мы выводим имена и идентификаторы каждого студента.
Агрегация объектов позволяет создавать более сложные иерархии и связи между объектами. Это основа для построения более гибких и расширяемых структур данных в объектно-ориентированном программировании.
(наверх)
Примеси (mixins) в Python - это специальный подход в объектно-ориентированном программировании, который позволяет добавлять дополнительное поведение и функциональность классам без использования наследования.
Примеси являются небольшими классами, которые содержат методы и свойства, предназначенные для повторного использования в других классах. Они предоставляют независимые от иерархии наследования функциональные возможности, которые можно добавить в любой класс.
Пример использования примесей в Python:
class LoggableMixin:
def log(self, message):
print(f"Logging: {message}")
class Database:
def save(self):
print("Saving to the database")
class File:
def write(self):
print("Writing to the file")
class User(Database, LoggableMixin):
def __init__(self, name):
self.name = name
user = User("Alice")
user.save() # Вызов метода из класса Database
user.log("User created") # Вызов метода из примеси LoggableMixin
В этом примере у нас есть примесь LoggableMixin
, которая содержит метод log()
, предназначенный для логирования сообщений. Затем у нас есть классы Database
и File
, которые представляют различные объекты для сохранения данных.
Класс User
наследует от класса Database
и использует примесь LoggableMixin
. Теперь объекты класса User
могут использовать методы и свойства как из класса Database
, так и из примеси LoggableMixin
.
При создании объекта класса User
(в данном случае user) мы можем вызывать метод save()
из класса Database
и метод log()
из примеси LoggableMixin
.
Примеси позволяют добавлять дополнительное поведение к классам без создания сложных иерархий наследования. Они способствуют повторному использованию кода и улучшают гибкость и расширяемость классов в Python. Однако следует быть внимательным при использовании примесей, чтобы не создавать излишнюю сложность и не злоупотреблять ими, чтобы сохранить читаемость и понятность кода.
(наверх)
Композиция объектов в Python - это способ создания сложных объектов путем объединения других объектов в их состав. При композиции объекты "составляют" друг друга и образуют новую структуру, где каждый объект выполняет определенную роль или функцию.
Пример использования композиции объектов в Python:
class Engine:
def start(self):
print("Engine started")
def stop(self):
print("Engine stopped")
class Car:
def __init__(self):
self.engine = Engine() # Композиция объекта Engine
def start(self):
print("Car started")
self.engine.start()
def stop(self):
print("Car stopped")
self.engine.stop()
car = Car()
car.start() # Запуск автомобиля и двигателя
car.stop() # Остановка автомобиля и двигателя
В этом примере у нас есть класс Engine
, представляющий двигатель, и класс Car
, представляющий автомобиль. В классе Car
мы создаем объект engine
, который представляет двигатель и является компонентом автомобиля.
При вызове метода start()
у объекта car
, он вызывает метод start()
у объекта engine
, что приводит к запуску двигателя. Аналогично, при вызове метода stop()
у объекта car
, он вызывает метод stop()
у объекта engine
, что приводит к остановке двигателя.
Композиция объектов позволяет создавать более сложные структуры, где каждый объект выполняет свою функцию и может быть независимо изменен или заменен. Она способствует повторному использованию кода и упрощению процесса разработки, так как компоненты могут быть созданы и отлажены отдельно, а затем объединены вместе.
Композиция объектов также способствует созданию более гибкого и модульного кода, где объекты могут быть заменены или расширены без влияния на остальную структуру. Это способствует принципу "композиция перед наследованием", где предпочтительно использовать композицию объектов вместо наследования, чтобы управлять зависимостями и избегать сложностей, связанных с иерархией классов.
Однако следует быть осторожным с избыточной композицией, чтобы не создавать слишком сложные и перегруженные структуры объектов. Важно найти баланс и использовать композицию там, где она действительно необходима для обеспечения четкой и понятной структуры программы.
- Какие типы и структуры данных бывают в python?
- Что такое мутабельные и иммутабельные типы данных?
- Что может быть в качестве ключа словаря?
- Что такое хеш-функция?
- В чём особенность словаря в python?
- Списки, кортежи и множества в чём их отличие?
- Как работает механизм генераторов списков в python?
- Как работает механизм словарей в python?
- Что такое namedtuple в python и как его использовать?
- Как работает механизм множеств в python?
- Что такое графы и как их можно реализовать в Python?
- Какие типы данных в Python поддерживаются многопоточностью, и почему?
- Какие типы данных в Python поддерживаются многопроцессорностью, и почему?
- Что такое рекурсия и как она работает в Python?
- Что такое динамическое программирование и как оно используется в Python?
- Какие структуры данных в Python можно использовать для реализации очередей и стеков, и как они работают?
- Какие структуры данных в Python можно использовать для реализации графов, и как они работают?
- Какие структуры данных в Python можно использовать для реализации деревьев, и как они работают?
- Какие структуры данных в Python можно использовать для реализации куч (heap), и как они работают?
- Что такое "Big O notation", и как она используется для анализа производительности алгоритмов и структур данных в Python?
(наверх)
Объект | Тип |
---|---|
Строка | str |
Целое число | int |
Число с плавающей точкой | float |
Список | list |
Кортеж | tuple |
Словарь | dict |
Множество | set |
Логический | bool |
Функция | function |
Класс, определяемый пользователем | type |
Экземпляр класса | class |
Встроенная функция | builtin_function_or_method |
type | type |
(наверх)
Объекты в питоне бывают двух значительно отличающихся сортов: изменяемые (mutable) и неизменяемые (immutable). Неизменяемыми являются целые и действительные числа (int, float), строки (str), последовательности байтов (бинарные данные, bytes), а также кортежи, все элементы которых неизменяемы (tuple). Напротив, списки (list), словари (dict) и множества (set) являются изменяемыми.
(наверх)
Только неизменяемые типы данных. Ключами словаря могут являться только объекты, поддерживающие хеширование. Таким образом, использовать в качестве ключей списки, словари и другие изменяемые типы не получится. Если в словарь будут добавлены несколько значений с одним и тем же ключом, словарь сохранит последнее.
Не рекомендуется использоваться в качестве ключей числа с плавающей запятой, так как они хранятся в памяти в виде приближений.
(наверх)
Хэш-функция - это функция, которая принимает на вход какие-либо данные (например, строки) и возвращает число по некоторому заданному алгоритму.
Назначением хэш-функций является возможность помещения некоторого элемента (например, строки) в хэш-таблицу, на основе которых реализованы, например, словари и множества в Python.
Одинаковые данные будут иметь одинаковое хеш-значение.
- Даже небольшое изменение исходных данных может привести к совершенно иному хеш-значению.
- Хеш получается из хеш-функции, в обязанности которой входит преобразование данной информации в закодированный хеш.
- Очевидно, что количество объектов может быть намного больше, чем количество хеш-значений, и поэтому два объекта могут хешировать одно и то же. Это называется конфликтом хэша. Это означает, что если два объекта имеют одинаковый хэш-код, они не обязательно имеют одно и то же значение.
Cрок жизни хэша зависит только от области действия программы, и он может измениться, как только программа завершится.
(наверх)
Словари в Python - неупорядоченные коллекции произвольных объектов с доступом по ключу. Их иногда ещё называют ассоциативными массивами или хеш-таблицами.
Чтобы работать со словарём, его нужно создать. Сделать это можно несколькими способами:
- Во-первых, с помощью литерала:
d = {}
print(d)
{}
d = {'dict': 1, 'dictionary': 2}
prtin(d)
{'dict': 1, 'dictionary': 2}
- Во-вторых, с помощью функции dict:
d = dict(short='dict', long='dictionary')
prtin(d)
{'short': 'dict', 'long': 'dictionary'}
d = dict([(1, 1), (2, 4)])
prtin(d)
{1: 1, 2: 4}
- В-третьих, с помощью метода fromkeys:
d = dict.fromkeys(['a', 'b'])
prtin(d)
{'a': None, 'b': None}
d = dict.fromkeys(['a', 'b'], 100)
prtin(d)
{'a': 100, 'b': 100}
- В-четвертых, с помощью генераторов словарей, которые очень похожи на генераторы списков.
d = {a: a ** 2 for a in range(7)}
prtin(d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36}
Методы словарей
dict.clear()
- очищает словарь.dict.copy()
- возвращает копию словаря.classmethod dict.fromkeys(seq[, value])
- создает словарь с ключами изseq
и значениемvalue
(по умолчаниюNone
).dict.get(key[, default])
- возвращает значение ключа, но если его нет, не бросает исключение, а возвращаетdefault
(по умолчаниюNone
).dict.items()
- возвращает пары (ключ, значение).dict.keys()
- возвращает ключи в словаре.dict.pop(key[, default])
- удаляет ключ и возвращает значение. Если ключа нет, возвращаетdefault
(по умолчанию бросает исключение).dict.popitem()
- удаляет и возвращает пару (ключ, значение). Если словарь пуст, бросает исключениеKeyError
. Важно помнить, что словари неупорядочены.dict.setdefault(key[, default])
- возвращает значение ключа, но если его нет, не бросает исключение, а создает ключ со значениемdefault
(по умолчаниюNone
).dict.update([other])
- обновляет словарь, добавляя пары (ключ, значение) изother
. Существующие ключи перезаписываются. ВозвращаетNone
(не новый словарь!).dict.values()
- возвращает значения в словаре.
(наверх)
List (список)
Базовая структура данных в python. Элементы в списке хранятся последовательно, каждому из них присвоены индексы, начиная с нуля. В отличие от массива, список может хранить объекты любого типа.
Создание списка
my_list = [] # Создание пустого списка с помощью литерала списка
my_list = list() # Создание пустого списка с помощью встроенной функции
my_list = [1,2,['a','b'],4,5] # Инициализация списка
my_list = list('hello world') # Создание списка из итерируемого объекта
print(my_list)
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
my_list = [x for x in range(10)] # Генератор списков в действии
print(my_list)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Доступные методы
my_list.append(x)
- добавляет x в конец спискаmy_list.clear()
- очищаетmy_list.copy()
- возвращает копию спискаmy_list
my_list.count(x)
- возвращает кол-во элементов со значением xmy_list.extend(x)
- добавляет элементы списка x к концу спискаmy_list
my_list.index(x,start,end)
- возвращает индекс первого найденного x, можно задать промежуток для поиска (опционально)my_list.insert(index, x)
- вставляет x на заданную позициюmy_list.pop(index)
- возвращает элемент с указанным индексом и удаляет его, если индекс не указан - возвращается и удаляется последний элементmy_list.remove(x)
- удаляет первый элемент со значением xmy_list.reverse()
- инвертирует порядок элементов в спискеmy_list.sort(key=x)
сортирует список на основе функции x
В каких случаях использовать? Для хранения элементов, объединенных каким либо признаком. В случае, если изменение элементов и/или расширение списка не предполагается, следует использовать неизменяемый аналог - кортеж.
Tuple (кортёж)
Кортеж - это неизменяемый и более быстрый аналог списка. Он защищает хранимые данные от непреднамеренных изменений и может использоваться в качестве ключа в словарях (словарь - ассоциативный массив в python).
Создание кортежа.
my_tuple = () # Создание кортежа с помощью литерала
my_tuple = tuple() # Создание кортежа с помощью встроенной функции
my_tuple = (1,2,['a','b'],4,5) # Инициализация кортежа
my_tuple = tuple('hello world') # Создание кортежа из итерируемого объекта
print(my_tuple)
('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd')
my_tuple = tuple(2**x for x in [0, 1, 2, 3]) # Генератор кортежей
print(my_tuple)
(1, 2, 4, 8)
Доступные методы
my_tuple.count(x)
- возвращает кол-во элементов со значением xmy_tuple.index(x,start,end)
- возвращает индекс первого найденного x, можно задать промежуток для поиска (опционально)
В каких случаях использовать? Для хранения данных вместо списка (если они не предполагают изменений).
Set (множество)
Множество - это набор уникальных элементов в случайном порядке (неупорядоченный список). Множества примечательны тем, что операция проверки "принадлежит ли объект множеству" происходит значительно быстрее аналогичных операций в других структурах данных.
Создание множества
my_something = {} # !!! Попытка создать множество при помощи литерала даст нам словарь
type(my_something)
<class 'dict'>
my_set = set() # Создание при помощи встроенной функции
my_set = {1,2,3,4,5} # Инициализация множества
my_set = set('hello world') # Создания множества из итерируемого объекта
print(my_set)
{'r', 'o', 'e', 'h', 'd', 'w', 'l', ' '}
my_set = {x for x in range(10)} # Генератор множеств
print(my_set)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
Доступные методы
my_set.add(x)
- добавляет x во множествоmy_set.difference(x)
- возвращает множество элементовmy_set
, которые не входят во множествоx
my_set.difference_update(x)
- удаляет из множества my_set все элементы, которые входят во множествоx
my_set.discard(x)
- удаляет элементx
изmy_set
my_set.intersection(x)
- возвращает элементы общие для множествmy_set
иx
my_set.intersection_update(x)
- удаляет из множестваmy_set
элементы, которых нет во множествеx
my_set.isdisjoint(x)
- возвращаетtrue
еслиmy_set
иx
не содержат одинаковых значенийmy_set.issubset(x)
- возвращает true если все элементыmy_set
входят во множествоx
my_set.issuperset(x)
- возвращаетtrue
если все элементы x входят во множествоmy_set
my_set.pop()
- возвращает и удаляет первый (на данный момент) элемент множестваmy_set.remove(x)
- удаляетx
из множестваmy_set.symmetric_difference(x)
- возвращает все элементы изx
иmy_set
, которые встречаются только в одном из множествmy_set.symmetric_difference_update(x)
- обновляет исходное множество таким образом, что оно будет состоять из всех элементовx
иmy_set
, которые встречаются только в одном из множествmy_set.union(x)
- возвращает новое множество, состоящее из всех элементовx
иmy_set
my_set.update(x)
- добавляет вmy_set
все элементыx
В каких случаях использовать? Когда необходимо проверять принадлежит ли значение набору уникальных элементов и отсутствует необходимость поддерживать порядок в данном наборе.
(наверх)
Механизм генераторов списков в Python предоставляет удобный способ создания списков с помощью компактного и выразительного синтаксиса. Генераторы списков позволяют создавать списки на основе итерации или применения операций к элементам других последовательностей.
Основной синтаксис генераторов списков выглядит следующим образом:
[выражение for элемент in последовательность (если условие)]
Элементы в квадратных скобках []
образуют новый список, их значение определяется выражением, которое указывается в начале генератора списка. Затем мы указываем ключевое слово for
, за которым следует переменная, которая будет хранить значения элементов из последовательности, и непосредственно сама последовательность.
Также мы можем добавить условие if
, чтобы фильтровать элементы перед добавлением их в новый список. Условие является дополнительным и необязательным компонентом.
Рассмотрим несколько примеров генераторов списков:
- Генератор списка с использованием цикла:
numbers = [i for i in range(10)]
print(numbers) # Вывод: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- Генератор списка с условием:
even_numbers = [i for i in range(10) if i % 2 == 0]
print(even_numbers) # Вывод: [0, 2, 4, 6, 8]
- Генератор списка для преобразования элементов:
names = ["Alice", "Bob", "Charlie"]
name_lengths = [len(name) for name in names]
print(name_lengths) # Вывод: [5, 3, 7]
- Генератор списка для работы с объектами разных типов:
mixed_list = [x if isinstance(x, int) else 0 for x in [1, "two", 3, "four", 5]]
print(mixed_list) # Вывод: [1, 0, 3, 0, 5]
Механизм генераторов списков позволяет компактно и эффективно создавать новые списки на основе существующих данных и применять различные преобразования или фильтрацию. Он широко используется в Python для упрощения кода и повышения его читаемости.
(наверх)
Механизм словарей в Python представляет собой встроенную структуру данных, которая позволяет хранить и организовывать пары ключ-значение. Словари в Python являются изменяемыми (mutable) и неупорядоченными (unordered) коллекциями элементов. Они основаны на хэш-таблицах, что позволяет эффективно выполнять операции вставки, удаления и поиска.
Каждый элемент в словаре представляет собой пару ключ-значение, где ключи должны быть уникальными. Ключи в словаре должны быть неизменяемыми типами данных, такими как строки, числа или кортежи. Значения могут быть любого типа данных: числа, строки, списки, словари и т.д.
Примеры кода:
- Создание словаря:
# Пустой словарь
my_dict = {}
# Словарь с начальными значениями
my_dict = {'apple': 2, 'banana': 3, 'orange': 5}
- Добавление элементов в словарь:
my_dict = {}
my_dict['apple'] = 2
my_dict['banana'] = 3
my_dict['orange'] = 5
# Или можно добавить несколько элементов сразу
my_dict.update({'grape': 4, 'mango': 6})
- Получение значения по ключу:
my_dict = {'apple': 2, 'banana': 3, 'orange': 5}
print(my_dict['apple']) # Выводит: 2
# Можно использовать метод get() для получения значения
print(my_dict.get('banana')) # Выводит: 3
# Если ключа нет в словаре, метод get() вернет None или значение по умолчанию
print(my_dict.get('grape')) # Выводит: None
print(my_dict.get('grape', 0)) # Выводит: 0 (значение по умолчанию)
- Удаление элемента из словаря:
my_dict = {'apple': 2, 'banana': 3, 'orange': 5}
del my_dict['apple']
print(my_dict) # Выводит: {'banana': 3, 'orange': 5}
# Можно использовать метод pop() для удаления и получения значения элемента
value = my_dict.pop('banana')
print(my_dict) # Выводит: {'orange': 5}
print(value) # Выводит: 3
- Проверка наличия ключа в словаре:
my_dict = {'apple': 2, 'banana': 3, 'orange': 5}
print('apple' in my_dict) # Выводит: True
print('grape' in my_dict) # Выводит: False
- Итерация по словарю:
my_dict = {'apple': 2, 'banana': 3, 'orange': 5}
for key in my_dict:
print(key, my_dict[key])
# Вывод:
# apple 2
# banana 3
# orange 5
Словари в Python предоставляют удобный способ хранить и обрабатывать данные по ключам. Они широко используются во многих задачах, таких как обработка конфигураций, создание ассоциативных массивов и многие другие.
(наверх)
namedtuple
в Python - это класс из модуля collections
, который предоставляет удобный способ создания и использования неизменяемых (immutable
) объектов, похожих на кортежи, но с возможностью доступа к элементам через имена полей.
Определение namedtuple
выполняется с использованием функции namedtuple()
из модуля collections
. Синтаксис следующий:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'gender'])
В этом примере мы создали именованный кортеж с именем "Person" и полями "name", "age" и "gender". Теперь мы можем создавать экземпляры этого именованного кортежа, указывая значения для каждого поля:
person1 = Person(name='Alice', age=25, gender='Female')
person2 = Person(name='Bob', age=30, gender='Male')
Мы можем получить доступ к значениям полей, используя имена полей, как если бы они были атрибутами объекта:
print(person1.name) # Выводит 'Alice'
print(person2.age) # Выводит 30
namedtuple также предоставляет другие полезные методы, например, ._asdict()
, который возвращает именованный кортеж в виде словаря, и ._replace()
, который позволяет создать новый именованный кортеж с заменой значений полей:
person_dict = person1._asdict()
print(person_dict) # Выводит OrderedDict([('name', 'Alice'), ('age', 25), ('gender', 'Female')])
new_person = person2._replace(age=35)
print(new_person) # Выводит Person(name='Bob', age=35, gender='Male')
namedtuple
- это удобный способ организации данных, когда мы хотим иметь объекты с фиксированным набором полей, и не хотим использовать классы. Он обеспечивает простоту использования и более выразительный код, чем обычные кортежи.
(наверх)
Механизм множеств в Python предоставляет возможность работать с наборами уникальных элементов. Множество является неупорядоченной коллекцией, где каждый элемент может присутствовать только в единственном экземпляре. В Python множества реализованы как класс set
и поддерживают основные операции, такие как добавление элемента, удаление элемента, проверка на наличие элемента и операции над множествами, такие как объединение, пересечение и разность.
Вот некоторые основные операции и методы, которые можно использовать с множествами в Python:
Создание множества:
my_set = {1, 2, 3} # Инициализация множества с помощью фигурных скобок
my_set = set([1, 2, 3]) # Инициализация множества с помощью функции set()
Добавление элементов:
my_set.add(4) # Добавление элемента в множество
Удаление элементов:
my_set.remove(2) # Удаление элемента из множества
my_set.discard(3) # Удаление элемента из множества (без генерации ошибки, если элемент отсутствует)
my_set.clear() # Удаление всех элементов из множества
Проверка на наличие элемента:
if 1 in my_set:
print("Элемент присутствует в множестве")
Операции над множествами:
set1 = {1, 2, 3}
set2 = {3, 4, 5}
union_set = set1.union(set2) # Объединение множеств
intersection_set = set1.intersection(set2) # Пересечение множеств
difference_set = set1.difference(set2) # Разность множеств
Итерация по множеству:
for item in my_set:
print(item)
Множества в Python имеют мощные возможности для выполнения операций над ними и проверки принадлежности элементов. Они особенно полезны в случаях, когда нужно обрабатывать наборы данных, не содержащие повторяющихся элементов, или когда нужно выполнить операции, связанные с теорией множеств, такие как объединение, пересечение и разность.
(наверх)
Графы являются структурами данных, которые состоят из набора вершин (узлов) и ребер (связей) между этими вершинами. Они широко используются в компьютерных науках и информатике для моделирования различных сценариев, таких как социальные сети, дорожные сети, связи между веб-страницами и т.д. Графы могут быть направленными (ребра имеют определенное направление) или ненаправленными (ребра не имеют направления).
В Python существует несколько способов реализации графов. Рассмотрим два наиболее распространенных: с использованием словаря и с использованием классов.
- Реализация графов с использованием словаря:
В этом подходе мы используем словарь для представления графа, где ключи - это вершины, а значения - это список смежных вершин.
graph = {
'A': ['B', 'C'],
'B': ['C', 'D'],
'C': ['D'],
'D': ['C'],
'E': ['F'],
'F': ['C']
}
В этом примере граф содержит вершины от 'A' до 'F', и связи между ними определены в словаре. Например, вершина 'A' имеет смежные вершины 'B' и 'C', вершина 'B' имеет смежные вершины 'C' и 'D', и так далее.
- Реализация графов с использованием классов:
В этом подходе мы определяем класс для вершины графа и класс для самого графа. Класс вершины содержит информацию о самой вершине и ее смежных вершинах. Класс графа содержит список вершин и методы для добавления новых вершин и ребер.
Пример кода:
class Vertex:
def __init__(self, key):
self.key = key
self.connections = {}
def add_neighbor(self, neighbor, weight=0):
self.connections[neighbor] = weight
def get_connections(self):
return self.connections.keys()
class Graph:
def __init__(self):
self.vertices = {}
def add_vertex(self, vertex):
new_vertex = Vertex(vertex)
self.vertices[vertex] = new_vertex
def add_edge(self, start, end, weight=0):
if start not in self.vertices:
self.add_vertex(start)
if end not in self.vertices:
self.add_vertex(end)
self.vertices[start].add_neighbor(end, weight)
def get_vertices(self):
return self.vertices.keys()
В этом примере класс Vertex
представляет вершину графа, а класс Graph
представляет сам граф. Метод add_vertex
добавляет новую вершину в граф, метод add_edge
добавляет ребро между двумя вершинами, а методы get_connections
и get_vertices
возвращают смежные вершины и список всех вершин соответственно.
(наверх)
В Python существуют несколько типов данных, которые поддерживают многопоточность. Они обеспечивают безопасное взаимодействие между потоками выполнения и позволяют эффективно синхронизировать доступ к общим данным. Ниже перечислены некоторые из таких типов данных:
- Список (
List
):
Списки в Python являются изменяемыми и потокобезопасными структурами данных, благодаря чему могут быть использованы в многопоточных средах. Они могут использоваться для совместного доступа и модификации данных в разных потоках.
from threading import Thread
def append_to_list(my_list, item):
my_list.append(item)
shared_list = []
thread1 = Thread(target=append_to_list, args=(shared_list, 1))
thread2 = Thread(target=append_to_list, args=(shared_list, 2))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(shared_list) # Результат: [1, 2]
- Словарь (
Dictionary
):
Словари в Python также являются изменяемыми и потокобезопасными структурами данных. Они могут быть использованы для общего доступа к данным в разных потоках.
from threading import Thread
def update_dictionary(my_dict, key, value):
my_dict[key] = value
shared_dict = {}
thread1 = Thread(target=update_dictionary, args=(shared_dict, 'key1', 'value1'))
thread2 = Thread(target=update_dictionary, args=(shared_dict, 'key2', 'value2'))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(shared_dict) # Результат: {'key1': 'value1', 'key2': 'value2'}
- Очередь (
Queue
):
Модульqueue
в Python предоставляет потокобезопасные очереди, которые могут использоваться для обмена данными между потоками. Очереди обеспечивают правильную синхронизацию и блокировки при обращении к элементам.
from queue import Queue
from threading import Thread
def process_queue(queue):
while not queue.empty():
item = queue.get()
# Обработка элемента
shared_queue = Queue()
shared_queue.put(1)
shared_queue.put(2)
thread1 = Thread(target=process_queue, args=(shared_queue,))
thread2 = Thread(target=process_queue, args=(shared_queue,))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
- Замок (
Lock
):
Замки (Lock
) представляют собой механизм синхронизации, который обеспечивает эксклюзивный доступ к разделяемым данным. Они используются для предотвращения одновременного доступа к данным из нескольких потоков.
from threading import Thread, Lock
def increment_counter(counter, lock):
for _ in range(10000):
lock.acquire()
counter += 1
lock.release()
shared_counter = 0
lock = Lock()
thread1 = Thread(target=increment_counter, args=(shared_counter, lock))
thread2 = Thread(target=increment_counter, args=(shared_counter, lock))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(shared_counter) # Результат: 20000
Все эти типы данных обеспечивают безопасность потоков и правильную синхронизацию при работе с множественными потоками. Использование подобных структур данных позволяет избежать гонок данных (data races
) и других проблем, связанных с многопоточностью.
(наверх)
В Python существуют определенные типы данных и структуры, которые поддерживают многопроцессорность. Многопроцессорность в Python достигается с помощью модуля multiprocessing
, который предоставляет возможность создания и управления процессами. Некоторые из типов данных и структур, поддерживающих многопроцессорность, включают:
- Разделяемая память (
Shared Memory
):
Разделяемая память позволяет разным процессам обмениваться данными, используя общий сегмент памяти. В Python это достигается с использованием классаValue
из модуляmultiprocessing
.
from multiprocessing import Process, Value
def increment_counter(counter):
counter.value += 1
shared_counter = Value('i', 0)
processes = []
for _ in range(10):
process = Process(target=increment_counter, args=(shared_counter,))
processes.append(process)
process.start()
for process in processes:
process.join()
print(shared_counter.value) # Результат: 10
В этом примере создается разделяемая переменная shared_counter
с помощью класса Value
, и каждый процесс инкрементирует ее значение. Разделяемая переменная позволяет безопасно обмениваться данными между процессами.
- Очередь (
Queue
):
Очереди из модуляmultiprocessing
предоставляют механизм для передачи данных между процессами. Они являются потокобезопасными и обеспечивают правильную синхронизацию доступа к данным.
from multiprocessing import Process, Queue
def process_queue(queue):
while not queue.empty():
item = queue.get()
# Обработка элемента
shared_queue = Queue()
shared_queue.put(1)
shared_queue.put(2)
processes = []
for _ in range(2):
process = Process(target=process_queue, args=(shared_queue,))
processes.append(process)
process.start()
for process in processes:
process.join()
В этом примере создается разделяемая очередь shared_queue
, которая заполняется элементами. Затем создаются процессы, которые получают элементы из очереди и выполняют обработку.
- Пул процессов (
Process Pool
):
Пул процессов из модуляmultiprocessing
предоставляет возможность создания пула фиксированного количества процессов, которые могут выполнять задачи параллельно. Он предоставляет удобный интерфейс для распределения работы между процессами.
from multiprocessing import Pool
def square(number):
return number ** 2
numbers = [1, 2, 3, 4, 5]
with Pool(processes=2) as pool:
result = pool.map(square, numbers)
print(result) # Результат: [1, 4, 9, 16, 25]
В этом примере используется пул процессов для расчета квадратов чисел из списка numbers
. map метод пула процессов позволяет распределить задачи между процессами и получить результаты.
- Блокировки (
Lock
): Блокировки из модуля multiprocessing используются для обеспечения безопасного доступа к общим ресурсам из разных процессов. Они позволяют синхронизировать доступ к данным и предотвратить гонки данных (data races
).
from multiprocessing import Process, Lock
def increment_counter(counter, lock):
lock.acquire()
try:
counter.value += 1
finally:
lock.release()
shared_counter = Value('i', 0)
lock = Lock()
processes = []
for _ in range(10):
process = Process(target=increment_counter, args=(shared_counter, lock))
processes.append(process)
process.start()
for process in processes:
process.join()
print(shared_counter.value) # Результат: 10
В этом примере создается разделяемая переменная shared_counter
с помощью класса Value
и блокировка lock
с помощью класса Lock
. Каждый процесс захватывает блокировку перед доступом к общей переменной и освобождает ее после завершения операции.
Таким образом, с использованием модуля multiprocessing
в Python можно работать с различными типами данных и структурами, обеспечивая многопроцессорность и безопасное взаимодействие между процессами.
(наверх)
(наверх)
Какие структуры данных в Python можно использовать для реализации очередей и стеков, и как они работают?
(наверх)
(наверх)
(наверх)
(наверх)
Что такое "Big O notation", и как она используется для анализа производительности алгоритмов и структур данных в Python?
(наверх)
- Что такое GIL?
- Многопоточные и многопроцессорные программы в python
- Разница между потоками и процессами
- Что такое условия гонки и потокобезопасность?
- Алгоритм планирования доступа потоков к общим данным
(наверх)
В Python используется глобальная блокировка интерпретатора (Global Interpreter Lock — GIL), накладывающая некоторые ограничения на потоки. А именно, нельзя использовать несколько процессоров одновременно. Блокировка, позволяет только одному потоку управлять интерпретатором Python. Это означает, что в любой момент времени будет выполняться только один конкретный поток. Из этого следует, что с потоками невозможно использовать несколько ядер процессора.
GIL был введен в Python потому, что управление памятью CPython не является потокобезопасным. Имея такую блокировку Python может быть уверен, что никогда не будет условий гонки.
Во многопоточных программах отсутствие GIL может негативно сказываться на производительности процессоро-зависымых программ.
Python подсчитывает количество ссылок для корректного управления памятью. Это означает, что созданные в Python объекты имеют переменную подсчёта ссылок, в которой хранится количество всех ссылок на этот объект. Как только эта переменная становится равной нулю, память, выделенная под этот объект, освобождается.
(наверх)
Параллелизм дает возможность работать над несколькими вычислениями одновременно в одной программе. Такого поведения в Python можно добиться несколькими способами:
- Используя многопоточность
threading
, позволяя нескольким потокам работать по очереди. - Используя несколько ядер процессора
multiprocessing
. Делать сразу несколько вычислений, используя несколько ядер процессора. Это и называется параллелизмом. - Используя асинхронный ввод-вывод с модулем
asyncio
. Запуская какую то задачу, продолжать делать другие вычисления, вместо ожидания ответа от сетевого подключения или от операций чтения/записи.
(наверх)
Поток threading
- это независимая последовательность выполнения каких то вычислений. Поток thread
делит выделенную память ядру процессора, а так же его процессорное время со всеми другими потоками, которые создаются программой в рамках одного ядра процессора. Программы на языке Python имеют, по умолчанию, один основной поток. Можно создать их больше и позволить Python переключаться между ними. Это переключение происходит очень быстро и кажется, что они работают параллельно.
Понятие процесс в multiprocessing
- представляет собой так же независимую последовательность выполнения вычислений. В отличие от потоков threading
, процесс имеет собственное ядро и следовательно выделенную ему память, которое не используется совместно с другими процессами. Процесс может клонировать себя, создавая два или более экземпляра в одном ядре процессора.
Асинхронный ввод-вывод не является ни потоковым (threading
), ни многопроцессорным (multiprocessing
). По сути, это однопоточная, однопроцессная парадигма и не относится к параллельным вычислениям.
(наверх)
Состояние гонки возникает, когда несколько потоков могут одновременно получать доступ к общей структуре данных или местоположению в памяти и изменять их, в следствии чего могут произойти непредсказуемые вещи.
Если два пользователя одновременно редактируют один и тот же документ онлайн и второй пользователь сохранит данные в базу, то перезапишет работу первого пользователя. Чтобы избежать условий гонки, необходимо заставить второго пользователя ждать, пока первый закончит работу с документом и только после этого разрешить второму пользователю открыть и начать редактировать документ.
Потокобезопасность работает путем создания копии локального хранилища в каждом потоке, чтобы данные не сталкивались с другим потоком.
(наверх)
Потоки используют одну и ту же выделенную память. Когда несколько потоков работают одновременно, то нельзя угадать порядок, в котором потоки будут обращаются к общим данным. Результат доступа к совместно используемым данным зависит от алгоритма планирования. который решает, какой поток и когда запускать. Если такого алгоритма нет, то конечные данные могут быть не такими как ожидаешь.
- Как в питоне обстоят дела с памятью (управлением памятью)
- Сколько стоит проверка элемента в нотации?
- Если есть два объекта и они указывают друг на друга
(наверх)
(наверх)
Управление памятью в Python происходит автоматически, благодаря сборщику мусора, который удаляет объекты, которые больше не используются, для освобождения памяти.
Python использует два основных механизма управления памятью: ссылочный подсчет и сборку мусора.
-
Ссылочный подсчет - это механизм, который считает количество ссылок на каждый объект в памяти. Когда количество ссылок на объект становится равным нулю, объект удаляется.
-
Сборка мусора - это процесс, который автоматически освобождает память, занятую объектами, которые больше не используются. Сборщик мусора ищет объекты, которые не могут быть достигнуты ни из одной переменной, и удаляет их.
В Python есть также модуль gc
, который предоставляет функции для контроля над сборщиком мусора. Например, можно задать максимальный размер кучи (heap
), после которого сборщик мусора будет вызываться. Также можно использовать функцию gc.collect()
, чтобы явно вызвать сборку мусора вручную.
Python также имеет функцию sys.getsizeof()
, которая позволяет получить размер объекта в байтах. Она может быть полезна для оптимизации использования памяти в программе.
Кроме того, в Python есть возможность использовать различные библиотеки для управления памятью, такие как memory_profiler
и objgraph
, которые позволяют анализировать использование памяти в программе и находить утечки памяти.
(наверх)
Проверка элемента в Python зависит от типа объекта, в котором нужно произвести проверку, и используемого метода проверки.
В общем случае, проверка элемента в Python происходит достаточно быстро и может быть выполнена за константное время O(1), когда элемент находится в списке (list) или словаре (dictionary), и за линейное время O(n), когда элемент нужно найти в кортеже (tuple) или множестве (set).
Например, для проверки наличия элемента в списке используется оператор in
или метод list.count()
. Оператор in
возвращает True
, если элемент присутствует в списке, и False
в противном случае. Метод list.count()
возвращает количество вхождений элемента в список.
Для проверки наличия элемента в словаре используется метод dict.get()
или оператор in
. Оператор in
проверяет наличие ключа в словаре и возвращает True
, если ключ присутствует, и False
в противном случае. Метод dict.get()
возвращает значение, связанное с указанным ключом, если ключ присутствует в словаре, и None
в противном случае.
Для проверки наличия элемента в кортеже используется оператор in
. Оператор in
проверяет наличие элемента в кортеже и возвращает True
, если элемент присутствует, и False
в противном случае. Поскольку кортежи являются неизменяемыми, поиск элемента может занять линейное время O(n), где n - размер кортежа.
Для проверки наличия элемента в множестве используется, угадайте какой оператор - in
. Множества в Python реализованы в виде хэш-таблиц, поэтому проверка наличия элемента занимает константное время O(1), если элемент находится в множестве.
Также стоит учитывать, что при работе с большими коллекциями данных, время проверки элементов может увеличиваться, и может потребоваться оптимизация алгоритма.
(наверх)
Если в Python есть два объекта, которые указывают друг на друга, то такая ситуация называется циклической ссылкой или циклической зависимостью.
Циклическая ссылка возникает, когда объект A содержит ссылку на объект B, а объект B содержит ссылку на объект A. Например:
a = [1, 2]
b = [3, 4]
a.append(b)
b.append(a)
В этом случае объект a
содержит ссылку на объект b
через метод append()
, а объект b
содержит ссылку на объект a
через тот же метод.
Когда происходит циклическая ссылка, то сборщик мусора Python не может автоматически удалить эти объекты из памяти, потому что они по-прежнему имеют ссылки друг на друга и, следовательно, остаются взаимозависимыми.
Для решения этой проблемы Python использует алгоритм под названием "сборка мусора с подсчетом ссылок". Сборщик мусора проверяет, сколько ссылок ведет к каждому объекту в памяти. Если на объект не указывает ни одна ссылка, то он автоматически удаляется из памяти.
Однако, если объекты создают циклическую ссылку, то каждый объект в цикле имеет хотя бы одну ссылку на себя, что препятствует сборке мусора. В этом случае для удаления объектов из памяти требуется использовать специальные методы, такие как gc.collect()
, которые вызывают сборку мусора вручную и позволяют обойти проблему циклических ссылок.
Кроме того, при использовании объектов с циклическими ссылками важно следить за использованием памяти, чтобы избежать утечек памяти, когда объекты не удаляются из-за взаимной ссылки.
(наверх)
- Что такое итератор?
- Что такое генератор?
- Что такое декоратор?
- Какие декораторы стандартной библиотеки вы знаете?
- Что такое list comprehension, какой синтаксис создания генераторов?
- Является ли range итератором?
(наверх)
Итераторы — объекты, которые позволяют обходить коллекции. Коллекции не должны обязательно существовать в памяти и быть конечными.
Итерируемый — объект, в котором есть метод __iter__
. В свою очередь, итератор — объект, в котором есть два метода: __iter__
и __next__
. Почти всегда iterator
возвращает себя из метода __iter__
, так как они выступают итераторами для самих себя, но есть исключения.
В целом стоит избегать прямого вызова __iter__
и __next__
. При использовании for
или генераторов списков Python вызывает эти методы сам. Если всё-таки необходимо вызвать методы напрямую, лучше использовать встроенные функции iter
и next
и в параметрах передаём итератор или контейнер. Например, если c
— итерируемый, используемiter(c)
вместо c.__iter__()
. Если a
— итератор, используем next(a)
, а не a.__next__()
. Это похоже на использование len
.
Раз уж речь зашла о len
, то стоит упомянуть, что итераторы не должны иметь и часто не имеют определённой длины. Поэтому в них часто нет имплементации __len__
. Чтобы подсчитать количество элементов в итераторе, приходится делать это вручную или использовать sum
.
Некоторые итерируемые (iterable)
не являются итераторами, но используют другие объекты как итераторы. Например, объект list
относится к итерируемым, но не является итератором. В нём реализован метод __iter__
, но отсутствует метод __next__
. Итераторы объектов list
относятся к типу listiterator
. У объектов list
есть определённая длина, а у listiterator
нет.
>>> a = [1, 2]
>>> type(a)
<type 'list'>
>>> type(iter(a))
<type 'listiterator'>
>>> it = iter(a)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> len(a)
2
>>> len(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'listiterator' has no len()
Когда итератор завершает работу, интерпретатор Python ожидает возбуждения исключения StopIteration
. Однако, итераторы могут работать с бесконечными множествами. В таких случаях надо позаботиться о выходе из цикла.
Пример итератора - считает с нуля до бесконечности. Это упрощённая версия itertools.count
.
class count_iterator:
n = 0
def __iter__(self):
return self
def __next__(self):
y = self.n
self.n += 1
return y
Пример использования. В последней строке сделана попытка превратить итератор в список. Это приводит к бесконечному циклу.
>>> counter = count_iterator()
>>> next(counter)
0
>>> next(counter)
1
>>> next(counter)
2
>>> next(counter)
3
>>> list(counter) # Бесконечный цикл
Если у объекта нет метода __iter__
, его можно обойти, если определить метод __getitem__
. В этом случае встроенная функция iter возвращает итератор с типом iterator
, который использует __getitem__
для обхода элементов списка. Этот метод возвращает StopIteration
или IndexError
, когда обход завершается. Пример:
class SimpleList(object):
def __init__(self, *items):
self.items = items
def __getitem__(self, i):
return self.items[i]
И пример использования:
>>> a = SimpleList(1, 2, 3)
>>> it = iter(a)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Ещё один интересный пример: генерация последовательности Хофштадтера. В приведённом ниже коде итератор используется для генерации последовательности с помощью вложенных повторений.
Q(n)=Q(n−Q(n−1))+Q(n−Q(n−2))
Например, qsequence([1, 1])
генерирует точную последовательность Хофштадтера. Мы используем исключение StopIteration
, чтобы показать, что последовательность не может продолжаться, так как для генерации следующего элемента должен использоваться несуществующий индекс. Если в параметрах укзать значения [1, 2], последовательность немедленно заканчивается.
class qsequence:
def __init__(self, s):
self.s = s[:]
def __next__(self):
try:
q = self.s[-self.s[-1]] + self.s[-self.s[-2]]
self.s.append(q)
return q
except IndexError:
raise StopIteration()
def __iter__(self):
return self
def current_state(self):
return self.s
Пример использования:
>>> Q = qsequence([1, 1])
>>> next(Q)
2
>>> next(Q)
3
>>> [next(Q) for __ in range(10)]
[3, 4, 5, 5, 6, 6, 6, 8, 8, 8]
(наверх)
Генераторами называют итераторы, определение которых выглядит как определение функций.
Ещё одно определение: генераторы — функции, которые внутри используют выражение yield
. Генераторы не могут возвращать значения, вместо этого выдают элементы по готовности. Python автоматизирует запоминание контекста генератора, то есть текущий поток управления, значение локальных переменных и так далее. Каждый вызов метода __next__
у объекта генератора возвращает следующее значение. Метод __iter__
также реализуется автоматически. То есть генераторы можно использовать везде, где требуются итераторы.
def count_generator():
n = 0
while True:
yield n
n += 1
Как это применяется на практике.
>>> counter = count_generator()
>>> counter
<generator object count_generator at 0x106bf1aa0>
>>> next(counter)
0
>>> next(counter)
1
>>> iter(counter)
<generator object count_generator at 0x106bf1aa0>
>>> iter(counter) is counter
True
>>> type(counter)
<type 'generator'>
Теперь посмотрим на реализацию последовательности Q Хофштадтера с помощью генератора. Заметьте, эта реализация значительно проще использованного выше подхода. Однако здесь уже невозможно использовать методы типа current_state. Извне невозможно получить доступ к переменным, которые хранятся в контексте генератора.
Существует словарь gi_frame.f_locals
, но он относится к CPython, но не входит в стандарт языка Python.
Одно из возможных решений — получение одновременно списка и результата.
def hofstadter_generator(s):
a = s[:]
while True:
try:
q = a[-a[-1]] + a[-a[-2]]
a.append(q)
yield q
except IndexError:
Return
Итерация в данном примере завершается простым return
без параметров. Внутри происходит возбуждение исключения StopIteration
. Следующий пример связан с распределением Бернулли, которое реализуется с помощью двух генераторов. Речь идёт о бесконечной последовательности случайных булевых значений. При этом вероятность True
равна p
, а вероятность False
определяется формулой q=1-p
. Затем применяется экстрактор фон Неймана, который принимает процесс Бернулли с 0 < p < 1
как источник энтропии и возвращает чистый процесс Бернулли с p = 0.5
import random
def bernoulli_process(p):
if p > 1.0 or p < 0.0:
raise ValueError("p should be between 0.0 and 1.0.")
while True:
yield random.random() < p
def von_neumann_extractor(process):
while True:
x, y = next(proccess), next(process)
if x != y:
yield x
C помощью генераторов удобно реализовывать дискретные динамические системы. Пример ниже показывает, как с помощью генераторов реализуется отображение тент.
>>> def tent_map(mu, x0):
... x = x0
... while True:
... yield x
... x = mu * min(x, 1.0 - x)
...
>>>
>>> t = tent_map(2.0, 0.1)
>>> for __ in range(30):
... print(next(t))
...
0.1
0.2
0.4
0.8
0.4
0.8
0.4
0.8
0.4
0.8
0.4
0.8
0.4
0.8
0.4
0.8
0.4
0.799999999999
0.400000000001
0.800000000003
0.399999999994
0.799999999988
0.400000000023
0.800000000047
0.399999999907
0.799999999814
0.400000000373
0.800000000745
0.39999999851
0.79999999702
Ещё один пример касается последовательности Коллатца.
def collatz(n):
yield n
while n != 1:
n = n / 2 if n % 2 == 0 else 3 * n + 1
yield n
В этом примере не нужно вручную использовать StopIteration
. Это исключение срабатывает автоматически, когда поток управления достигает конца функции.
Пример использования генератора:
>>> # Если гипотеза Коллатца верна, list(collatz(n)) с любым n
... # всегда завершается
>>> list(collatz(7))
[7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
>>> list(collatz(13))
[13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
>>> list(collatz(17))
[17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
>>> list(collatz(19))
[19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
(наверх)
Декоратор в Python - это функция, которая принимает другую функцию в качестве аргумента, добавляет какое-то поведение к этой функции и возвращает ее. Декораторы используются для модификации поведения функций без изменения их исходного кода.
Декораторы в Python реализованы в виде обычных функций, которые обычно имеют имя, начинающееся с символа @
, за которым следует имя декоратора. Например:
@decorator_function
def some_function():
# ...
Здесь decorator_function
- это имя декоратора, который будет применен к функции some_function()
. Обратите внимание, что перед именем функции стоит символ @
.
Примеры использования декораторов:
Декоратор, который измеряет время выполнения функции:
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Time taken: {end_time - start_time:.6f} seconds")
return result
return wrapper
@timer_decorator
def some_function():
time.sleep(2)
return "Done"
Здесь timer_decorator
- это декоратор, который добавляет функциональность для измерения времени выполнения функции. Он принимает функцию в качестве аргумента, определяет внутри себя функцию-обертку wrapper, которая вызывает переданную функцию и измеряет время ее выполнения. Функция-обертка затем возвращает результат выполнения переданной функции.
Декоратор, который кэширует результаты функции:
def cache_decorator(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cache_decorator
def fibonacci(n):
if n in [0, 1]:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
Здесь cache_decorator
- это декоратор, который кэширует результаты выполнения функции. Он определяет функцию-обертку wrapper, которая проверяет, есть ли результат выполнения функции с заданными аргументами в кэше. Если результат уже есть в кэше, то он возвращается, иначе вызывается переданная функция, результат ее выполнения сохраняется в кэше и возвращается.
Таким образом, декораторы в Python позволяют добавлять дополнительную функциональность к существующим функциям без изменения их исходного кода. Это делает код более читаемым и удобным для использования.
(наверх)
Стандартная библиотека Python содержит множество полезных декораторов. Ниже перечислены некоторые из них:
-
functools.lru_cache()
- декоратор для кэширования результатов функции с использованием алгоритма LRU (Least Recently Used). -
functools.singledispatch()
- декоратор для создания полиморфной функции, которая вызывает различные реализации функции в зависимости от типа аргумента. -
contextlib.contextmanager()
- декоратор для создания менеджера контекста, который позволяет использовать блок with для выполнения операций до и после выполнения блока кода. -
unittest.skipIf()
иunittest.skipUnless()
- декораторы для пропуска тестов, если заданное условие истинно или ложно соответственно. -
asyncio.coroutine()
иasyncio.ensure_future()
- декораторы для определения и запуска асинхронных функций в asyncio. -
property
- декоратор для определения атрибутов класса как свойств, которые вычисляются при доступе к ним. -
staticmethod
- декоратор для определения статических методов класса, которые не требуют доступа к экземпляру класса. -
classmethod
- декоратор для определения методов класса, которые принимают класс в качестве первого аргумента. -
functools.partial()
- функция-фабрика для создания новой функции, которая является частичным применением другой функции с заданными аргументами. -
wrapt.decorator()
- библиотека, содержащая декораторы для создания оберток вокруг функций с поддержкой множества возможностей, таких как поддержка декораторов, передача аргументов и т.д.
Это лишь некоторые примеры декораторов из стандартной библиотеки Python. Большинство из них используются для создания более чистого, понятного и эффективного кода.
(наверх)
List comprehension - это способ создания нового списка на основе существующего списка (или другой итерируемой последовательности), используя синтаксис, который позволяет применять операции и фильтры к элементам списка. Это удобный способ создания списков с минимальным объемом кода.
Пример создания списка с помощью list comprehension:
numbers = [1, 2, 3, 4, 5]
squares = [x**2 for x in numbers]
print(squares) # Output: [1, 4, 9, 16, 25]
Это эквивалентно следующему коду с использованием цикла for:
numbers = [1, 2, 3, 4, 5]
squares = []
for x in numbers:
squares.append(x**2)
print(squares) # Output: [1, 4, 9, 16, 25]
Также существует генератор, который работает похожим образом, но вместо создания списка генерирует значения по мере необходимости. Генераторы создаются с помощью генераторного выражения, которое заключается в круглые скобки вместо квадратных скобок в случае с list comprehension.
Пример создания генератора с помощью генераторного выражения:
numbers = [1, 2, 3, 4, 5]
squares = (x**2 for x in numbers)
for num in squares:
print(num) # Output: 1, 4, 9, 16, 25
Это эквивалентно следующему коду с использованием цикла for и функции-генератора:
def squares(nums):
for x in nums:
yield x**2
numbers = [1, 2, 3, 4, 5]
squares_gen = squares(numbers)
for num in squares_gen:
print(num) # Output: 1, 4, 9, 16, 25
Генераторы обычно используются для работы с большими объемами данных, когда нет необходимости хранить все значения в памяти, и когда требуется ленивое вычисление результатов.