Давайте отвлечемся на минутку и посмотрим, куда мы пришли. Итак, в настоящий момент у нас имеется две реализации базы данных: на основе модуля shelve и на основе модуля pickle, в которой каждая запись сохраняется в отдельном файле, — этого вполне достаточно для хранения простых данных. Наши записи представлены простыми словарями, которые обеспечивают более простой и понятный доступ к полям, чем списки (то есть не по числовым индексам, а по ключам). Однако словари имеют некоторые ограничения, которые могут оказаться существенными по мере разработки программы.
Во-первых, у нас не предусмотрено место для централизованного хранения логики обработки записей. Операции извлечения фамилии и увеличения оклада, например, могут выполняться так:
> >> import shelve
> >> db = shelve.open(‘people-shelve’)
> >> bob = db[‘bob’]
> >> bob[‘name’].split()[-1] # вернет фамилию Боба ‘Smith’
> >> sue = db[‘sue’]
> >> sue[‘pay’] *= 1.25 # увеличит оклад Сью
>>> sue[‘pay’]
75000.0
> >> db[‘sue’] = sue
> >> db.close()
Такое решение вполне пригодно для небольших программ. Но если когда-нибудь потребуется изменить логику извлечения фамилии или увеличения оклада, нам, возможно, придется обновить подобный программный код во множестве мест в программе. На практике даже просто отыскать все такие фрагменты может оказаться достаточно сложным делом — копирование одинаковых фрагментов программного кода рано или поздно обязательно даст знать о себе.
Подобные фрагменты предпочтительнее скрывать, то есть инкапсулировать. Эти операции можно было бы реализовать в виде функций в одном модуле и тем самым избежать избыточности, но при таком подходе функции никак не будут связаны с самими записями. Нам же желательно связать логику обработки с данными, хранящимися в базе данных, чтобы ее проще было понимать, отлаживать и многократно использовать.
Другой недостаток использования словарей для представления записей заключается в том, что со временем их становится трудно расширять. Например, представьте, что имеется набор полей данных или различные процедуры, увеличивающие оклад для разных сотрудников по- разному (например, кто-то может получать ежегодную прибавку, а кто- то — нет). Если нам когда-нибудь потребуется расширить программу, сделать это будет очень сложно, так как нет простого и естественного способа расширить простые словари. С учетом дальнейшего роста нам хотелось бы, чтобы наше программное обеспечение предусматривало возможность расширения и настройки естественными способами.
Если вы уже погружались в изучение Python, то, наверное, знаете, что это тот случай, когда начинает проявляться привлекательность ООП:
- Структурирование
- Инкапсуляция
- Специализация
- Использование классов
- Добавляем поведение
- Добавляем наследование
- Реструктуризация программного кода
- Расширение методов
- Формат отображения
- Специализация конструктора
- Альтернативные классы
- Добавляем возможность сохранения
- Другие разновидности баз данных
- Автобусы признаны опасными
Структурирование
Благодаря ООП появляется возможность связать логику обработки записей с самими записями — классы представляют собой программные единицы, объединяющие логику и данные, а наследование позволяет легко избежать избыточности программного кода.
Инкапсуляция
Благодаря ООП можно скрыть детали реализации таких операций, как обработка имени или увеличение оклада, внутри методов — то есть в дальнейшем мы легко сможем изменять реализацию методов, не влияя на работоспособность программного кода, использующего их.
Специализация
Применение ООП обеспечивает естественный способ дальнейшего расширения. Классы могут расширяться и специализироваться за счет создания новых подклассов, без изменения или нарушения работоспособности существующего программного кода.
Таким образом, в объектно-ориентированном программировании мы специализируем и повторно используем программный код, а не переписываем его заново. ООП в Python является дополнительной возможностью, которая, честно признаться, лучше подходит для решения стратегических, а не тактических задач. ООП лучше подходит, когда у вас имеется время для предварительного планирования, что может оказаться непозволительной роскошью, когда ваши пользователи уже начали штурмовать ворота.
Преимущества структурирования и повторного использования программного кода в крупных системах, которые продолжают развиваться в течение длительного времени, перевешивают затраты на изучение ООП и способны существенно сократить время разработки. Даже в нашем простом случае возможность специализации и снижения избыточности, которую дают классы, может оказаться решающим преимуществом.
Использование классов
ООП в Python отличается простотой использования, в значительной степени благодаря динамической модели типов. Фактически программировать в объектно-ориентированном стиле настолько просто, что я сразу же перейду к примеру: пример 1.14 реализует наши записи уже не в виде словарей, а в виде экземпляров класса.
Пример 1.14. PP4E\Preview\person_start.py
class Person:
def __init__(self, name, age, pay=0, job=None): self.name = name self.age = age self.pay = pay self.job = job
if __name__ == ‘__main__’:
bob = Person(‘Bob Smith’, 42, 30000, ‘software’) sue = Person(‘Sue Jones’, 45, 40000, ‘hardware’) print(bob.name, sue.pay)
print(bob.name.split()[-1]) sue.pay *= 1.10 print(sue.pay)
Это очень простой класс — он содержит единственный метод-конструктор, заполняющий экземпляр класса данными, переданными в виде аргументов при обращении к имени класса. Тем не менее этого вполне достаточно для представления записи, а кроме того, сюда уже можно добавить такие элементы, как значения по умолчанию для полей pay и job, чего нельзя сделать в словарях. Программный код самотестирования в конце этого файла создает два экземпляра класса (две записи) и обращается к их атрибутам (полям). Ниже приводится вывод, полученный в результате запуска этого сценария в среде IDLE (при запуске из командной строки результаты получаются такими же):
Bob Smith 40000
Smith
44000.0
Это еще не база данных, но мы могли бы, как и прежде, вставить эти объекты в список или в словарь, чтобы объединить их в одно целое:
> >> from person_start import Person
> >> bob = Person(‘Bob Smith’, 42)
> >> sue = Person(‘Sue Jones’, 45, 40000)
> >> people = [bob, sue] # список “базы данных”
> >> for person in people:
print(person.name, person.pay)
Bob Smith 0
Sue Jones 40000
> >> x = [(person.name, person.pay) for person in people]
>>> x
[(‘Bob Smith’, 0), (‘Sue Jones’, 40000)]
>>> [rec.name for rec in people if rec.age >= 45] # SQL—подобный запрос [‘Sue Jones’]
>>> [(rec.age ** 2 if rec.age >= 45 else rec.age) for rec in people] [42, 2025]
Обратите внимание, что для Боба был установлен оклад (поле pay) по умолчанию, равный 0, потому что при создании записи мы не указали сумму в соответствующем аргументе (может быть, Сью его поддерживает?). Мы также могли бы реализовать класс, представляющий базу данных, возможно, как подкласс списка или словаря, добавив в него методы вставки и удаления, реализующие особенности функционирования базы данных. Однако пока мы откажемся от этого, потому что гораздо полезнее реализовать сохранение записей в хранилище, которое уже обладает методами записи и чтения. Но прежде чем попытаться использовать хранилище, добавим в наши записи немного логики.
Добавляем поведение
Пока что наш класс — это всего лишь данные: он заменил ключи словаря атрибутами объекта, но не добавляет ничего нового сверх того, что у нас было прежде. Чтобы задействовать всю мощь классов, необходимо добавить реализацию поведения. Заключая реализацию поведения в методы класса, мы сможем изолировать клиентов от влияния изменений в будущем. А объединяя методы в единое целое с данными, мы обеспечиваем естественное место, где другие будут искать наш программный код. В некотором смысле классы объединяют в себе записи и программы, обрабатывающие эти записи, — методы реализуют логику интерпретации и изменения данных (этот стиль программирования потому и называется объектно-ориентированным, что при таком подходе всегда обрабатываются данные объектов).
Например, в примере 1.15 добавляется логика получения фамилии и увеличения оклада в виде методов. Для доступа к обрабатываемому экземпляру (записи) методы используют аргумент self.
Пример 1.15. PP4E\Preview\person.py
class Person:
def __init__(self, name, age, pay=0, job=None): self.name = name self.age = age self.pay = pay self.job = job
def lastName(self):
return self.name.split()[-1]
self.pay *= (1.0 + percent)
if __name__ == ‘__main__’:
bob = Person(‘Bob Smith’, 42, 30000, ‘software’)
sue = Person(‘Sue Jones’, 45, 40000, ‘hardware’) print(bob.name, sue.pay)
print(bob.lastName())
sue.giveRaise(.10)
print(sue.pay)
Если запустить этот сценарий, он выведет те же результаты, что и предыдущий, но теперь эти результаты возвращаются методами, а не извлекаются с помощью жестко зашитой логики, которая всегда оказывается избыточной, когда бы она ни применялась:
Bob Smith 40000
Smith
44000.0
Добавляем наследование
И наконец, рассмотрим еще одно расширение наших записей, прежде чем перейти к реализации их сохранения: поскольку теперь записи реализованы в виде класса, они обретают естественную возможность специализации через механизм наследования в Python. Пример 1.16 специализирует предыдущий класс Person, предусматривая 10-процентную надбавку, добавляемую при увеличении оклада менеджеров (любые совпадения с реальными примерами из жизни являются случайными).
Пример 1.16. PP4E\Preview\manager.py
from person import Person
class Manager(Person):
def giveRaise(self, percent, bonus=0.1): self.pay *= (1.0 + percent + bonus)
if __name__ == ‘__main__’:
tom = Manager(name=’Tom Doe’, age=50, pay=50000) print(tom.lastName())
tom.giveRaise(.20)
print(tom.pay)
Если запустить этот сценарий, он выведет следующее:
Doe
65000.0
Здесь объявление класса Manager находится в отдельном модуле, но это объявление точно так же можно поместить в модуль person (Python не требует создавать отдельные модули для каждого класса). Он наследует конструктор и метод lastName от своего суперкласса и специализирует метод giveRaise (как будет показано позднее, существуют различные способы реализации этого расширения). Поскольку данное дополнение было оформлено в виде нового подкласса, оно никак не отразится на работе экземпляров оригинального класса Person. Экземпляры, представляющие информацию о Бобе и Сью, например, унаследуют оригинальную логику увеличения оклада, а экземпляр, представляющий информацию о Томе, получит специализированную версию, потому что он является экземпляром другого класса. В ООП программы разрабатываются за счет специализации программного кода, а не его изменения.
Фактически программа, использующая наши объекты, вообще ничего не должна знать об особенностях реализации метода giveRaise — объект сам определяет порядок выполнения той или иной операции, опираясь на класс, экземпляром которого он является. Пока объект поддерживает ожидаемый интерфейс (в данном случае метод giveRaise), он будет совместим с вызывающей программой, независимо от конкретного типа объекта и даже независимо от особенностей реализации этого метода, которая может действовать совершенно иначе.
Если вам уже приходилось изучать язык Python, возможно, вы знаете, что такое поведение называется полиморфизмом. Это одно из основных свойств языка, и оно объясняет значительную долю гибкости программного кода. Результат вызова метода giveRaise в следующем фрагменте зависит от того, к какому классу принадлежит обрабатываемый объект obj, — Том получит 20-процентное повышение оклада, а не 10-процентное, потому что соответствующий ему экземпляр является экземпляром специализированного класса Manager:
>>> from person import Person
>>> from manager import Manager
>>> bob = Person(name=’Bob Smith’, age=42, pay=10000)
>>> sue = Person(name=’Sue Jones’, age=45, pay=20000)
>>> tom = Manager(name=’Tom Doe’, age=55, pay=30000)
> >> db = [bob, sue, tom]
> >> for obj in db:
obj.giveRaise(.10) # метод по умолчанию или специализированный
> >> for obj in db:
print(obj.lastName(), ‘=>’, obj.pay)
Smith => 11000.0
Jones => 22000.0
Doe => 36000.0
Реструктуризация программного кода
Прежде чем двинуться дальше, рассмотрим еще несколько альтернативных вариантов реализации. Большинство из них подчеркивают преимущества модели ООП в Python и рассматриваются здесь для краткого знакомства.
Расширение методов
Во-первых, обратите внимание на некоторую избыточность в примере 1.16: расчет увеличения оклада производится в двух местах (в двух классах). Мы могли бы реализовать специализированный класс Manager, не замещая унаследованный метод giveRaise новой реализацией, а расширяя его:
class Manager(Person):
def giveRaise(self, percent, bonus=0.1):
Person.giveRaise(self, percent + bonus)
Вся хитрость заключается в непосредственном вызове версии метода суперкласса и явной передаче ему аргумента self. При таком подходе мы также переопределяем метод, но на этот раз мы просто вызываем универсальную версию после добавления 10-процентной надбавки (предусмотренной по умолчанию) к указанному значению в процентах. Этот прием позволяет уменьшить избыточность программного кода (оригинальная логика метода giveRaise находится в одном только месте, что упрощает возможность ее изменения в будущем), и его особенно удобно использовать при переопределении методов-конструкторов суперклассов.
Если вы уже знакомы с особенностями ООП в Python, то должны знать, что этот прием работает благодаря возможности вызова методов как относительно экземпляра, так и относительно имени класса. Вообще говоря, следующие два вызова являются эквивалентными, и можно использовать обе формы:
instance.method(arg1, arg2)
class.method(instance, arg1, arg2)
В действительности, первая форма отображается во вторую — при вызове метода относительно экземпляра интерпретатор Python отыскивает в дереве наследования ближайший класс, в котором имеется требуемый метод, и вызывает его, автоматически передавая экземпляр в первом аргументе. В любом случае, внутри метода giveRaise аргумент self будет ссылаться на экземпляр, являющийся объектом вызова.
Формат отображения
Чтобы получить дополнительное удовольствие от использования ООП, мы могли бы добавить в наши классы несколько методов перегрузки операторов. Например, метод __str__, реализованный здесь, возвращает отформатированную строку для отображения наших объектов при печати объектов целиком — такое представление выглядит гораздо лучше, чем предусмотренное по умолчанию:
class Person:
def __str__(self):
return ‘<%s => %s>’ % (self.__class__.__name__, self.name)
tom = Manager(‘Tom Jones’, 50)
print(tom) # выведет: <Manager => Tom Jones>
Здесь атрибут __class__ содержит ссылку на ближайший класс, экземпляром которого является объект self, даже при том, что метод __str__ может оказаться унаследованной версией. Метод __str__ позволяет выводить экземпляры непосредственно, вместо того чтобы выводить отдельные атрибуты. В метод __str__ можно было бы добавить цикл, выполняющий обход словаря атрибутов __dict__ экземпляра и отображающий все атрибуты. Но это лишь краткий обзор, поэтому оставим это предложение для самостоятельного упражнения.
Мы могли бы даже реализовать метод __add__, чтобы оператор + автоматически вызывал метод giveRaise. Нужно ли это — другой вопрос. Использование оператора + для увеличения оклада может быть истолковано неправильно теми, кто впоследствии будет читать наш программный код.
Специализация конструктора
Наконец, обратите внимание, что в примере 1.16 при создании экземпляра класса Manager мы не передаем конструктору аргумент job. При необходимости мы могли бы передавать это значение в виде именованного аргумента, как показано ниже:
tom = Manager(name=’Tom Doe’, age=50, pay=50000, job=’manager’)
Причина, по которой мы в примере не включили передачу аргумента job, заключается в том, что в этом нет необходимости: если создается новый экземпляр класса Manager, занимаемая должность уже подразумевается классом. Тем не менее, чтобы не оставлять поле job пустым, возможно, имеет смысл явно реализовать конструктор для класса Manager, который будет заполнять это поле автоматически:
class Manager(Person):
def __init__(self, name, age, pay):
Person.__init__(self, name, age, pay, ‘manager’)
Теперь при создании экземпляра класса Manager его поле job будет заполняться автоматически. Вся хитрость заключается в явном вызове версии метода суперкласса, так же, как мы делали при реализации метода giveRaise выше. Единственное отличие здесь — необычное имя метода-конструктора.
Альтернативные классы
В последующих примерах мы не будем использовать ни одно из трех расширений, представленных в этом разделе, но для демонстрации их в действии соберем все эти идеи в примере 1.17, где представлены альтернативные реализации классов Person и Manager.
Пример 1.17. PP4E\Preview\person_alternative.py
Альтернативные реализации классов Person и Manager с данными, методами и с перегрузкой операторов (не используется в объектах, предусматривающих возможность сохранения)
class Person:
универсальное представление человека: данные+логика
def __init__(self, name, age, pay=0, job=None): self.name = name self.age = age self.pay = pay self.job = job
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent): self.pay *= (1.0 + percent)
def __str__(self):
return (‘<%s => %s: %s, %s>’ %
(self.__class__.__name__, self.name, self.job, self.pay))
class Manager(Person):
класс со специализированным методом giveRaise, наследующий обобщенные методы lastName и __str__
def __init__(self, name, age, pay):
Person.__init__(self, name, age, pay, ‘manager’)
def giveRaise(self, percent, bonus=0.1): Person.giveRaise(self, percent + bonus)
if __name__ == ‘__main__’:
bob = Person(‘Bob Smith’, 44)
sue = Person(‘Sue Jones’, 47, 40000, ‘hardware’)
tom = Manager(name=’Tom Doe’, age=50, pay=50000) print(sue, sue.pay, sue.lastName()) for obj in (bob, sue, tom):
obj.giveRaise(.10) # вызовет метод giveRaise объекта obj print(obj) # вызовет обобщенную версию метода __str__
Обратите внимание на полиморфизм в цикле for, находящемся в программном коде самопроверки этого модуля: все три объекта используют один и тот же конструктор, метод lastName и методы вывода, но при обращении к методу giveRaise вызывается версия в зависимости от класса, на основе которого был создан экземпляр. Если запустить сценарий из примера 1.17, он выведет в стандартный поток вывода приведенные ниже строки; поле job в экземпляре класса Manager заполняется конструктором, форматированный вывод наших объектов осуществляется с помощью нового метода __str__, а новая версия метода giveRaise в классе Manager действует точно так же, как и прежде:
<Person => Sue Jones: hardware, 40000> 40000 Jones
<Person => Bob Smith: None, 0.0>
<Person => Sue Jones: hardware, 44000.0>
<Manager => Tom Doe: manager, 60000.0>
Такая реструктуризация программного кода часто применяется по мере роста и развития иерархий классов. Фактически мы никак не сможем увеличить оклад тем, у кого он оказался равным нулю (Бобу явно не повезло), поэтому нам, вероятно, необходимо предусмотреть возможность прямого изменения оклада, но оставим это усовершенствование до следующей версии. Самое приятное, что гибкость и удобочитаемость, присущие языку Python, существенно упрощают реструктуризацию программного кода — вы легко и просто сможете реструктурировать свои программы. Если прежде вы не пользовались языком Python, то со временем обнаружите, что разработка программ на Python выполняется быстро, поэтапно и в интерактивном режиме, что хорошо подходит для постоянно изменяющихся потребностей реальных проектов.
Добавляем возможность сохранения
Пришло время продолжить. Теперь у нас имеются реализации записей, поддающиеся специализации и включающие логику их обработки, в форме классов. Осталось сделать последний маленький шаг и реализовать сохранение наших записей, основанных на классах. Мы могли бы снова сохранять каждую запись в отдельном файле с помощью модуля pickle, но модуль shelve предоставляет точно такую же возможность, а кроме того, его гораздо проще использовать. Как это сделать, демонстрируется в примере 1.18.
Пример 1.18. PP4E\Preview\make_db_classes.py
import shelve
from person import Person from manager import Manager
bob = Person(‘Bob Smith’, 42, 30000, ‘software’) sue = Person(‘Sue Jones’, 45, 40000, ‘hardware’) tom = Manager(‘Tom Doe’, 50, 50000)
db = shelve.open(‘class-shelve’)
db[‘bob’] = bob db[‘sue’] = sue db[‘tom’] = tom db.close()
Этот сценарий создает три экземпляра (два экземпляра оригинального класса и один — его специализированной версии) и присваивает их ключам вновь созданного хранилища. Другими словами, сценарий создает хранилище с экземплярами классов. Для нашего программного кода база выглядит в точности, как словарь экземпляров классов, с той лишь разницей, что словарь верхнего уровня отображается в файл хранилища, как и прежде. Убедиться, что все работает, поможет сценарий в примере 1.19, который читает содержимое хранилища и выводит значения полей записей.
Пример 1.19. PP4E\Preview\dump_db_classes.py
import shelve
db = shelve.open(‘class-shelve’) for key in db:
print(key, ‘=>\n ‘, db[key].name, db[key].pay)
bob = db[‘bob’]
print(bob.lastName()) print(db[‘tom’].lastName())
Обратите внимание, что в этом примере нам не требуется импортировать класс Person, чтобы извлекать экземпляры из хранилища или вызывать их методы. Когда экземпляры сохраняются с помощью модуля shelve или pickle, используемая этими модулями система сохранения записывает в файл не только значения атрибутов экземпляров, но и дополнительную информацию, позволяющую позднее автоматически определить местоположение классов при извлечении экземпляров (модули с определениями классов просто должны находиться в пути поиска модулей при выполнении операции загрузки). Это сделано специально, потому что определение класса и его экземпляры в хранилище сохраняются отдельно; вы можете изменить класс, чтобы изменить порядок интерпретации экземпляров при загрузке (подробнее об этом рассказывается далее в книге). Ниже приводятся результаты запуска сценария dump_db_classes.py сразу после создания хранилища с помощью сценария make_db_classes.py:
bob =>
Bob Smith 30000 sue =>
Sue Jones 40000 tom =>
Tom Doe 50000
Smith
Doe
Как показано в примере 1.20, изменение информации в базе данных выполняется так же просто, как и прежде (сравните с примером 1.13), но на этот раз вместо ключей словарей используются атрибуты экземпляров, а жестко зашитую логику изменений заменили вызовы методов классов. Обратите внимание, что нам по-прежнему необходимо извлечь запись, изменить ее и вновь присвоить тому же самому ключу.
Пример 1.20. PP4E\Preview\update_db_classes.py
import shelve
db = shelve.open(‘class-shelve’)
sue = db[‘sue’]
sue.giveRaise(.25)
db[‘sue’] = sue
tom = db[‘tom’]
tom.giveRaise(.20)
db[‘tom’] = tom
db.close()
И наконец, ниже приводятся результаты повторного запуска сценария dump_db_classes.py после запуска сценария update_db_classes.py. Том и Сью теперь имеют новые оклады, потому что теперь соответствующие объекты сохраняются в хранилище. Кроме того, мы могли бы открыть и исследовать содержимое хранилища в интерактивной оболочке Python — несмотря на свою долговечность, хранилище является всего лишь объектом Python, содержащим другие объекты Python.
bob =>
Bob Smith 30000 sue =>
Sue Jones 50000.0 tom =>
Tom Doe 65000.0
Smith
Doe
Том и Сью получили прибавку к окладу, потому что теперь эти объекты — объекты, сохраненные в базе данных. Хотя модуль shelve также способен сохранять объекты более простых типов, таких как списки и словари, однако классы позволяют нам объединять данные и поведение в единые сохраняемые элементы. В некотором смысле атрибуты экземпляров и методы классов равносильны записям и обрабатывающим их программам, используемым в более традиционных решениях.
Другие разновидности баз данных
К настоящему моменту мы создали вполне функциональную базу данных: наши классы одновременно реализуют хранение данных записей и их обработку и заключают в себе реализацию поведения. А модули pickle и shelve обеспечивают простой способ сохранения нашей базы данных между запусками программы. Это не реляционная база данных (она хранит объекты, а не таблицы, и запросы имеют вид программного кода на языке Python, обрабатывающего объекты), но ее вполне достаточно для многих видов программ.
Если потребуются более широкие функциональные возможности, мы сможем перевести это приложение на использование более мощных инструментов. Например, если нам потребуется полноценная поддержка запросов на языке SQL, мы сможем использовать библиотеки, позволяющие сценариям на языке Python путем переноса взаимодействовать с реляционными базами данных, такими как MySQL, PostgreSQL и Oracle.
Механизмы ORM (Object Relational Mapper — объектно-реляционное отображение), такие как SQLObject и SqlAlchemy, предлагают иной подход, сохраняющий представление записей в виде объектов Python, но преобразуя их в и из представления таблиц в реляционных базах данных, в некотором смысле обеспечивая сочетание лучших черт обоих миров — с синтаксисом классов Python сверху и надежными базами данных внутри.
Кроме того, существует открытая система ZODB, реализующая более функциональную объектную базу данных для программ на языке Python, с поддержкой особенностей, отсутствующих в хранилищах shelve, включая параллельное изменение записей, подтверждение и откат транзакций, автоматическое обновление компонентов, изменившихся в оперативной памяти, и многие другие. Мы познакомимся с этими, более совершенными инструментами, созданными сторонними разработчиками, в главе 17. А теперь перейдем к созданию лица нашей системы.
Автобусы признаны опасными
На протяжении многих лет Python пользовался мощной и добровольной поддержкой и отдельных лиц, и организаций. В настоящее время конференции и другие некоммерческие мероприятия в сообществе Python проходят при содействии некоммерческой организации Python Software Foundation (PSF). Организации PSF предшествовала организация PSA — группа, которая первоначально была образована в ответ на когда-то давно возникшее в телеконференции Python обсуждение полусерьезного вопроса: «Что будет, если Гвидо попадет под автобус?»
В настоящее время создатель языка Python, Гвидо ван Россум (Guido van Rossum), по-прежнему является верховным арбитром при поступлении предложений о внесении изменений в Python. Он официально был помазан на пост Великодушного Пожизненного Диктатора (Benevolent Dictator for Life, BDFL) на первой же конференции Python, и по-прежнему окончательное решение о принятии изменений в языке остается за ним (и многие изменения, ведущие к несовместимости, за исключением версии 3.0, несовместимость которой была предусмотрена заранее, он обычно отклоняет: это хорошая черта для языков программирования, потому что Python должен изменяться достаточно медленно и изменения не должны нарушать обратную совместимость).
Но, как бы то ни было, огромное количество пользователей Python помогает поддерживать язык, работает над расширениями, исправляет ошибки и так далее. Это по-настоящему совместный проект. Фактически разработка Python сейчас является совершенно открытым процессом — любой желающий сможет получить самые свежие файлы с исходными текстами или отправить свои исправления, посетив веб-сайт проекта (подробности вы найдете по адресу http://www.python.org).
Разработка Python, как пакета открытого программного обеспечения, в действительности находится в руках большого количества программистов, разбросанных по всему свету. Поэтому, даже если Великодушный Пожизненный Диктатор когда-нибудь передаст факел преемнику, Python практически наверняка продолжит пользоваться поддержкой своих пользователей. По своей природе открытые проекты, хотя и не без отступлений, обычно отражают потребности сообществ их пользователей в большей степени, чем потребности отдельных личностей или учредителей.
С учетом популярности Python нападение со стороны автобуса уже не кажется таким опасным, как раньше. Впрочем, Гвидо может считать иначе.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011