Привет, Хабр! Сегодня поговорим о 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

Что здесь происходит? Код выполняет одновременно три функции.

  1. yourfunction(1), yourfunction(2) и yourfunction(3) выполняются одновременно.

  2. yourfunction(4), yourfunction(5) и yourfunction(6) также выполняются одновременно.

  3. 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)


  1. vilgeforce
    26.06.2023 11:17
    +22

    ОЧЕНЬ ПЛОХО. " многопроцессорности, multiprocessing " - бредятина, "process" переводится как "процесс", а не "процессор". Дальше разбирать смысла нет

    Зато теперь понятны истории про кривой биллинг, самоподключающиеся подписки и прочий трэш у МТС


    1. vilgeforce
      26.06.2023 11:17
      +3

      Набежали боты и резко начали статью плюсовать, а мой коммент - минусовать. МТС так у вас пиар работает?


  1. 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}'


    1. saibaneko
      26.06.2023 11:17

      да не докопипастили судя по всему
      return your_function(*args, **kwargs) + '!'


  1. NeoCode
    26.06.2023 11:17
    +7

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

    Вот например. "Генератор списка + понимание генератора словарей/множеств". Вообще говоря, "списковое включение" (list comprehension) это некоторое выражение, используемое при создании списковых литералов и позволяющее вычислить каждый элемент списка. Есть "энергичные" и "ленивые" вычисления, и соответственно списковые включения (вообще, не в Питоне) могут быть как энергичными, так и ленивыми. В Питоне они вроде как энергичные, то есть выражение вычисляется сразу и список (массив, словарь) заполняется сразу. Теоретически, еще может быть ленивый вариант, когда само выражение сохраняется в специальном объекте, а вычисление элементов происходит только при необходимости или явно (некий аналог "разыменования"). Ленивые включения уже в большей степени соответствуют "генераторам", т.е. вообще говоря бесстековым асимметричным сопрограммам, которые могут инкапсулировать генерирующее выражение внутри себя и возаращать за каджый вызов очередное значение. И кажется, в Питоне есть и то и другое (и включения, и генераторы), отличие только в типе скобок.

    "Ну а **kwargs позволяют нашим функциям принимать любое количество аргументов ключевого слова" - тут просто гугл транслейт (никогда не поверю что они по-русски на самом деле так называются). В интерпретируемых языках каждый объект (в том числе и аргумент) это обычно пара "имя-значение", хранящаяся в некотором словаре. Ну и соответственно args - это список неименованных аргументов (просто массив как в "высокоуровневых" вариадиках C#/Java/Swift?), а kwargs - список именованных аргументов (тех самых пар "имя-значение", т.е. словарь). Кстати, идея весьма интересная.

    Пример с декораторами - я вообще не увидел в коде где добавляется восклицательный знак.


    1. 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'>


      1. fireSparrow
        26.06.2023 11:17
        +4

        tuple-comprehension

        Всё-таки в данном случае круглые скобки не имеют никакого отношения к tuple


  1. ivankudryavtsev
    26.06.2023 11:17
    +13

    А кортежи в Python через [] задаются, я думал, что это списки всегда? А кортежи через (). Статью школьник писал? Особенно мне про декораторы понравилось и про работу с несколькими файлами...

    Я точно знаю что от нас в МТС как минимум 1 хороший Python инженер ушел, он разбирается. Не верю, что у вас никто не умеет :)