Главная страница

Питон для нормальных. Учебник Москва Базальт спо макс пресс 2018


Скачать 2.54 Mb.
НазваниеУчебник Москва Базальт спо макс пресс 2018
АнкорПитон для нормальных
Дата05.10.2022
Размер2.54 Mb.
Формат файлаpdf
Имя файлаsysoeva_sysoev_piton_dlya_normalnyh_ch1.pdf
ТипУчебник
#715174
страница9 из 14
1   ...   6   7   8   9   10   11   12   13   14
Её первые два аргумента те же, что и у arange: начало и конец диапазона, а третий аргумент — количество элементов в диапазоне. К сожалению, по умолча- нию эти функции по разному обрабатывают второй аргумент: arange берёт его невключительно, linspace — включительно. Это можно поправить с помощью

116
Глава 5. Массивы. Модуль numpy
A[:, 0]
A[2:, 2:5]
A[0, 3:]
Рис. 5.2. Примеры срезов массивов: чёрным — срезы, в результате которых из двумерного массива получаются одномерные (из столбца и строки исходного мас- сива соответственно); тёмносерым — срез, в результате которого получается дву- мерный массив меньшего размера.
опционного, четвёртого аргумента linspace, если установить его в False. Вот пример задания одинаковых массивов с помощью arange и linspace:
>>> a = numpy . arange (0 , 2 , 0.25)
>>>
p r
i n
t
( a )
[ 0.
0.25 0.5 0.75 1.
1.25 1.5 1.75]
>>> b = numpy . l i n s p a c e (0 , 2 , 8 , False )
>>>
p r
i n
t
( a )
[ 0.
0.25 0.5 0.75 1.
1.25 1.5 1.75]
Массивы построены на базе списков и сохраняют некоторые их черты. В част- ности, массивы можно обходить циклом любым из изложенных выше способов, а функция len возвращает длину массива. Важным свойством массивов является то, что над ними можно производить операции как над простыми числами, при этом операция, в нашем случае вычисление синуса, будет произведена с каждым элементом массива.
Модуль numpy дублирует и расширяет функционал модуля math, он содержит все основные тригонометрические, логарифмические и прочие функции, а также константы, поэтому при работе с numpy надобности в math обычно не возникает.
Одна из важнейших особенностей массивов — возможность делать срезы (см.
рис. 5.2). Срез представляет собою массив из части элементов исходного массива,

