Интересности и полезности python. Часть 3
В таких языках как C++ есть переменные, хранящиеся на стеке и в динамической памяти. При вызове функции мы помещаем все аргументы на стек, после чего передаём управление функции. Она знает размеры и смещения переменных на стеке, соответственно может их правильно интерпретировать. При этом у нас есть два варианта: скопировать на стек память переменной или положить ссылку на объект в динамической памяти (или на более высоких уровнях стека). Очевидно, что при изменении значений на стеке функции, значения в динамической памяти не поменяются, а при изменении области памяти по ссылке, мы модифицируем общую память, соответственно все ссылки на эту же область памяти «увидят» новое значение.
В python отказались от подобного механизма, заменой служит механизм связывания (assignment) имени переменной с объектом, например при создании переменной: var = "john"
Интерпретатор создаёт объект «john» и «имя» var, а затем связывает объект с данным именем. При вызове функции, новых объектов не создаётся, вместо этого в её области видимости создаётся имя, которое связывается с существующим объектом. Но в python есть изменяемые и неизменяемые типы. К первым, например, относятся числа: при арифметических операциях существующие объекты не меняются, а создаётся новый объект, с которым потом связывается существующее имя. Если же со старым объектом после этого не связано ни одного имени, оно будет удалено с помощью механизма подсчёта ссылок. Если же имя связано с переменной изменяемого типа, то при операциях с ней изменяется память объекта, соответственно все имена, связанные с данной областью памяти «увидят» изменения.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Когда вы определяете функцию внутри другой функции и используете локальные переменные внешней функции во вложенной, вы создаете замыкание. Время жизни этих переменных "продляется" в особой области видимости enclosing даже после завершения работы внешней функции. Пример: make_adder возвращает функцию-прибавлятор. Объект из переменной a будет жить и работать даже после выхода из make_adder:
def make_adder(a):
def adder(x):
return a + x
return adder
plus_5 = make_adder(5)
print(plus_5(3)) # 8
Здесь я хочу коснуться одной популярной проблемы. Дело в том, что если мы создадим несколько функций внутри одного контекста, то они будут разделять одну область видимости enclosing. Рассмотрим пример создания трех функций в цикле:
def make_adders():
adders = []
for a in range(3):
def adder(x):
return a + x
adders.append(adder)
return adders
adders = make_adders()
for adder in adders:
print(adder(2)) # 4 4 4
Вместо функций прибавляющих разные числа от 0 до 2, мы получили 3 одинаковых функции, потому что внутри себя они поддерживают ссылку на одну и ту же переменную
a
, значение которой останется равным 2 после выполнения всего цикла целиком.Есть простой прием, помогающий "зафиксировать" значения переменной в моменте: достаточно добавить во вложенную функцию дополнительный аргумент со значением по умолчанию, равным нужной переменной
a=a
:def make_adders():
adders = []
for a in range(3):
def adder(x, a=a): # FIX!
return a + x
adders.append(adder)
return adders
adders = make_adders()
for adder in adders:
print(adder(2)) # 2 3 4
Еще лучше переименовать аргумент, чтобы избежать конфликтов имен и замечаний IDE, например, так:
def adder(x, that_a=a): # FIX!
return that_a + x
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Итерабельный объект (в оригинальной терминологии – «iterable») – это объект, который может возвращать значения по одному за раз. Примеры: все контейнеры и последовательности (списки, строки и т.д.), файлы, а также экземпляры любых классов, в которых определён метод
__iter__()
или __getitem__()
. Итерабельные объекты могут быть использованы внутри цикла for, а также во многих других случаях, когда ожидается последовательность (функции sum(), zip(), map() и т.д.).Подробнее:
Рассмотрим итерируемый объект (Iterable). В стандартной библиотеке он объявлен как абстрактный класс collections.abc.Iterable:
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
У него есть абстрактный метод __iter__
который должен вернуть объект итератора. И метод __subclasshook__
который проверяет наличие у класса метод __iter__.
Таким образом, получается, что итерируемый объект это любой объект который реализует метод __iter__
class SomeIterable1(collections.abc.Iterable):
def __iter__(self):
pass
class SomeIterable2:
def __iter__(self):
pass
print(isinstance(SomeIterable1(), collections.abc.Iterable))
# True
print(isinstance(SomeIterable2(), collections.abc.Iterable))
# True
Но есть один момент, это функция iter()
. Именно эту функцией использует например цикл for для получения итератора. Функция iter() в первую очередь для получения итератора из объекта, вызывает его метод __iter__.
Если метод не реализован, то она проверяет наличие метода __getitem__
и если он реализован, то на его основе создается итератор. __getitem__
должен принимать индекс с нуля. Если не реализован ни один из этих методов, тогда будет вызвано исключение TypeError
.from string import ascii_letters
class SomeIterable3:
def __getitem__(self, key):
return ascii_letters[key]
for item in SomeIterable3():
print(item)
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
В зависимости от контекста, может означать либо функцию-генератор, либо итератор генератора (чаще всего, последнее). Методы
__iter__ и __next__
у генераторов создаются автоматически.С точки зрения реализации, генератор в Python — это языковая конструкция, которую можно реализовать двумя способами: как функция с ключевым словом yield или как генераторное выражение. В результате вызова функции или вычисления выражения, получаем объект-генератор типа types.GeneratorType. Канонический пример - генератор, порождающий последовательность чисел Фибоначчи, которая, будучи бесконечна, не смогла бы поместиться ни в одну коллекцию. Иногда термин применяется для самой генераторной функции, а не только объекта, возвращенного ей в качестве результата.
Так как в объекте-генераторе определены методы next и iter, то есть реализован протокол итератора, с этой точки зрения, в Python любой генератор является итератором.
Когда выполнение функции-генераторы завершается (при помощи ключевого слова return или достижения конца функции), возникает исключение StopIteration.
Что такое генераторная функция
Генераторная функция - функция, в теле которой встречается ключевое слово yield. Будучи вызвана, такая функция возвращает объект-генератор (generator object) (итератор генератора (generator iterator)).
Что делает yield
yield замораживает состояние функции-генератора и возвращает текущее значение. После следующего вызова __next__() функция-генератор продолжает своё выполнение с того места, где она была приостановлена.
В чем отличие [x for x in y] от (x for x in y)
Первое выражение возвращает список (списковое включение), второе – генератор.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Генератор хранит в памяти не все элементы, а только внутреннее состояние для вычисления очередного элемента. На каждом шаге можно вычислить только следующий элемент, но не предыдущий. Пройти генератор в цикле можно только один раз.
Как объявить генератор
использовать синтаксис (x for x in seq)
оператор yield в теле функции вместо return
встроенная функция iter, которая вызывает у объекта метод __iter__(). Этот метод должен возвращать генератор.
Как получить из генератора список
Передать его в конструктор списка: list(x for x in some_seq). Важно, что после этого по генератору уже нельзя будет итерироваться.
Что такое подгенератор
В Python 3 существуют так называемые подгенераторы (subgenerators). Если в функции-генераторе встречается пара ключевых слов
yield from
, после которых следует объект-генератор, то данный генератор делегирует доступ к подгенератору, пока он не завершится (не закончатся его значения), после чего продолжает своё исполнение.На самом деле
yield
является выражением. Оно может принимать значения, которые отправляются в генератор. Если в генератор не отправляются значения, результат данного выражения равен None.yield from
также является выражением. Его результатом является то значение, которое подгенератор возвращает в исключении StopIteration (для этого значение возвращается при помощи ключевого слова return).@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Можно ли извлечь элемент генератора по индексу
Нет, будет ошибка. Генератор не поддерживает метод getitem.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
MRO – method resolution order, порядок разрешения методов. Алгоритм, по которому следует искать метод в случае, если у класса два и более родителей.
В классических классах поиск при наследовании по ссылкам на имена осуществляется в следующем порядке:
1. Сначала экземпляр
2. Затем его класс
3. Далее все суперклассы его класса с обходом сначала с глубину, а затем слева направо
Используется первое обнаруженное вхождение. Такой порядок называется DFLR (Обход вглубину и слева направо).
При наследовании классов нового стиля применяется правило MRO (порядок разрешения методов), т.е линеаризованный обход дерева классов, причем вложенный элемент наследования становится доступным в атрибуте mro данного класса. Такой алгорим называется C3-линеаризация. Наследование по правилу MRO осуществляется приблизительно в следующем порядке.
Перечисление всех классов, наследуемых экземпляром, по правилу поиска DFLR для классических классов, причем класс включается в результат поиска столько раз, сколько он встречается при обходе.
Просмотр в полученном списке дубликатов классов, из которых удаляются все, кроме последнего (крайнего справа) дубликата в списке.
Упорядочение по правилу MRO применяется при наследовании и вызове встроенной функции super(), которая всегда вызывает следующий по правилу MRO класс (относительно точки вызова).
Пример наследования в неромбовидных иерархаических деревьях:
class attr = 3 # D:3 E:2
class B(D) pass # | |
class E: attr = 2 # B C:1
class C(E): attr = 1 # / /
class A(B, C): pass # A
X = A() # |
print(X.attr) # X
# DFLR = [X, A, B, D, C, E]
# MRO = [X, A, B, D, C, E, object]
# И в версии 3.х и в версии 2.х (всегда) выводит строку "3"
Пример наследования в ромбовидных иерархаических деревьях:
class attr = 3 # D:3 D:3
class B(D) pass # | |
class C(D): attr = 1 # B C:1
class A(B, C): pass # / /
X = A() # A
print(X.attr) # |
# X
# DFLR = [X, A, B, D, C, D]
# MRO = [X, A, B, C, D, object] (сохраняет только последний дубликат D)
# Выводит строку "1" в версии 3.х, строку "3" в версии 2.х ("1" если D(object))
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Основное различие между этими двумя методами состоит в том, что
__new__
обрабатывает создание объекта, а __init__
обрабатывает его инициализацию.__new__
вызывается автоматически при вызове имени класса (при создании экземпляра), тогда как __init__
вызывается каждый раз, когда экземпляр класса возвращается __new__
, передавая возвращаемый экземпляр в __init__
в качестве параметра self, поэтому даже если вы сохранили экземпляр где-нибудь глобально/статически и возвращали его каждый раз из __new__
, для него все-равно будет каждый раз вызываться __init__
.Из вышесказанного вытекает что сначала вызывается
__new__
, а потом __init__
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
В Python 3 при возбуждении исключения в блоке except, старое исключение сохраняется в атрибуте данных context и если новое исключение не обработано, то будет выведена информация о том, что новое исключение возникло при обработке старого («During handling of the above exception, another exception occurred:»). Также, можно связывать исключения в одну цепь или заменять старые новыми. Для этого используется конструкция raise новое_исключение from старое_исключение либо raise новое_исключение from None. В первом случае указанное исключение сохраняется в атрибуте
__cause__
и атрибут __suppress_context__
(который подавляет вывод исключения из __context__)
устанавливается в True. Тогда, если новое исключение не обработано, будет выведена информация о том, что старое исключение является причиной нового («The above exception was the direct cause of the following exception:»). Во втором случае __suppress_context__
устанавливается в True и __cause__
в None. Тогда при выводе исключения оно, фактически, будет заменено новым (хотя старое исключение всё ещё хранится в __context__)
.В Python 2 нет сцепления исключений. Любое исключение, выброшенное в блоке except, заменяет старое.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Прикрепляем два XML (1 файл, 2 файл) – это ответы на поисковые запросы, сделанные к одному из наших партнёров. В ответах лежат варианты перелётов (тег Flights) со всей необходимой информацией, чтобы отобразить билет на Aviasales.
На основе этих данных, нужно сделать вебсервис, в котором есть эндпоинты, отвечающие на следующие запросы:
- Какие варианты перелёта из DXB в BKK мы получили?
- Самый дорогой/дешёвый, быстрый/долгий и оптимальный варианты
- В чём отличия между результатами двух запросов (изменение маршрутов/условий)?
Язык реализации: python3 Формат ответа: json Используемые библиотеки и инструменты — всё на твой выбор.
Оценивать будем умение выполнять задачу имея неполные данные о ней, умение самостоятельно принимать решения и качество кода.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
type это метакласс, который Питон внутренне использует для создания всех классов.
Когда вы пишете:
class Foo(Bar):
pass
Питон делает следующее:
- Есть ли у класса
Foo
атрибут __metaclass__
?- Если да, создаёт в памяти объект-класс с именем Foo, используя то, что указано в
__metaclass__.
- Если Питон не находит metaclass, он ищет
__metaclass__
в родительском классе Bar и попробует сделать то же самое.- Если же
__metaclass__
не находится ни в одном из родителей, Питон будет искать __metaclass__
на уровне модуля.- И если он не может найти вообще ни одного
__metaclass__
, он использует type для создания объекта-класса.@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет! Меня зовут Руслан. Около 12 лет я занимаюсь разработкой, из них девять — на Python. За это время я собеседовался на разные позиции десятки раз и сам провёл примерно пару сотен собеседований. Не всегда успешно :/ В этой статье поговорим о том, как снизить вероятность провалов и к чему быть готовым.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
- перехватить создание класса
- изменить класс
- вернуть модифицированный
Зачем вообще использовать метаклассы
Основное применение метаклассов это создание API. Типичный пример — Django ORM.
Она позволяет написать что-то в таком духе:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Однако если вы выполните следующий код:
guy = Person(name='bob', age='35')
print guy.age
вы получите не
IntegerField
, а int, причём значение может быть получено прямо из базы данных.Это возможно, потому что models.Model определяет
__metaclass__
, который сотворит некую магию и превратит класс Person, который мы только что определили простым выражением в сложную привязку к базе данных.Django делает что-то сложное выглядящее простым, выставляя наружу простой API и используя метаклассы, воссоздающие код из API и незаметно делающие всю работу.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Неявная типизация, латентная типизация или утиная типизация (англ. Duck typing) – вид динамической типизации, применяемой в некоторых языках программирования (Perl, Smalltalk, Python, Objective-C, Ruby, JavaScript, Groovy, ColdFusion, Boo, Lua, Go, C#), когда границы использования объекта определяются его текущим набором методов и свойств, в противоположность наследованию от определённого класса. То есть считается, что объект реализует интерфейс, если он содержит все методы этого интерфейса, независимо от связей в иерархии наследования и принадлежности к какому-либо конкретному классу.
Утиная типизация решает такие проблемы иерархической типизации, как:
- невозможность явно указать (путём наследования) на совместимость интерфейса со всеми настоящими и будущими интерфейсами, с которыми он идейно совместим;
-экспоненциальное увеличение числа связей в иерархии типов при хотя бы частичной попытке это сделать.
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Условие:
Ваша задача — написать функцию, которая принимает неограниченное количество списков и возвращает только те элементы, что есть в каждом списке.
Пример:
find_values([11, 10, 3], [10, 3, 5, 11], [11, 10]) -> [11, 10]
find_values([8, 4, 7, "hi"], [8, "hi"], [4, "hi"]) -> ['hi']
find_values([1, 4, 3], [6, 2, 8], ["4", "hi"]) -> []
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Условие:
Ваша задача — написать функцию, которая проверит, все ли значения увеличиваются на один
Пример:
[-1, 0, 1, 2, 3] -> True
[-1, 0, 1, 3, 4]) -> False
[0, 1] -> True
[1, 0] -> False
#задача_с_собеседования
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Большинство исследователей данных пишут много кода, поэтому такой список пригодится и дата-сайентистам, и инженерам. Он будет полезен и для соискателей, и для тех, кто проводит собеседования, и для тех, кто просто изучает Python.
@machinelearning_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
На шахматной доске 8 х 8 стоит ферзь. Отметьте положение ферзя на доске и все клетки, которые бьет ферзь. Клетку, где стоит ферзь, отметьте буквой Q, клетки, которые бьет ферзь, отметьте звездочками *, остальные клетки заполните точками. Шахматный ферзь может ходить по вертикали, горизонтали и по диагоналям.
Входные данные:
Координаты ферзя на шахматной доске в формате номер столбца (буква от a до h, слева направо) и номер строки (цифра от 1 до 8, снизу вверх).
Пример ввода:
c4
Выходные данные:
Программа выводит стилизованное изображение шахматной доски со схемой возможных передвижений ферзя.
Пример вывода:
. . * . . . * .
. . * . . * . .
* . * . * . . .
. * * * . . . .
* * Q * * * * *
. * * * . . . .
* . * . * . . .
. . * . . * . .
Решение
Способ 1:
x, y, board = *('abcdefgh87654321'.index(i) % 8 for i in input()), range(8)
[print(*['?Q**.'[len({j - x, x - j, i - y, y - i})] for j in board]) for i in board]
Способ 2:
x, y = ('abcdefgh87654321'.index(i) % 8 for i in input())
directions = lambda i, j: (j - i == x - y) + (j + i == x + y) + ((j == x) != (i == y))
[print(*['.*Q'[directions(i, j)] for j in range(8)]) for i in range(8)]
Способ 3:
coor = input()
board = [['.'] * 8 for _ in range(8)]
y, x = 8 - int(coor[1]), ord(coor[0]) - 97
for i in range(8):
for j in range(8):
if (y == i) or (x == j) or abs(y - i) == abs(x - j):
board[i][j] = '*'
board[y][x] = 'Q'
for line in board:
print(*line)
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
Это вариант классической задачи Иосифа Флавия. В кругу стоят n человек, пронумерованных числами от 1 до n. Начинается расчет, при котором каждый k-й по счету человек выбывает из круга, после чего счет продолжается со следующего за ним человека. Напишите программу, определяющую номер человека, который останется в кругу последним.
Входные данные:
Числа n и k на отдельных строках.
#Пример ввода
9 3
Выходные данные:
Номер последнего оставшегося человека.
#Пример вывода
1
Решение
Способ 1:
n, k = int(input()), int(input())
last = 0
for i in range(1, n + 1):
last = (last + k) % i
print(last + 1)
Способ 2 – рекурсия:
def lastSurvivor(n, k):
if n == 1:
return 1
elif n > 1:
return (1 + (lastSurvivor(n - 1, k) + k - 1) % n)
n, k = int(input()), int(input())
print(lastSurvivor(n, k))
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
В этой статье мы предложим практические задания для самостоятельного решения на Python. Практика написания кода – лучший способ, прокачать свои навыки.
10 Простых заданий + 10 сложных заданий.
✔️ Смотреть
@python_job_interview
Please open Telegram to view this post
VIEW IN TELEGRAM
✔️Задача
Вам будет дано число, и вам нужно будет вернуть его в виде строки в расширенной форме.
Примеры
expanded_form(12) # Должно вернуть
Примечание
Все числа должны быть целыми числами больше 0.
👇Свой вариант решения в комментарии
@python_job_interview
Вам будет дано число, и вам нужно будет вернуть его в виде строки в расширенной форме.
Примеры
expanded_form(12) # Должно вернуть
'10 + 2' expanded_form(42)
#Должно вернуть '40 + 2'
expanded_form(70304) # Должно вернуть '70000 + 300 + 4'
Примечание
Все числа должны быть целыми числами больше 0.
👇Свой вариант решения в комментарии
@python_job_interview