Python Lounge: работа и стажировки для программистов – Telegram
Python Lounge: работа и стажировки для программистов
3.91K subscribers
93 photos
1.17K links
Здесь можно найти интересующую вас работу и стажировки для программистов, а так же полезные статьи про Python.

Размещение вакансии только - @perezvonyubot

Ссылка на канал: @python_lounge

Мы входим в сеть promopoisk.com

Реклама: @adtgassetsbot
Download Telegram
Python packages

Предположим, вы разрабатываете большое приложение, которое включает в себя множество модулей. Очевидно, что складывать всё в одну папку не просто неудобно, а в некоторых случаях даже нерационально. Создадим для примера модуль main.py и рядом с ним пакет с модулями (папку с файлами) примерно в таком виде:

├── main.py
└── package
  ├── __init__.py
  ├── module1.py
  └── module2.py

Модуль __init__.py нужен для инициализации пакета, а также он вызывается при импорте пакета или модулей из него. Тем не менее, __init__.py может был пустым, достаточно его присутствия. Простыми словами, такой модуль нужен для того, работать с папкой как с Python-пакетом.

В main.py мы уже можем импортировать пакет и модули примерно такими способами:

import package
from package import module1
import package.module2 as m2

Предположим, что у нас есть уже какие-то функции в module1.py и module2.py. Теперь давайте напишем кое-что в __init__.py для примера:

from .module1 import function1
from . import module2 as m2

variable = 'hello from __init__.py'

Точка указывает на то, что мы импортируем модули из этого же пакета, а не откуда-то еще. Если указать без точки или просто import module2, то возникнет исключение.

Теперь в main.py мы можем выполнить следующие команды:

from package import function1
from package import m2
from package import variable

Возможности пакетов и модулей в Python этим не ограничиваются, но для вступления и ознакомления этой информации должно хватить.
Изменение списка во время итерации по нему

Предположим, что требуется удалить все нечетные числа из списка. Проблема со следующим кодом должна быть довольно очевидной:

odd = lambda x: bool(x % 2) # функция проверки числа на нечетность

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for i in range(10):
  if odd(numbers[i]):
    del numbers[i] # удаление элемента из списка во время итерации по нему

# Output:
Traceback (most recent call last):
if odd(numbers[i]):
IndexError: list index out of range

Удаление элемента из списка во время его итерации – это проблема, которая хорошо известна любому опытному разработчику.

К счастью, Python включает в себя ряд элегантных парадигм программирования, которые при правильном использовании могут привести к значительному упрощению и оптимизации кода. Одна из таких парадигм – list comprehensions. Генераторы списков часто используют именно для решение подобных проблем, что и показывает реализация кода ниже:

odd = lambda x: bool(x % 2) # функция проверки числа на нечетность
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

numbers = [n for n in numbers if not odd(n)] # генератор нового списка
print(numbers)

# Output:
[2, 4, 6, 8, 10]
Форматирование строк

В этом посте разберем доступные в Python методы форматирования строк. Для начала определим две переменные, которые потом и будем вставлять в строки.

name = 'Adrian'
age = 17

Самый древний и уже редко используемый способ – с помощью оператора %. Написание %s и %d зависит от того, что мы туда передаем и что хотим увидеть в итоге, но это уже тема отдельного поста.

'My name is %s and I am %d years old' % (name, age)

В Python 2.6 появился метод .format() с немного отличающимся синтаксисом от существующего оператора %. К слову, этот метод активно используется и по сей день, в отличии от его предшественника.

'My name is {} and I am {} years old'.format(name, age)

Начиная с версии Python 3.6, доступны f-строки – свежий и наиболее производительный способ форматирования строк.

f'My name is {name} and I am {age} years old'
Аннотации типов

Всем известно, что Python – язык с динамической типизацией. Аннотации типов нужны для того, чтобы повысить информативность исходного кода, но они никак не влияют на поведение программы.

Для примера потребуется импортировать типы List (список) и Dict (словарь) из модуля typing:

from typing import List, Dict

Типы данных указывается через двоеточие после именно переменной после её создания. Создадим словарь с числами и сразу же список, состоящий из значений этого словаря:

numbers: Dict[str, int] = {'one': 1, 'two': 2}
values: List[int] = numbers.values()

Потом создадим функцию, возвращающую сумму переданных значений. В аргументах точно так же можно указывать их типы, а также через стрелочку можно указать типа возвращаемого значения:

