<jupyter_start><jupyter_text>Лекция 4 "Функции" Финансовый университет при Правительстве РФ, лектор <NAME>   Создание функции и ее вызов Функция описывается с помощью ключевого слова def no следующей схеме: `def   ([]):    ["""  Строка  документирования  """]             [return  ]`* __Имя функции__ должно быть корректным уникальным идентификатором    * состоящим из латинских букв, цифр и знаков подчеркивания (причем имя функции не может начинаться с цифры).    * В качестве имени нельзя исnользовать ключевые слова, кроме того, следует избегать совпадений с названиями  встроенных  идентификаторов.    * Регистр  символов в  названии  функции  имеет значение. * После имени функции в  круглых скобках можно указать один или несколько __nараметров__ через  запятую.      * Если функция не  принимает параметры, то  просто указываются  круглые скобки.     * После круглых скобок ставится двоеточие. * После двоеточия может следовать необязательная __строка документирования__ фукнции (распространено использование многострочной строки, заключенной в тройные кавычки)  * __Тело функции__ является составной конструкцией.  Как и  в  любой составной конструкции, инструкции внутри функции выделяются одинаковым количеством пробелов слева.<jupyter_code>def func(): 
    pass<jupyter_output><empty_output><jupyter_text>Необязательная инструкция return  nозволяет вернуть значение из функции. После исполнения этой  инструкции выполнение функции будет завершено.<jupyter_code>def  func(): 
    print ("Текст до инструкции return") 
    return  "Возвращаемое  значение" 
    print ("Эта инструкция никогда не будет выполнена") 
print(func())  # вызываем функцию 
def print_ok(): 
    """  Пример  функции  без  параметров  """ 
    print("Сообщение при удачно выполненной операции") 
    
def echo(m): 
    """  Пример функции с параметром """ 
    print(m) 
    
def summa(x, у): 
    """  Пример функции с параметрами, 
    возвращающей сумму двух переменных"""
    return x + у

def pow2(x, у): 
    """  Пример функции с параметрами, 
    возвращающей !!!значение х в степени y (было: сумму двух переменных) !!!"""
    return x ** у
print_ok()
echo("Сообщение")
?echo
help(echo)
echo.__doc__
x = summa(5, 2)
x
y, x = 10, 40
z = summa(y, x)
z<jupyter_output><empty_output><jupyter_text>Имя переменной в вызове функции может не совпадать с именем переменной в определении функции. Кроме того, глобальные переменные х и у не конфликтуют с одноименными переменными в определении функции, т.к.  они расположены в разных областях видимости. Переменные, указанные в определении функции, являются **локальными** и доступны только внутри функции.<jupyter_code># в Python нет ограничений на тип значений передваемых в функцию:
summa('str', 'ing')
summa([1, 3], [5, 7])<jupyter_output><empty_output><jupyter_text>Все в языке Python является объектом, например, строки, списки и даже типы данных и функции.Инструкция `def`  создает объект, имеющий тиn  `function`,  и  сохраняет ссылку на  него  в  идентификаторе, указанном  nосле инструкции `def`. Таким образом, мы можем сохранить ссылку на функцию в  другой nеременной. Для этого  название функции указывается без круглых скобок.<jupyter_code>f = summa
v = f(10, 20)
v
type(summa)
type(f)
# функции в Python можно передавать в качестве аргументов других функций:
def  func(fp,  а,  b): 
    """ Через переменную fp будет доступна ссылка на 
    функцию summa() """
    return fp(а, b)  #  Вызываем функцию summa()
func(summa, 10, 20)
func(f, 10, 20)
def razn(a, b):
    return a - b
func(razn, 10, 20)<jupyter_output><empty_output><jupyter_text>Объекты функций nоддерживают множество __атрибутов__. Обратиться к атрибутам функции можно, указав атрибут nосле названия функции через точку. Наnример, через атрибут `__nаme__` можно получить название функции в  виде строки, через атрибут `__doc__` - строку документирования и т.д.<jupyter_code>summa.__name__
summa.__doc__<jupyter_output><empty_output><jupyter_text>Выведем  названия  всех  атрибутов функции с  nомощью встроенной функции dir():<jupyter_code>dir(summa)<jupyter_output><empty_output><jupyter_text>Расположение определений функций Все инструкции в nрограмме на Python выnолняются nоследовательно сверху вниз. Это означает, что прежде чем использовать идентификатор в nрограмме, его необходимо nредварительно определить, nрисвоив ему значение. Поэтому, __определение функции__ должно быть выполнено __перед вызовом функции__ (обычно это означает, что определение находится раньше вызова).<jupyter_code># неверно!
f2()

