Некоторые специальные возможности Python

Python — потрясающий язык программирования! Это один из самых популярных языков для разработки приложений искусственного интеллекта и машинного обучения. Обладая очень простым для изучения синтаксисом, Python имеет некоторые особенности, которые отличают его от других языков. И в этом руководстве мы как раз поговорим об этих особых атрибутах языка программирования Python.

Прочитав данное руководство, вы узнаете:

  • что собой представляют представления списков и словарей;
  • как использовать функции zip() и enumerate();
  • что такое контекст функции и декораторы;
  • для чего нужны генераторы в Python;

Итак, приступим!

Оглавление

Данное руководство разбито на четыре части. Вот они:

  1. Представление списков
  2. Представление словарей
  3. Функции zip() и enumerate()
  4. Контекст функций
  5. Декораторы
  6. Генераторы Pyhon с примерами из библиотеки Keras

Импорт необходимых модулей

Ниже приведен код для импорта необходимых нам библиотек.

from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt
import math

Представление списков

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

original_list = [1, 2, 3, 4]
times3_list = []
 
for i in original_list:
    times3_list.append(i*3)
print(times3_list)

Результат:

[3, 6, 9, 12]

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

time3_list_awesome_method = [i*3 for i in original_list]
print(time3_list_awesome_method)

Результат:

[3, 6, 9, 12]

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

even_list_awesome_method = [i for i in original_list if i%2==0]
print(even_list_awesome_method)

Результат:

[2, 4]

Также в подобной конструкции можно использовать ключевое слово else. Допустим, мы хотим оставить четные числа как есть, а нечетные заменить нулями.

new_list_awesome_method = [i if i%2==0 else 0 for i in original_list]
print(new_list_awesome_method)

Результат:

[0, 2, 0, 4]
[python_ad_block]

С помощью представления списков также можно заменить вложенные циклы for. Рассмотрим следующий пример:

colors = ["red", "green", "blue"]
animals = ["cat", "dog", "bird"]
newlist = []
for c in colors:
    for a in animals:
        newlist.append(c + " " + a)
print(newlist)

Результат:

['red cat', 'red dog', 'red bird', 'green cat', 'green dog', 'green bird', 'blue cat', 'blue dog', 'blue bird']

Тот же результат может быть достигнут при помощи представления списков:

colors = ["red", "green", "blue"]
animals = ["cat", "dog", "bird"]
 
newlist = [c+" "+a for c in colors for a in animals]
print(newlist)

Синтаксис

Синтаксис для представления списков имеет следующий вид:

newlist = [expression for item in iterable if condition == True]

или:

newList = [expression if condition == True else expression for item in iterable]

Представление словарей

Представление словарей похоже на представление списков за исключением того, что в качестве элементов у нас теперь пары ключ-значение. Рассмотрим пример. Мы изменим каждое значение словаря, объединив его со строкой «number«.

original_dict = {1: 'one', 2: 'two', 3: 'three', 4: 'four'}
new_dict = {key:'number ' + value for (key, value) in original_dict.items()}
print(new_dict)

Результат:

Output
{1: 'number one', 2: 'number two', 3: 'number three', 4: 'number four'}

И опять же возможны условные выражения. Мы можем добавлять пары ключ-значения в новый словарь на основании определенных критериев.

#Only add keys which are greater than 2
new_dict_high_keys = {key:'number ' + value for (key, value) in original_dict.items() if key>2}
print(new_dict_high_keys)
 
# Only change values with key>2
new_dict_2 = {key:('number ' + value if key>2 else value) for (key, value) in original_dict.items() }
print(new_dict_2)

Результат:

{3: 'number three', 4: 'number four'}
{1: 'one', 2: 'two', 3: 'number three', 4: 'number four'}

Функции zip() и enumerate() в Python

В Python итерируемый объект определяется как любая структура данных, способная возвращать все свои элементы по одному. То есть вы можете использовать цикл for для дальнейшей обработки всех элементов один за другим. В Python есть две дополнительные конструкции, которые упрощают использование циклов for. Это функции enumerate() и zip().

От редакции Pythonist. По теме итерируемых объектов и их перебора рекомендуем почитать статью «Встроенные функции для перебора последовательностей в Python».

Enumerate

