питон. ООП_на_Python_Учебное пособие_var5a. Методическое пособие по дисциплине Введение в компьютерные технологии Москва Физический факультет мгу имени м в ломоносова 2022
Скачать 0.51 Mb.
|
9. Модули Встроенные в язык программирования функции доступны сразу. Чтобы их вызвать, не надо выполнять никаких дополнительных действий. Однако за время существования языка на нем было написано столько функций и классов, которые оказались востребованными множеством программистов и в разных областях, что включить весь этот объем кода в сам язык если возможно, то нецелесообразно. Чтобы разрешить проблему доступа к дополнительным возможностям языка, в программировании стало общепринятой практикой использовать так называемые модули, пакеты и библиотеки. Каждый модуль содержит коллекцию функций и классов, предназначенных для решения задач из определенной области. Так в модуле math языка Python содержатся математические функции, модуль random позволяет генерировать псевдослучайные числа, в модуле datetime содержатся классы для работы с датами и временем, модуль sys предоставляет доступ к системным переменным и т. д. Количество модулей для языка Python огромно, что связано с популярностью языка. Часть модулей собрана в так называемую стандартную библиотеку. Стандартная она потому, что поставляется вместе с установочным пакетом. Однако существуют сторонние библиотеки. Они скачиваются и устанавливаются отдельно. Для доступа к функционалу модуля, его надо импортировать в программу. После импорта интерпретатор "знает" о существовании дополнительных классов и функций и позволяет ими пользоваться. В Питоне импорт осуществляется командой import . При этом существует несколько способов импорта. Рассмотрим работу с модулем на примере math: import math 16 В глобальной области видимости появилось имя math . Если бы до импорта вы упомянули бы имя math , то возникла бы ошибка NameError . Теперь же в программе завелся объект math , относящийся к классу module . Чтобы вызвать функцию из модуля, надо впереди написать имя модуля, поставить точку, далее указать имя функции, после чего в скобках передать аргументы, если они требуются. Например, чтобы вызвать функцию pow из math , надо написать так: print(math.pow(2, 2)) # 4.0 Для обращения к константе скобки не нужны: print(math.pi) # 3.141592653589793 Описание модулей и их содержания можно посмотреть в официальной документации на сайте python.org. Второй способ импорта позволяет импортировать не весь модуль, а только необходимые функции из него: from math import gcd, sqrt, hypot Перевести можно как "из модуля math импортировать функции gcd , sqrt и hypot ". В таком случае при их вызове не надо перед именем функции указывать имя модуля: from math import gcd, sqrt, hypot print(gcd(100, 150)) # 50 print(sqrt(16)) # 4.0 print(hypot(3, 4)) # 5.0 Чтобы импортировать сразу все функции из модуля: from math import * Импорт через from не лишен недостатка. В программе уже может быть идентификатор с таким же именем, как имя одной из импортируемых функций или констант. Ошибки не будет, но одно из них окажется "затерто": pi = 3.14 print(pi) # 3.14 from math import pi print(pi) # 3.141592653589793 Здесь исчезает значение 3.14, присвоенное переменной pi. Это имя теперь указывает на число из модуля math. В этой связи более опасен именно импорт всех функций. Так как в этом случае очень легко не заметить подмены значений идентификаторов. Однако можно изменить имя идентификатора из модуля на какое угодно: pi = 3.14 from math import pi as P print(P) # 3.141592653589793 print(pi) # 3.14 В данном случае константа pi из модуля импортируется под именем P. Смысл подобных импортов в сокращении имен, так как есть модули с длинными именами, а имена функций и классов в них еще длиннее. Если в программу импортируется всего пара сущностей, и они используются в ней часто, то имеет смысл переименовать их на более короткий вариант. Сравните: import calendar print( calendar .weekheader(2)) # Mo Tu We Th Fr Sa Su и from calendar import weekheader as week print(week(3)) # Mon Tue Wed Thu Fri Sat Sun 17 Во всех остальных случаях лучше оставлять идентификаторы содержимого модуля в пространстве имен самого модуля и получать доступ к ним через имя модуля, то есть выполнять импорт командой import имя_модуля, а вызывать, например, функции через имя_модуля.имя_функции(). 10. Объектно-ориентированноепрограммирование Итак, что же такое объектно-ориентированное программирование? Судя по названию, ключевую роль здесь играют некие объекты, на которые ориентируется весь процесс программирования. Реальный мир для нас представляется в виде множества объектов, обладающих определенными свойствами, взаимодействующих между собой и вследствие этого изменяющимися. Эта привычная для взгляда человека картина мира была перенесена в программирование. Ключевую разницу между программой, написанной в структурном стиле, и объектно- ориентированной можно выразить так. В первом случае, на первый план выходит логика, понимание последовательности выполнения действий для достижения поставленной цели. Во втором – важнее представить программу как систему объектов, взаимодействие которых способно решить ту или иную задачу. Объектно-ориентированное программирование – это способ организации программы, позволяющий использовать один и тот же код многократно. В отличие от функций и модулей, ООП позволяет не только разделить программу на фрагменты, но и описать предметы реального мира в виде удобных сущностей — объектов, а также организовать связи между этими объектами. Основным «кирпичиком» ООП является класс — сложный тип данных, включающий набор переменных и функций для управления значениями, хранящимися в этих переменных. Переменные называют атрибутами или свойствами, а функции — методами. Класс является фабрикой объектов, т. е. позволяет создать неограниченное количество экземпляров, основанных на этом классе. Основными понятиями, используемыми в ООП, являются класс, объект, наследование, инкапсуляция и полиморфизм. В языке Python класс равносилен понятию тип данных. Что такое класс или тип? Проведем аналогию с реальным миром. Если мы возьмем конкретный стол, то это объект, но не класс. А вот общее представление о столах, их назначении – это класс. Ему принадлежат все реальные объекты столов, какими бы они ни были. Класс столов дает общую характеристику всем столам в мире, он их обобщает. То же самое с целыми числами в Python. Тип int – это класс целых чисел. Числа 5, 100134, -10 и т. д. – это конкретные объекты этого класса. В языке программирования Python объекты класса принято называть также экземплярами. Это связано с тем, что в нем все классы сами являются объектами класса type. Точно также как все модули являются объектами класса module. Следующее по важности понятие объектно-ориентированного программирования – наследование. Вернемся к столам. Пусть есть класс столов, описывающий общие свойства всех столов. Однако можно разделить все столы на письменные, обеденные и журнальные и для каждой группы создать свой класс, который будет наследником общего класса, но также вносить ряд своих особенностей. Таким образом, общий класс будет родительским, а классы групп – дочерними, производными. Дочерние классы наследуют особенности родительских, однако дополняют или в определенной степени модифицируют их характеристики. Когда мы создаем конкретный 18 экземпляр стола, то должны выбрать, какому классу столов он будет принадлежать. Если он принадлежит классу журнальных столов, то получит все характеристики общего класса столов и класса журнальных столов. Но не особенности письменных и обеденных. Основное (но не единственное) преимущество, которое дает концепция наследования в программировании, – это вынос одинакового кода из разных классов в один родительский класс. Другими словами, наследование позволяет сводить на нет повторение кода в разных частях программы. Инкапсуляция в ООП понимается двояко. Во многих языках этот термин обозначает сокрытие данных, то есть невозможность напрямую получить доступ к внутренней структуре объекта, так как это небезопасно. Например, наполнить желудок едой можно напрямую, положив еду в желудок. Но это опасно. Поэтому прямой доступ к желудку закрыт. Чтобы наполнить его едой, надо совершить ритуал, через элемент интерфейса под названием рот. В Python нет такой инкапсуляции, хотя она является одним из стандартов ООП. В Python можно получить доступ к любому атрибуту объекта и изменить его. Однако есть механизм, позволяющий имитировать сокрытие данных, если это так уж необходимо. Отсутствие сокрытия данных в Python делает программирование на нем проще, но привносит ряд особенностей, связанных с пространствами имен. Второй смысл инкапсуляции – объединение описания свойств объектов и их поведения в единое целое, то есть в класс. Инкапсуляция в этом смысле вытекает из самой идеи объектно-ориентированного программирования и, соответственно, имеется во всех ОО-языках. Полиморфизм можно перевести как множество форм. В ООП под полиморфизмом понимается следующее. Объекты разных классов, с разной внутренней реализацией, то есть программным кодом, могут иметь "одинаковые" методы. На самом деле у методов совпадают только имена, а вложенный в них код (то, что они делают) различен. Вот и получается, что у одного имени как бы множество форм. Например, для чисел есть операция сложения, обозначаемая знаком +. Однако мы можем определить класс, объекты которого также будут поддерживать операцию, обозначаемую этим знаком. Но это вовсе не значит, что объекты должны быть числами, и будет получаться какая-то сумма. Операция + для объектов нашего класса может значить что-то иное. Но интерфейс, в данном случае это знак +, у чисел и нашего класса будет одинаков. А полиморфизм проявляется во внутренней реализации и результате операции. Полиморфизм полезен не только тем, что дает возможность объектам пользовательских классов участвовать в стандартных операциях. Если у объектов разных классов есть одноименный метод, то коллекция таких разнородных объектов может быть обработана в одном цикле. 11. Созданиеклассовиобъектов Класс описывается с помощью ключевого слова class по следующей схеме: class <Название класса>[(<Класс1>, ..., <Класс№>)]: """ Строка документирования """ <Описание атрибутов и методов> Инструкция создает новый объект и присваивает ссылку на него идентификатору, указанному после ключевого слова class. Это означает, что название класса должно полностью соответствовать правилам именования переменных. После названия класса в круглых скобках можно указать один или несколько базовых классов через запятую. Если же класс не наследует базовые классы, то круглые скобки можно не указывать. Следует заметить, что все выражения внутри инструкции class выполняются при создании класса, а 19 не его экземпляра. Для примера создадим класс, внутри которого просто выводится сообщение: class MyClass : """ Это строка документирования """ print( "Инструкции выполняются сразу" ) Этот пример содержит лишь определение класса Myclass и не создает экземпляр класса. Как только поток выполнения достигнет инструкции class, сообщение, указанное в функции print(), будет сразу выведено. С точки зрения пространства имен класс можно представить подобным модулю. Также как в модуле в классе могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которому возможен через имя класса: class B : n = 5 def adder( v ): return v + B .n print( B .n) # Вывод: 5 print( B .adder(4)) # Вывод: 9 Однако в случае классов используется особая терминология. Имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B. Атрибуты-переменные называют полями или свойствами (в других языках понятия "поле" и "свойство" не совсем одно и то же). Полем является n. Атрибуты-функции называются методами. Методом в классе B является adder. Количество свойств и методов в классе может быть любым. Создание атрибута класса аналогично созданию обычной переменной. Метод внутри класса создается так же, как и обычная функция, – с помощью инструкции def. Среди атрибутов можно выделить две группы. Атрибуты первой группы принадлежат самому классу, а не конкретному экземпляру. Доступ к таким атрибутам вне тела класса осуществляется через точечную нотацию через объект объявления класса. <Имя класса>.<Имя атрибута> Если этот атрибут — метод, то по аналогии можно его вызвать. <Имя класса>.<Имя метода>([<Параметры>]) Во второй группе находятся атрибуты, принадлежащие конкретному экземпляру. В отличии от многих других языков программирования, эти атрибуты создаются уже во время существования объекта. Чтобы создать экземпляр класса используется синтаксис: <экземпляр класса> = <Название класса>([<Параметры>]) Когда экземпляр класса существует, можно задавать атрибуты уникальные для этого самого экземпляра: <экземпляр класса>.<Имя атрибута> = <Значение> И по аналогии получать доступ к уже существующим атрибутам: <экземпляр класса>.<Имя атрибута> При этом если не удается найти атрибут с таким именем у экземпляра, то поиск продолжается в атрибутах, принадлежащих классу. В связи с этим можно считать, что атрибуты самого класса разделяются между всеми его экземплярами. 20 class MyClass : x = 50 # Создаем атрибут объекта класса MyClass c1, с2 = MyClass (), MyClass () # Создаем два экземпляра класса c1.y = 10 # Создаем атрибут экземпляра класса c1 с2.у = 20 # Создаем атрибут экземпляра класса c2 print(c1.x, ' ' , c1.y) # Вывод: 50 10 print(с2.x, ' ' , с2.у) # Вывод: 50 20 В этом примере мы определяем класс MyClass и создаем атрибут объекта класса: х. Этот атрибут будет доступен всем создаваемым экземплярам класса. Затем создаем два экземпляра класса и добавляем одноименные атрибуты: у. Значения этих атрибутов будут разными в каждом экземпляре класса. Но если создать новый экземпляр (например, сЗ), то атрибут у в нем определен не будет. Таким образом, с помощью классов можно имитировать типы данных, поддерживаемые другими языками программирования (например, тип struct, доступный в языке С). Обычно все методы класса объявляются на уровне класса, а не экземпляра. Вызов такого метода через экземпляр класса с помощью синтаксиса <экземпляр класса>.<Имя метода>([<Параметры>]) неявно преобразуется к <Имя класса>.<Имя метода>(<экземпляр класса>, [<параметры>]) Иными словами, методам класса в первом параметре, который необходимо указывать явно, автоматически передается ссылка на экземпляр класса. Общепринято этот параметр называть именем self, хотя это и не обязательно. Доступ к атрибутам и методам класса внутри определяемого метода производится через переменную self с помощью точечной нотации. Обратите внимание на то, что при вызове метода не нужно передавать ссылку на экземпляр класса в качестве параметра, как это делается в определении метода внутри класса. Ссылку на экземпляр класса интерпретатор передает автоматически. Определим класс MyСlass с атрибутом х и методом print_x(), выводящим значение этого атрибута, а затем создадим экземпляр класса и вызовем метод: class MyClass : def __init__( self ): # Конструктор self .x = 1 # Атрибут экземпляра класса def print_x( self ): # self — это ссылка на экземпляр класса print( self .х) # Выводим значение атрибута с = MyClass () # Создание экземпляра класса # Вызываем метод print_x() c.print_x() # self не указывается при вызове метода print(с.х) # К атрибуту можно обратиться непосредственно Все атрибуты класса в языке Python являются открытыми (public), т. е. доступными для непосредственного изменения как из самого класса, так и из других классов и из основного кода программы. Очень важно понимать разницу между атрибутами объекта класса и атрибутами экземпляра класса. Атрибут объекта класса существует в единственном экземпляре, доступен всем экземплярам класса, его изменение можно видеть во всех экземплярах класса. Атрибут экземпляра класса хранит уникальное значение для каждого экземпляра, и изменение его в одном экземпляре класса не затронет значения одноименного атрибута в других экземплярах того же класса. Рассмотрим это на примере, создав класс с атрибутом объекта класса (х) и атрибутом экземпляра класса (у): 21 class MyClass : х = 10 # Атрибут объекта класса общий для всех экземпляров def __init__( self ): self .y = 20 # Атрибут экземпляра класса уникальный для каждого экземпляра c1 = MyClass () # Создаем первый экземпляр класса с2 = MyClass () # Создаем второй экземпляр класса #Выведем значения атрибута х, а затем изменим значение и опять произведем вывод: print (c1.x, с2.х) # Вывод: 10 10 MyClass .х = 88 # Изменяем атрибут объекта (класса MyClass) print (c1.x, с2.х) # Вывод: 88 88 print (c1.у, с2.у) # Вывод: 20 20 cl.y = 88 # Изменяем атрибут экземпляра класса с1 print (c1.y, с2.у) # Вывод: 88 20 Как видно из примера, изменение атрибута объекта класса x затронуло значение в обоих экземплярах класса. Аналогичная операция с атрибутом у изменяет значение только в экземпляре c1. Следует также учитывать, что в одном классе могут одновременно существовать атрибут объекта и атрибут экземпляра с одним именем. Изменение атрибута объекта класса мы производили следующим образом: MyClass.х = 88 # Изменяем атрибут объекта класса Если после этой инструкции вставить инструкцию: cl.x = 200 # Создаем атрибут экземпляра то будет создан атрибут экземпляра класса, а не изменено значение атрибута объекта класса. Чтобы увидеть разницу, нужно вывести их значения: print (cl.x, MyClass.х) # 200 88 Метод __init__() При создании экземпляра класса интерпретатор автоматически вызывает метод инициализации __init__(). Такой метод принято называть конструктором класса. Формат метода: def __init__(self[, <3начение1>[, . .., <ЗначениеN>]]): <Инструкции> С помощью метода __init__() атрибутам класса можно присвоить начальные значения. При создании экземпляра класса параметры этого метода указываются после имени класса в круглых скобках: < Экземпляр класса> = <Имя класса> ([<3начение1> [, ...,<ЗначениеN>]]) Пример использования метода: class MyClass : def __init__( self , valuel , value2 ): # Конструктор self .x = valuel self .у = value2 c = MyClass (100, 300) # Создаем экземпляр класса print(с.x, с.у) # Вывод: 100 300 Метод __del__() Перед уничтожением экземпляра автоматически вызывается метод, называемый деструктором. В языке Python деструктор реализуется в виде предопределенного метода__del__() (листинг 13.6). Следует заметить, что метод не будет вызван, если на экземпляр класса существует хотя бы одна ссылка. Впрочем, поскольку интерпретатор самостоятельно заботится об удалении объектов, использование деструктора в языке Python не имеет особого смысла. |