5.1. Создание и индексация массивов
117
взятых по некоторому простому правилу, например, каждый второй или первые десять. Вот небольшая программа, иллюстрирующая создание срезов массивов:
f r
o m
numpy i
m p
o r
t
*
a1 = arange (0 , 4 , 0.5)
p r
i n
t
( a1 )
a2 = a1 [0:4]
p r
i n
t
( a2 )
a3 = a1 [0:
l e
n
( a1 ):2]
p r
i n
t
( a3 )
Массив a2 состоит из первых четырёх (от нулевого включительно до чет- вёртого невключительно) элементов массива a1, массив a3 — из всех чётных элементов a1 (элементы от нулевого до последнего с шагом 2). Для обозначения индексации используются двоеточия, сначала ставится номер начального индек- са, потом конечного, потом шаг, все они — целые числа (точно в том же порядке,
как и в функциях range и arange). Если шаг и стоящее перед ним двоеточие опустить, шаг будет автоматически единичным. Вот вывод программы:
[ 0.
0.5 1.
1.5 2.
2.5 3.
3.5]
[ 0.
0.5 1.
1.5]
[ 0.
1.
2.
3.]
Нужно отметить, что массив-срез делит с исходным массивом память. Это значит, что копирование элементов в новый массив не происходит, вместо этого просто делается сложная ссылка. Поэтому изменение элементов исходного мас- сива приводит к изменению элементов массива-среза и наоборот. Чтобы скопи- ровать элементы, нужно воспользоваться функцией copy. Здесь у внимательных читателей должен возникнуть эффект d´ej`a vu, потому что нечто подобное уже было в разделе про списки.
f r
o m
numpy i
m p
o r
t
*
a1 = arange (0 , 0.6 , 0.1)
a2 = a1 [:
l e
n
( a1 )/2]
p r
i n
t
( a1 , a2 )
a1 [0] = 10
a1 [ -1] = a1 [ -1] + 3
p r
i n
t
( a1 , a2 )
a2 [1] = 0
p r
i n
t
( a1 , a2 )
Вывод программы:
[0.
0.1 0.2 0.3 0.4 0.5] [ 0.
0.1 0.2]
[10.
0.1 0.2 0.3 0.4 3.5] [10.
0.1 0.2]
[10.
0.
0.2 0.3 0.4 3.5] [10.
0.
0.2]

118
Глава 5. Массивы. Модуль numpy
В приведённом примере мы поменяли нулевой и последний элементы массива a1,
в результате в массиве a2 нулевой элемент тоже изменился, затем мы поменяли первый элемент a2, и первый элемент a1 также изменился. Теперь тот же пример,
но с использованием функции copy:
f r
o m
numpy i
m p
o r
t
*
a1 = arange (0 , 0.6 , 0.1)
a2 = copy ( a1 [:
l e
n
( a1 )/2])
p r
i n
t
( a1 , a2 )
a1 [0] = 10
a1 [ -1] = a1 [ -1] + 3
p r
i n
t
( a1 , a2 )
a2 [1] = 0
p r
i n
t
( a1 , a2 )
Теперь в выводе видно, что изменение одного из массивов не приводит к изме- нению другого:
[0.
0.1 0.2 0.3 0.4 0.5] [0.
0.1 0.2]
[10.
0.1 0.2 0.3 0.4 3.5] [0.
0.1 0.2]
[10.
0.1 0.2 0.3 0.4 3.5] [0.
0.
0.2]
В приведённых примерах было использовано ещё одно свойство индексации:
если номер начального элемента опустить, то автоматически подставляется ноль,
если опустить номер конечного — длина массива.
Функция array() не единственная функция для создания массивов. Обычно элементы массива вначале неизвестны, а массив, в котором они будут хранить- ся, уже нужен. Поэтому имеется несколько функций для того, чтобы создавать массивы с каким-то исходным содержимым (по умолчанию тип элементов созда- ваемого массива — float64): ones создаёт массив из единиц, zeros — массив из нулей, empty — массив, значения элементов которого могут быть какими угод- но (берётся то, что лежало в ячейках памяти до того, как в них был размещён массив). Все эти функции принимают единственный параметр — длину вновь создаваемого массива. Вот пример их работы:
f r
o m
numpy i
m p
o r
t
*
a1 = ones (5)
a2 = zeros (3)
a3 = empty (4)
p r
i n
t
( a1 )
p r
i n
t
( a2 )
p r
i n
t
( a3 )
Вывод:
[1.
1.
1.
1.
1.]
[0.
0.
0.]
[4.39718425e-322 0.00000000e+000 0.00000000e+000 2.84236875e-316]

5.1. Создание и индексация массивов
119
Видно, что в a3 помещены 4 произвольных числа. В нашем случае они получи- лись равными или близкими к нулю, но рассчитывать на это в общем случае не приходится.
Можно вручную задать тип данных:
a3 = empty (5 , dtype =
i n
t
)
p r
i n
t
( a3 )
Вывод:
[500 0
0 0
0]
В данном примере как раз хорошо видно, что нулевой элемент массива далеко не равен нулю.
Если массив слишком большой, чтобы его печатать, numpy автоматически скрывает центральную часть массива и выводит только его уголки:
A = arange (0 , 3000 , 1)
p r
i n
t
( A )
Вывод:
[
0 1
2 ..., 2997 2998 2999]
Если вам действительно нужно увидеть весь массив, используйте функцию numpy.set_printoptions
:
A = arange (0 , 3000 , 1)
s e t _ p r i n t o p t i o n s ( t h r e s h o l d = nan )
p r
i n
t
( A )
И вообще, с помощью этой функции можно настроить печать массивов «под се- бя». Функция numpy.set_printoptions принимает несколько аргументов? опи- сание которых при необходимости можно с лёгкостью найти в официальной до- кументации к numpy в интернете.
Действительные числа из numpy типа float64 обладают практически всеми теми же свойствами, что и встроенные числа Python, но могут принимать три дополнительных специальных значения: inf, -inf или nan, что можно перевести как бесконечность, минус бесконечность и неопределённость. Если поделить дей- ствительное число типа numpy.float64 на 0, получится inf -inf в зависимости от его знака, а вместо ошибки будет выдано только предупреждение, которое позволит продолжить вычисления. Программа:
a = np . array ([2. , 0. , 5.])
f o
r el i
n a :
p r
i n
t
(1./ el )
выдаст:

120
Глава 5. Массивы. Модуль numpy
0.5
/usr/bin/ipython3:2: RuntimeWarning: divide by zero encountered in double_scalars inf
0.2 5.2
Арифметические операции и функции с массивами
Python — объектно-ориентированный язык программирования. Каждая пе- ременная Python — объект, хотя часто это совсем незаметно. Поэтому многие переменные Python имеют встроенные методы — присущие им функции — и свойства. Так, массивы имеют ряд очень простых, но полезных методов, сильно упрощающих жизнь при написании сложных программ. Собственно, именно по- этому в сложных программах удобнее использовать именно массивы, а не списки.
Самые часто используемые занесены в таблицу 5.1. Пусть у нас есть массив вида a=numpy.array([0.1, 0.25, -1.75, 0.4, -0.9])
:
Таблица 5.1. Некоторые методы массивов
Метод
Описание a.sum()
Сумма элементов массива:
len(a)
X
i=0
a i
= 0.1 + 0.25 − 1.75 + 0.4 − 0.9 = −1.9
a.mean()
Среднее элементов массива:
a =
1
len(a)
len(a)
X
i=0
a i
= (0.1 + 0.25 − 1.75 + 0.4 − 0.9)/5 = −0.38
a.min()
Минимальный элемент массива: −1.75
a.min()
Максимальный элемент массива: 0.4
a.var()
Дисперсия элементов массива:
σ
2
a
=
1
len(a)
len(a)
X
i=0
(a i
− a) = ((0.1 + 0.38)
2
+ (0.25 + 0.38)
2
(−1.78 +
0.38)
2
+ (0.4 + 0.38)
2
+ (−0.9 + 0.38)
2
)/5 = 0.6766
a.std()
Среднеквадратичное отклонение элементов массива от среднего:
σ
a
= 0.822556988907
Вот небольшая программа, показывающая, как эти функции работают:
f r
o m
numpy i
m p
o r
t
*
a = array ([0.1 , 0.25 , -1.75 , 0.4 , -0.9])
p r
i n
t
( a .
s u
m
())
p r
i n
t
( a . mean ())
p r
i n
t
( a .
m i
n
())

5.2. Арифметические операции и функции с массивами
121
p r
i n
t
( a .
m a
x
())
p r
i n
t
( a . var ())
p r
i n
t
( a . std ())
Вывод программы:
-1.9
-0.38
-1.75 0.4 0.6766 0.822556988907
Обратите внимание на круглые скобки после имени каждой функции — они указывают, что вызывается и исполняется соответствующая функция. Если скоб- ки забыть, никаких вычислений не будет произведено, а вместо их результа- тов будут напечатаны строки документации соответствующих функций. То есть a.min()
— это результат вычисления, минимальный элемент массива a, но a.min
— это сам метод вычисления:
p r
i n
t
( a .
m i
n
)
Вывод:

Поскольку Python язык очень глубоко объектный, сами по себе методы, как и их параметры и результаты, тоже являются объектами. Тип результата встро- енных функций определяется их природою: min, max и sum всегда того же типа,
что и элементы массива, а вот var, mean и std всегда действительнозначные,
поскольку для их вычисления выполняются деление и — для std — извлечение квадратного корня.
Библиотека numpy унаследовала от Fortran ряд особенностей работы с мас- сивами, не присущих C, Pascal, Java, C# и прочим распространённым языкам общего назначения: массивы numpy можно складывать, как простые числа, при- бавлять к ним число, умножать и делить друг на друга и на число. При этом все операции происходят поэлементно. Вот пример программы сложения двух массивов и умножения массива на число:
f r
o m
numpy i
m p
o r
t
*
a = array ([0.1 , 0.25 , -1.75 , 0.4 , -0.9])
b = array ([0.9 , 1.75 , -0.15 , 0.4 , 0.4])
c = a + b p
r i
n t
( c )
d = a + 1.5
p r
i n
t
( d )
Программа выведет:

122
Глава 5. Массивы. Модуль numpy
[ 1.
2.
-1.9 0.8 -0.5]
[ 1.6 1.75 -0.25 1.9 0.6 ]
Такие операции называются иногда «векторными» в том смысле, что оди- наковые действия производятся сразу с большим набором данных. Складывать можно только массивы одинаковой длины. Аналогично можно перемножать мас- сивы. Число можно прибавлять к любому массиву или умножать массив на чис- ло, в результате получается массив того же размера, что и исходный, а каждый его элемент есть результат сложения (или умножения) соответствующего элемен- та исходного массива и числа. Такой синтаксис операций позволяет существенно упростить запись сложных программ, избежав множества циклов, которые при- шлось бы писать на С или Pascal.
В векторных операциях можно использовать как целые массивы, так и их срезы:
f r
o m
numpy i
m p
o r
t
*
a = array ([0.1 , 0.25 , -1.75 , 0.4 , -0.9])
b = array ([0.9 , 1.75 , -0.15 , 0.4 , 0.4])
c = a [:3] + b [1:4]
p r
i n
t
( c )
d = a [2:4]* a [0:2]
p r
i n
t
( d )
Вот вывод программы:
[ 1.85 0.1
-1.35]
[-0.175 0.1
]
Обратите внимание ещё раз: при перемножении массивов действия производятся поэлементно, в результате чего получается новый массив той же длины, а вовсе не их векторное произведение, как это принято в некоторых математических пакетах. Вот пример:
>>>
i m
p o
r t
numpy
>>> numpy . array ([ -1.75 , 0.4]) * numpy . array ([0.1 , 0.25])
array ([ -0.175 ,
0.1
])
в то время как в результате скалярного произведения должно было получиться:
-1.75*0.1 + 0.4*0.25 = -0.075
Кроме арифметических операций над массивами можно также векторно вы- полнять все заложенные в numpy функции, например, взять синус или логарифм от массива (если написать просто log(X), то получится натуральный логарифм,
иначе нужно написать основание логарифма вторым аргументом log(X, 10)).
Вот пример такой программы:
f r
o m
numpy i
m p
o r
t
*
t = arange (0 , pi , pi /6)
x = sin ( t )

5.2. Арифметические операции и функции с массивами
123
y = log ( t )
p r
i n
t
( t )
p r
i n
t
( x )
p r
i n
t
( y )
Вывод:
[0.
0.52359878 1.04719755 1.57079633 2.0943951 2.61799388]
[0.
0.5 0.8660254 1.
0.8660254 0.5]
[-inf
-0.64702958 0.0461176 0.45158271 0.73926478 0.96240833]
Здесь мы протабулировали (вычислили значения функций при изменении ар- гумента в некоторых пределах) две функции: sin(t) и ln(t) на полуинтервале
[0; π) с шагом π/6. Как видим, можно проделывать над массивами сколь угодно сложные векторные операции. Так как натуральный логарифм нуля равен минус бесконечности, то получился соответствующий ответ: -inf.
Массивы numpy можно обходить циклом, как списки. Чтобы сделать наше табулирование более удобным и привычным для чтения, модифицируем преды- дущую программу, чтобы она выдавала результат своей работы в три столбца.
Для этого заменим последнюю строку, где стоят операторы print, на две новые:
f o
r i
i n
r a
n g
e
(
l e
n
( t )):
p r
i n
t
( t [ i ] , x [ i ] , y [ i ])
Вывод программы:
0.0 0.0 -inf
0.523598775598 0.5 -0.647029583379 1.0471975512 0.866025403784 0.0461175971813 1.57079632679 1.0 0.451582705289 2.09439510239 0.866025403784 0.739264777741 2.61799387799 0.5 0.962408329055
Полученные столбцы чисел выглядят понятно, но не очень презентабельно,
поскольку каждая запись выглядит по-своему — все они имеют разное число знаков после точки. Для получения стройных колонок можно воспользоваться встроенным методом форматирования, поменяв последнюю строку приведённой программы на следующую:
p r
i n
t
( " {:12.8 f }\ t {:12.8 f }\ t {:12.8 f } " .
f o
r m
a t
( t [ i ] ,
x [ i ] , y [ i ]))
Здесь символ
’\t’
означает табуляцию, впереди идёт строка форматирования,
где :12.8f означает, что на это место будет подставлено действительное чис- ло (на это указывает f) с 12 знаками, из них после десятичной точки 8. Три подставляемых числа взяты в скобки. Вывод программы:

124
Глава 5. Массивы. Модуль numpy
0.00000000 0.00000000
-inf
0.52359878 0.50000000
-0.64702958 1.04719755 0.86602540 0.04611760 1.57079633 1.00000000 0.45158271 2.09439510 0.86602540 0.73926478 2.61799388 0.50000000 0.96240833
В Python есть специальные операторы +=, -=, которые увеличивают и умень- шают одно число на другое, а также операторы *= и /=, которые умножают и делят одно число на другое, например:
>>> a = 10
>>> a += 4
>>>
p r
i n
t
( a )
14
>>> a -= 8
>>>
p r
i n
t
( a )
6
По сути эти инструкции аналогичны инструкциям типа a = a + b или a = a - b
, где b — число, на которое нужно увеличить или уменьшить a. Преимущество использования операторов +=, -= кроме краткости записи состоит в том, что при их использовании новый объект не создаётся, а только модифицируется исход- ный, в то время как запись a = a + b или a = a - b приводит к уничтожению старого объекта с именем a (его должен будет удалить из памяти сборщик мусо- ра) и созданию нового с тем же именем. Поэтому использование операторов +=,
-=
наиболее актуально при работе со сложными типами вроде массивов numpy,
так как на их создание и размещение в памяти, а также удаление тратится го- раздо больше ресурсов. Вот пример:
>>> x = numpy . array ([0. , 2. , 0.5 , -1.7 , 3.])
>>> x array ([ 0. ,
2. ,
0.5 , -1.7 ,
3. ])
>>> x += 1
>>>
p r
i n
t
( x )
[ 1.
3.
1.5 -0.7 4. ]
>>> x -= 2.5
>>>
p r
i n
t
( x )
[ -1.5 0.5 -1.
-3.2 1.5]
>>> x += numpy . l i n s p a c e (0 , 4 , 5)
>>>
p r
i n
t
( x )
[ -1.5 1.5 1.
-0.2 5.5]
Как видим, прибавлять и вычитать из массивов таким образом можно как числа,
так и другие массивы (нужно, чтобы совпал размер).
Использование операторов +=, -= в ряде случаев позволяет избежать некото- рых сложно уловимых ошибок с именами переменных и, иногда, с их типами.

5.3. Двумерные массивы, форма массивов
125
Скажем, нужно увеличить переменную со сложным именем, а затем использо- вать её в дальнейших вычислениях. Вот пример, когда переменная со сложным названием komputilo_de_nombro_de_eventoj используется в качестве счётчика событий (нужно посчитать число положительных элементов массива x, описан- ного ранее):
>>> k o m p u t i l o _ d e _ n o m b r o _ d e _ e v e n t o j = 0
>>>
f o
r ela i
n x :
i f
ela > 0:
k o m p u t i l o _ d e _ n p m b r o j _ d e _ e v e n t o j = \
k o m p u t i l o _ d e _ n o m b r o _ d e _ e v e n t o j + 1
>>>
p r
i n
t
( k o m p u t i l o _ d e _ n o m b r o _ d e _ e v e n t o j )
0
Программа отработала без сообщений об ошибках, но результат получил- ся неверный: 0 вместо 3. Всё дело в том, что мы ошиблись в имени пере- менной в строке внутри условного оператора. В результате на каждом ша- ге цикла, когда условие оказалось истинно, создавалась новая переменная komputilo_de_npmbroj_de_eventoj
1   ...   6   7   8   9   10   11   12   13   14


написать администратору сайта