В традиционных языках программирования вам нужна переменная цикла для перебора различных значений контейнера. В Python это упростили: вы можете получить доступ к переменной цикла вместе со значением итерируемого объекта. Функция enumerate(x) возвращает две итерируемые переменные. Одна из них изменяется в диапазоне от 0 до len(x) - 1, а другая представляет собой элементы x

Пример ее использования enumerate():

name = ['Triangle', 'Square', 'Hexagon', 'Pentagon']
 
# enumerate returns two iterables
for i, n in enumerate(name):
    print(i, 'name: ', n)

Результат:

0 name:  Triangle
1 name:  Square
2 name:  Hexagon
3 name:  Pentagon

По умолчанию функция enumerate стартует со значения 0, но мы можем задать и другое начальное значение. Иногда это может быть удобным:

data = [1,4,1,5,9,2,6,5,3,5,8,9,7,9,3]
for n, digit in enumerate(data[5:], 6):
    print("The %d-th digit is %d" % (n, digit))

Результат:

The 6-th digit is 2
The 7-th digit is 6
The 8-th digit is 5
The 9-th digit is 3
The 10-th digit is 5
The 11-th digit is 8
The 12-th digit is 9
The 13-th digit is 7
The 14-th digit is 9
The 15-th digit is 3

Zip

Функция zip() позволяет создавать итерируемый объект, состоящий из кортежей. Zip принимает в качестве аргумента несколько последовательностей (m1, m2,…, mn), в результате чего создается итерируемый объект, состоящий из i кортежей. В каждом кортеже содержится по одному элементу из каждого контейнера. Таким образом, i-й кортеж представляет собой(m1i, m2i,…, mni).

Если переданные объекты имеют разную длину, тогда длина сформированного итерируемого объекта (т.е. количество кортежей) будет равна длине минимального объекта.

От редакции Pythonist. Подробнее о функции zip() и ее применении можно почитать в статье «Используем zip() для парной итерации».

Ниже дан пример совместного применения функций zip() и enumerate().

sides = [3, 4, 6, 5]
colors = ['red', 'green', 'yellow', 'blue']
shapes = zip(name, sides, colors)
 
# Tuples are created from one item from each list
print(set(shapes))
 
# Easy to use enumerate and zip together for iterating through multiple lists in one go
for i, (n, s, c) in enumerate(zip(name, sides, colors)):
    print(i, 'Shape- ', n, '; Sides ', s)

Результат:

{('Triangle', 3, 'red'), ('Square', 4, 'green'), ('Hexagon', 6, 'yellow'), ('Pentagon', 5, 'blue')}
0 Shape-  Triangle ; Sides  3
1 Shape-  Square ; Sides  4
2 Shape-  Hexagon ; Sides  6
3 Shape-  Pentagon ; Sides  5

Контекст функций

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

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

Ниже дан пример применения этих возможностей.

def circle(r):
    area = 0
    def area_obj():
        nonlocal area
        area = math.pi * r * r
        print("area_obj")
    return area_obj    
 
def circle(r):
    area_val = math.pi * r * r
    def area():
        print(area_val)
    return area    
 
# returns area_obj(). The value of r passed is retained
circle_1 = circle(1)
circle_2 = circle(2)
 
# Calling area_obj() with radius = 1
circle_1()
# Calling area_obj() with radius = 2
circle_2()

Результат:

3.141592653589793
12.566370614359172

Декораторы в Python

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

Рассмотрим функцию square_decorator(), которая в качестве аргумента принимает функцию и в результате также выдают функцию.

  • Внутренняя вложенная функция square_it() принимает аргумент arg.
  • Функция square_it() применяет функцию к arg и возводит результат в квадрат.
  • Мы можем передать функцию, например sin, в функцию square_decorator(), которая, в свою очередь, вернет sin⁡2(x).
  • Вы также можете написать свою собственную настраиваемую функцию и применить для нее функцию square_decorator() в качестве декоратора, используя для этого специальный символ @, как показано ниже. Функция plus_one(x) возвращает x + 1. Эта функция декорирована функцией square_decorator() и, следовательно, мы получаем (x + 1)2.
def square_decorator(function):
    def square_it(arg):
        x = function(arg)
        return x*x
    return square_it
 
size_sq = square_decorator(len)
print(size_sq([1,2,3]))
 
sin_sq = square_decorator(math.sin)
print(sin_sq(math.pi/4))
 
@square_decorator
def plus_one(a):
    return a+1
 
a = plus_one(3)
print(a)

