Модули в программе на Python: функции, вызов, примеры

5 (100%) 3 vote[s]

В этом разделе рассмотрим, что такое модульность в Python, основную концепцию, объявления и использования функций, рассмотрим области видимости переменных, узнаем, что такое рекурсия, модули, пакеты модулей.

Функции

Мы можем взять любую часть кода, дать ему имя и вынести из основного потока программы. Например, программа, которая ищет на отрезке от 0 до 99 все числа, записанные одинаковыми цифрами, может быть записана так:

  1. n = 100
  2. for x in range(n):
  3.     is_cool = x < 10 or (x / 10 == x % 10)
  4.     print x, is_cool

Или так:

  1. def check_if_is_cool(number):
  2.     is_cool = number < 10 or (number / 10 == number % 10)
  3.     print number, is_cool
  4.  
  5. n = 100
  6. for x in range(n):
  7.     check_if_is_cool(x)

Или вообще так. И это будет самый правильный вариант, так как вывод данных имеет отношение к самой программе и остается в цикле, а в отдельную функцию выносится лишь проверка, которая сама по себе может понадобиться и в другом месте:

  1. def is_cool(number): # limited by 0 <= number <= 99
  2.     return number < 10 or (int(number) / 10 == number % 10)
  3.  
  4. n = 100
  5. for x in range(n):
  6.     print x, is_cool(x)

Функция задается с
помощью ключевого слова def, за которым следует название функции (за
этим названием мы дальше можем ее вызвать) и круглые скобки с перечнем
аргументов функции. Следующая двоеточие указывает интерпретатору ждать
вложенный блок кода (как и в случае с алгоритмическими конструкциями), далее
следует сам блок — тело функции, выделенное отступлениями, которое, конечно,
может содержать любые конструкции. 

Аргументы функции — это данные, которые передаются в функцию при ее вызове и оказывают на него обрабатываться для получения результата. Сам полученный результат возвращается с помощью оператора return:

  1. def is_repeat(string):
  2.     result = string == string[0] * len(string)
  3.     return result

При возвращении результата работа функции прекращается. То есть пример из предыдущей лекции:

  1. n = int(raw_input('input N:'))
  2. flag = False
  3. for i in range(n):
  4.     if (i+1) % 11 == 0:
  5.         if (i+1) % 2 == 0:
  6.             flag = True
  7. print flag

можно переписать следующим образом:

  1. def dividable_11_2(x):
  2.     return x % 11 == 0 and x % 2 == 0
  3.  
  4. def has_dividable_11_2_before_n(n):
  5.     for i in range(n):
  6.         if dividable_11_2(i + 1):
  7.             return True
  8.     return False
  9.  
  10. n = int(raw_input('input N:'))
  11. print has_dividable_11_2_before_n(n)

Как только условие сбудется, функция вернет результат и закончит работу. Если цикл закончится и результат до сих пор не возвращен, это означает, что условие не оправдалась для одного из чисел на интервале и следует вернуть False.

Аргументы функции

Функции могут принимать 0, 1, 2 или сколько угодно аргументов. При выполнении интерпретатор создаст для них одноименные переменные, которые будут доступны функции во время работы, и инициализирует их переданными значениями. Если аргументов функции несколько, они воспринимаются именно в том порядке, в котором записаны в объявлении функции.

Аргументы, которые записываются при объявлении просто через запятую, является обязательными . То есть, если функция объявлена с 1 аргументом, а мы попробуем вызвать ее, не передав значений, получим сообщение об ошибке.

Но часто бывает полезно иметь какие-то значение по умолчанию , чтобы даже если в функцию не переданными необходимые данные, она все равно верно отрабатывала. Например, вычисляя результат для самого распространенного варианта. Такие значения задаются прямо в списке аргументов при объявлении функции:

  1. def has_dividable_11_2_before_n(n = 99):

Соответственно, функция может быть вызвана без передачи таких аргументов. То есть они не являются обязательными:

  1. print has_dividable_11_2_before_n()