def sum(a: int, b: int) -> int:
return a + b

Теперь используем эту функцию, передав туда распакованный список:

sum(*values)
# Output: 3

Однако помните, что аннотации типов не влияют на ход исполнения программы, поэтому в ранее написаную функцию мы с таким же успехом можем передать и строки, хотя указывали целочисленный типа данных:

sum('Python ', 'Lounge')
# Output: 'Python Lounge'
Генераторы

Выглядят функции-генераторы также как и обычные, но содержат выражения с ключевым словом yield для последовательного генерирования значений, которые могут быть использованы в циклах for, либо их получения при помощи функции next.

На каждой yield работа функции временно приостанавливается, при этом сохраняется состояние исполнения, включая локальные переменные, указатель на текущую инструкцию, внутренний стек и состояние обработки исключения. При последующем обращении к итератору генератора функция продолжает своё исполнение с места, на котором была приостановлена. Этим функции-генераторы отличаются от обычных функций, при вызове которых исполнение всякий раз начинается с начала.

Если функция достигает инструкции return, либо конца, возбуждается исключение StopIteration и итератор исчерпывает себя.

def generator():
for item in ('first', 'second', 'third'):
yield item

Теперь можно вызывать эту функцию и использовать функцию next.

gen = generator()
value = next(gen)
print(value)
# Output: first

Достаточно часто, а точнее практически всегда, генераторы используют в циклах for. Преимущество такого подхода в том, что можно получить все возможные значения и не словить ошибку StopIteration.

for item in gen:
print(item, end=' ')
# Output: second third

Теперь если вызвать функцию next еще раз, то получим то самое исключение.

next(gen)
# Output:
Traceback (most recent call last):
File "main.py", line 12, in <module>
  next(gen)
StopIteration
Генераторы коллекций

В Python есть специальная конструкция, позволяющая быстрее и удобнее создавать заполненные коллекции на основе имеющихся, при этом применяя какое-либо выражение к каждому элементу.

1. Генератор списка (list comprehension)

word = 'hello'
new_list = [c.upper() for c in word]
# ['H', 'E', 'L', 'L', 'O']

2. Генератор множества (set comprehension)

elements = [1, 2, 1, 3, 2]
new_set = {n * 0.1 for n in elements}
# {0.1, 0.2, 0.3}

3. Генератор словаря (dictionary comprehension)

numbers = {'one': 1, 'two': 2, 'three': 3}
new_dict = {v: k for k, v in numbers.items()}
# {1: 'one', 2: 'two', 3: 'three'}
yield from

Рассмотрим одну страшную конструкцию в Python 3.3+. Для начала напомню, генератор – это объект, который можно проитерировать только однажды. Записывается так:

def gen():
  for x in range(100):
    yield x

# Или покороче вот так
f = (x for x in range(100)) 

Когда всего лишь один генератор – всё хорошо. Используем его как итератор и радуемся. Но не редки ситуации, в которых есть несколько генераторов:

gen_1 = (x for x in range(5))
gen_2 = (x for x in range(5, 10))

И стоит задача – проитерировать сначала один генератор, потом второй. Но вернуть значения не в виде списка, а в виде нового генератора.

Здесь на помощь приходит следующая конструкция: yield from <expr>, где <expr> - выражение, вычисление которого даёт итерируемый объект, из которого и вычленяется итератор. 

Используя yield form, поставленная выше задача решается так:

def generator():
yield from gen_1
yield from gen_2

gen = generator()

И теперь можно просто проитерироваться по полученному генератору, в итоге получив результаты обоих первоначальных генераторов. Воспользуемся генератором списка, работа которого показана в предыдущем посте.

result = [n for n in gen]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Перечисления

Python 3 поддерживает, благодаря классу Enum, простой механизм работы с перечислениями. Перечисления удобно использовать для хранения списков констант. Константы, в противном случае, оказываются беспорядочно разбросанными в коде.

from enum import Enum, auto

class Monster(Enum):
   ZOMBIE = auto()
   WARRIOR = auto()
   BEAR = auto()
   
print(Monster.ZOMBIE)
# Monster.ZOMBIE

Из документации по Python 3 можно узнать о том, что перечисление — это набор символических имён, привязанных к уникальным, неизменным значениям. Члены одного перечисления можно сравнивать на идентичность. Перечисления можно обходить.

for monster in Monster:
   print(monster)