Результат:

9
0.4999999999999999
16

Генераторы в Python

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

Генератор может быть вызван через функцию next(). При каждом вызове next() возвращается следующее значение генератора. Ниже приведен пример создания последовательности Фибоначчи до числа x.

def get_fibonacci(x):
    x0 = 0
    x1 = 1
    for i in range(x):
        yield x0
        temp = x0 + x1
        x0 = x1
        x1 = temp
 
 
f = get_fibonacci(6)
for i in range(6):
    print(next(f))

Результат:

0
1
1
2
3
5

Пример генератора данных из библиотеки Keras

Одно из типичных применений генераторов — это использование генератора данных в библиотеке Keras. Причина, по которой он полезен, заключается в том, что мы не хотим хранить все данные в памяти, а хотим создавать их на лету, когда это необходимо в процессе обучения. Дело в том, что в Keras модель нейронной сети обучается батчами, поэтому генератор должен выдавать строго определенные пакеты данных.

def datagen(data, seq_len, batch_size, targetcol, kind):
    "As a generator to produce samples for Keras model"
    batch = []
    while True:
        # Pick one dataframe from the pool
        key = random.choice(list(data.keys()))
        df = data[key]
        input_cols = [c for c in df.columns if c != targetcol]
        index = df.index[df.index < TRAIN_TEST_CUTOFF]
        split = int(len(index) * TRAIN_VALID_RATIO)
        if kind == 'train':
            index = index[:split]   # range for the training set
        elif kind == 'valid':
            index = index[split:]   # range for the validation set
        # Pick one position, then clip a sequence length
        while True:
            t = random.choice(index)      # pick one time step
            n = (df.index == t).argmax()  # find its position in the dataframe
            if n-seq_len+1 < 0:
                continue # can't get enough data for one sequence length
            frame = df.iloc[n-seq_len+1:n+1]
            batch.append([frame[input_cols].values, df.loc[t, targetcol]])
            break
        # if we get enough for a batch, dispatch
        if len(batch) == batch_size:
            X, y = zip(*batch)
            X, y = np.expand_dims(np.array(X), 3), np.array(y)
            yield X, y
            batch = []

Данная функция берет случайную строку из датафрейма pandas и вырезает несколько следующих за ней строк в качестве выборки временного интервала. Этот процесс повторяется несколько раз, чтобы собрать достаточно временных интервалов для батча. Когда мы собрали достаточно данных, пакет отправляется на обучение при помощи выражения yield. Как вы уже заметили, в генераторах нет выражения return и в данном примере эта функция будет работать все время. Это полезно и удобно, потому что позволяет нашему процессу обучения проходить любое количество эпох.

Без генератора нам постоянно приходилось бы конвертировать наш датафрейм и хранить в памяти все возможные интервалы времени в процессе всего цикла обучения. Это даст нам весьма много повторяющихся данных (так как временные интервалы пересекаются) и займет очень много памяти.

Именно поэтому в Keras уже есть предопределенная функция генератора. Ниже приведен пример использования функции ImageDataGenerator().

Мы загрузили набор данных cifar10 с изображениями 32×32 в переменную x_train. Данные подключаются к генератору через метод flow(). Функция next() возвращает следующий батч данных. В приведенном ниже примере есть четыре вызова функции next(). В каждом случае возвращается 8 изображений, так как размер батча равен 8.

Ниже приведен код, который отображает все картинки после каждого вызова функции next().

(x_train, y_train), _ = keras.datasets.cifar10.load_data()
datagen = ImageDataGenerator()
data_iterator = datagen.flow(x_train, y_train, batch_size=8)
 
fig,ax = plt.subplots(nrows=4, ncols=8,figsize=(18,6),subplot_kw=dict(xticks=[], yticks=[]))
 
for i in range(4):
    # The next() function will load 8 images from CIFAR
    X, Y = data_iterator.next()
    for j, img in enumerate(X):
        ax[i, j].imshow(img.astype('int'))

Результат:

Дальнейшее обучение

Документация Python

Книги

Справочник API

Заключение

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

А именно, мы изучили:

  • назначение представления списков и словарей;
  • как использовать функции zip() и enumerate();
  • вложенные функции, контекст функций и декораторы;
  • генераторы в Python и ImageDataGenerator в библиотеке Keras;

Перевод статьи Mehreen Saeed «More special features in Python«.