def f2():
    print('Функция f2!')
for i in range(5):
    if i > 2:
        f2()
    def f2():
        print('Функция f2!')
# работает т.к. определение функции было выполнено ранее, чем вызов        
# Оnределение функции в зависимости от условия
n = input("Введите 1 для вызова первой функции:") 
n = n.rstrip("\r")
if n == "1": 
    def echo(): 
        print("Вызов первой функции") 
else: 
    def  echo(): 
        print ("Апьтернативная функция") 
echo() # Вызываем функцию<jupyter_output>Введите 1 дпя  вызова первой функции:1
Вызов первой функции<jupyter_text>Инструкция `def` всего лишь присваивает ссылку на объект функции идентификатору, расположенному после ключевого слова def. Если определение одной функции встречается в программе несколько раз, то будет использоваться функция, которая расположена последней.   Анонимные функции Помимо обычных функций язык Python  позволяет использовать анонимные функции, которые называются __лямбда-функциями__. Анонимная функция описывается с помощью ключевого слова `lambda` по следующей схеме:`lamda [[,  ... ,  ]]:  `После ключевого слова `lambda` можно указать передаваемые параметры. В качестве параметра ``  указывается  выражение,  результат  выполнения  которого будет возвращен функцией.  Как видно из  схемы, у лямбда-функций нет имени.  По этой причине их и называют анонимными функциями. В  качестве  значения  __лямбда-функция  возвращает  ссылку  на  объект-функцию__, которую можно сохранить в переменной или передать в качестве параметра в другую функцию. Вызвать лямбда-функцию можно, как и обычную, с помощью круглых скобок, внутри которых расположены передаваемые параметры.<jupyter_code>fl = lambda: 10 + 20  # функция без nараметров 
f2  = lambda х, y:  х + y  # функция с двумя nараметрами 
f3  = lambda х, y, z: х + y + z  # функция с тремя nараметрами 
print(fl())
print(f2(5, 10))
print(f3(5, 10, 30))
arr  =  ["единица1",  "Единый",  "Единица2"] 
arr.sort() 
print(arr)
# распространенный пример использования лямбда-функций:
arr.sort(key=lambda s: s.lower()) 
print(arr)
ls = ['дыня', 'вишня', 'арбуз', 'яблоко', 'абрикос']
sorted(ls)
sorted(ls, key=lambda s:len(s))<jupyter_output><empty_output><jupyter_text>> -----  Функции: необязательные параметры и сопоставление по ключам Чтобы  сделать  некоторые параметры  необязательными,  следует  в  определении  функции присвоить этому параметру начальное значение.<jupyter_code>def summa(x, y=2):
    return x + y
summa(5)
summa(5, 4)<jupyter_output><empty_output><jupyter_text>Синтаксис определения параметров функции не позволяет указывать параметры, не имеющие значений по умолчанию, после параметров со значениями по умолчанию.<jupyter_code># ошибка:
def bad(a, b=1, с):
    return a + b + c<jupyter_output><empty_output><jupyter_text>Язык Python  nозволяет также передать значения в функцию, используя сопоставление по ключам.  Для этого nри вызове функции nараметрам nрисваиваются значения. Последовательность указания параметров может быть nроизвольной.  ```pythondef summa(x, у):     """  Пример функции с параметрами,     возвращающей  сумму двух  переменных"""    return x + у```<jupyter_code>summa(y=10, x=20)<jupyter_output><empty_output><jupyter_text>Сопоставление по ключам очень удобно использовать, если функция имеет несколько необязательных параметров. В этом случае не нужно перечислять все значения, а достаточно присвоить значение нужному параметру.<jupyter_code>def multi_summa(x=1, y=1, z=1):
    return x + 10* y + 100* z
multi_summa(1, 2, 3)
multi_summa(1, 2)
multi_summa(5)
multi_summa()
multi_summa(z=3)<jupyter_output><empty_output><jupyter_text>Значения по умолчанию создаются на этапе выполнения инструкции `def` (то есть в момент создания функции), а не в момент ее вызова. Для неизменяемых аргументов, таких как строки или числа, это не имеет никакого значения, но, при использовании изменяемых аргументов в качестве значения по умолчанию, может появиться труднообнаружимая ловушка.<jupyter_code>def append_if_even(x, lst=[]): # ОШИБКА! 
    if x % 2 == 0: 
        lst.append(x) 
    return lst 