# Monster.ZOMBIE
# Monster.WARRIOR
# Monster.BEAR
Заходит однажды тестировщик программного обеспечения в бар. Забегает в бар. Пролезает в бар. Танцуя, проникает в бар. Крадется в бар. Врывается в бар. Прыгает в бар.

И заказывает 0 пива. Заказывает 99999 пив. Заказывает -1 пиво. Заказывает ящерицу в стакане. Заказывает "qwertyuiop". Заказывает одно пиво каждую милисекунду. Заказывает пиво, но отменяет заказ. Заказывает пиво вместе с 10000 посетителями одновременно. Заказывает пиво не заходя в бар. Заказывает drop table.

А кульминация этого анекдота находится на моем новом канале @dank_code
Распознавание лиц и верификация пользователей по ним – будущее или уже существующая реальность? Даже больше, каждый уже может написать свою подобную программу. Это настолько просто, что требуется буквально двадцать строк кода и всего лишь полчаса времени для понимания.

Читать статью
Функция map

На вход принимает два обязательных аргмумента: функция-обработчик и итерируемый объект, чаще всего это список. Суть map’a заключается в том, что он применяет переданную функцию к каждому элементу последовательности. А возвращает так называемый map-объект, который в дальнейшем можно конвертировать в обычный список и не только.

def add_five(a):
return a + 5

old_nums = [1, 2, 3, 4, 5]
new_nums = map(add_five, old_nums)
print(list(new_nums))

# Output: [6, 7, 8, 9, 10]


Обратите внимание, мы не пишем скобки у функции add_five при вызове map – это означает то, что мы передаем объект функции, а не результат её выполнения.

Тоже самое мы могли записать через анонимую функцию прямо в map'e.

old_nums = [1, 2, 3, 4, 5]
new_nums = map(lambda a: a + 5, old_nums)
print(list(new_nums))

# Output: [6, 7, 8, 9, 10]


Однако в данном примере нагляднее использовать генератор списка.

old_nums = [1, 2, 3, 4, 5]
new_nums = [a + 5 for a in old_nums]
print(new_nums)

# Output: [6, 7, 8, 9, 10]


Хоть map и мощная функция, но лучше исходить от конкретных случаев. А вот вам действительно полезный пример из практики. Мы можем считать несколько чисел с консоли, которые вводят через пробел, разделив всё это функцией split.

a, b, c = map(int, input().split())
Операции над множествами

Set – множество, в котором отсутствуют повторяющиеся элементы:

>>> a = set([1,2,3,4])
>>> b = set([3,4,5,6])
>>> a | b # объединение
{1, 2, 3, 4, 5, 6}
>>> a & b # пересечение
{3, 4}
>>> a < b # подмножества
False
>>> a - b # разница
{1, 2}
>>> a ^ b # симметрическая разность
{1, 2, 5, 6}
Метод get для словарей

У словарей есть метод get, который возвращает значение по ключу, переданному в аргументах функции. Однако если такого ключа в словаре нет, то исключение не выбрасывается, а возвращается значение по умолчанию, которое передаётся вторым аргументом, или же None, если второй аргумент не передан.

В качестве примера создадим словарь с именами, которые можно получить по определенным значениям, и напишем функцию, которая возвращает приветственную фразу со взятием имени из этого словаря:

names = {
382: 'Alice',
590: 'Bob',
951: 'Dilbert'
}

def greeting(user_id):
name = names.get(user_id, 'there')
   return f'Hi {name}!'

Смотрим на результат:

greeting(382)
# 'Hi Alice!'

Вызов функции с аргументом в виде несуществующего в словаре ключа:

greeting(0)
# 'Hi there!'
В общем, мы создали объединенный чат нескольких каналов для взаимопомощи в программировании и просто общения – @python_chat_ru
uuid

Стандартный модуль uuid — быстрый и простой способ сгенерировать UUID (universally unique identifier, глобально уникальный идентификатор).

from uuid import uuid4

user_id = uuid4()
print(user_id)


Так мы создаём случайное 128-битное число, которое наверняка будет уникальным.

Существует более 2¹²² возможных UUID. Это более 5 ундециллионов, то есть 36 нулей.

Вероятность нахождения дубликатов в заданном наборе крайне мала. Даже при наличии триллиона UUID вероятность того, что среди них есть дубликат, гораздо меньше, чем один к миллиарду.