Перепишем предыдущую программу так, чтобы она искала числа на промежутке, которые делятся нацело на любые заданные числа:

  1. def is_dividable(x, dividers = [11, 2]):
  2.     for divider in dividers:
  3.         if x % divider != 0:
  4.             return False
  5.     return True
  6.  
  7. def has_dividable_numbers(n = 99, dividers = [11, 2]):
  8.     for i in range(n):
  9.         if is_dividable(i+1, dividers):
  10.             return True
  11.     return False
  12.  
  13. n = int(raw_input('input N:'))
  14. print "11, 2 : " + str(has_dividable_numbers(n))
  15. print "5 : " + str(has_dividable_numbers(n, [5]))
  16. print "2, 3, 5 : " + str(has_dividable_numbers(n, [2, 3, 5]))

Если функция предусматривает оба типа аргументов, обязательные записываются первыми (как при объявлении, как и при вызове) все передаваемые значения передаются в аргументы слева направо по порядку. Например, имеем функцию с 3 обязательными аргументами и 3 необязательными:

def multiple_args_function(arg1, arg2, arg3, arg4 = 0, arg5 = 1, arg6 = 2):
    print arg1, arg2, arg3, arg4, arg5, arg6
    • При вызове multiple_args_function (1) — получим ошибку, так как 1 подставляется в arg1, а значение других обязательных аргументов остаются неопределенными.
    • При вызове multiple_args_function (1, 2, 3) — 1, 2 и 3 будут подставлены в качестве значения 3 обязательных аргументов, для необязательных будут взяты значения по умолчанию.
    • При вызове multiple_args_function (1, 2, 3, 4) — 1, 2 и 3 будут подставлены в обязательные аргументы, 4 — в 1-й необязательный arg4.
    • И т.д.

Если нам необходимо передать аргументы в ином порядке, мы можем при вызове функции передать значение вместе с именами аргументов:

multiple_args_function (1, 2, 3, arg5 = 4) — 1, 2 и 3 будут подставлены в обязательные аргументы, 4 — в arg5.

За соответствием типов данных в аргументах должен следить сам программист, Python 2 отслеживает только количество и порядок аргументов и, если при вызове функции были переданы некорректные значения, такой случай имеет проверяться функцией, или приведет к ошибкам выполнения (пусть мы вызываем функцию has_dividable_numbers (n, 9)):

Во многих других языках программирования возможность указать типы аргументов при объявлении функции есть. Начиная с версии интерпретатора 3.0, она появилась и в Python.

Области видимости переменных

Как уже отмечалось в видео-лекции, все пространство программы разделено на области видимости, в каждой из которых доступен свой набор переменных.

Код основной программы определяет глобальную область видимости . Все сущности из нее (функции и переменные) доступны везде в рамках программы, включая вложенные области.

Каждая объявленная функция создает собственную — локальную область видимости . В ней могут создаваться локальные имена (переменные и функции), которые будут доступны в пределах данной функции и во вложенных областях видимости (например, если в функции объявлены другие вложенные в нее функции). Кроме того, из локальных областей видимости можно прочитать значение из внешних, но нельзя их изменить.

В локальной области видимости могут свободно создаваться переменные и функции с такими же именами, которые существуют в других областях видимости. При этом они «перекрывать» имена из внешних областей.

# головна програма -- глобальна область видимості А
a = 1                           # глобальна змінна A.a
b = 2                           # глобальна змінна A.b

def myfunction1(arg_a, arg_b):  # ім’я A.myfunction1 знаходиться 
                                # у глобальній області видимості,
                                # всередині можна звертатися до будь-яких
                                # функцій та # змінних A.*,
                                # але вони не можуть бути змінені. 
                                # Аргументи функції зберігаються
                                # як локальні змінні A.arg_a та A.arg_b

    arg_a = 'one'               # локальна змінна A.myfunction1.arg_a,
                                # доступна лише всередині myfunction1 та
                                # у вкладеній функції inner_function
    c = 'three'                 # інша локальна змінна A.myfunction1.c

    def inner_function(name):   # вложенная функция создает новую вложенную
                                # область видимости 
                                # A.myfunction1.inner_function с локальной
                                # переменной A.myfunction1.inner_function.name,
                                # к которой имеет полный доступ

        inner_a = 1             # локальная переменная
                                # A.myfunction1.inner_function.inner_a
        return 'Hello ' + str(name) + '!'

    print '   myfunction1 scope:'
    print inner_a               # myfunction1 ничего не знает о вложенной
                                # области видимости -- ошибка
    print arg_a                 # локальная переменная -- есть доступ
    print arg_b                 # локальная переменная -- есть доступ
    print c                     # локальная переменная -- есть доступ
    print b                     # глобальная переменная (A.b) -- есть доступ
                                # для читання
    print inner_function(c)     # локальная функция -- есть доступ
    print 'and NOW I\'m calling the myfunction2!'
    myfunction2(arg_a, arg_b)   # myfunction2 находится на том же
                                # уровне (определена непосредственно
                                # в области A), следовательно может быть вызвана
                                # из соседней функции

