Перемещение объекта в pygame
Привет! Я пишу игру на pygame, и у меня возник вопрос: кат перемещать объекты по экрану. Я , конечно, знаю как перемещать объекты вручную, но как это автоматизировать? Скажем, чтобы в игре по типу динозаврика, препятствия перемещались с определенной скоростью на него, либо же персонаж. Также, у меня получилось сделать, чтобы объекты сами перемещались, но они просто клонируются на экране, а мне также нужно и удалять «старые’ препятствия
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Перемещение спрайта в pygame
Доброго времени суток. Начал писать несложный шутер с помощью pygame, есть небольшая проблема с.
Постоянное перемещение персонажа из одного угла экрана в другой (pygame)
import pygame window=pygame.display.set_mode((400,400)) pygame.display.set_caption(‘hello py.
Pygame, прорисовка объекта множество раз
Здравствуйте, помогите разобраться со следующей проблемой. Эксперементируя со своим кодом я пытался.
Как с библиотекой pygame сделать коллизию для объекта?
Надо сделать, чтобы игрок мог запрыгивать на платформу (у неё и у игрока есть спрайты), как это.
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
При импорте pygame пишет что нет модуля pygame.base
Устанавливаю для kivy.Всё есть (sython,gsstreamer,kivy,pyscripter,python) , а вот фраем. pygame не.
Ошибка в pygame AttributeError: ‘pygame.Rect’ object has no attribute ‘blit’
Создаю игру, крестики нолики Почему возникает ошибка и как ее исправить? AttributeError.
Pygame ошибка module ‘pygame’ has no ‘init’ member
Всем здравствуйте. Недавно начал работать с VS code и выдает такая ошибка. Прикрепляю полностью.

pygame.error: cannot convert without pygame.display initialized
Только начал изучать спрайты Выдаёт ошибку: Traceback (most recent call last): File.

Плавное перемещение объекта с tkinter
Здравствуйте У меня программа рисует три объекта : два овала(a, b) и прямоугольник(c). Как.

Не работает перемещение объекта QGraphicsEllipseItem
Пытаюст сделать приложение для визуализации графов. Для этого реализовал класс окна, класс поля, в.
5.4. Практикум: Движение — это жизнь!
В рамках урока по Практическому применению навыков вы научитесь создавать основную часть интерактивной графической программы, в которой можно перемещать объект по экрану с помощью кнопки стрелок клавиатуры.
Вы научитесь:
- Совмещать на практике все полученные в ходе изучения онлайн курса навыки и знания путем создания интересной и увлекательной графической программы
Пришло время объединить все знания.
Ваша задача — создать программу, которая будет «слушаться» нажатия на кнопки клавиатуры и перемещать объект по экрану в нужном вам направлении.

1. Для того чтобы «отслеживать» нажатие на кнопку вам понадобится функция get_key(), а также функции рисования, чтобы отображать объект, способный перемещаться.
В пустой файл Python импортируйте функции ti_system и ti_draw. Или используйте один из шаблонов и добавьте в него недостающие функции.

2. Программа должна понимать, какая кнопка нажата, чтобы предпринимать соответствующие действия. Поэтому вам нужно сохранить значение кнопки в переменной:
k = get_key()
Начните рисовать свой объект (круг) в центре экрана:
x=159
y=106
Это действие также можно выполнить, если написать следующее: x,y = 159,106.
Нажатие на кнопку стрелки будет изменять одно из этих значений и тем самым переносить объект в новую позицию, создавая эффект перемещения.

Примечание для учителя: Можете попросить учащихся самостоятельно ввести функции для рисования и стирания объектов. clear() стирает содержимое экрана, функция clear(x,y,w,h) стирает прямоугольную область.
3. Запустите основной цикл следующим образом:
while k != «esc»:
Создайте цветной круг. Радиус и цвет заливки выбирайте на свое усмотрение.

Примечание для учителя: Цвет и радиус можно выбрать произвольно.
4. Проверьте, какая кнопка была нажата.
if k == «up»:
y = y — 10 (или y — = 10)
Эти выражения означают следующее: Если нажата кнопка «стрелка вверх«, нужно вычесть 10 из значения у. Это приведет к тому, что круг переместится по экрану вверх на 10 пикселей.
Теперь напишите три выражения с оператором if для других трех стрелок (вниз, влево и вправо). При этом нужно указать, насколько должен произойти сдвиг по оси х и у соответственно.
Все выражения if являются частью блока выражения while.
Внизу блока нужно будет снова ввести выражение для считывания нажатия кнопки (k = get_key()), все это внутри цикла с оператором while, т.к. первое выражение k = get_key() не являлось его частью.
Когда закончите, протестируйте программу.

5. Используйте кнопки стрелок, чтобы перемещать круг. Получилось? Если да, то примите наши поздравления! Только при движении круг оставляет за собой след, это можно исправить.
Нужно стереть старый круг перед тем, как перемещать его. Для этого понадобятся следующие выражения:
set_color(255,255,255)
fill_circle(x,y,10)
Их нужно вводить до того выражения, которое изменяет значения х и у.

6. Теперь круг «мигает», потому что вы его окрашиваете в желтый, а затем сразу в белый. Вам нужно сделать так, чтобы экран обновлялся именно тогда, когда будет нарисован желтый круг.
Поэтому вверху своей программы (перед выражением с оператором while) поместите следующее выражение:
use_buffer()
Его можно найти в меню: TI Draw > Control.
Теперь после выражения, которое отвечает за рисование желтого круга, введите следующую функцию:
paint_buffer()
Снова запустите программу.
Теперь стало лучше? Экран обновляется только после того, как нарисован желтый круг.

Примечание для учителя: Немного информации о функциях use_buffer() и paint_buffer():
Данные две функции предоставляют вам «контроль» над тем, когда именно обновляются нарисованные на экране объекты.
- Функция use_buffer() подразумевает, что компьютер будет рисовать объекты в буфере памяти, то есть той области памяти, которая не видна на экране.
- Функция paint_buffer() является сигналом для перемещения всех данных из буфера на экран.
Поэтому стирание желтого круга и рисование нового происходит не на экране, и это происходит одновременно, поэтому все, что вы видите, это желтый круг (в разных позициях).
7. И, наконец, что будет происходить, когда круг будет выходить за пределы экрана? Если, например, все время перемещать его в одном направлении. Дополните свою программу еще несколькими выражениями с оператором if, чтобы система понимала, где находится край, и предпринимала соответствующие действия. Здесь у вас два варианта:
- Если круг доберется до края, он должен оставаться на месте и не выходить за пределы экрана:
(если x - Если круг выйдет за пределы одной из сторон экрана, нарисовать его с противоположной стороны:
(если x

Дополнительная задача:
Измените свою программу таким образом, чтобы круг продолжал перемещаться в том же направлении, даже если не была нажата соответствующая кнопка.
Примечание для учителя: Образец кода для данной задачи:
# Unit 5 Application: move it!
from ti_system import get_key
rom ti_draw import *
#===========================
use_buffer()
key = get_key()
x,y = 159,106
while key!=»esc»:
set_color(255,255,0)
fill_circle(x,y,10)
set_color(0,0,0)
draw_circle(x,y,10)
paint_buffer()
set_color(255,255,255)
fill_circle(x,y,10)
draw_circle(x,y,10)
if key == «up»:
y = y — 10 # or y-=10
if key == «down»:
y+=10
if key ==»left»:
x-=10
if key ==»right»:
x+=10
if x if x>317:x=0
if y if y>211:y=0
key = get_key(0)
12. Классы и объекты¶
12.1. Объектно-ориентированное программирование¶
Python является объектно-ориентированным языком программирования, что означает наличие в языке средств объектно-ориентированного программирования (ООП).
Объектно-ориентированное программирование возникло в 1960 годы, но только в середине 1980-х оно стало основной парадигмой программирования, используемой при создании новых программ. ООП было разработано, чтобы справиться с быстро растущими размерами и сложностью программных систем, и упростить последующие сопровождение и модификацию этих больших и сложных систем.
До сих пор мы писали программы с использованием парадигмы процедурного программирования. Процедурное программирование фокусируется на создании функций или процедур, которые работают с данными. Объектно-ориентированное программирование фокусируется на создании объектов, которые содержат и данные и функциональность.
12.2. Определяемые пользователем типы данных¶
Класс, в сущности, определяет новый тип данных. Мы уже некоторое время пользуемся встроенными типами данных Python, а теперь готовы создать наш собственный (пользовательский) тип.
Рассмотрим понятие математической точки. В пространстве двух измерений, точка — это два числа (координаты), с которыми работают как с одним объектом. В математике координаты точки часто записываются в скобках, разделенные запятой. Например, (0, 0) представляет начало координат, а (x, y) представляет точку, расположенную на x единиц правее и на y единиц выше, чем начало координат.
Естественный способ представления точки на языке Python — с помощью двух чисел. Но остается вопрос: как именно объединить эти два числа в один составной объект? Очевидное и быстрое решение состоит в том, чтобы использовать список или кортеж, и в некоторых случаях оно будет наилучшим.
Альтернативой является определение нового типа, называемого также классом. Этот подход требует немного больше усилий, но имеет преимущества, которые вскоре станут вам понятны.
Определение нашего класса Point (англ.: точка) выглядит так:
class Point: pass
Определения классов могут встречаться в программе где угодно, но обычно их помещают в начале, после предложений import . Синтаксические правила для определения класса такие же, как и для других составных предложений. Первая строка — заголовок, начинающийся с ключевого слова class , за которым следуют имя класса и двоеточие, следующие строки — тело класса.
Приведенное выше определение создает новый класс Point . Предложение pass ничего не делает; мы воспользовались им потому, что тело составного предложения не может быть пустым.
Для этой цели подойдет и документирующая строка:
class Point: "Point class for storing mathematical points."
Создав класс Point , мы создали новый тип Point . Представители этого типа называются экземплярами или объектами этого типа. Создание экземпляра класса выполняется с помощью вызова класса. Классы, как и функции, можно вызывать, и мы создаем объект типа Point , вызывая класс Point :
>>> type(Point) >>> p = Point() >>> type(p)
Переменная p содержит ссылку на новый объект типа Point .
Можно думать о классе, как о фабрике по изготовлению объектов. Тогда наш класс Point — фабрика по изготовлению точек. Сам класс не является точкой, но содержит все, что необходимо для производства точек.
12.3. Атрибуты¶
Как и объекты реального мира, экземпляры классов обладают свойствами и поведением. Свойства определяются элементами-данными, которые содержит объект.
Можно добавить новые элементы-данные к экземпляру класса с помощью точечной нотации:
>>> p.x = 3 >>> p.y = 4
Этот синтаксис подобен синтаксису для обращения к переменной или функции модуля, например, math.pi или string.uppercase . И модули, и экземпляры класса создают свое собственное пространство имен, и синтаксис для доступа к элементам тех и других — атрибутам — один и тот же. В данном случае атрибуты, к которым мы обращаемся, — элементы-данные в экземпляре класса.
Следующая диаграмма состояний показывает результат выполненных присваиваний:

Переменная p ссылается на объект класса Point , который содержит два атрибута. Каждый из атрибутов ссылается на число.
Тот же самый синтаксис используется для получения значений атрибутов:
>>> print p.y 4 >>> x = p.x >>> print x 3
Выражение p.x означает: возьмите объект, на который указывает переменная p , затем возьмите значение атрибута x этого объекта. В приведенном примере мы присваиваем полученное значение переменной с именем x . Переменная x и атрибут x не вступают в конфликт имен, поскольку принадлежат разным пространствам имен.
Точечную нотацию можно использовать как часть любого выражения, так что следующие предложения совершенно типичны:
print '(%d, %d)' % (p.x, p.y) distance_squared = p.x * p.x + p.y * p.y
Первая строка выводит (3, 4) . Вторая строка вычисляет значение 25.
12.4. Инициализирующий метод и self ¶
Поскольку наш класс Point предназначен для представления математических точек в двумерном пространстве, все экземпляры этого класса должны иметь атрибуты x и y . Но пока это не так для наших объектов Point .
>>> p2 = Point() >>> p2.x Traceback (most recent call last): File "", line 1, in ? AttributeError: Point instance has no attribute 'x' >>>
Для решения этой проблемы добавим в наш класс инициализирующий метод.
class Point: def __init__(self, x=0, y=0): self.x = x self.y = y
Метод ведет себя как функция, но является частью объекта. Доступ к методу, как и доступ к атрибутам-данным, осуществляется при помощи точечной нотации. Инициализирующий метод вызывается автоматически, когда вызывается класс.
Чтобы получше разобраться, как работают методы, давайте добавим еще один метод, distance_from_origin (англ.: расстояние от начала):
class Point: def __init__(self, x=0, y=0): self.x = x self.y = y def distance_from_origin(self): return ((self.x ** 2) + (self.y ** 2)) ** 0.5
Создадим несколько экземпляров точек, посмотрим на их атрибуты, и вызовем наш новый метод для этих объектов:
>>> p = Point(3, 4) >>> p.x 3 >>> p.y 4 >>> p.distance_from_origin() 5.0 >>> q = Point(5, 12) >>> q.x 5 >>> q.y 12 >>> q.distance_from_origin() 13.0 >>> r = Point() >>> r.x 0 >>> r.y 0 >>> r.distance_from_origin() 0.0
В определении метода первый параметр всегда указывает на экземпляр класса. Традиционно этому параметру дают имя self. В только что рассмотренном примере параметр self последовательно указывает на объекты p , q , и r .
12.5. Объекты как параметры¶
Объект можно передать в качестве параметра, как любое другое значение. Например:
def print_point(p): print '(%s, %s)' % (str(p.x), str(p.y))
Функция print_point принимает объект Point в качестве аргумента и выводит его значение. Если выполнить print_point(p) с объектом p , определенным выше, то функция выведет (3, 4) .
12.6. Равенство объектов¶
Смысл слова ‘равенство’ кажется совершенно ясным. Но если говорить об объектах, то мы скоро обнаружим неоднозначность этого слова.
Например, что означает утверждение, что значения двух переменных типа Point равны? Что соответствующие объекты Point содержат одинаковые данные (координаты точки)? Или что обе переменные указывают на один и тот же объект?
Чтобы выяснить, ссылаются ли две переменные на один и тот же объект, используется оператор == . Например:
>>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Point() >>> p2.x = 3 >>> p2.y = 4 >>> p1 == p2 False
Хотя p1 и p2 содержат одинаковые координаты, они являются разными объектами. Но если присвоить переменной p1 значение p2 , то две переменных будут альтернативными именами одного и того же объекта:
>>> p2 = p1 >>> p1 == p2 True
Этот тип равенства называется поверхностным равенством, потому что он сравнивает только ссылки, а не содержимое объектов.
Для того, чтобы сравнить содержимое объектов — проверить глубокое равенство — можно написать функцию, подобную этой:
def same_point(p1, p2): return (p1.x == p2.x) and (p1.y == p2.y)
Теперь, если создать два разных объекта, содержащих одинаковые данные, с помощью same_point можно выяснить, представляют ли они одну и ту же математическую точку.
>>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Point() >>> p2.x = 3 >>> p2.y = 4 >>> same_point(p1, p2) True
А если две переменные ссылаются на один и тот же объект, для них выполняется как поверхностное, так и глубокое равенство.
12.7. Прямоугольники¶
Пусть нам нужен класс для представления прямоугольников. Вопрос в том, какую информацию необходимо указать, чтобы описать прямоугольник? Для простоты предположим, что стороны прямоугольника ориентированы горизонтально и вертикально.
Есть несколько вариантов. Мы могли бы указать координаты центра прямоугольника и его размер (ширину и высоту). Или указать координаты одного из углов и размер прямоугольника. Или указать координаты двух противоположных углов. Традиционный способ таков: указать левый верхний угол прямоугольника и его размер.
Определим новый класс Rectangle (англ.: прямоугольник):
class Rectangle: pass
И создадим экземпляр этого класса:
box = Rectangle() box.width = 100.0 box.height = 200.0
Этот код создает новый объект Rectangle с двумя атрибутами — числами с плавающей точкой – width (англ.: ширина) и height (англ.: высота). А для того, чтобы указать левый верхний угол, можно вставить объект внутрь объекта!
box.corner = Point() box.corner.x = 0.0 box.corner.y = 0.0
Операторы точка можно сочетать, как видно из этого примера. Выражение box.corner.x означает: возьмите объект, на который указывает box , получите его атрибут corner ; затем возьмите объект, на который указывает этот атрибут, и получите атрибут x этого последнего объекта.
Следующий рисунок иллюстрирует, что у нас получилось:

12.8. Объекты как возвращаемые значения¶
Функции могут возвращать объекты. Например, функция find_center берет Rectangle в качестве аргумента и возвращает Point с координатами центра прямоугольника:
def find_center(box): p = Point() p.x = box.corner.x + box.width/2.0 p.y = box.corner.y - box.height/2.0 return p
Следующий код демонстрирует использование функции:
>>> center = find_center(box) >>> print_point(center) (50.0, 100.0)
12.9. Объекты изменяемы¶
Состояние объекта изменяется путем присваивания значений его атрибутам. Например, чтобы изменить размер прямоугольника без изменения его местоположения, изменим значения width и height :
box.width = box.width + 50 box.height = box.height + 100
Обобщим этот код, определив функцию grow_rect (англ.: увеличить прямоугольник):
def grow_rect(box, dwidth, dheight): box.width += dwidth box.height += dheight
12.10. Копирование¶
Альтернативные имена могут сделать программу трудночитаемой, так как изменения, сделанные в одном месте, могут возыметь неожиданное действие в другом. Сложно отслеживать все переменные, ссылающиеся на некоторый объект.
Вместо использования альтернативных имен для одного и того же объекта, во многих случаях полезно получить копию объекта. Модуль copy содержит функцию copy , которая способна скопировать любой объект:
>>> import copy >>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = copy.copy(p1) >>> p1 == p2 False >>> same_point(p1, p2) True
После импортирования модуля copy , с помощью функции copy мы создаем новый объект класса Point . Объекты p1 и p2 являются разными объектами, но содержат одинаковые данные.
Для копирования простых объектов вроде Point , которые не содержат вложенных объектов, функции copy достаточно. Такое копирование называется поверхностным копированием.
Для объектов, подобных объектам Rectangle , которые содержат ссылку на объект Point , функция copy не совсем то, что обычно требуется. Она скопирует ссылку на объект Point , так, что и старый объект Rectangle , и новый, будут ссылаться на один и тот же объект Point .
Если мы создадим прямоугольник b1 и сделаем его копию b2 с помощью copy , то результат будет таким:

Это, скорее всего, не то, что мы хотели получить. В этом случае вызов функции grow_rect с одним объектом Rectangle не повлияет на другой, однако, вызов move_rect (см. упражнения в конце главы) с любым из прямоугольников отразится на обоих! Такое поведение сбивает с толку и чревато ошибками.
К счастью, модуль copy содержит метод deepcopy , который копирует не только сам объект, но и все вложенные объекты. Неудивительно, что эта операция называется глубоким копированием.
>>> b2 = copy.deepcopy(b1)
Теперь b1 и b2 — совершенно разные объекты.
Используя deepcopy , можно переписать grow_rect так, чтобы вместо изменения существующего объекта Rectangle , он создавал новый объект Rectangle с таким же расположением левого верхнего угла, но с другими размерами:
def grow_rect(box, dwidth, dheight): import copy new_box = copy.deepcopy(box) new_box.width += dwidth new_box.height += dheight return new_box
12.11. Time¶
В качестве еще одного примера определенного пользователем типа, создадим класс Time (англ.: время) для хранения времени дня. Определение класса будет таким:
class Time: pass
Теперь мы можем создать новый объект класса Time и установить значения атрибутов для часов, минут и секунд:
time = Time() time.hours = 11 time.minutes = 59 time.seconds = 30
В следующих разделах мы напишем две версии функции add_time (англ.: сложить время) для вычисления суммы двух объектов Time . Они еще раз продемонстрируют нам два типа функций, с которыми мы познакомились в главе 8: чистые и модифицирующие.
12.12. Снова чистые функции¶
Вот черновая версия функции add_time :
def add_time(t1, t2): sum = Time() sum.hours = t1.hours + t2.hours sum.minutes = t1.minutes + t2.minutes sum.seconds = t1.seconds + t2.seconds return sum
Функция создает новый объект Time , инициализирует его атрибуты, и возвращает ссылку на него. Это — чистая функция, поскольку она не изменяет ни один из переданных ей объектов и не имеет побочных эффектов, вроде вывода значения на печать или получения ввода от пользователя.
Вот пример использования этой функции. Мы создадим два объекта Time : current_time , содержащий текущее время, и bread_time , содержащий количество времени, необходимое хлебопечке для приготовления хлеба. Затем воспользуемся функцией add_time чтобы узнать, во сколько хлеб будет готов.
>>> current_time = Time() >>> current_time.hours = 9 >>> current_time.minutes = 14 >>> current_time.seconds = 30 >>> bread_time = Time() >>> bread_time.hours = 3 >>> bread_time.minutes = 35 >>> bread_time.seconds = 0 >>> done_time = add_time(current_time, bread_time)
Определим функцию print_time для вывода объекта Time , воспользовавшись оператором форматирования сток:
def print_time(time): print "%02i:%02i:%02i" % (time.hours, time.minutes, time.seconds)
Теперь выведем полученный нами результат:
>>> print_time(done_time) 12:49:30
Программа выводит 12:49:30 , и это правильный результат. Однако, в некоторых случаях результат работы функции add_time будет неверным. Можете сами привести пример такого случая?
Проблема с функцией add_time в том, что функция не учитывает случаи, когда сумма секунд или минут превышает 60. Когда это случается, необходимо выполнить перенос из переполнившегося разряда в разряд минут или часов.
Вот вторая, улучшенная, версия нашей функции:
def add_time(t1, t2): sum = Time() sum.hours = t1.hours + t2.hours sum.minutes = t1.minutes + t2.minutes sum.seconds = t1.seconds + t2.seconds if sum.seconds >= 60: sum.seconds = sum.seconds - 60 sum.minutes = sum.minutes + 1 if sum.minutes >= 60: sum.minutes = sum.minutes - 60 sum.hours = sum.hours + 1 return sum
Хотя эта версия более корректна, функция перестала быть компактной. Чуть позже будет предложен другой подход, который даст нам более короткий код.
12.13. Снова модифицирующие функции¶
Бывают случаи, когда изменение функцией объектов, переданных ей как параметры, оказывается полезным. Обычно вызывающий код сохраняет ссылки на объекты, которые он передает функции в качестве параметров, так что все изменения, сделанные функцией, доступны в вызывающем коде. Как вы помните, функции, работающие таким образом, называются модифицирующими.
Функцию increment , добавляющую указанное число секунд к объекту Time , наиболее естественно написать как модифицирующую. Вот ее черновая версия:
def increment(time, seconds): time.seconds = time.seconds + seconds if time.seconds >= 60: time.seconds = time.seconds - 60 time.minutes = time.minutes + 1 if time.minutes >= 60: time.minutes = time.minutes - 60 time.hours = time.hours + 1
Первая строка выполняет основную операцию. Остальной код обрабатывает специальные случаи, которые мы обсудили выше.
Корректна ли эта функция? Что случится, если количество секунд, переданное функции, намного больше, чем 60? В этом случае недостаточно одного переноса 1 в разряд минут; мы должны выполнять переносы до тех пор, пока значение seconds продолжает быть меньше 60. Одно из возможных решений — заменить предложение if предложением while :
def increment(time, seconds): time.seconds = time.seconds + seconds while time.seconds >= 60: time.seconds = time.seconds - 60 time.minutes = time.minutes + 1 while time.minutes >= 60: time.minutes = time.minutes - 60 time.hours = time.hours + 1
Теперь функция работает правильно, но это не самое эффективное решение.
12.14. Прототипирование и разработка дизайна программы¶
В этой книге мы широко используем подход к разработке программ, называемый прототипированием. Согласно этому подходу, вначале пишется грубый черновой вариант кода, или прототип, который выполняет основную работу. Затем прототип тестируется при различных условиях, и по результатам тестирования делаются доработки и устраняются найденные недостатки.
Хотя этот подход в целом эффективен, но, если пренебречь тщательным обдумыванием решаемой задачи, он может привести к излишне усложненному и ненадежному коду. Усложненному – поскольку придется иметь дело со многими специальными случаями. И ненадежному — поскольку нельзя утверждать, что все такие случаи учтены и все ошибки найдены.
Разработка дизайна программы предполагает тщательный анализ поставленной задачи и принятие ключевых решений относительно того, как именно написать программу. Предварительная разработка дизайна программы делает последующее программирование намного проще.
В данном случае, анализ подскажет нам, что объект Time , представляющий количество времени, есть не что иное, как трехразрядное число с основанием 60! Действительно, секунды — это младший разряд единиц, минуты — разряд “шестидесяток”, а часы представлены самым старшим разрядом. “Единица” старшего разряда соответствует 3600 секундам.
Когда мы писали функции add_time и increment , мы на самом деле выполняли сложение в системе счисления с основанием 60, вот почему нам пришлось делать переносы из одного разряда в другой.
Это наблюдение предлагает другой подход к задаче в целом: мы можем преобразовать три компонента объекта Time в одно единственное число, и далее выполнять арифметические действия с этим числом. Следующая функция преобразует объект Time в целое число:
def convert_to_seconds(t): minutes = t.hours * 60 + t.minutes seconds = minutes * 60 + t.seconds return seconds
Все, что нам нужно теперь, — это способ преобразовать целое число обратно в Time :
def make_time(seconds): time = Time() time.hours = seconds/3600 seconds = seconds - time.hours * 3600 time.minutes = seconds/60 seconds = seconds - time.minutes * 60 time.seconds = seconds return time
Посмотрите внимательно на приведенный код, чтобы убедиться, что преобразование выполняется корректно. Если вы согласны с этим, то перепишите add_time с использованием этих функций:
def add_time(t1, t2): seconds = convert_to_seconds(t1) + convert_to_seconds(t2) return make_time(seconds)
Эта версия гораздо короче первоначальной, и проверить ее корректность гораздо проще (исходя из предположения, что вызываемые ей функции сами по себе корректны).
12.15. Когда сложнее значит проще¶
Преобразование представления чисел из системы счисления с одним основанием в другую, и затем обратно, на первый взгляд кажется сложнее, чем прямое манипулирование привычными нам тремя компонентами времени: часами, минутами и секундами. А раз так, то не лучше ли полагаться на привычку, когда имеем дело со временем?
Но если мы нашли решение, основанное на представлении количества времени числом с основанием 60, и написали функции преобразования ( convert_to_seconds и make_time ), мы получаем более короткую и более надежную программу, которую легче читать и отлаживать.
Кроме того, к такой программе легче добавлять новые возможности. Представьте, например, что нам потребуется делать вычитание объектов Time , чтобы найти интервал времени между ними. Наивный подход состоит в том, чтобы реализовать вычитание с заемом из старших разрядов. Используя же функции преобразования, можно решить эту задачу гораздо проще, и с большей вероятностью получить корректный результат.
Таким образом, иногда, делая решение более сложным, или более общим, мы делаем его использование и дальнейшее развитие более простым (!) и надежным. Потому что становится меньше специальных случаев и меньше возможностей для ошибок.
12.16. Глоссарий¶
атрибут Один из именованных элементов некоторого составного типа. глубокое копирование Копирование содержимого объекта, а также всех вложенных объектов произвольного уровня вложенности. Реализовано в функции deepcopy модуля copy . класс Определенный пользователем тип данных. Можно думать о классе как о шаблоне для создания объектов — экземпляров этого класса. объект Экземпляр класса. Объекты часто используются для моделирования предметов или концепций реального мира. поверхностное копирование Копирование содержимого объекта, включая ссылки на вложенные объекты. Реализовано в функции copy модуля copy . разработка дизайна программы Деятельность, предполагающая анализ задачи, выработку и (часто) документирование основных решений относительно создаваемой программы прежде, чем начнется написание программы. разработка через прототипирование Способ разработки программ, предполагающий создание прототипа программы и дальнейшее его улучшение через тестирование и отладку. экземпляр класса Объект класса.
12.17. Упражнения¶
- Создайте объект Point и выведите его с помощью print. Затем с помощью функции id напечатайте уникальный идентификатор объекта. Убедитесь, что выведенные шестнадцатеричное и десятичное значения — одно и то же число.
- Перепишите функцию distance из главы 5 так, чтобы ее параметрами были два объекта Point вместо четырех чисел.
- Напишите функцию с именем move_rect , которая принимает в качестве параметров объект Rectangle и два числа; имена числовых параметров dx и dy . Функция должна изменить положение прямоугольника, прибавив dx к координате x , и прибавив dy к координате y вложенного объекта corner .
- Перепишите функцию move_rect так, чтобы она создавала и возвращала новый объект Rectangle , вместо изменения существующего.
- Напишите логическую функцию after , которая принимает в качестве параметров два объекта Time , t1 и t2 , и возвращает True , если t1 следует за t2 хронологически, и False , если это не так.
- Перепишите функцию increment так, чтобы она не содержала циклов.
- Теперь перепишите функцию increment как чистую функцию и напишите вызовы обеих функций.
Python/Объектно-ориентированное программирование на Python
Согласно Алану Кэю — автору языка программирования Smalltalk — объектно-ориентированным может называться язык, построенный с учетом следующих принципов [1] :
- Все данные представляются объектами
- Программа является набором взаимодействующих объектов, посылающих друг другу сообщения
- Каждый объект имеет собственную часть памяти и может иметь в составе другие объекты
- Каждый объект имеет тип
- Объекты одного типа могут принимать одни и те же сообщения (и выполнять одни и те же действия)
Объекты, типы и классы [ править ]
Определение класса [ править ]
Для определения класса используется оператор class :
class имя_класса(надкласс1, надкласс2, . ): # определения атрибутов и методов класса
У класса могут быть базовые (родительские) классы (надклассы), которые, если они есть, указываются в скобках после имени определяемого класса.
Минимально возможное определение класса выглядит так:
class A: pass
В терминологии Python члены класса называются атрибутами, функции класса — методами, а поля класса — свойствами (или просто атрибутами).
Определения методов аналогичны определениям функций, но (за некоторыми исключениями, о которых ниже) методы всегда имеют первый аргумент, называемый по общепринятому соглашению self :
class A: def m1(self, x): # блок кода метода
Определения атрибутов — это обычные операторы присваивания, которые связывают некоторые значения с именами атрибутов.
class A: attr1 = 2 * 2
В языке Python класс не является чем-то статическим, поэтому добавить атрибуты можно и после определения:
class A: pass def my_method(self, x): return x * x A.m1 = my_method A.attr1 = 2 * 2
Создание экземпляра [ править ]
Для создания объекта — экземпляра класса (то есть, инстанцирования класса), достаточно вызвать класс по имени и задать параметры конструктора:
class Point: def __init__(self, x, y, z): self.coord = (x, y, z) def __repr__(self): return "Point(%s, %s, %s)" % self.coord
>>> p = Point(0.0, 1.0, 0.0) >>> p Point(0.0, 1.0, 0.0)
Переопределив классовый метод __new__ , можно управлять процессом создания экземпляра. Этот метод вызывается до метода __init__ и должен вернуть новый экземпляр либо None (в последнем случае будет вызван __new__ родительского класса). Метод __new__ используется для управления созданием неизменчивых (immutable) объектов, управления созданием объектов в случаях, когда __init__ не вызывается, например, при десериализации (unpickle). Следующий код демонстрирует один из вариантов реализации шаблона Одиночка:
>>> class Singleton(object): obj = None # Атрибут для хранения единственного экземпляра def __new__(cls, *dt, **mp): # класса Singleton. if cls.obj is None: # Если он еще не создан, то cls.obj = object.__new__(cls, *dt, **mp) # вызовем __new__ родительского класса return cls.obj # вернем синглтон . >>> obj = Singleton() >>> obj.attr = 12 >>> new_obj = Singleton() >>> new_obj.attr 12 >>> new_obj is obj # new_obj и obj - это один и тот же объект True
Конструктор, инициализатор, деструктор [ править ]
Специальные методы вызываются при создании экземпляра класса (конструктор), при инициализировании экземпляра класса (инициализатор) и при удалении класса (деструктор). В языке Python реализовано автоматическое управление памятью, поэтому конструктор и деструктор требуются достаточно редко, для ресурсов, требующих явного освобождения.
Следующий класс имеет конструктор, инициализатор и деструктор:
class Line: def __new__(cls): # Конструктор return super(Line, cls).__new__(cls) def __init__(self, p1, p2): # Инициализатор self.line = (p1, p2) def __del__(self): # Деструктор print("Удаляется линия %s - %s" % self.line)
>>> l = Line((0.0, 1.0), (0.0, 2.0)) >>> del l Удаляется линия (0.0, 1.0) - (0.0, 2.0) >>>
В момент вызова деструктора (например, по завершении программы) среда исполнения может быть уже достаточно «истощённой»Шаблон:Что, поэтому в деструкторе следует делать только самое необходимое. Кроме того, не обработанные в деструкторе исключения игнорируются.
Время жизни объекта [ править ]
Обычно время жизни объекта, определённого в программе на Python, не выходит за рамки времени выполнения процесса этой программы.
Для преодоления этого ограничения объект можно сохранить, а после — восстановить. Как правило, при записи объекта производится его сериализация, а при чтении — десериализация.
>>> import shelve >>> s = shelve.open("somefile.db") >>> s['myobject'] = [1, 2, 3, 4, 'свечка'] >>> s.close() >>> import shelve >>> s = shelve.open("somefile.db") >>> print s['myobject'] [1, 2, 3, 4, '\xd1\x81\xd0\xb2\xd0\xb5\xd1\x87\xd0\xba\xd0\xb0']
Инкапсуляция и доступ к свойствам [ править ]
Инкапсуляция является одним из ключевых понятий ООП. Все значения в Python являются объектами, инкапсулирующими код (методы) и данные и предоставляющими пользователям общедоступный интерфейс. Методы и данные объекта доступны через его атрибуты.
Сокрытие информации о внутреннем устройстве объекта выполняется в Python на уровне соглашения между программистами о том, какие атрибуты относятся к общедоступному интерфейсу класса, а какие — к его внутренней реализации. Одиночное подчеркивание в начале имени атрибута говорит о том, что атрибут не предназначен для использования вне методов класса (или вне функций и классов модуля), однако, атрибут все-таки доступен по этому имени. Два подчеркивания в начале имени дают несколько большую защиту: атрибут перестает быть доступен по этому имени. Последнее используется достаточно редко.
Есть существенное отличие между такими атрибутами и личными (private) членами класса в таких языках как C++ или Java: атрибут остается доступным, но под именем вида _ИмяКласса__ИмяАтрибута , а при каждом обращении Python будет модифицировать имя в зависимости от того, через экземпляр какого класса происходит обращение к атрибуту. Таким образом, родительский и дочерний классы могут иметь атрибут с именем, например, «__f», но не будут мешать друг другу.
>>> class parent(object): def __init__(self): self.__f = 2 def get(self): return self.__f . >>> class child(parent): def __init__(self): self.__f = 1 parent.__init__(self) def cget(self): return self.__f . >>> c = child() >>> c.get() 2 >>> c.cget() 1 >>> c.__dict__ '_child__f': 1, '_parent__f': 2> # на самом деле у объекта "с" два разных атрибута
Особым случаем является наличие двух подчеркиваний в начале и в конце имени атрибута. Они используются для специальных свойств и функций класса (например, для перегрузки операции). Такие атрибуты доступны по своему имени, но их использование зарезервировано для специальных атрибутов, изменяющих поведение объекта.
Доступ к атрибуту может быть как прямой:
class A(object): def __init__(self, x): # атрибут получает значение в инициализаторе self.x = x a = A(5) print a.x >>> 5
Так и с использованием свойств с заданными методами для получения, установки и удаления атрибута:
class A(object): def __init__(self, x): self._x = x def getx(self): # метод для получения значения return self._x def setx(self, value): # метод для присваивания нового значения self._x = value def delx(self): # метод для удаления атрибута del self._x x = property(getx, setx, delx, "Свойство x") # определяем x как свойство a = A(5) print a.x # Синтаксис доступа к атрибуту при этом прежний >>> 5
Разумеется, первый способ хорош только если значение атрибута является атомарной операцией по изменению состояния объекта. Если же это не так, то второй способ позволит выполнить все необходимые действия в соответствующих методах.
Существуют два способа централизованно контролировать доступ к атрибутам. Первый основан на перегрузке методов __getattr__() , __setattr__() , __delattr__() , а второй — метода __getattribute__() . Второй метод помогает управлять чтением уже существующих атрибутов.
Эти способы позволяют организовать полностью динамический доступ к атрибутам объекта или, что используется очень часто, имитации несуществующих атрибутов. По такому принципу функционируют, например, все системы RPC для Python, имитируя методы и свойства, реально существующие на удаленном сервере.
Полиморфизм [ править ]
В компилируемых языках программирования полиморфизм достигается за счёт создания виртуальных методов, которые в отличие от невиртуальных можно перегрузить в потомке. В Python все методы являются виртуальными, что является естественным следствием разрешения доступа на этапе исполнения. (Следует отметить, что создание невиртуальных методов в компилируемых языках связано с меньшими накладными расходами на их поддержку и вызов).
>>> class Parent(object): def isParOrPChild(self) : return True def who(self) : return 'parent' >>> class Child(Parent): def who(self): return 'child' >>> x = Parent() >>> x.who(), x.isParOrPChild() ('parent', True) >>> x = Child() >>> x.who(), x.isParOrPChild() ('child', True)
Явно указав имя класса, можно обратиться к методу родителя (как впрочем и любого другого объекта).
>>> class Child(Parent): def __init__(self): Parent.__init__(self)
В общем случае для получения класса-предка применяется функция super .
class Child(Parent): def __init__(self): super(Child, self).__init__()
Используя специально предусмотренное исключение NotImplementedError , можно имитировать чисто виртуальные методы:
>>> class abstobj(object): def abstmeth(self): raise NotImplementedError('Method abstobj.abstmeth is pure virtual') >>> abstobj().abstmeth() Traceback (most recent call last): File "", line 1, in module> File "", line 2, in method NotImplementedError: Method abstobj.abstmeth is pure virtual
Или, с использованием декоратора, так:
>>> def abstract(func): def closure(*dt, **mp): raise NotImplementedError("Method %s is pure virtual" % func.__name__) return closure >>> class abstobj(object): @abstract def abstmeth(self): pass >>> abstobj().abstmeth() Traceback (most recent call last): File "", line 1, in module> File "", line 2, in method NotImplementedError: Method abstobj.abstmeth is pure virtual
Изменяя атрибут __class__ , можно перемещать объект вверх или вниз по иерархии наследования (впрочем, как и к любому другому типу)
>>> c = child() >>> c.val = 10 >>> c.who() 'child' >>> c.__class__ = Parent >>> c.who() 'parent' >>> c.val 10
Однако, в этом случае никакие преобразования типов не делаются, поэтому забота о согласованности данных всецело лежит на программисте. Кроме того, присваивание атрибуту __class__ не должно применяться по поводу и без. Прежде чем решиться на его использование, необходимо рассмотреть менее радикальные варианты реализации изменения объекта, то есть по сути шаблона проектирования State.
Более того, полиморфизм в Python вообще не связан с наследованием, поэтому его можно считать сигнатурно-ориентированным полиморфизмом (signature-oriented polymorphism) [2] . Например, чтобы экземпляру класса «прикинуться» файловым объектом, ему достаточно реализовать методы, относящиеся к файлам (обычно .read() , .readlines() , .close() и т. п.).
Переопределение встроенных типов [ править ]
Встроенные типы и их методы имеют синтаксическую поддержку в языке Python или другие особые «привилегии». Конечно, любая операция может быть представлена синтаксисом вызова функции, однако, для частого применения это неудобно.
Воспользоваться точно такой же синтаксической поддержкой может и любой определённый пользователем класс. Для этого нужно лишь реализовать методы со специальными именами. Самый простой пример — переопределить функцию:
>>> class Add: . def __call__(self, x, y): # переопределение метода, . return x + y # который отвечает за операцию вызова функции . >>> add = Add() >>> add(3, 4) # это эквивалентно add.__call__(3, 4) 7
Аналогично поддаются переопределению все операции встроенных типов. Ещё один пример связан с вычислением длины объекта с помощью функции len() . Эта встроенная функция вызывает специальный метод:
>>> class wrongList(list): # определяем собственный класс для списка . def __len__(self): # который всегда считает, что имеет нулевую длину . return 0 . >>> w = wrongList([1,2,3]) >>> len(w) # это эквивалентно w.__len__() 0
Методы __getitem__,__setitem__,__delitem__,__contains__ позволяют создать интерфейс для словаря или списка( dict ).
Достаточно просто переопределить и числовые типы. Скажем, следующий класс использует инфиксную операцию * :
class Multiplyable: def __init__(self, value): self.value = value def __mul__(self, y): return self.value * y def __rmul__(self, x): return x * self.value def __imul__(self, y): return Multiplyable(self.value * y) def __str__(self): return "Multiplyable(%s)" % self.value >>> m = Multiplyable(1) >>> print m Multiplyable(1) >>> m *= 3 >>> print m Multiplyable(3)
Последний из методов — .__str__() — отвечает за представление экземпляра класса при печати оператором print и в других подобных случаях.
Аналогичные методы имеются и у соответствующих встроенных типов:
>>> int.__add__ slot wrapper '__add__' of 'int' objects> >>> [].__getitem__ built-in method __getitem__ of list object at 0x00DA3D28> >>> class a(object):pass >>> a.__call__ method-wrapper '__call__' of type object at 0x00DDC318>
Не все из них существуют на самом деле: большая часть имитируется интерпретатором Python для удобства программиста. Такое поведение позволяет экономить время при наиболее важных операциях (например, сложение целых не приводит к поиску и вызову метода __add__ у класса int ) и память не расходуется на этот поиск и вызов, но приводит к невозможности изменения методов у встроенных классов.
Отношения между классами [ править ]
Наследование и множественное наследование [ править ]
При описании предметной области классы могут образовывать иерархию, в корне которой стоит базовый класс, а нижележащие классы (подклассы) наследуют свои атрибуты и методы, уточняя и расширяя поведение вышележащего класса (надкласса). Обычно принципом построения классификации является отношение «IS-A» («есть» — между экземпляром и классом) и «AKO» («a kind of» — «разновидность» — между классом и суперклассом) [3] .
Python поддерживает как одиночное наследование, так и множественное, позволяющее классу быть производным от любого количества базовых классов.
>>> class Par1(object): # наследуем один базовый класс - object def name1(self): return 'Par1' >>> class Par2(object): def name2(self): return 'Par2' >>> class Child(Par1, Par2): # создадим класс, наследующий Par1, Par2 (и, опосредованно, object) pass >>> x = Child() >>> x.name1(), x.name2() # экземпляру Child доступны методы из Par1 и Par2 'Par1','Par2'
В Python (из-за «утиной типизации») отсутствие наследования ещё не означает, что объект не может предоставлять тот же самый интерфейс.
Множественное наследование в Python применяется в основном для добавления примесей (mixins) — специальных классов, вносящих некоторую черту поведения или набор свойств [4] .
Порядок разрешения доступа к методам и полям [ править ]
За достаточно простым в использовании механизмом доступа к атрибутам в w:Python кроется довольно сложный алгоритм. Далее будет приведена последовательность действий, производимых интерпретатором при разрешении запроса object.field (поиск прекращается после первого успешно завершённого шага, иначе происходит переход к следующему шагу).
- Если у object есть метод __getattribute__ , то он будет вызван с параметром ‘field’ (либо __setattr__ или __delattr__ в зависимости от действия над атрибутом)
- Если у object есть поле __dict__ , то ищется object.__dict__[‘field’]
- Если у object.__class__ есть поле __slots__ , то ‘field’ ищется в object.__class__.__slots__
- Проверяется object.__class__.__dict__[‘fields’]
- Производится рекурсивный поиск по __dict__ всех родительских классов (при множественном наследовании поиск производится в режиме deep-first, в том порядке как базовые классы перечислены в определении класса-потомка). Алгоритм поиска разный для «классических» и «новых» классов.
- Если у object есть метод __getattr__ , то вызывается он с параметром ‘field’
- Вызывается исключение AttributeError .
Если поиск окончен успешно, то проверяется, является ли атрибут классом «нового стиля». Если является, то проверяется наличие у него метода __get__ (либо __set__ или __delete__ , в зависимости от действия над атрибутом), если метод найден, то происходит следующий вызов object.field.__get__(object) и возвращается его результат (такие атрибуты называется в Python атрибутами со связанным поведением (binded behavior) и используются, например, для создания свойств [5] ).
Эта последовательность распространяется только на пользовательские атрибуты. Системные атрибуты, такие как __dict__ , __len__ , __add__ и другие, имеющие специальные поля в С-структуре описания класса находятся сразу.
«Новые» и «классические» классы [ править ]
В версиях до 2.2 некоторые объектно-ориентированные возможности Python были заметно ограничены. Например, было невозможно наследовать встроенные классы и классы из модулей расширения. Свойства (property) не выделялись явно. Начиная с версии 2.2, объектная система Python была существенно переработана и дополнена. Однако для совместимости со старыми версиями Python было решено сделать две объектные модели: «классические» типы (полностью совместимые со старым кодом) и «новые» [6] . В версии Python3 поддержка «старых» классов будет удалена.
Для построения «нового» класса достаточно унаследовать его от другого «нового». Если нужно создать «чистый» класс, то можно унаследоваться от object — родительского типа для всех «новых» классов.
class OldStyleClass: pass # класс "старого" типа class NewStyleClass(object): pass # и "нового"
Все стандартные классы — классы «нового» типа. [7]
Агрегация. Контейнеры. Итераторы [ править ]
Агрегация, когда один объект входит в состав другого, или отношение «HAS-A» («имеет»), реализуется в Python с помощью ссылок. Python имеет несколько встроенных типов контейнеров: список, словарь, множество. Можно определить собственные классы контейнеров со своей логикой доступа к хранимым объектам. (Следует заметить, что в Python агрегацию можно считать разновидностью ассоциации, так реально объекты не вложены друг в друга в памяти и, более того, время жизни элемента может не зависеть от времени жизни контейнера.)
Следующий класс из модуля utils.py среды web.py является примером контейнера-словаря, дополненного возможностью доступа к значениям при помощи синтаксиса доступа к атрибутам:
class Storage(dict): def __getattr__(self, key): try: return self[key] except KeyError, k: raise AttributeError, k def __setattr__(self, key, value): self[key] = value def __delattr__(self, key): try: del self[key] except KeyError, k: raise AttributeError, k def __repr__(self): return ' + dict.__repr__(self) + '>'
Вот как он работает:
>>> v = Storage(a=5) >>> v.a 5 >>> v['a'] 5 >>> v.a = 12 >>> v['a'] 12 >>> del v.a
Для доступа к контейнерам очень удобно использовать итераторы:
>>> cont = dict(a=1, b=2, c=3) >>> for k in cont: . print k, cont[k] . a 1 c 3 b 2
Ассоциация и слабые ссылки [ править ]
Отношение использования («USE-A») экземпляров одного класса другими является достаточно общим отношением. При использовании один класс обычно зависит от интерфейса другого класса (хотя эта зависимость может быть и взаимной). Если один объект использует другой, он обязательно содержит ссылку на него. Объекты могут ссылаться и друг на друга. В этом случае возникают циклические ссылки. Если ссылающиеся друг на друга объекты удалить, то они уже не могут быть удалены интерпретатором Python с помощью механизма подсчета ссылок. Удалением таких объектов занимается сборщик мусора.
Ассоциацию объектов без присущих ссылкам проблем можно осуществить с помощью слабых ссылок. Слабые ссылки не препятствуют удалению объекта.
Для работы со слабыми ссылками применяется модуль weakref .
Метаклассы [ править ]
Обычных возможностей объектно-ориентированного программирования хватает далеко не всегда. В некоторых случаях требуется изменить сам характер системы классов: расширить язык новыми типами классов, изменить стиль взаимодействия между классами и окружением, добавить некоторые дополнительные аспекты, затрагивающие все используемые в приложении классы, и т. п.
При объявлении метакласса за основу можно взять класс type . Пример:
# описание метакласса class myobject(type): # небольшое вмешательство в момент выделения памяти для класса def __new__(cls, name, bases, dict): print "NEW", cls.__name__, name, bases, dict return type.__new__(cls, name, bases, dict) # небольшое вмешательство в момент инициализации класса def __init__(cls, name, bases, dict): print "INIT", cls.__name__, name, bases, dict return super(myobject, cls).__init__(name, bases, dict) # порождение класса на основе метакласса (заменяет оператор class) MyObject = myobject("MyObject", (), <>) # обычное наследование другого класса из только что порожденного class MySubObject(MyObject): def __init__(self, param): print param # получение экземпляра класса myobj = MySubObject("parameter")
Разумеется, вместо оператора print код метакласса может выполнять более полезные функции: регистрировать класс, передавать действия с классами на удаленную систему, использовать классы для других целей (например, как декларации или ограничения) и т. п.
Методы [ править ]
Метод [ править ]
Синтаксис описания метода ничем не отличается от описания функции, разве что его положением внутри класса и характерным первым формальным параметром self , с помощью которого внутри метода можно ссылаться на сам экземпляр класса (название self является соглашением, которого придерживаются программисты на Python):
class MyClass(object): def mymethod(self, x): return x == self._x
Статический метод [ править ]
Статические методы в Python являются синтаксическими аналогами статических функций в основных языках программирования. Они не получают ни экземпляр ( self ), ни класс ( cls ) первым параметром. Для создания статического метода (только «новые» классы могут иметь статические методы) используется декоратор staticmethod
>>> class D(object): @staticmethod def test(x): return x == 0 . >>> D.test(1) # доступ к статическому методу можно получать и через класс False >>> f = D() >>> f.test(0) # и через экземпляр класса True
Статические методы реализованы с помощью свойств (property).
Метод класса [ править ]
Классовые методы в Python занимают промежуточное положение между статическими и обычными. В то время как обычные методы получают первым параметром экземпляр класса, а статические не получают ничего, в классовые методы передается класс. Возможность создания классовых методов является одним из следствий того, что в Python классы также являются объектами. Для создания классового (только «новые» классы могут иметь классовые методы) метода можно использовать декоратор classmethod
>>> class A(object): def __init__(self, int_val): self.val = int_val + 1 @classmethod def fromString(cls, val): # вместо self принято использовать cls return cls(int(val)) . >>> class B(A):pass . >>> x = A.fromString("1") >>> print x.__class__.__name__ A >>> x = B.fromString("1") >>> print x.__class__.__name__ B
Классовые методы достаточно часто используются для перегрузки конструктора. Классовые методы, как и статические, реализуются через свойства (property).
Мультиметоды [ править ]
Примером для иллюстрации сути мультиметода может служить функция add() из модуля operator :
>>> import operator as op >>> print op.add(2, 2), op.add(2.0, 2), op.add(2, 2.0), op.add(2j, 2) 4 4.0 4.0 (2+2j)
В языке Python достаточно легко реализовать и определённые пользователем мультиметоды [8] . Например, эмулировать мультиметоды можно с помощью модуля multimethods.py (из Gnosis Utils) :
from multimethods import Dispatch class Asteroid(object): pass class Spaceship(object): pass def asteroid_with_spaceship(a1, s1): print "A-> def asteroid_with_asteroid(a1, a2): print "A-> def spaceship_with_spaceship(s1, s2): print "S-> collide = Dispatch() collide.add_rule((Asteroid, Spaceship), asteroid_with_spaceship) collide.add_rule((Asteroid, Asteroid), asteroid_with_asteroid) collide.add_rule((Spaceship, Spaceship), spaceship_with_spaceship) collide.add_rule((Spaceship, Asteroid), lambda x,y: asteroid_with_spaceship(y,x)) a, s1, s2 = Asteroid(), Spaceship(), Spaceship() collision1 = collide(a, s1)[0] collision2 = collide(s1, s2)[0]
Устойчивость объектов [ править ]
Объекты всегда имеют своё представление в памяти компьютера и их время жизни не больше времени работы программы. Однако зачастую необходимо сохранять данные между запусками приложения и/или передавать их на другие компьютеры. Одним из решений этой проблемы является устойчивость объектов (англ. object persistence ) которая достигается с помощью хранения представлений объектов (сериализацией) в виде байтовых последовательностей и их последующего восстановления (десериализация).
Модуль pickle является наиболее простым способом «консервирования» объектов в Python.
Следующий пример показывает, как работает сериализация и десериализация:
# сериализация >>> import pickle >>> p = set([1, 2, 3, 5, 8]) >>> pickle.dumps(p) 'c__builtin__\nset\np0\n((lp1\nI8\naI1\naI2\naI3\naI5\natp2\nRp3\n.' # де-сериализация >>> import pickle >>> p = pickle.loads('c__builtin__\nset\np0\n((lp1\nI8\naI1\naI2\naI3\naI5\natp2\nRp3\n.') >>> print p set([8, 1, 2, 3, 5])
Получаемая при сериализации строка может быть передана по сети, записана в файл или специальное хранилище объектов, а позже — прочитана. Сериализации поддаются не все объекты. Некоторые объекты (например, классы и функции) представляются своими именами, поэтому для десериализации требуется наличие тех же самых классов. Нужно отметить, что нельзя десериализовать данные из непроверенных источников с помощью модуля pickle , так как при этом возможны практически любые действия на локальной системе. При необходимости обмениваться данными по незащищенным каналам или с ненадежными источниками можно воспользоваться другими модулями для сериализации.
В основе сериализации объекта стоит представление его состояния. По умолчанию состояние объекта — это все, что записано в его полях. Пользовательские классы могут управлять сериализацией, предоставляя состояние объекта явным образом (методы __getstate__ , __setstate__ и др.).
На стандартном для Python механизме сериализации построена работа модуля shelve (shelve (англ. глаг.) — ставить на полку; сдавать в архив). Модуль предоставляет функцию open . Объект, который она возвращает, работает аналогично словарю, но объекты сериализуются и сохраняются в файле:
>>> import shelve >>> s = shelve.open("myshelve.bin") >>> s['abc'] = [1, 2, 3] >>> s.close() # . >>> s = shelve.open("myshelve.bin") >>> s['abc'] [1, 2, 3]
Сериализация pickle — не единственная возможная, и подходит не всегда. Для сериализации, не зависящей от языка программирования, можно использовать, например, XML.
Примечания [ править ]
- ↑Introduction to Object-Oriented Programming
- ↑в списке рассылки comp.lang.python
- ↑«AKO» и «IS-A»
- ↑Beazley, 2009
- ↑How-To Guide for Descriptors by R. Hettinger (недоступная ссылка — история) Проверено 2007-10-06 г.Архивировано из первоисточника 6 октября 2007.
- ↑New-style Classes
- ↑Объяснение Гвидо ван Россума об объединении типов и классов
- ↑Charming Python: Multiple dispatch
Литература [ править ]
- David M. Beazley Python Essential Reference. — 4th Edition. — Addison-Wesley Professional, 2009. — 717 с. — ISBN 978-0672329784
