Привет, Хабр! Сегодня поговорим о Python и некоторых концепциях языка, которые пригодятся многим. Мы сделали подборку, которая в первую очередь будет полезна начинающим разработчикам, хотя, возможно, найдут в ней что-то и более опытные программисты. Самое интересное — под катом.
Мультипроцессинг в Python
Здесь речь идёт о встроенном модуле multiprocessing. Он позволяет запускать более одной функции одновременно. А это бывает весьма полезно.
import time
import datetime
def yourfunction(x):
start = datetime.datetime.now()
time.sleep(1)
end = datetime.datetime.now()
return f'x={x} start at {start}, end at {end}'
if __name__ == '__main__':
with multiprocessing.Pool(processes=3) as pool:
data = pool.map(yourfunction, [1, 2, 3, 4, 5, 6, 7])
for row in data:
print(row)
x=1 start at 2023-04-16 13:39:32.035510, end at 2023-04-16 13:39:33.037308
x=2 start at 2023-04-16 13:39:32.035795, end at 2023-04-16 13:39:33.037324
x=3 start at 2023-04-16 13:39:32.037349, end at 2023-04-16 13:39:33.037629
x=4 start at 2023-04-16 13:39:33.037725, end at 2023-04-16 13:39:34.040135
x=5 start at 2023-04-16 13:39:33.037892, end at 2023-04-16 13:39:34.040160
x=6 start at 2023-04-16 13:39:33.037986, end at 2023-04-16 13:39:34.040161
x=7 start at 2023-04-16 13:39:34.040454, end at 2023-04-16 13:39:35.045383
Что здесь происходит? Код выполняет одновременно три функции.
yourfunction(1), yourfunction(2) и yourfunction(3) выполняются одновременно.
yourfunction(4), yourfunction(5) и yourfunction(6) также выполняются одновременно.
yourfunction(7) работает отдельно от других.
Кортежи (tuple) в Python. Обычная распаковка и распаковка кортежа с *
Кортежи (tuple) в Python — неизменяемые структуры данных. Ну а концепция распаковки может быть крайне полезна в ряде случаев.
person = ['bob', 30, 'male']
name, age, gender = person
# name='bob', age=30, gender='male'
Так, можно использовать распаковку кортежей для одновременного назначения нескольких переменных. Далее, добавляем * перед переменными для распаковки всего остального в эту переменную.
fruits = ['apple', 'orange', 'pear', 'pineapple', 'durian', 'banana']
first, second, *others = fruits
# first='apple', second='orange'
# others = ['pear', 'pineapple', 'durian', 'banana']
Тернарный оператор
Тернарный оператор — это оператор, который используется для демонстрации какого-то условия, то есть вместо условной конструкции. Вот как его можно использовать.
score = 57
if score > 90:
grade = 'A*'
elif score > 50:
grade = 'pass'
else:
grade = 'fail'
# grade = 'pass'
Обычный блок if-elif-else.
score = 57
grade = 'A*' if score > 90 else 'pass' if score > 50 else 'fail'
# grade = 'pass'
А ещё можно сжать блок if-elif-else в ОДНУ строку, используя тернарный оператор.
Генератор списка + понимание генератора словарей/множеств
А вот практическое применение этой концепции. Так, с генератором списка можно создавать собственный список в одной строке кода.
lis = [expression for i in iterable if condition]
l1 = [i for i in range(1,4)] # [1,2,3]
l2 = [i*2 for i in range(1,4)] # [2,4,6]
l3 = [i**2 for i in range(1,4)] # [1,4,9]
l4 = [i for i in range(1,4) if i%2==1] # [1,3]
Также генератор множества и генератор словаря можно применять для создания множеств и словарей таким же образом, как и в случае создания списков, используя генератор списков.
set1 = {i for i in range(1,4)} # {1,2,3}
d1 = {i:i**2 for i in range(1,4)} # {1:1, 2:4, 3:9}
* *args и **kwargs
Ещё одна интересная возможность. Так, *args дают возможность функциям принимать любое количество позиционных аргументов. Они будут храниться в кортеже args.
def test(a, b, *args):
print(f'{a=} {b=} {args=}')
test(1,2,3,4,5) # a=1 b=2 args=(3,4,5)
Ну а **kwargs позволяют функциям принимать любое количество аргументов ключевого слова (которые будут храниться в словаре kwargs).
def test(a, b, **kwargs):
print(f'{a=} {b=} {kwargs=}')
test(a=1, b=2, c=3, d=4) # a=1 b=2 kwargs={'c': 3, 'd': 4}
Одновременная работа с несколькими .py-файлами
Проблема в том, что даже у не очень занятого разработчика всегда есть несколько проектов с большим количеством разных файлов. Вот так можно обеспечить работу с ними.
# helper.py
def test123():
print('test123 is called')
# main.py
from helper import test123
test123() # test123 is called
if __name__ == ‘__main__’
Ну а здесь речь идёт о том, что строка из заголовка оценивается как True в файле .py. Правда, лишь в том случае, если мы запускаем сам файл напрямую. Строка же используется для того, чтобы случайно не запустить строки кода, которые не планировалось использовать.
# helper.py
def test123():
print('test123 is called')
if __name__ == '__main__':
# this line only runs if we run helper.py DIRECTLY
print('print statement from helper.py')
# main.py
from helper import *
test123() # test123 is called
Библиотеки построения веб-API Python
В этом пункте — без кода. Речь о том, что библиотеки эти крайне полезны для разработчика на Python, причём некоторые узнают об их существовании достаточно поздно. Вот две простые для освоения даже начинающими программистами библиотеки на Python:
Python FastAPI — простое создание API.
Python Flask — создание API и простых веб-приложений при помощи Flask.
Декораторы
О них многие тоже узнают поздно (есть, конечно, и исключения из этого правила). Вот участок кода для примера:
def add_exclamation_mark(your_function):
def inner(*args, **kwargs):
return your_function(*args, **kwargs)
return inner
@add_exclamation_mark
def greet(name):
return f'hello {name}'
Зачем здесь нужен декоратор? Декораторы — функции, которые:
принимают другую функцию
настраивают работу функции
возвращают другую функцию
И когда @add_exclamation_mark ставится над функцией greet, мы меняем работу greet, декорируя её.
# @add_exclamation_mark
# def greet(name)
#
# ^ THIS IS THE SAME AS BELOW:
#
# greet = add_exclamation_mark(greet)
print(greet('tim')) # hello tim!
Из-за декоратора функция greet
ведет себя по-другому, и теперь у нее есть дополнительный !
после возвращаемого значения.
Генераторы + ключевое слово yield
Это слово похоже на return. Но за малым исключением — функция в этом случае не останавливается после чего-то. Функция с yield становится функцией-генератором и может иметь несколько выходных данных.
def simple_generator():
yield 'apple'
yield 'orange'
yield 'pear'
for fruit in simple_generator():
print(fruit)
# apple orange pear
Лямбда-функции
О них многие знают, но не все используют. На самом деле всё просто.
def add(x, y):
return x + y
# this is the same as
add = lambda x,y : x + y
Мы используем лямбда-функцию, когда нам ненадолго требуется безымянная функция. Вот пример:
def test():
return 'hello'
# this is the same as
test = lambda : 'hello'
def test2(a,b,c,d):
return (a+b) / (c-d)
# this is the same as
test2 = lambda a,b,c,d : (a+b) / (c-d)
Лямбда-функции могут иметь любое количество аргументов, но у каждой может быть только одно выражение. Выражение вычисляется и возвращается. Эти функции могут быть использованы везде, где требуется объект-функция.
assert + raise + custom-исключения
И ещё одна полезная концепция. Здесь ключевое слово assert даёт возможность провести тест на работоспособность в середине кода. Если оценка >100, возникает ошибка AssertionError, после чего программа принудительно завершает работу.
assert score <= 100
# ensuring that score cannot be above 100.
Ключевое слово raise позволяет принудительно вызвать исключение (также можно настроить сообщение в исключении).
if score > 100:
raise Exception('score cannot be higher than 100')
# ensuring that score cannot be above 100.
Ну и плюс можно создавать свои собственные типы Exception, наследуя от класса Exception.
class ScoreException(Exception):
def __init__(self):
super().__init__('score cannot be higher than 100')
if score > 100:
raise ScoreException()
Конечно, в этой подборке указаны далеко не все фишки, которые используют как опытные, так и не очень разработчики практически каждый день. Если у вас есть такая фишка, расскажите о ней в комментариях — порадуйте коллег.
Комментарии (8)
Kwent
26.06.2023 11:17+4Сейчас мы добавили дополнительный ! после возвращаемого значения.
Помогите глупому новичку, где вы это добавили?
def add_exclamation_mark(your_function): def inner(*args, **kwargs): return your_function(*args, **kwargs) return inner def greet(name): return f'hello {name}'
saibaneko
26.06.2023 11:17да не докопипастили судя по всему
return your_function(*args, **kwargs) + '!'
NeoCode
26.06.2023 11:17+7Вообще все очень поверхностно. Я не знаю питона и писал на нем один раз в жизни, но достаточно глубоко изучаю языковые концепции как таковые. Не знаю, это только здесь так, или большинство статей и книг тоже такие - ориентируют читателя на на глубокое понимание происходящего, а на такой вот тяп-ляп-и-в-продакшен?
Вот например. "Генератор списка + понимание генератора словарей/множеств". Вообще говоря, "списковое включение" (list comprehension) это некоторое выражение, используемое при создании списковых литералов и позволяющее вычислить каждый элемент списка. Есть "энергичные" и "ленивые" вычисления, и соответственно списковые включения (вообще, не в Питоне) могут быть как энергичными, так и ленивыми. В Питоне они вроде как энергичные, то есть выражение вычисляется сразу и список (массив, словарь) заполняется сразу. Теоретически, еще может быть ленивый вариант, когда само выражение сохраняется в специальном объекте, а вычисление элементов происходит только при необходимости или явно (некий аналог "разыменования"). Ленивые включения уже в большей степени соответствуют "генераторам", т.е. вообще говоря бесстековым асимметричным сопрограммам, которые могут инкапсулировать генерирующее выражение внутри себя и возаращать за каджый вызов очередное значение. И кажется, в Питоне есть и то и другое (и включения, и генераторы), отличие только в типе скобок.
"Ну а **kwargs позволяют нашим функциям принимать любое количество аргументов ключевого слова" - тут просто гугл транслейт (никогда не поверю что они по-русски на самом деле так называются). В интерпретируемых языках каждый объект (в том числе и аргумент) это обычно пара "имя-значение", хранящаяся в некотором словаре. Ну и соответственно args - это список неименованных аргументов (просто массив как в "высокоуровневых" вариадиках C#/Java/Swift?), а kwargs - список именованных аргументов (тех самых пар "имя-значение", т.е. словарь). Кстати, идея весьма интересная.
Пример с декораторами - я вообще не увидел в коде где добавляется восклицательный знак.
ti_zh_vrach
26.06.2023 11:17+1В Питоне они вроде как энергичные, то есть выражение вычисляется сразу и список (массив, словарь) заполняется сразу.
В Питоне, по крайней мере 3.10, оба варианта встречаются. В этой статье они как-то увернулись от варианта "tuple-comprehension":
>>> seq = [x**2 for x in range(10)] >>> type(seq) <class 'list'>
>>> seq = (x**2 for x in range(10)) >>> type(seq) <class 'generator'>
fireSparrow
26.06.2023 11:17+4tuple-comprehension
Всё-таки в данном случае круглые скобки не имеют никакого отношения к tuple
ivankudryavtsev
26.06.2023 11:17+13А кортежи в Python через
[]
задаются, я думал, что это списки всегда? А кортежи через()
. Статью школьник писал? Особенно мне про декораторы понравилось и про работу с несколькими файлами...Я точно знаю что от нас в МТС как минимум 1 хороший Python инженер ушел, он разбирается. Не верю, что у вас никто не умеет :)
vilgeforce
ОЧЕНЬ ПЛОХО. " многопроцессорности, multiprocessing " - бредятина, "process" переводится как "процесс", а не "процессор". Дальше разбирать смысла нет
Зато теперь понятны истории про кривой биллинг, самоподключающиеся подписки и прочий трэш у МТС
vilgeforce
Набежали боты и резко начали статью плюсовать, а мой коммент - минусовать. МТС так у вас пиар работает?