def myfunction2(arg_a, arg_b):  # создает область видимости A.myfunction2 
                                # с 2 локальными переменными --
                                # аргументами функции
    arg_a = 'ONE'               # локальная переменная A.myfunction2.arg_a
    print '   myfunction2 scope:'
    print arg_a                 # локальная переменная -- есть доступ
    print arg_b                 # ллокальная переменная -- есть доступ

# вернулись в глобальную область видимости А
myfunction1(a,b)                # функция и переменные находятся в текущей
                                # области видимости - есть доступ
myfunction2(a,b)                # функция и переменные находятся в текущей
                                # области видимости - есть доступ
print '   main program scope:'
print a                         # переменная в текущей области видимости - есть доступ
print b                         # переменная в текущей области видимости - есть доступ
print c                         # переменная из области видимости А.myfunction1 -- 
                                # нет доступа, ошибка
print inner_function('Ivanko'); # функция из области видимости А.myfunction1 -- 
                                # нет доступа, ошибка

Рекурсия

Рекурсия — интересный прием в программировании, когда функция вызывает сама себя:

  1. def factorial(i):
  2.     if i == 0:   
  3.         return 1
  4.     else:
  5.         return i * factorial(i - 1)

Это удобно в случаях, когда мы можем разбить решаемую сложную задачу на одну или несколько более простых, а потом собрать результаты решения меньших задач в 1 результат для сложной.

В случае с факториалом идея заключается в следующем:

    • Мы знаем, что 0! = 1 — это самый случай, для которого задача решается элементарно.
    • Мы знаем, что факториал любого числа легко рассчитать, зная факториал предыдущего: 1! = 0! * 1, 2! = 1! * 2, n! = (N-1)! * N — значит для того, чтобы вычислить n! достаточно знать значения предыдущего факториала, для (n-1)! — еще предыдущего и т.д.
    • Так что мы заставляем нашу функцию вычислять более простые факториалы раз за разом, пока задача не встанет к элементарному 0 !, для которого мы знаем точный ответ. На каждом уровне углубление функция возвращает вычисленное значение (i-1)! и, «возвращаясь назад», мы можем просто умножить его на i, чтобы получить решение более сложной задачи i.

То есть:

  1. def factorial(i):
  2.     if i == 0:   
  3.         return 1                # для i = 0 решением задачи будет 1
  4.                                 # else не обязательно: если условие сбылась,
  5.                                 # функция уже завершилась
  6.     return i * factorial(i - 1) # иначе решаем более простую задачу
  7.                                 # и строим решение этой, отталкиваясь от нее

Другим классическим примером использования рекурсии для решения задачи является игра » ханойские башни «, которая решается именно рекурсивно.

В общем случае использования рекурсии является неэффективным, так как при каждом вызове создается новая область видимости и компьютером выделяется отдельная область памяти для выполнения функции. При этом предварительно выделена память также остается задействованной в ожидании решение вложенной задачи. Все обслуживающие процессы занимают время и потребует много ресурсов компьютера, поэтому решение задач с большой вложенностью с помощью рекурсии часто невозможно чисто по техническим причинам.

С другой стороны, в основе рекурсии всегда лежит цикл. Поэтому любая рекурсия может быть заменена одним или несколькими циклами без необходимости выделения такого количества ресурсов. К сожалению, решение задачи без использования рекурсии часто, мягко говоря, неочевидным.