append_if_even(2)
append_if_even(4)
# пример решения задачи без ошибки:
def append_if_even(x, lst=None):
    if lst is None:
        lst = []
    if x % 2 == 0: 
        lst.append(x) 
    return lst 
append_if_even(2)
append_if_even(4)<jupyter_output><empty_output><jupyter_text>Возвращение нескольких значений из функции<jupyter_code>def min_med_max(l):
    "Возвращает минимальное, медианное и максимальное значение итерируемого объекта (списка)"
    ls = sorted(l)
    l_min = ls[0]
    l_max = ls[-1]
    l_med = ls[len(ls)//2]
    return l_min, l_med, l_max # удобный синтаксис для запаковки результатов в кортеж
min_med_max([3, 1, 100, 11, 7])
# распаковка кортежа, возвращаемого функцией
min1, med1, max1 = min_med_max([3, 1, 100, 11, 7])
print(f'min1: {min1}, med1: {med1}, max1: {max1}')
all_res = min_med_max([3, 1, 100, 11, 7])
all_res<jupyter_output><empty_output><jupyter_text>Распаковка аргументов и параметров Если значения параметров, которые планируется передать в  функцию, содержатся в  кортеже или списке, то перед объектом следует указать символ * .<jupyter_code>t1 = (5, 10 ,15)
t2 = [25, 35, 45]
t3 = (5, 10 ,15, 20)
# неудобный способ передачи параметров:
multi_summa(t1[0], t1[1], t1[2])
# распаковка:
multi_summa(*t1)
multi_summa(*t2)
# Ошибка! количество передаваемых параметров 
# должно равняться количеству объявленных в функции параметров:
multi_summa(*t3)
# решение проблемы:
multi_summa(*t3[:3])<jupyter_output><empty_output><jupyter_text>Если значения параметров содержатся в словаре, то распаковать словарь можно, указав перед ним две звездочки \*\* :<jupyter_code>d1 = {'x': 11, 'y': 12, 'z': 13}
multi_summa(**d1)
t3 = [0, -1]
d2 = {'z': -2}
# сначала позиционные параметры, потом пары имя - значение:
multi_summa(*t3, **d2)<jupyter_output><empty_output><jupyter_text>Переменное число параметров в функции<jupyter_code># распаковывание последовательности в списке параметров функции:
def all_summa(*t): 
    """Функция принимает произвольное количество параметров""" 
    res = 0
    for i in t: 
        res += i 
    return  res 
all_summa(10, 20, 30, 40, 50)
t4 = [10, 20, 30, 40, 50, 60, 70]
all_summa(*t4)<jupyter_output><empty_output><jupyter_text>Если перед параметром в  определении функции указать две звездочки \*\*, то все  именованные параметры будут сохранены в словаре.<jupyter_code>def d_summa(**d): 
    for k, v in d.items():  #  Перебираем словарь с переданными параметрами 
        print(f"{k} => {v}", end="; ") 
d_summa(a=1, x=10, z=-2)
d3 = {'f': 3, 'g': 4, 'e': 5}
d_summa(**d3)<jupyter_output>f => 3; g => 4; e => 5;<jupyter_text>При комбинировании  параметров  параметр с  двумя звездочками указывается  самым последним.<jupyter_code>def c_summa(*t, **d): 
    for v in t: 
        print(v, end="; ") 
    for k, v in d.items(): 
        print(f"{k} => {v}", end="; ") 
c_summa(*t4, **d3)<jupyter_output>10; 20; 30; 40; 50; 60; 70; f => 3; g => 4; e => 5;<jupyter_text>----  > ------   Глобальные и локальные переменные  Передача параметров функций Объекты в  функцию передаются по ссылке. Если объект относится к  неизменяемому типу, то изменение значения внутри функции не затронет значение переменной вне функuии.<jupyter_code>def change(a, b): 
    а = 20
    b = 'str'
v1 = 30
v2 = 'val'
change(v1, v2)
print(f'v1: {v1}')
print(f'v2: {v2}')
def change2(a): 
    a.append(10)
s1 = [1]
# изменяемый объект может быть неявно изменен внутри функции:
change2(s1)
s1<jupyter_output><empty_output><jupyter_text>Глобальные и локальные переменные __Глобальные  переменные__ - это  переменные,  объявленные  в  программе  вне  функции. В  Pythoп  глобальные  персменные видны  в  любой части  модуля,  включая функции.<jupyter_code>def func_glob(glob2): 
    print("Значение  глобальной  nеременной  glоb = ",  glob) 
    glob2 += 10 
    print("Значение  локальной  переменной  glob2 = ", glob2) 

glob, glob2 = 10, 5 
func_glob(77)  #  Вызываем функцию 
print("Значение  глобальной  переменной  glob2 = ", glob2)<jupyter_output>Значение  глобальной  nеременной  glоb =  10
Значение  локальной  переменной  glob2 =  87
Значение  глобальной  переменной  glob2 =  5<jupyter_text>Переменной `glob2`  внутри функции присваивается значение, переданное при вызове функции. По этой причине создается новое имя `glob2`,  которое является локальным. Все изменения этой переменной внутри функции не затронут значение одноименной глобальной переменной. __Локальные переменные__ - это переменные, которым внутри функции присваивается значение.  Если имя локальной переменной совпадает с  именем глобальной переменной, то все операции внутри функции осуществляются с локальной переменной, а значение глобальной переменной не изменяется.  Локальные персменные видны только внутри тела функции.<jupyter_code>def func():
    loc = 77 # Локальная  переменная 
    glob = 25 # Локальная  переменная 
    print('Значение glob внутри функции: ', glob)

glob = 10 #  Глобальная  переменная 
func() # Вызываем функцию 

print("Значение glob вне  функции: ", glob) 
try: 
    print(loc)  #  Вызовет исключение NameError 
except NameError:  #  Обрабатываем исключение 
    print("Переменная lос не видна вне функции")<jupyter_output>Значение glob внутри функции:  25
Значение glob вне  функции:  10
Переменная lос не видна вне функции<jupyter_text>Как видно из примера, переменная `loc`, объявленная внутри функции `func()`, недоступна вне функции. Объявление внутри функции локальной переменной `glob` не изменило значения одноименной глобальной переменной. Если обращение к переменной производится до присваивания значения (даже если существует одноименная глобальная переменная), то будет возбуждено исключение `UnboundLocalError`. Для того чтобыI  значение глобальной переменной можно было  изменить внутри функции, необходимо объявить переменную глобальной с  помощью ключевого слова `global`.<jupyter_code>def func():
    global glob
    loc = 77 #  Локальная  переменная 
    glob = 25 #  Локальная  переменная 
    print('Значение glob внутри функции: ', glob)

glob = 10 #  Глобальная  переменная 
func() # Вызываем функцию 
print("Значение glob вне  функции: ", glob)<jupyter_output>Значение glob внутри функции:  25
Значение glob вне  функции:  25<jupyter_text>Поиск  идентификатора, используемого внутри функции, производится в следующем порядке: 1.  Поиск объявления идентификатора внутри функции (в локальной области видимости). 2.  Поиск объявления идентификатора в глобальной области. 3.  Поиск во встроенной области видимости (встроенне функции, классы и т. д.).  Получить все идентификаторы и их значения nозволяют следующие функции: * `globals()` - возвращает словарь с  глобальными идентификаторами; * `locals()` - возвращает словарь с локальными идентификаторами;* `vars( [] )` - если  вызывается  без  параметра  внутри  функции,  то  возвращает словарь с локальными идентификаторами. Если вызывается без параметра вне функции, то возвращает словарь с  глобальными идентификаторами. При указании объекта в  качестве  параметра  возвращает  идентификаторы  этого  объекта  (эквивалентно  вызову `.__dict__` )<jupyter_code>globals()
locals()
def func(): 
    loc = 54 
    glob2 = 25 
    print("Локальные идентификаторы внутри  функции", sorted(vars().keys())) 
func()
test_f('test')<jupyter_output><empty_output><jupyter_text>Вложенные функции Одну функцию можно вложить в другую функцию, причем уровень вложенности неограничен.  В этом случае вложенная функция получает свою собственную локальную область видимости и  имеет достуn к идентификаторам внутри функции-родителя.<jupyter_code>def f1(x): 
    def f2(): 
        print(x) 
    return f2 
fa = f1(12)
fa()
fb = f1(22)
fb()<jupyter_output>22
{'fraction_non_alphanumeric': 0.08832768709578072, 'fraction_numerical': 0.019279334770557436, 'mean_word_length': 4.8592565860700105, 'pattern_counts': {'":': 1, '<': 95, '<?xml version=': 0, '>': 106, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '563609', 'license': 'WTFPL', 'n_tokens_mistral': 6873, 'n_tokens_neox': 7928, 'n_words': 1756, 'path': 'Course I/Алгоритмы Python/Part1/лекции/jupyter/lec04_functions/lec4_v2019s2_v6.ipynb', 'repo_name': 'GeorgiyDemo/FA', 'size': '16242'}