Python — потрясающий язык программирования! Это один из самых популярных языков для разработки приложений искусственного интеллекта и машинного обучения. Обладая очень простым для изучения синтаксисом, Python имеет некоторые особенности, которые отличают его от других языков. И в этом руководстве мы как раз поговорим об этих особых атрибутах языка программирования Python.
Прочитав данное руководство, вы узнаете:
- что собой представляют представления списков и словарей;
- как использовать функции
zip()
иenumerate()
; - что такое контекст функции и декораторы;
- для чего нужны генераторы в Python;
Итак, приступим!
Оглавление
Данное руководство разбито на четыре части. Вот они:
- Представление списков
- Представление словарей
- Функции zip() и enumerate()
- Контекст функций
- Декораторы
- Генераторы 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()
, которая, в свою очередь, вернетsin2(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
Книги
- Think Python: How to Think Like a Computer Scientist by Allen B. Downey
- Programming in Python 3: A Complete Introduction to the Python Language by Mark Summerfield
- Python Programming: An Introduction to Computer Science by John Zelle
Справочник API
Заключение
В этом руководстве вы открыли для себя некоторые специальные возможности языка Python.
А именно, мы изучили:
- назначение представления списков и словарей;
- как использовать функции zip() и enumerate();
- вложенные функции, контекст функций и декораторы;
- генераторы в Python и ImageDataGenerator в библиотеке Keras;
Перевод статьи Mehreen Saeed «More special features in Python«.