Умение давать хорошие имена переменным гораздо более важно, чем обычно кажется учащимся.
Кроме того, это гораздо менее тривиально, чем кажется!
Этому правда нужно учиться!
Посмотрите, как выглядит реальный код: раз, два, три.
Итак, имена переменных, функций, типов должны быть осмысленными и отражать назначение.
Слишком длинные имена, однако, не рекомендуются.
В именах допускаются сокращения, которые не затрудняют понимание имени и назначения функций и переменных.
Допускается использование односложных имен переменных для счётчиков (i, j, k) в циклах for.
В именах должны использоваться только английские слова.
Транслитерации русских слов не допускаются.
Если имя переменной или функции состоит из нескольких слов, то они должны разделяться символом подчеркивания. Например:
calc_sqrt – божественно
calculate_square_root – допустимо, но длинновато
vychislenie_kornya – недопустимо (используются русские имена)
f – недопустимо (непонятно назначение функции)
Обычно в качестве имён функций и переменных используются одно или несколько слов (или их сокращений) маленькими буквами с разделением символом подчёркивания.
Например, factorial, calc_factorial(n), calc_fact(n), iter, find_next_value(), num_apples, apple_num и т.д. Для перевода слов можно использовать deepl.
Обычно имена переменных для чисел — это какое-то существительное или пара существительных: total, tot_sum, cur_elem, iter, prev_elem, next_elem, item_sq, num_apples, amount, tot_amt, seq_len, str_len, average, fib1, fib2, result, floor_num, hall_num, area, numerator, denominator, denom, quot, ratio.
Если переменная указывает на список, кортеж, словарь, множество или другую коллекцию, то используют множественное число: pupils, edges и т.д.
Хорошие имена для функций часто одного из трёх типов:
глагол+существительное: find_roots(), get_data и т.д.,
слово is и описание проверки: is_prime(), is_valid(), is_vip_customer(),
либо слово, описывающее, что произойдёт с данными: average(), min_divisor(),factor().
Имена констант записываются полностью заглавными буквами.
Если имя константы состоит из нескольких слов, для их разделения используются подчеркивания.
Например, EPSILON, MAX_SIZE.
Ранее была задача вычисления числа сочетаний из n элементов по k,
для чего необходимо вычисление факториалов трех величин: n, k и n-k.
Для этого можно сделать три цикла, что приводит к увеличению размера программы за счет трехкратного повторения похожего кода.
Вместо этого лучше сделать одну функцию, вычисляющую факториал любого данного числа n и трижды использовать
эту функцию в своей программе. Соответствующая функция может выглядеть так:
deffactorial(n):
f = 1for i in range(2, n + 1):
f *= i
return f
Этот текст должен идти в начале программы, вернее, до того места,
где мы захотим воспользоваться функцией factorial.
Первая строчка этого примера является описанием нашей функции.
factorial — идентификатор, то есть имя нашей функции.
После идентификатора в круглых скобках идет список параметров,
которые получает наша функция. Список состоит из перечисленных через запятую
идентификаторов параметров. В нашем случае список состоит из одной величины n.
В конце строки ставится двоеточие.
Далее идет тело функции, оформленное в виде блока, то есть с отступом.
Внутри функции вычисляется значение факториала числа n
и оно сохраняется в переменной f. Функция завершается инструкцией return f,
которая завершает работу функции и возвращает значение переменной f.
Инструкция return может встречаться в произвольном месте функции, ее исполнение
завершает работу функции и возвращает указанное значение в место вызова.
Если функция не возвращает значения, то инструкция return
используется без возвращаемого значения, также в функциях, не возвращающих значения,
инструкция return может отсутствовать.
Теперь мы можем использовать нашу функцию несколько раз.
В этом примере мы трижды вызываем функцию factorial для вычисления трех факториалов:
factorial(n), factorial(k), factorial(n-k).
n = int(input())
k = int(input())
print(factorial(n) // (factorial(k) * factorial(n - k)))
Мы также можем, например, объявить функцию binomial, которая принимает два целочисленных параметра
n и k и вычисляет число сочетаний из n по k:
Обратите внимание, не в каждой функции должен быть вывод на экран, ввод с клавиаутры или возвращаемое значение.
Читайте условие, обычно в каждой функции есть что-то одно из этого!
Ретурнобулофобия — боязнь начинающих программистов использовать логическое выражение в инструкции return при возвращении значения из булевой функции.
Симптомы: использование следующей конструкции в программе
deff():
...
if expression:
returnTrueelse:
returnFalse
Лечение: использование следующей конструкции:
deff():
...
return expression
Другие примеры проявления этого недуга:
if expression:
returnFalseelse:
returnTrue#if expression:
var = Trueelse:
var = False
Напомним, что логические выражения в духе 1 < x <= 10or x > 20 можно «класть» в переменные, чтобы использовать потом.
Часто это позволяет сделать код куда более «контролируемым» и читаемым.
Пример:
x_between_or_greater = 1 < x <= 10or x > 20if (x_between_or_greater and y > 0) or (not x_between_or_greater and y < 0):
print('В реальных примерах условия сложнее')
Кстати, скобки в условиях можно было не ставить, у and выше приоритет.
Внутри функции можно использовать переменные, объявленные вне этой функции
deff():
print(a)
a = 1
f()
Здесь переменной a присваивается значение 1, и функция f
печатает это значение, несмотря на то, что выше функции f эта переменная
не инициализируется. Но в момент вызова функции f переменной a
уже присвоено значение, поэтому функция f может вывести его на экран.
Такие переменные (объявленные вне функции, но доступные внутри функции)
называются глобальными.
Но если инициализировать какую-то переменную внутри функции,
использовать эту переменную вне функции не удастся. Например:
deff():
a = 1
f()
print(a)
Получим NameError: name 'a' is not defined. Такие переменные, объявленные внутри функции,
называются локальными. Эти переменные становятся недоступными после выхода из функции.
Интересным получится результат, если попробовать изменить значение глобальной переменной внутри функции:
deff():
a = 1
print(a)
a = 0
f()
print(a)
Будут выведены числа 1 и 0. То есть несмотря на то, что значение переменной a
изменилось внутри функции, то вне функции оно осталось прежним! Это сделано в целях
«защиты» глобальных переменных от случайного изменения из функции
(например, если функция будет вызвана из цикла по переменной i, а в этой функции
будет использована переменная i также для организации цикла, то эти переменные должны
быть различными). То есть если внутри функции модифицируется значение некоторой переменной,
то переменная с таким именем становится локальной переменной, и ее модификация не приведет
к изменению глобальной переменной с таким же именем.
Более формально: интерпретатор Питон считает переменную локальной, если внутри
нее есть хотя бы одна инструкция, модифицирующая значение переменной (это может быть
оператор =, += и т.д., или использование этой переменной
в качестве параметра цикла for, то эта переменная считается локальной
и не может быть использована до инициализации. При этом даже если инструкция,
модицифицирующая переменную никогда не будет выполнена: интерпретатор это проверить
не может, и переменная все равно считается локальной. Пример:
deff():
print(a)
ifFalse:
a = 0
a = 1
f()
Возникает ошибка: UnboundLocalError: local variable 'a' referenced before assignment.
А именно, в функции f идентификатор a становится локальной переменной,
т.к. в функции есть команда, модифицирующая переменную a, пусть даже никогда и
не выполняющийся (но интерпретатор не может это отследить). Поэтому вывод переменной a
приводит к обращению к неинициализированной локальной переменной.
Чтобы функция могла изменить значение глобальной переменной, необходимо объявить эту переменную
внутри функции, как глобальную, при помощи ключевого слова global:
deff():global a
a = 1
print(a)
a = 0
f()
print(a)
В этом примере на экран будет выведено 1 1, так как переменная a объявлена, как глобальная,
и ее изменение внутри функции приводит к тому, что и вне функции переменная
будет доступна.
Тем не менее, лучше не изменять значения глобальных переменных внутри функции. Если функция должна поменять
какую-то переменную, то как правило это лучше сделать, как значение, возвращаемое функцией.
Кроме глобальных переменных бывают nonlocal-переменные.
Они очень похожи на глобальные.
При ссылке переменную, которая не определена в текущей функции, будет использована "ближайшая" из локальных переменных в объемлющих функциях,
либо в глобальной области видимости.
Если же переменную пометить как nonlocal, то поиск переменной не будет производиться за пределами инструкций def ни в глобальной области видимости объемлющего модуля, ни во встроенной области видимости, даже если переменные с такими именами там существуют.
Переменные, помеченные как nonlocal, можно менять внутри функции, при этом будет изменяться её значение в соответствующей объемлющей функции. В отличие от global декларация nonlocal не позволяет создать переменную во внешней области видимости.
deftester():
state = 1
print(state)
defnested():
state = 2
print(state)
nested()
print(state)
tester()
>>> 1>>> 2>>> 1
deftester():
state = 1
print(state)
defnested():nonlocal state
state = 2
print(state)
nested()
print(state)
tester()
>>> 1>>> 2>>> 2
В питоне есть специальная функция, которые позволяют получить полный список объявленных глобальных переменных: globals(),
а также функция, позволяющая получить список локальных переменных: locals().
В частности они позволяют проверить, объявлена ли в данный момент переменная.
defremove_magic(it):return [name for name in it ifnot name.startswith('__')]
a = 123
print('Сначала', remove_magic(globals()))
# Сначала ['remove_magic', 'a']
print('foo определена:', 'foo'in globals())
# foo определена: Falsedeffoo(x, y):
print('Внутри foo, локальные', remove_magic(locals()))
# Внутри foo, локальные ['x', 'y']
z = x + y
print('Внутри foo, локальные', remove_magic(locals()))
# Внутри foo, локальные ['x', 'y', 'z']return z
print('Потом', remove_magic(globals()))
# Потом ['remove_magic', 'a', 'foo']
print('foo определена:', 'foo'in globals())
# foo определена: True
foo(1, 2)
Однако приведённый пример является анти-паттерном.
Лямбда функции предназначены для использования там,
где функция передаётся в качестве параметра и более нигде не используется.
Вообще, синтаксис лямбд достаточно полон, чтобы в одну строчку можно было реализовать вообще любую программу.
Вместе с тернаным оператором условия лямбды позволяют делать совершенно нечитаемые работающие однострочники.
# Числа Фибоначчи
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1else1:f(x,f), range(20))))
>>> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
# Факториал
print((lambda n,f:f(n,1,f))(20,(lambda n,m,f:m if n<=1else f(n-1,m*n,f))))
>>> 2432902008176640000# В некоторых случаях даже функции не нужны. Первые простые числа
print([x for x in range(2, 101) if all(x % i for i in range(2, int(x**0.5)+1))])
>>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
Одно из интересных понятий функционального программирования — это замыкания (closure).
В питоне не слишком часто используют замыкания явно, в то время, как, скажем, в javascript без замыканий обойтись практически невозможно.
Смысл замыкания состоит в том, что определение функции "замораживает" окружающий её контекст на момент определения.
Это может делаться различными способами, например, за счёт параметризации создания функции.
При замыкании будут использованы переменные из объемлющих функций.
Для того, чтобы переменная попала в замыкание, она должна быть упомянута в коде функции:
defprint_msg(msg):deffun():
print(msg)
return fun # Помните, что в питоне всё - это объект. Функция тоже, её можно вернуть и воспользоваться потом.
printer = print_msg("Hello")
printer()
>>>Hello
defmultiplier( n ):# multiplier возвращает функцию умножения на ndefmul( k ):return n * k
return mul
mul3 = multiplier(3) # mul3 - функция, умножающая на 3
print(mul3(3), mul3(5))
>>> 915
Правильно захваченные переменные при этом можно даже успешно модифицировать:
deftester(start):
state = start # В каждом вызове сохраняется свое значение statedefnested(label):nonlocal state # Объект state находится
print(label, state) # в объемлющей области видимости
state += 1# Изменит значение переменной, объявленной как nonlocalreturn nested
>>> F = tester(0)
>>> F('spam') # Будет увеличивать значение state при каждом вызове
spam 0>>> F('ham')
ham 1>>> F('eggs')
eggs 2
Но вообще тема эта достаточно сложная.
В результате неаккуратного использования может быть отстрелено произвольное количество конечностей.
Например, нужно иметь в виду, что в замыкание попадает имя переменной, а не её значение.
defgen_adders():
i = 5defadd5(x):return x + i
i = 10defadd10(x):return x + i
i = 15return add5, add10
adder1, adder2 = gen_adders()
print(adder1(0)) # -> 15
print(adder2(0)) # -> 15
В данном случае обе функции используют одну и ту же захваченную из функции gen_adders переменную i, что и приводит к такому «неожиданному» поведению.