Это, в том числе, касается задач обработки вложенных структур. Например, предположим, что у нас есть список с числами и другими вложенными списками, и нам необходимо вычислить сумму всех чисел, которые находятся внутри структуры. Если бы мы точно знали уровень вложенности, задачу было бы легко решить соответствующим количеством вложенных циклов. Но структура может быть произвольной, поэтому самым простым является сложить все элементы внешнего списка, выделить из него вложены списки и решить для них такую ​​же задачу, добавив затем их суммы к сумме элементов текущего списка.

  1. def list_sum(el):             # ищем сумму элементов el
  2.     if isinstance(el, list):  # если el список, для этого необходимо сложить все его элементы
  3.         sum = 0
  4.         for item in el:
  5.             sum = sum + list_sum(item) # добавляем к сумме el сумму каждого его элемента,
  6.                                        # а следующий list_sum пусть сам решает, как ее считать
  7.         return sum            # возвращаем сумму для списка
  8.     else:                     # иначе el число и нечего составлять - его и возвращаем
  9.         return el
  10.  
  11. print list_sum([1, 1, -1, [2, -2, [[4, -4, [777]], 3, [5, -5], -3]], -1])

Помните : так как рекурсия по сути также является циклом, но имеет более сложный запись, заблудиться в ней, забыв об условиях выхода гораздо проще, чем в цикле!

Модули и пакеты

Если код программы является сложным, разбиение ее на отдельные функции помогает упростить его для визуального восприятия. Если этого недостаточно, есть смысл вынести часть функций и связанных с ними объявлений за пределы основного файла программы.

Такие дополнительные файлы с кодом, используется в программе, называются модулями. Чаще всего они содержат объявления функций и констант, которые далее могут быть подключены (импортируемые) в главную программу и свободно в ней использоваться.

Например, мы можем создать файл example_math.py и объявить в нем функции для вычисления факториала, чисел Фибоначчи и других расчетов. Далее мы можем создать новую программу и подключить к ней модуль example_math (он должен находиться в одной папке с программой, имя модуля совпадает с именем файла, только без расширения .py) и использовать все эти функции в новой программе. 

Как видите, еще одним преимуществом модулей является то, что объявления, сделанные в модуле, могут использоваться не в одной, а в любой программе. 

Аналогичным образом работает встроенный модуль math, который мы уже использовали ранее. Он содержит константы math.e и math.pi, что позволяет обращаться к готовым значений вместо повторного определения их в своем коде. В нем же находится ряд математических функций, которые вы так же можете использовать, не задумываясь об их технической реализации. 

Импорт
из модулей и его виды

Все, что необходимо, это подключить модуль в свою программу:

import math

и использовать его функции и константы, обращаясь к ним через имя модуля с точкой:

print math.pi

Запись math.pi означает,
что значение pi находится в пространстве имен модуля math. Это очень
похоже с областями видимости функций: каждый модуль имеет собственное
пространство имен — это необходимо для того, чтобы модуль имел собственный
набор переменных и функций и никоим образом не зависел от других модулей,
которые вы подключите.

Например, в своем модуле example_math вы подключили модуль math, чтобы взять с него значение математической константы e (конечно, модули можно подключать как к самой программы, так и в других модулей). А потом определили в нем свою функцию exp ():

def exp(x):
    return math.e ** x

Если вы подключите к основной программе и math, и prometheus_math, у вас будет две функции с одинаковыми именами. Но это не приведет к ошибке, так как они определены в разных пространствах имен: prometheus_math.exp и math.exp и при обращении к ним интерпретатор четко знать, какую именно вы имеете в виду.

Также вы можете импортировать функции любого модуля сразу в пространство имен вашей программы (или модуля):

from math import *
print pow(10, 2)

В таком случае все объявления модуля будут скопированы в текущий пространство имен. Но появляется риск того, что в вашем пространстве имен возникнут одинаковые имена и в таком случае ваша программа может повести себя не так, как вы ожидаете.

Для того, чтобы избежать путаницы с подключенных модулей часто импортируют не все объявления, а только необходимые. При этом перечисленные имена (вместе со значениями или функциями, за ними стоят) будут скопированы в текущий пространство имен, а все остальные останутся недоступными:

from math import exp, pi, pow
print pow(10, 2)
print e  # ошибка
print math.e  # ошибка

Если вам необходимо импортировать одноименные функции по нескольким модулей, для них можно задать псевдонимы. Тогда функция будет тоже скопирована в текущий пространство имен, но под другим названием:

from math import exp, pi, pow as math_power
from example_math import exp as my_exp
print exp(10) # вызовет exp из модуля math
print my_exp(10) # вызовет exp из модуля example_math
print math_power(10, 2) # вызовет pow из модуля math

