Математический практикум по Питону.
Создание функций (определение и объявление). Показана важность ранее пройденных объектов: словарь (dict) и кортеж (couple).
Это предварительная версия! Любые замечания приветствуются.
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as models
Напомню:
import csv # Для рабом с csv файлами.
data = []
with open( 'MTLR_180101_190110.txt' ) as f:
data_rows = csv.reader( f, delimiter = ';' )
data = list( data_rows )
close = list( map( lambda x: float(x[-2]), data) ) # Или сразу.
#close Не будем его выводить. Там много данных.
plt.plot( close )
plt.ylabel( 'Цена рубли')
plt.xlabel( 'День начиная с 2018 г')
Text(0.5, 0, 'День начиная с 2018 г')
Как выполнить данное считывания и для других эмитентов, т.е. для других файлов? Можно создать функцию, которая обособит код.
Мы уже сталкивались с ламбда функциями. Напомню.
# Задаем ламбда функцию, которая удваивает число (объект).
dbl = lambda x : 2*x
# Применим функцию путем её вызова.
dbl( 2 ), dbl( -1.1 ), dbl( 3.5 )
(4, -2.2, 7.0)
# На всякий случай приведем и такой пример.
dbl( 'раз ' ) # Мы все это уже знаем...
'раз раз '
Ламбда функция не позволяет задать больше одного действия. Фактически это обособленное выражение. Дабы можно было отделить несколько последовательных операций необходимо использовать функции. Покажем их сначала как вариант ламбда функций.
Функция задаяется (объявляется и определяется) ключевым словом def в начале строки. Далее, через пробел, идет имя, в скобках список её аргументов. Строка звершается двоеточием.
# В данном случае, функция имеет название dblF и один аргумент x.
def dblF( x ):
# Сдесь могут идти действия. Но у нас простая функция. Поэтому их нет.
# Для возврата значения из функции ипользется оператор return.
return 2*x # Перед возвратом значения вычисляется выражение.
dblF( 2 ), dblF( -1.3 ), dblF( 'ещё раз ' ) # Вызов полностью совпадает.
(4, -2.6, 'ещё раз ещё раз ')
# Ламбда функция от двух аргументов. Возводит в степень.
mypow = lambda x, y: x**y
# Аналогичная функция mypowW имеет два аргумента.
def mypowW( x, y):
return x**y
# Вызвали функции от двух аргументов.
mypow( 2, 3), mypowW( 2, 3), mypowW( 25, 0.5)
(8, 8, 5.0)
Конечно можно строить композицию.
# Напомню, что вызовы делаются по значению,
# т.е. сначала вычисляется значение аргумента,
mypow( dbl(1), 4), mypowW( dblF(1), 4), dbl( mypowW(3, 2) )
# а потом это значение передается дальше.
(16, 16, 18)
именные аргументы
# В качестве примера возьмем другую функцию.
def mysum( a, b): # Теперь у функции два аргумента
return a + b * 2; # Возвращаем значение выражения.
mysum( 3, 2) # 3 + 2 * 2
7
В предыдущем вызове функции mysum число 3 будет присвоено первому аргументу функции, т.е. переменной a, а число 2 второму аргументу, т.е. переменной b. Таким образом, значения присваиваются аргументам исходя из того порядка в котором они идут при вызове функции. В Питоне значения аргументов можно передавать не только по номеру аргумента в списке (по позиции), но и по его названию. В Питоне есть возможность этот порядок изменить и выполнить вызов функции явно указав какому аргументу какое значение присовить. Для этого при вызове используется оператор присвоения.
# Для явного указания какому аргументу
# какое значение присовить используется оператор присвоения.
mysum( b = 3, a = 2) # аргументу b присвоено значение 3,
# а a число 2. 2 + 3 * 2
8
Естественно что существует ряд ограничений.
mysum( c = 5 ) # Имя аргумента должно сущестовать.
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-16-f87b72b32905> in <module> ----> 1 mysum( c = 5 ) # Имя аргумента должно сущестовать. TypeError: mysum() got an unexpected keyword argument 'c'
Можно комбинировать режимы. Но при двух аргументов мало что скомбинируешь. Тем не менее...
mysum(3, b=2)
7
# При таком вызове система запутается.
mysum( 3, a = 2) # Точнее будет повторное присвоение первому аргументу.
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-18-bb8f5e3d417b> in <module> 1 # При таком вызове система запутается. ----> 2 mysum( 3, a = 2) # Точнее будет повторное присвоение первому аргументу. TypeError: mysum() got multiple values for argument 'a'
# А так нельзя потомучто после присвоения переменной значения по имени
mysum( b = 3, 2) # позиции не учитываются.
File "<ipython-input-19-d671d05ce8fb>", line 2 mysum( b = 3, 2) # позиции не учитываются. ^ SyntaxError: positional argument follows keyword argument
mysum(0, b=-1)
-2
Но можно так.
# Функция от трех аргументов.
def mymac(a, m, s): # mac -- Multiply–accumulate operation.
return a + m*s
mymac(10, s=3, m=2) # Так можно. 10 + 2*3
16
-- Упр. Напиши функцию вычисляющую корень квадратного уравнения. Например, а) больший корень. б) оба корня ввиде набора.
Значения по умолчанию
# при вызове функции необходио указать значение всех аргументов.
mysum( 5 )
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-23-ab8b05171279> in <module> 1 # при вызове функции необходио указать значение всех аргументов. ----> 2 mysum( 5 ) TypeError: mysum() missing 1 required positional argument: 'b'
# Можно сделать значения по умолчанию
def mysum2( a, b = 5): # У аргумента b задано значение по умолчанию,
return a + b * 2; # которое используется в случае,
# если оно не задано явно.
mysum2( 3 ) # Так можно. 3 + 5 * 2
13
mysum2( 3, 2) # А можно и указать значение. 3 + 2 * 2
7
# Обычные переменные не могут следовать после переменных
# с заданным значением по умолчанию.
def mysum3( a, b = 5, c):
return a + b + c
File "<ipython-input-27-bdb07b823b50>", line 3 def mysum3( a, b = 5, c): ^ SyntaxError: non-default argument follows default argument
Упр. Напиши функцию вычисляющую логарифм. По умолчанию основание пусть будет натуральным. Иначе, оно должно быть указано.
#d = (10, 3, 2)
d = [10, 3, 7]
mymac(d)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-29-6d5f2e4a3854> in <module> ----> 1 mymac(d) TypeError: mymac() missing 2 required positional arguments: 'm' and 's'
mymac( *d )
31
[d, 11, 15]
[[10, 3, 7], 11, 15]
[*d, 11, 15]
[10, 3, 7, 11, 15]
l=[1,2,3]
test(l=l)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-34-cdd99c4bf7f2> in <module> ----> 1 test(l=l) NameError: name 'test' is not defined
lp={'a':3,'b':6}
[*lp]
['a', 'b']
lp=[2,3,4]
*lp
File "<ipython-input-38-1c13b5ce39f4>", line 4 SyntaxError: can't use starred expression here
Позиционные аргументы
def test(*args):
print(type(args))
return 0
test(2, 6)
<class 'tuple'>
0
def test(*args):
print(args)
return 0
test(2, 6)
(2, 6)
0
def test(*args):
for arg in args:
print('агумент ', arg)
return 0
test(2, 6)
агумент 2 агумент 6
0
def test(*args):
s = 0
for i, arg in enumerate(args):
print( str(i) + ') агумент ', arg)
s += arg
return s
test(2, 6)
0) агумент 2 1) агумент 6
8
test(2, 6, c=6)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-47-311eecab0e33> in <module> ----> 1 test(2, 6, c=6) TypeError: test() got an unexpected keyword argument 'c'
Именные аргументы
def test(**data):
print(type( data ))
return
test(2, 6, c=6)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-49-311eecab0e33> in <module> ----> 1 test(2, 6, c=6) TypeError: test() takes 0 positional arguments but 2 were given
test(c=6)
<class 'dict'>
def test(**data):
print( data.keys() )
test(a=4, b=6, d=11, ff=-2)
dict_keys(['a', 'b', 'd', 'ff'])
def test(**data):
for x in data.keys():
print(x, data[x])
test(a=4, b=6, d=11, ff=-2)
a 4 b 6 d 11 ff -2
def test(**data):
s = 0
for n,v in data.items():
print(n, v)
if n.startswith('a'):
s += v
return s
test(a=4, b=6, aa=3, d=11, ff=-2)
a 4 b 6 aa 3 d 11 ff -2
7
def test( **data ):
return sum(list(map(lambda x:data[x], data.keys() )))
test(a=4, b=6, aa=3, d=11, ff=-2)
22
dd = {'a':4, 'b':6, 'aa':3, 'd':11, 'ff':-2}
dd
{'a': 4, 'b': 6, 'aa': 3, 'd': 11, 'ff': -2}
Только так:
test( **dd )
22
test( *dd )
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-61-770bd707d97d> in <module> ----> 1 test( *dd ) TypeError: test() takes 0 positional arguments but 5 were given
test( dd )
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-62-865c0ea2c6ff> in <module> ----> 1 test( dd ) TypeError: test() takes 0 positional arguments but 1 was given
test( data = dd )
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-63-2d31aa769001> in <module> ----> 1 test( data = dd ) <ipython-input-57-b6b14de8c6e0> in test(**data) 1 def test( **data ): ----> 2 return sum(list(map(lambda x:data[x], data.keys() ))) TypeError: unsupported operand type(s) for +: 'int' and 'dict'
test( data = *dd )
File "<ipython-input-64-e4391fee5f1c>", line 1 test( data = *dd ) ^ SyntaxError: invalid syntax
test( data = **dd )
File "<ipython-input-65-07fe31113c97>", line 1 test( data = **dd ) ^ SyntaxError: invalid syntax
И те и те
def test(*args, **kwargs):
s = 0
for v in args:
print( v )
s += v
for n,v in kwargs.items():
print(n, v)
if n.startswith('a'):
s += v
return s
test(2, 3, bb=5, aa=4, cc=d)
2 3 bb 5 aa 4 cc [10, 3, 7]
9
dd = {'bb' : 5, 'aa' : 4, 'cc' : d }
test(2, 3, **dd)
2 3 bb 5 aa 4 cc [10, 3, 7]
9
ll = [5, -3]
test( *ll, **dd)
5 -3 bb 5 aa 4 cc [10, 3, 7]
6
Тело
# Тело функции ествественно может быть сложным
def doCnt( a, c=0 ): # Вычисляем .
eq = list(map( lambda x: x==c, a))
print(eq)
return np.mean( eq )
doCnt( [1, 2, 3, 0, 5, 4, 0, 8], 2 ), 1/8 # 8 элементов.
[False, True, False, False, False, False, False, False]
(0.125, 0.125)
doCnt( [1, 2, 3, 0, 5, 4, 0, 8] )
[False, False, False, True, False, False, True, False]
0.25
Упр. Напиши функцию вычисляющую среднее и среднеквадратичное отклонение массива.
def txt2data( name ): # Функция принимает на вход название файла.
with open( name ) as f:
data_rows = csv.reader( f, delimiter = ';' )
data = list( data_rows )
# close = list( map( lambda x: float(x[-2]), data) ) Или сразу
return data
mechel = txt2data( 'MTLR_180101_190110.txt' )
mtl_cl = np.array( list( map( lambda x: float(x[-2]), mechel) )) # -2 это цена закрытия.
vtb = txt2data( 'VTBR_180101_190110.txt' )
vtb_cl = np.array( list( map( lambda x: float(x[-2]), vtb) ))
plt.plot( mtl_cl )
plt.plot( vtb_cl )
plt.ylabel( 'Цена рубли')
plt.xlabel( 'День начиная с 2018 г')
Text(0.5, 0, 'День начиная с 2018 г')
Чего-то с графиком цены не то... они разного масштаба.
Пусть будет график процентного изменения.
plt.plot( mtl_cl/mtl_cl[0] ) # Поделим все элементы массива на самое первое значение.
plt.plot( vtb_cl/vtb_cl[0] ) # Аналогично для второго эмитента.
plt.ylabel( 'Процентое изменение') # Отображаем не цену, а процентное изменение.
plt.xlabel( 'День начиная с 2018 г')
plt.legend( ["Мечел", "ВТБ"] )
<matplotlib.legend.Legend at 0x7f442c104048>
gaz = txt2data( 'GAZP_180101_190110.txt' )
gaz_cl = np.array( list( map( lambda x: float(x[-2]), gaz) ))
Как во всех этих элементах не запутаться? Напомню, есть такая вещь как словарь.
Каталог эмитентов
ticket = dict()
ticket['MTLR'] = mtl_cl
ticket['VTBR'] = vtb_cl
ticket['GAZP'] = gaz_cl
i = list(ticket.items())
i;#!!
# Первый элемент кортежа имя (ключ),
i[0][0], type( i[0][1] ) # второе сами данные (котировки).
('MTLR', numpy.ndarray)
ii=i[1]
ii[0], type( ii[1] )
('VTBR', numpy.ndarray)
plt.plot( ii[1]/ii[1][0] )
[<matplotlib.lines.Line2D at 0x7f442c0a4940>]
def doPlt( ii, lab ):
k, t = ii
plt.plot( t/t[0] )
lab.append( k )
doPlt(ii, [])
lab = []
list( map( lambda x : doPlt(x, lab), ticket.items() ))
plt.ylabel( 'Процентое изменение')
plt.xlabel( 'День начиная с 2018 г')
plt.legend( lab )
<matplotlib.legend.Legend at 0x7f442c033c88>
Упражнение. Как сделать так чтобы подписи в легенде были не сокращенные названия, а полноценные имена (как в предыдущем графике).
Обратно к обработке строки
translit = str.maketrans({'п':'p', 'р':'r', 'о':'o', 'е':'e', 'к':'k', 'а':'a', 'в':'v'})
translit # Словарь соотетствия: по одному символу дается другой.
{1087: 'p', 1088: 'r', 1086: 'o', 1077: 'e', 1082: 'k', 1072: 'a', 1074: 'v'}
'проверка'.translate( translit ) # Используем для посимвольного преобразования.
'proverka'
# Иногда (когда буква соответсвует букве) можно обойтись и строчкой.
traslit = str.maketrans( 'проверка', 'proverka') # Строится словарь.
traslit
{1087: 112, 1088: 114, 1086: 111, 1074: 118, 1077: 101, 1082: 107, 1072: 97}
# Когда букв нет в словаре трансляции, они не обрабатываются.
'проверь'.translate( translit )
'proverь'
translit = str.maketrans({'п':'p', 'р':'r', 'о':'o', 'е':'e', 'к':'k', 'а':'a', 'в':'v', 'я':'ya'})
'якорь'.translate( translit )
'yakorь'
Да и сам список
stud = ['Алексей',"Дима", "Аня", "Саша"]
stud
['Алексей', 'Дима', 'Аня', 'Саша']
stud.sort()
stud
['Алексей', 'Аня', 'Дима', 'Саша']
Как получить табельный номер студента?
stud_num = enumerate( stud ) # Нумирует список.
stud_num # Как обычно пока это просто генератор.
<enumerate at 0x7f442c1453f0>
stud_num = list( stud_num ) # Формируем из него список.
stud_num # Список состоит из кортежей
# (индекс и, соответствующее, значение элемента списка).
[(0, 'Алексей'), (1, 'Аня'), (2, 'Дима'), (3, 'Саша')]
stud_num[2] # Кортеж из номера и значения списка (имени).
(2, 'Дима')
# Теперь можно сделать аналог списка через словарь.
studd = dict(stud_num)
studd
{0: 'Алексей', 1: 'Аня', 2: 'Дима', 3: 'Саша'}
type(studd), studd[2], type(stud), stud[2],
(dict, 'Дима', list, 'Дима')
studd.keys()
dict_keys([0, 1, 2, 3])
studd[2]
'Дима'
stud_name = list( map( lambda x: (x[1], x[0]), stud_num ))
stud_name
[('Алексей', 0), ('Аня', 1), ('Дима', 2), ('Саша', 3)]
# А теперь словарь, который по имени возвращает номер.
studn = dict(stud_name)
studn
{'Алексей': 0, 'Аня': 1, 'Дима': 2, 'Саша': 3}
studn['Дима']
2
На самом деле ранее используемое понятие сложного индекса является объектом питона: slice.
# Как и ранее, указывается начальный индекс,
a = slice( 1, -1, 1) # последний не включительно и шаг.
a, type(a)
(slice(1, -1, 1), slice)
# Имея объект, который работает с понятием сложного индекса,
# можно вместо индекса подать объект срез.
b = [1, 2, 3, 4, 5]
b[ a ] # Применяем как обычно при использовании сложного индекса.
[2, 3, 4]
# Результат совпадает с применением сложного индекса напрямую.
b[ 1: -1: 1]
[2, 3, 4]
b[1:-1:2]
[2, 4]
t = slice( 1, -1, 2)
b[t]
[2, 4]
# Раз срез -- объект, то его можно, например,
def subsum( a, s): # передавать как значение функции.
return sum( a[s] )
# Четный и нечетный поддиапазон индексов.
l = [ slice(0, -1, 2), slice(1, -1, 2) ]
subsum( b, l[0]), subsum( b, l[1])
(4, 6)
a.start
1
# Как и объект touple
a.start = 3 # объект срез является константным.
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-120-dea125fc95e5> in <module> 1 # Как и объект touple ----> 2 a.start = 3 # объект срез является константным. AttributeError: readonly attribute
# Можно использовать его метод indices для получения сложного индекса без отрцательных первых двух чисел.
c = a.indices(10) # Для 10го объекта. Нумерация с 0.
c # Раз индексов бесконечно много (-1), то получим значение.
(1, 9, 1)
slice(-1, -3, -1).indices(10)
(9, 7, -1)
slice(-3, -13, -1).indices(15)
(12, 2, -1)
b[2:-1:-1]
[]
# Если же взять ограниченный срез,
a = slice(1,5,1) # то поведение будет иное.
a.indices(2) # Указываем сколько фактически элементов.
(1, 2, 1)
a.indices(10) # Мы упремся в "последний" элемент.
(1, 5, 1)
# Так как речь идет о настоящем индексе,
a.indices( -2 ) # то отрицательным он быть не может.
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-128-cc4b23d6c0ef> in <module> 1 # Так как речь идет о настоящем индексе, ----> 2 a.indices( -2 ) # то отрицательным он быть не может. ValueError: length should not be negative
a = slice(1, -1, -1)
a.indices(5)
(1, 4, -1)
Красивая печать
import functools as ft
import math as mt
fmt = ft.partialmethod( str.format, a = mt.pi, b =2.12 )
ft.partialmethod?
# *args, **keywords !
Init signature: ft.partialmethod(func, *args, **keywords) Docstring: Method descriptor with partial application of the given arguments and keywords. Supports wrapping existing descriptors and handles non-descriptor callables as instance methods. File: /data/conda/anaconda3/envs/py36/lib/python3.6/functools.py Type: type Subclasses:
fmt.func( "{a:05.1f}, {b:.2f}", **fmt.keywords )
'003.1, 2.12'
def mylog( f ):
def aa( *args ):
print('log pow call')
return f( *args )
return aa
@mylog
def my_pow(x, y):
return x**y
my_pow(2, 3)
log pow call
8
@mylog
def my_add(x, y):
return x**y
my_add(2, 3)
log pow call
8
def my_pow(x, y):
return x**y
my_pow.__name__
'my_pow'
def mylog( f ):
def aa( *args ):
print('log call fun', f.__name__)
return f( *args )
return aa
@mylog
def my_pow(x, y):
return x**y
@mylog
def my_add(x, y):
return x**y
my_pow(2, 3)
log call fun my_pow
8
my_add(2, 3)
log call fun my_add
8
my_add.__name__
'aa'
def mylog( f ):
@ft.wraps( f )
def aa( *args ):
print('log call fun', f.__name__)
return f( *args )
return aa
@mylog
def my_pow(x, y):
return x**y
@mylog
def my_add(x, y):
return x**y
my_add.__name__
'my_add'
my_add(2,3)
log call fun my_add
8
my_add.__wrapped__(2,3)
8
Упр. Написать декоратор, который проверяет права на вызов функции. т.е. если например в аргументе key содержится нужный ключ. В случае наличия прав функция вызывается, иначе нет (делается пустое действие).
Кэш
@mylog
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
fib(5)
log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib log call fun fib
5
Для упрощения восприятия сделаем вручную
def fib(n):
print("call fin with", n)
if n < 2:
return n
return fib(n-1) + fib(n-2)
fib(5)
call fin with 5 call fin with 4 call fin with 3 call fin with 2 call fin with 1 call fin with 0 call fin with 1 call fin with 2 call fin with 1 call fin with 0 call fin with 3 call fin with 2 call fin with 1 call fin with 0 call fin with 1
5
@ft.lru_cache(maxsize=None)
def fib(n):
print("call fin with", n)
if n < 2:
return n
return fib(n-1) + fib(n-2)
fib(5)
call fin with 5 call fin with 4 call fin with 3 call fin with 2 call fin with 1 call fin with 0
5