Вполне недурно для двух строк кода.
Дробные числа в Python могут иметь значение NaN, которое ничему не равно, включая самого себя. Один из способов получить их – использовать модуль math.

>>> math.nan == math.nan
False

Кроме того, объект NaN не является уникальным в отличие от None, вы можете иметь несколько различных объектов NaN из разных источников:

>>> float('nan')
nan
>>> float('nan') is float('nan')
False

Также это означает, что нельзя использовать NaN в качестве ключа в словаре:

>>> d = dict()
>>> d[float('nan')] = 1
>>> d
{nan: 1}

>>> d[float('nan')]
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
KeyError: nan
​​Новая рубрика #кодподписчика

На днях одна подписчица присала мне свой интересный и крайне необычный скрипт, поэтому я решил создать новую рубрику на своём канале, в которой буду выкладывать интересные работы своих подписчиков.

В общем, суть программы в том, что берется произвольная картинка, разбивается на кусочки, создаются папки на рабочем столе и эти кусочки первоначальной картинки ставятся как иконки на папки. То есть получается некая мозаика, которую можно собрать в единую картинку прямо на рабочем столе вашего компьютера. По-моему, это отличный и вполне необычный способ отвлечься от работы, особо ничего не открывая.

Теперь по технической части: скрипт написан на Python с использованием модулей win32api и pillow. Весь исходный код доступен здесь на гитхабе, советую посмотреть. Также подписчица просила в раздел Issues написать ваши идеи насчет того, что можно добавить и улучшить.

Да, теперь каждый может прислать своё любое нетривиальное творение мне в личные сообщения, и я с большой вероятностью опубликую.
Буквально недавно вышел Python 3.8, делать обзор его новых фишек на канал?
Anonymous Poll
97%
Да!
3%
Нет(
Моржовый оператор (Walrus Operator) в Python 3.8

Новый способ присваивания выражения (:=), или оператор «морж», был самой обсуждаемой функцией, представленной в последней версии Python. Новое дополнение к языку было предложено в PEP 572. Сразу посмотрим, как это выглядит в коде:

(x:= 5)
print(x) # 5

Присваивания с помощью моржа требуется делать в круглых скобках, и перед знаком равно пишется двоеточие. Внутри скобок либо создается новая переменная, либо происходит присвоение значения уже существующий переменной.

Но фишка заключается в том, что вся эта конструкция, скажем так, возвращает присвоенное значение, как это делают обычные функции.

a = (b:= 7)
print(a, b) # 7 7

Пойдем дальше и рассмотрим пример, в котором раскрывается весь потенциал моржового оператора:

# пусть a – контейнер или последовательность
if (n:= len(a)) > 10:
print(f'Слишком много элементов, а именно {n}')

То есть мы только что прямо в проверке условия объявили новую переменную n и далее использовали ее вместо того, чтобы вычислять значение заново. Для сравнения посмотрим, как мы это могли сделать раньше:

n = len(a)
if n > 10:
print(f'Слишком много элементов, а именно {n}')

# или

if len(a) > 10:
print(f'Слишком много элементов, а именно {len(a)}')

В первом случае появляется лишняя строка кода, в которой объявляется ненужная переменная. Во втором случае дважды вызвана одна и та же функция, а это потеря в производительности.

Если вам понравился моржовый оператор, то дайте мне фидбек – я сделаю отдельный пост с конкретными примерами.
Моржовый оператор – примеры

В предыдущем посте мы обсудили синтаксис нового оператора присваивания. В личные сообщения я получил большое количество просьб продолжить, поэтому в этом посте мы рассмотрим частные случаи, когда морж в коде выглядит солидно.

Но для начала стоит отметить ещё один важный момент – моржовый оператор не создаёт новую область видимости:

if (n:= len(a)) > 10:
print(f'Слишком много элементов')
print(f'Количество элементов: {n}')

Теперь к практичным примерам использования.

1. Повторно используем значение в списке:

foo = [y:= f(x), y * 2, y * 3]

# вместо

foo = [f(x), f(x) * 2, f(x) * 3]

2. Повторное использование значения из условия в генераторных выражениях:

[y for x in data if (y:=f(x))]

# вместо

result = []
for x in data:
result = f(x)
if result:
results.append(result)

3. Чтение данных из файла внутри циклов while:

while (block := f.read(256)) != '':
process(block)

# вместо

while True:
stuff()
if fail_condition:
break