Обратите внимание: для подключения интерпретатор ищет модули в текущей папке. При подключении модуль внутри файла (программы или иного модуля) — это папка, в которой находится этот файл. При подключении модуль в интерактивном режиме интерпретатора — это папка, в которой он был запущен. Если в текущей папке файл модуля не найдено, интерпретатор проверяет некоторый путь, указанный в настройках Python (отличается для разных операционных систем). Там находится большинство стандартных модулей, туда же можно добавлять собственные модули, туда же можно добавлять модули, найденные в интернете, для использования в своих программах.

Инициализация модулей

Кроме объявления функций и значений модуль может содержать исполняемый код. Такой код будет выполнен в момент подключения модуля и он используется для подготовки модуля для работы. 

Например, уже знакомый вам модуль sys, который мы использовали для получения аргументов, переданных программе. Значение sys.argv, в котором хранятся эти аргументы — не функция и не константа, а следовательно, не может быть инициализирован заранее. Это может быть примером инициализации модуля: при его подключении выполняется некоторый код, который обращается к среде, в котором запущена программа, получает оттуда параметры ее запуска и сохраняет в переменную argv модуля, делая их таким образом доступными в нашей программе.

Также это значит, что Python не видит принципиального различия между модулем и программой. Любая ваша программа может быть импортирована как модуль — при этом она будет выполнена, не всегда целесообразно. Любой модуль может быть запущен как программа — правда, многие из них содержат только объявления, то видимого результата работы не будет. Такое разделение является скорее условным и делается программистом для удобства построения программ.

Пакеты модулей

Когда модулей становится слишком много, возникает необходимость группировать их дальше. Для этого модули раскладываются по папкам.

Вы знаете, что интерпретатор ищет модули в текущей папке и в специально предназначенном для этого месте, следовательно необходимо каким-то образом показать ему, что папка рядом с вашей программой — не просто папка с файлами, а содержит модули для подключения. Для этого в папке должен находиться файл __init__.py  — он может быть пустым, но само его наличие сигнализирует интерпретатору, что папка с ним есть пакетом модулей и может использоваться в программе.

Как и модули, пакеты создают новые пространства имен:

import my_python_package.example_math # ищем модуль example_math 
                                             # в пакете my_python_package (в одноименной папке)
print my_python_package.example_math.exp(1)

Все виды импорта распространяется и на пакеты. Только в именах добавляется дополнительный элемент через точку — название пакета. Пакеты могут вкладываться в другие пакеты, аналогично добавляя новые пространства имен. Дальнейшее структурирования программы ограничивается только вашей фантазией.

Как и модули, пакеты могут содержать код, который будет выполнен при инициализации пакета — он записывается в самом файле __init__.py

На заметку: полезные функции

isinstance (x, y) — проверяет, является ли x значением типа y.

print isinstance('string', str) # True<br>a = ['q']<br>print isinstance(a, str) # False
print isinstance(a, list) # True

sorted (x) — возвращает список — отсортированную последовательность (строка или список), которая состоит из элементов x.

print sorted('abdgc') # ['a', 'b', 'c', 'd', 'g']
print sorted([3, 2, 1]) # [1, 2, 3]

Функции списков

x.sort () — сортирует элементы списка x по возрастанию.

x = [7, 5, 3, 1]
x.sort()
print x # [1, 3, 5, 7]

x.extend (y) — добавляет элементы списка y в конец списка x.

x = [7, 5, 3, 1]
x.extend([1, 2])
print x # [7, 5, 3, 1, 1, 2]

x.insert (i, y) — вставляет элемент y на позицию i в списке x, при этом существующие элементы списка сдвигаются.

x = [7, 5, 3, 1]
x.insert(1, 0)
print x # [7, 0, 5, 3, 1]

Функции модуля random

random.randint (a, b) — возвращает случайное целое число на отрезке от a до b включительно.

print random.randint(10, 20)
print random.randint(1, 100500)

random.choice (x) — возвращает случайный элемент из непустой последовательности (списка или строки) x.

print random.choice(['back', 'forward', 'left', 'right'])
print random.choice('abcdefghijklmnopqrstuvwxyz')

random.shuffle (x) — случайным образом «перемешивает» элементы последовательности (списка или строки) x, сохраняя результат в x.

