Подводя некоторые итоги, можно отметить, что модуль является контейнером, который может содержать:
Ни одна из этих частей модуля не является обязательной. Позже мы подробно обсудим, в каких ситуациях следует проектировать ту или иную структуру модуля, а сейчас же займемся деталями того, как взаимодействуют различные компоненты модуля.
В предыдущей лекции мы говорили, что атрибуты модуля - это глобальные переменные, видимые во всем модуле, потому доступные всем методам. Это позволяет методу всю необходимую информация или часть ее получать не через механизм формальных - фактических параметров, а через атрибуты модуля. Это особенно удобно, когда разным методам необходима одна и та же информация. Такой способ передачи входной информации методу позволяет также уменьшить число параметров, передаваемых при вызове метода.
А может ли метод передать информацию глобальным переменным модуля? Могут ли методы модуля обмениваться информацией, используя глобальные переменные. Ответ - да, могут, но есть одна проблема. Пусть X - имя глобальной переменной, введенное в модуле, Y - локальная переменная, определенная в теле метода. Тогда в теле метода присваивание Y = X допустимо, и локальная переменная Y станет ссылкой на объект, связанный с глобальной переменной. Присваивание X = Y также допустимо, но по правилам Python в теле метода появится локальная переменная с именем X и она станет ссылкой на объект, связанный с локальной переменной Y. На атрибут модуля X присваивание в теле метода никак не повлияет. Так что напрямую изменять атрибуты в теле метода прямым присваиванием нового значения невозможно. Тем не менее есть два способа справиться с поставленной задачей. Первый из них хорошо знаком и подробно обсуждался,
когда мы рассматривали процедуру сортировки списка. Напомню: "не продается вдохновенье, но можно рукопись продать". Нельзя изменить связь между именем и объектом, но можно изменить сам объект. Нельзя атрибуту в теле метода присвоить новое значение, но можно изменить объект, связанный с атрибутом. Рассмотрим пример:
#Пример метода reverse, получающего информацию от атрибута Arr и изменяющего его состояние
Arr = [5, 7, 3, 15, 8]
def reverse():
n = len(Arr)
i = 0; j = n - 1
while(i < j):
Arr[i], Arr[j] = Arr[j], Arr[i]
i = i + 1; j = j - 1
def test3():
print ('Arr before reverse:', Arr )
reverse()
print ('Arr after reverse:', Arr )
test3()
Как видите, метод reverse не имеет параметров, получая информацию от атрибута Arr. Но метод не только получает информацию от атрибута, - он изменяет состояние объекта, связанного с атрибутом.
Существует способ, когда изменить атрибут можно прямым присваиванием. Чтобы справиться с проблемой в Python пришлось отказаться от концепции, когда имена предварительно не объявляются, и разрешить в теле метода объявлять имена переменных как глобальные. Синтаксически, такое объявление выглядит так:
global <кортеж имен глобальных переменных модуля>
Если в теле функции дано такое объявление, то имена в кортеже воспринимаются как глобальные, что позволяет передавать им информацию.
Заметьте, имена в этом списке, могут предварительно не вводиться в модуле. Они появятся там в результате выполнения соответствующей функции. Приведу пример такого способа передачи данных от функции, использующий глобальные переменные модуля. Содержательно этот пример связан со следующей задачей. Когда-то школьники, с которыми я занимался, писали игру "Отгадай число", где компьютер задумывал число, выбирая его случайным образом из некоторого интервала. Сам интервал при каждом запуске игры менялся случайным образом, и его величина зависела от уровня игры. Наш пример моделирует фрагменты этой игры:
import random
def MinMax(a):
"""
Процедура MinMax вычисляет максимальный и минимальный элементы -
случайные границы интервала чисел
и передает информацию через создаваемые глобальные переменные Min, Max
"""
global Min, Max
Min = random.randint(0, a)
Max = random.randint(Min + 1, 2 * a)
Вызов процедуры MinMax создаст на уровне модуля глобальные переменные, доступные в других функциях. Функция CreateNumber имеет параметр, задающий уровень игры, и использует глобальные переменные Min и Max при вычислении интервала для выбора случайного числа:
def CreateNumber(level):
"""
Функция возвращает случайное число
в диапазоне, зависящем от глобальных переменных Min, Max
и уровня игры - level
"""
if level == 1:
return random.randint(Min, Max)
if level == 2:
return random.randint(Min * 20, Max * 20)
if level > 2:
return random.randint(Min * 40, Max * 40)
Построим соответствующий тест:
def test4():
print('уровень 1')
MinMax(10)
n = CreateNumber(1)
print('число ', n, 'из интервала ', Min, ' - ', Max)
print('уровень 2')
MinMax(10)
n = CreateNumber(2)
print('число ', n, 'из интервала ', Min * 20, ' - ', Max * 20)
print('уровень 4')
MinMax(10)
n = CreateNumber(4)
print('число ', n, 'из интервала ', Min * 40, ' - ', Max * 40)
test4()
Вот результаты работы:
Подводя промежуточные итоги, можно отметить, что имена, связываемые с объектами на уровне модуля, являются глобальными и видимы во всем модуле за исключением тех областей модуля, где действуют локальные переменные с тем же именем. Имена функций, объявленных в модуле, являются глобальными. Имена, связываемые с объектами, на уровне функции, являются локальными. Видимость глобальных имен в теле метода позволяет передать методу глобальную информацию, в частности использовать атрибуты метода. Но и метод, в свою очередь, может изменять глобальную информацию, в частности атрибуты метода, что позволяет методам обмениваться информацией через атрибуты метода.
До сих пор мы рассматривали двухуровневую структуру модуля. На уровне модуля задавалась глобальная информация, на уровне метода - локальная. Имена методов являлись глобальными, имена формальных параметров - локальными.
Однако, структура модуля может быть более сложной, - она может быть многоуровневой, поскольку в теле метода может быть объявлен метод, являющийся вложенным по отношению к охватывающему методу. Также как модуль является контейнером для методов, так и сам метод является контейнером для методов.
На число уровней вложенности не накладывается ограничений, хотя содержательно трудно представить более чем трехуровневую структуру. По отношению к вложенному методу, имена, введенные в охватывающем методе, являются глобальными, - они доступны во вложенном методе. Более того, вложенный метод может изменять значения таких объектов. Но, как и в случае с глобальными именами, прежде чем присвоить новое значение имени, введенному в охватывающем методе, такое имя нужно объявить, как нелокальное имя. Для такого объявления используется ключевое слово nonlocal:
nonlocal <кортеж имен охватывающего метода>
Заметьте, имена в теле метода могут иметь три различных статуса: локальные, нелокальные и глобальные.
Придумать простые содержательные примеры многоуровневой структуры непросто. Я начну с достаточно сложного для понимания примера, носящего скорее формальный характер. Тем не менее он хорошо демонстрирует возможности функционального программирования языка Python .
Ранее мы рассматривали функции высших порядков, которые имеют функции в качестве аргументов. В данном примере я построю функцию f_cover(x), которая в качестве результата возвращает функцию. Естественно, определить возвращаемую функцию в теле функции f_cover(x). Так появляется трехуровневая структура. Вложенная функция f_inside(x) будет иметь параметр и использовать локальную переменную охватывающей функции как глобальную информацию. Более того, вложенная функция будет изменять эту глобальную переменную в качестве побочного эффекта, используя механизм nonlocal переменных. Приводимый код имеет небольшой размер, но стоит его изучить внимательно:
def f_cover(x):
"""
Вложенные функции. Nonlocal переменные
"""
y = x + 7
def f_inside(x):
nonlocal y
y = y + 10
return x + y
return f_inside
Давайте построим тест, в котором будет вызвана функция f_cover:
def test5():
f = f_cover(1)
res = f(2)
print("res = ", res)
res = f(2)
print("res = ", res)
res = f(2)
print("res = ", res)
test5()
Заметьте, имя функции f_inside - это локальное имя и непосредственно вызвать эту функцию по имени не представляется возможным, но имя f можно связать с функцией, возвращаемой в результате вызова f_cover (по сути с функцией f_inside) и функцию f можно вызывать многократно.
Вот результат вычислений:
Обратите внимание, результаты вызова функции с одним и тем же аргументом разные, поскольку возвращаемое значение зависит от глобального для этой функции аргумента у, которое изменяется при каждом вызове.
Мы построили пример трехуровневой структуры с использованием nonlocal переменных. Построим теперь содержательный пример трехуровневой структуры, чаще встречающийся на практике. Я уже говорил, что одно из основных назначений модуля - предоставлять сервисы, многократно используемые другими модулями. Классическим примером является пакет math. Сервис реализуется как метод модуля. Если метод сложный, то применяется декомпозиция и в результате появляются более простые методы. Возникает дилемма, на каком уровне определять эти вспомогательные методы, - на уровне модуля или встраивать их в тело метода, для которого строится декомпозиция. Зачастую, второй способ предпочтительнее, поскольку на глобальном уровне видны только методы, представляющие сервис, а вспомогательные методы скрыты (инкапсулированы) и недоступны для вызова.
В качестве примера работы такого подхода рассмотрим задачу вычисления площади выпуклого многоугольника, заданного списком координат вершин многоугольника. Список начинается с некоторой вершины А с перечислением всех вершин при обходе вершин по часовой стрелке. Декомпозиция метода достаточно очевидна. Соединим вершину А с остальными вершинами, разбив многоугольник на треугольники. Сумма площадей треугольников даст искомую площадь. Тройка вершин каждого треугольника, позволяет определить размеры сторон треугольника, а затем по формуле Герона вычислить его площадь.
Приведу соответствующий код, где все методы, появляющиеся в результате декомпозиции, встроены в тело метода, вычисляющего площадь многоугольника:
def PolygonSquare(vertices: list)->float:
"""
Площадь выпуклого многоугольника,
заданного списком его вершин - vertices
"""
import math
def Side (A:tuple, B:tuple)->float:
x1 = A[0]; y1 =A[1]
x2 = B[0]; y2 =B[1]
s = math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
return s
def Geron(s1:float, s2: float, s3:float)-> float:
p = (s1 + s2 + s3) / 2;
return math.sqrt(p * (p - s1) * (p - s2) * (p - s3))
def TriangleSquare()->float:
sideAB = Side(A, B)
sideAC = Side(A, C)
sideBC = Side(B, C)
s = Geron(sideAB, sideBC, sideAC)
return s
sp = 0
n = len(vertices)
A = vertices[0]
for i in range(n-2):
B = vertices[i+1]
C= vertices[i+2]
st = TriangleSquare()
sp += st
return sp
Заметьте, тройка вершин А, В, С является глобальной информацией для встроенного метода TriangleSquare. Этот метод не имеет параметров и всю информацию получает через глобальные переменные. Другим методам нужны параметры.
Построим простой тест, вычисляющий площадь квадрата:
def test6():
vertices= [(0, 0),(0, 1), (1, 1), (1, 0)]
square = PolygonSquare(vertices)
print('площадь мноугольника = ', square)
test6()
Вот результаты:
Подводя некоторые итоги, можно отметить, что модуль является контейнером, который может содержать:
Ни одна из этих частей модуля не является обязательной. Позже мы подробно обсудим, в каких ситуациях следует проектировать ту или иную структуру модуля, а сейчас же займемся деталями того, как взаимодействуют различные компоненты модуля.
В предыдущей лекции мы говорили, что атрибуты модуля - это глобальные переменные, видимые во всем модуле, потому доступные всем методам. Это позволяет методу всю необходимую информация или часть ее получать не через механизм формальных - фактических параметров, а через атрибуты модуля. Это особенно удобно, когда разным методам необходима одна и та же информация. Такой способ передачи входной информации методу позволяет также уменьшить число параметров, передаваемых при вызове метода.
А может ли метод передать информацию глобальным переменным модуля? Могут ли методы модуля обмениваться информацией, используя глобальные переменные. Ответ - да, могут, но есть одна проблема. Пусть X - имя глобальной переменной, введенное в модуле, Y - локальная переменная, определенная в теле метода. Тогда в теле метода присваивание Y = X допустимо, и локальная переменная Y станет ссылкой на объект, связанный с глобальной переменной. Присваивание X = Y также допустимо, но по правилам Python в теле метода появится локальная переменная с именем X и она станет ссылкой на объект, связанный с локальной переменной Y. На атрибут модуля X присваивание в теле метода никак не повлияет. Так что напрямую изменять атрибуты в теле метода прямым присваиванием нового значения невозможно. Тем не менее есть два способа справиться с поставленной задачей. Первый из них хорошо знаком и подробно обсуждался,
когда мы рассматривали процедуру сортировки списка. Напомню: "не продается вдохновенье, но можно рукопись продать". Нельзя изменить связь между именем и объектом, но можно изменить сам объект. Нельзя атрибуту в теле метода присвоить новое значение, но можно изменить объект, связанный с атрибутом. Рассмотрим пример:
#Пример метода reverse, получающего информацию от атрибута Arr и изменяющего его состояние
Arr = [5, 7, 3, 15, 8]
def reverse():
n = len(Arr)
i = 0; j = n - 1
while(i < j):
Arr[i], Arr[j] = Arr[j], Arr[i]
i = i + 1; j = j - 1
def test3():
print ('Arr before reverse:', Arr )
reverse()
print ('Arr after reverse:', Arr )
test3()
Как видите, метод reverse не имеет параметров, получая информацию от атрибута Arr. Но метод не только получает информацию от атрибута, - он изменяет состояние объекта, связанного с атрибутом.
Существует способ, когда изменить атрибут можно прямым присваиванием. Чтобы справиться с проблемой в Python пришлось отказаться от концепции, когда имена предварительно не объявляются, и разрешить в теле метода объявлять имена переменных как глобальные. Синтаксически, такое объявление выглядит так:
global <кортеж имен глобальных переменных модуля>
Если в теле функции дано такое объявление, то имена в кортеже воспринимаются как глобальные, что позволяет передавать им информацию.
Заметьте, имена в этом списке, могут предварительно не вводиться в модуле. Они появятся там в результате выполнения соответствующей функции. Приведу пример такого способа передачи данных от функции, использующий глобальные переменные модуля. Содержательно этот пример связан со следующей задачей. Когда-то школьники, с которыми я занимался, писали игру "Отгадай число", где компьютер задумывал число, выбирая его случайным образом из некоторого интервала. Сам интервал при каждом запуске игры менялся случайным образом, и его величина зависела от уровня игры. Наш пример моделирует фрагменты этой игры:
import random
def MinMax(a):
"""
Процедура MinMax вычисляет максимальный и минимальный элементы -
случайные границы интервала чисел
и передает информацию через создаваемые глобальные переменные Min, Max
"""
global Min, Max
Min = random.randint(0, a)
Max = random.randint(Min + 1, 2 * a)
Вызов процедуры MinMax создаст на уровне модуля глобальные переменные, доступные в других функциях. Функция CreateNumber имеет параметр, задающий уровень игры, и использует глобальные переменные Min и Max при вычислении интервала для выбора случайного числа:
def CreateNumber(level):
"""
Функция возвращает случайное число
в диапазоне, зависящем от глобальных переменных Min, Max
и уровня игры - level
"""
if level == 1:
return random.randint(Min, Max)
if level == 2:
return random.randint(Min * 20, Max * 20)
if level > 2:
return random.randint(Min * 40, Max * 40)
Построим соответствующий тест:
def test4():
print('уровень 1')
MinMax(10)
n = CreateNumber(1)
print('число ', n, 'из интервала ', Min, ' - ', Max)
print('уровень 2')
MinMax(10)
n = CreateNumber(2)
print('число ', n, 'из интервала ', Min * 20, ' - ', Max * 20)
print('уровень 4')
MinMax(10)
n = CreateNumber(4)
print('число ', n, 'из интервала ', Min * 40, ' - ', Max * 40)
test4()
Вот результаты работы:
Подводя промежуточные итоги, можно отметить, что имена, связываемые с объектами на уровне модуля, являются глобальными и видимы во всем модуле за исключением тех областей модуля, где действуют локальные переменные с тем же именем. Имена функций, объявленных в модуле, являются глобальными. Имена, связываемые с объектами, на уровне функции, являются локальными. Видимость глобальных имен в теле метода позволяет передать методу глобальную информацию, в частности использовать атрибуты метода. Но и метод, в свою очередь, может изменять глобальную информацию, в частности атрибуты метода, что позволяет методам обмениваться информацией через атрибуты метода.
До сих пор мы рассматривали двухуровневую структуру модуля. На уровне модуля задавалась глобальная информация, на уровне метода - локальная. Имена методов являлись глобальными, имена формальных параметров - локальными.
Однако, структура модуля может быть более сложной, - она может быть многоуровневой, поскольку в теле метода может быть объявлен метод, являющийся вложенным по отношению к охватывающему методу. Также как модуль является контейнером для методов, так и сам метод является контейнером для методов.
На число уровней вложенности не накладывается ограничений, хотя содержательно трудно представить более чем трехуровневую структуру. По отношению к вложенному методу, имена, введенные в охватывающем методе, являются глобальными, - они доступны во вложенном методе. Более того, вложенный метод может изменять значения таких объектов. Но, как и в случае с глобальными именами, прежде чем присвоить новое значение имени, введенному в охватывающем методе, такое имя нужно объявить, как нелокальное имя. Для такого объявления используется ключевое слово nonlocal:
nonlocal <кортеж имен охватывающего метода>
Заметьте, имена в теле метода могут иметь три различных статуса: локальные, нелокальные и глобальные.
Придумать простые содержательные примеры многоуровневой структуры непросто. Я начну с достаточно сложного для понимания примера, носящего скорее формальный характер. Тем не менее он хорошо демонстрирует возможности функционального программирования языка Python .
Ранее мы рассматривали функции высших порядков, которые имеют функции в качестве аргументов. В данном примере я построю функцию f_cover(x), которая в качестве результата возвращает функцию. Естественно, определить возвращаемую функцию в теле функции f_cover(x). Так появляется трехуровневая структура. Вложенная функция f_inside(x) будет иметь параметр и использовать локальную переменную охватывающей функции как глобальную информацию. Более того, вложенная функция будет изменять эту глобальную переменную в качестве побочного эффекта, используя механизм nonlocal переменных. Приводимый код имеет небольшой размер, но стоит его изучить внимательно:
def f_cover(x):
"""
Вложенные функции. Nonlocal переменные
"""
y = x + 7
def f_inside(x):
nonlocal y
y = y + 10
return x + y
return f_inside
Давайте построим тест, в котором будет вызвана функция f_cover:
def test5():
f = f_cover(1)
res = f(2)
print("res = ", res)
res = f(2)
print("res = ", res)
res = f(2)
print("res = ", res)
test5()
Заметьте, имя функции f_inside - это локальное имя и непосредственно вызвать эту функцию по имени не представляется возможным, но имя f можно связать с функцией, возвращаемой в результате вызова f_cover (по сути с функцией f_inside) и функцию f можно вызывать многократно.
Вот результат вычислений:
Обратите внимание, результаты вызова функции с одним и тем же аргументом разные, поскольку возвращаемое значение зависит от глобального для этой функции аргумента у, которое изменяется при каждом вызове.
Мы построили пример трехуровневой структуры с использованием nonlocal переменных. Построим теперь содержательный пример трехуровневой структуры, чаще встречающийся на практике. Я уже говорил, что одно из основных назначений модуля - предоставлять сервисы, многократно используемые другими модулями. Классическим примером является пакет math. Сервис реализуется как метод модуля. Если метод сложный, то применяется декомпозиция и в результате появляются более простые методы. Возникает дилемма, на каком уровне определять эти вспомогательные методы, - на уровне модуля или встраивать их в тело метода, для которого строится декомпозиция. Зачастую, второй способ предпочтительнее, поскольку на глобальном уровне видны только методы, представляющие сервис, а вспомогательные методы скрыты (инкапсулированы) и недоступны для вызова.
В качестве примера работы такого подхода рассмотрим задачу вычисления площади выпуклого многоугольника, заданного списком координат вершин многоугольника. Список начинается с некоторой вершины А с перечислением всех вершин при обходе вершин по часовой стрелке. Декомпозиция метода достаточно очевидна. Соединим вершину А с остальными вершинами, разбив многоугольник на треугольники. Сумма площадей треугольников даст искомую площадь. Тройка вершин каждого треугольника, позволяет определить размеры сторон треугольника, а затем по формуле Герона вычислить его площадь.
Приведу соответствующий код, где все методы, появляющиеся в результате декомпозиции, встроены в тело метода, вычисляющего площадь многоугольника:
def PolygonSquare(vertices: list)->float:
"""
Площадь выпуклого многоугольника,
заданного списком его вершин - vertices
"""
import math
def Side (A:tuple, B:tuple)->float:
x1 = A[0]; y1 =A[1]
x2 = B[0]; y2 =B[1]
s = math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
return s
def Geron(s1:float, s2: float, s3:float)-> float:
p = (s1 + s2 + s3) / 2;
return math.sqrt(p * (p - s1) * (p - s2) * (p - s3))
def TriangleSquare()->float:
sideAB = Side(A, B)
sideAC = Side(A, C)
sideBC = Side(B, C)
s = Geron(sideAB, sideBC, sideAC)
return s
sp = 0
n = len(vertices)
A = vertices[0]
for i in range(n-2):
B = vertices[i+1]
C= vertices[i+2]
st = TriangleSquare()
sp += st
return sp
Заметьте, тройка вершин А, В, С является глобальной информацией для встроенного метода TriangleSquare. Этот метод не имеет параметров и всю информацию получает через глобальные переменные. Другим методам нужны параметры.
Построим простой тест, вычисляющий площадь квадрата:
def test6():
vertices= [(0, 0),(0, 1), (1, 1), (1, 0)]
square = PolygonSquare(vertices)
print('площадь мноугольника = ', square)
test6()
Вот результаты:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.