mylist = [1, 0, 3, 5, 7, 1, 2]
random.shuffle(mylist)
print mylist

random.random () — возвращает случайное действительное число на промежутке от 0.0 (включительно) до 1.0 (не включая).

print random.random()

random.uniform (a, b) — возвращает случайное действительное число на отрезке от a до b включительно.

print random.uniform(0, 0.1)
print random.uniform(10, 100)

Практические задания

Практическое задание 1

Разработать функцию clean_list (list_to_clean),

которая принимает 1 аргумент - список всех значений (строк, целых и действительных чисел) произвольной длины, и возвращает список, состоящий из тех же значений, но не содержит повторов элементов. 
Это значит, что в случае наличия значения в исходном списке в нескольких экземплярах первый "экземпляр" значение остается на своем месте, а второй, третий и др. удаляются.

Например
Вызов функции: clean_list ([1, 1.0, '1', 1, 1])

Возвращает: [1, 1.0, '1', -1]

Вызов функции: clean_list ([ 'qwe', 'reg', ' qwe ',' REG '])

Возвращает: [' qwe ',' reg ',' REG ']

Вызов функции: clean_list ([32, 32.1, 32.0, -123])

Возвращает: [32, 32.1, 32.0, -123 ]

Вызов функции: clean_list ([1, 2, 1, 1, 3, 4, 5, 4, 6, '2', 7, 8, 9, 0, 1, 2, 3, 4, 5])

Возвращает: [1, 2, 3, 4, 5, 6, '2', 7, 8, 9, 0] 

Смотреть ответ:

Практическое задание 2

Разработать функцию counter (a, b),

которая принимает 2 аргумента - целые неотрицательные числа a и b, и возвращает число - количество различных цифр числа b, содержащиеся в числе a.
Например
Вызов функции: counter (12345, 567)

Возвращает: 1

Вызов функции: counter (1233211, 12128)

Возвращает: 2

Вызов функции: counter (123, 45)

Возвращает: 0 

Смотреть ответ:

Практическое задание 3

Разработать функцию super_fibonacci (n, m),

которая принимает 2 аргумента - положительные целые числа n и m (0 <n, m <= 35),и возвращает число - n-й элемент последовательности супер-Фибоначчи порядка m.
Напоминаем, что последовательность Фибоначчи - это последовательность чисел, в которой каждый элемент равен сумме двух предыдущих. 
Последовательностью супер-Фибоначчи порядка m будем считать такую ​​последовательность чисел, в которой каждый элемент равен сумме m предыдущих. 
Первые m элементов (с порядковыми номерами от 1 до m) считать единицами.
Например
Вызов функции: super_fibonacci (2, 1)

Возвращает 1

Вызов функции: super_fibonacci (3, 5)

Возвращает: 1

Вызов функции: super_fibonacci (8, 2)

Возвращает: 21

Вызов функции: super_fibonacci (9, 3)

Возвращает: 57 

Смотреть ответ:

Практическое задание 4

 Разработать функцию file_search (folder, filename),

которая 
принимает 2 аргумента - список folder и строку filename,и 
возвращает строку - полный путь к файлу или папке filename в структуре folder.
Файловая структура folder задается следующим образом:
Список - это папка с файлами, его 0-й элемент содержит название папки, а все остальные могут представлять или файлы в этой папке (1 файл = 1 строка-элемент списка), или вложенные папки, которые так же представляются спискам. 
Как и в файловой системе компьютера, путь к файлу состоит из имен всех папок, в которых он содержится, в порядке вложенности (начиная с внешней и папку, в которой непосредственно находится файл), разделенных "/".
Считать, что имена всех файлов уникальны. 
Вернуть логическое значение False, если файл не найден.
Например
Вызов функции: file_search ([ 'C:', 'backup.log "," ideas.txt'], 'ideas.txt')

Возвращает: "C: /ideas.txt"

Вызов функции: file_search ([ 'D: "[" recycle bin '], [' tmp ', [' old '], [' new folder1 "," asd.txt "," asd.bak "," find.me.bak ']], "hey .py '],' find.me ')

Возвращает: False
...

Смотреть ответ:

Читайте больше по теме:

Подписаться
Уведомление о
guest
0 комментариев
Inline Feedbacks
View all comments
Просмотры: 608

Популярные записи