Стараетесь ли вы не использовать объектно-ориентированные особенности Python, предпочитая процедурную или функциональную модели? Данная тенденция характерна для некоторых языков, как например PHP, где многие разработчики избегают ООП. Однако при этом, вы, возможно, упускаете ряд возможностей. Java, требующая объектно-ориентированного подхода, демонстрирует плюсы этой модели буквально в каждой строчке написанного кода. Stephen B. Morris указывает преимущества использования «ООП в стиле Java» в Python.
В компании Edison, проверяя тестовые задания при трудоустройстве, мы часто сталкиваемся с тем, что многие кандидаты не владеют ООП. Для некоторых задач знание ООП необязательно, но, на наш взгляд, для программиста-профессионала знать ООП — это дело чести.
Ориентация на объекты
Программисты — существа ленивые, склонные придерживаться привычных свойств языка до тех пор, пока не появятся убедительные причины использовать что-то новое. Хороший пример этого — объектно-ориентированное программирование. PHP-разработчикам импонирует ООП в PHP, но применяют они его скупо, если вообще применяют. Аналогичная ситуация с разработчиками Python, предпочитающими не использовать объектно-ориентированные компоненты Python.
Java находится на противоположном конце языкового диапазона: это объектно-ориентированный язык, поэтому, работая на Java, никуда не деться от классов. Тем не менее, несмотря на объектно-ориентированное происхождение, большое количество Java-кода по-прежнему пишется в процедурном стиле.
Откуда такая предвзятость к ООП? Я думаю, виной этому личные предпочтения и суждения инженера. Если разработчик Python или PHP имеет обширный опыт применения одного из этих языков и не использовал объектно-ориентированные компоненты часто, нежелание возникает просто по привычке. Но ведь некоторые технические задачи лучше реализовывать в объектно-ориентированном контексте, чем использовать привычную процедурную или функциональную парадигму.
Да, объектно-ориентированное программирование может привести ко многим проблемам, таким как фрагментация динамической памяти (кучи) или другим неопределенным состояниям, как, например, прогрессивное ухудшение производительности. На самом деле, проблема с использованием динамической памяти — одна из причин, почему С++ много лет не мог заменить С в проектах по разработке встроенных систем. В 1990-е годы диск, процессор и память были в приоритете у разработчиков и архитекторов, и поэтому препятствовали использованию объектно-ориентированных языков (и, соответственно, препятствовали потенциальному росту производительности труда при использовании этих развивающихся языков).
Многие разработчики Python стараются не употреблять объектно-ориентированные компоненты языка, за исключением случаев, когда это неизбежно. В данной статье я сравню Java и Python с точки зрения сложности и скорости. Надеюсь, это даст объективную оценку.
Давайте рассмотрим примеры кода, начиная с Python.
Класс в Python
Объектно-ориентированная парадигма Python, как и Python в целом, довольно лаконична, что показывает простой класс в Примере 1.
Пример 1. Класс в Python.
class Student:
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def is_old(self):
return self.age > 100
Класс Student включает три элемента данных: имя, возраст и основной предмет.
Метод __init__() в Python наиболее близок к конструкторам в других языках. Обратите внимание на использование self.name для инициализации состояния экземпляра. Также включен простой метод is_old(), определяющий, является ли студент молодым или старым (старым — более 100 лет).
Код, показанный в Примере 1, демонстрирует одно из главных преимуществ объектно-ориентированного программирования: код и данные находятся в тесной взаимосвязи. Данные, естественно, представляют состояние объектов, таким образом, ООП объединяет код, данные и состояние в форме, удобной для программистов. Конечно, это можно сделать и без ООП, но оно существенно упрощает процесс.
Помните: большая часть исходного кода, существующего в мире, моделирует процессы реального мира. Объектно-ориентированное программирование может быть предельно ясной и удобной техникой для подобного моделирования. Возможно, это даже заставит вас использовать объектно-ориентированный подход любой ценой.
Эквивалентный класс в Java
Пример 2 показывает эквивалентный код на Java.
Пример 2. Класс Student на Java.
public class Student {
String name;
int age;
String major;
public Student() {
// TODO Auto-generated constructor stub
}
public Student(String name, int age, String major) {
this.name = name;
this.age = age;
this.major = major;
}
}
Java-код в Примере 2 очень похож на Python-код в Примере 1. Обратите внимание, что использование ООП дает довольно читаемый код на обоих языках. Пример 1 вряд ли собьет с толку разработчика Java, даже не имеющего представления о Python. Аналогично, программист Python, хорошо разбирающийся в объектно-ориентированных чертах Python, с легкостью поймет Java-код в Примере 2.
Итак, наш первый вывод. Хорошо написанный объектно-ориентированный код обеспечивает межъязыковую понятность.
Почему это важно? В наше мультиязычное время такая понятность дорогого стоит.
На современном этапе разработки программного обеспечения происходит стремительный рост приложений на Web и сопутствующее использование браузеров для доступа к приложениям. Пользователи ожидают от размещенных в вебе приложений «функций рабочего стола», как это принято называть. Такое юзабилити труднодостижимо при использовании только одного языка. Программисты должны владеть несколькими: Java, Scala, JavaScript, HTML, CSS, Python, SQL, и прочими.
Вопрос скорости: Python vs Java
Скорость всегда важна. Давайте скорректируем Пример 1 так, чтобы понять скорость приведенного кода.
Пример 3 показывает простую программу, которая пытается слегка нагрузить платформу.
Пример 3. Выполнение программы с замером времени
import time
class Student:
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def is_old(self):
return self.age > 100
start = time.clock()
for x in xrange(500000):
s = Student('John', 23, 'Physics')
print 'Student %s is %s years old and is studying %s' %(s.name, s.age, s.major)
print 'Student is old? %d ' %(s.is_old())
stop = time.clock()
print stop - start
Пример 3 — слегка дополненная версия Примера 1. Этот измененный код выполняет следующее:
- импортирует модуль time;
- замеряет время в начале программы;
- создает множество объектов класса Student;
- получает доступ к данным внутри каждого объекта;
- повторно замеряет время и вычитает первоначальное время;
- показывает время, требуемое для выполнения программы.
Конечно, это довольно грубый тест. Но давайте посмотрим на пример выполнения программы, во время которого создается 500 000 объектов. Вот отрывок вывода программы.
Student John is 23 years old and is studying Physics
Student is old? 0
29.8887370933
Это можно рассматривать как базовый тест: для выполнения программы с 500 000 объектов требуется около 30 секунд. А теперь давайте увеличим количество созданных объектов до 800 000.
Student John is 23 years old and is studying Physics
Student is old? 0
48.2298926572
Отсюда видно, что выполнение программы с 800 000 объектами занимает около 48 секунд. Давайте удвоим количество созданных объектов.
Student John is 23 years old and is studying Physics
Student is old? 0
97.3272409408
97 секунд для 1 600 000 объектов.
А теперь давайте сделаем то же самое на Java.
Запуск Java-кода
Пример 4 показывает простую программу на Java, которая слегка нагружает платформу.
Пример 4. Тестовая программа на Java
public class Student {
String name;
int age;
String major;
public Student() {
// TODO Auto-generated constructor stub
}
public Student(String name, int age, String major) {
this.name = name;
this.age = age;
this.major = major;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Student student = new Student("John", 23, "Physics");
System.out.println("Student " + student.getName() + " is "
+ student.getAge() + " years old and is studying " + student.getMajor());
}
long estimatedTime = System.currentTimeMillis() - startTime;
System.out.println("Time estimate: " + estimatedTime/1000);
}
}
Заметьте, что в Пример 4 я включил автоматически сгенерированные геттеры и сеттеры. Опытные разработчики Java используют их постоянно, геттеры и сеттеры автоматически генерируются самим Eclipse. Точно так же сгенерированы и два конструктора. Такой тип повышения производительности удобен, и, поскольку этот код генерируется машиной, он точно не содержит ошибок.
Давайте запустим код Java с 500 000 объектами, что мы делали и с Python.
Student John is 23 years old and is studying Physics
Student is old: false
Time estimate: 31
31 секунда для 500 000 объектов. Теперь запустим для 800 000 объектов.
Student John is 23 years old and is studying Physics
Student is old: false
Time estimate: 50
50 секунд для 800 000 объектов. Теперь выполним наш финальный Java тест для 1 600 000 объектов.
Student John is 23 years old and is studying Physics
Student is old: false
Time estimate: 104
104 секунды для 1 600 000 объектов.
Для сравнения занесем результаты в таблицу.
Сравнительный тест скорости
Число объектов | Скорость Java | Скорость Python |
500,000 | 31 | 30 |
800,000 | 50 | 48 |
1,600,000 | 104 | 97 |
Результат теста показывает, что код Python несущественно превосходит код Java. И это ожидаемо. Java можно назвать «тяжеловесом» среди распространенных языков; он обладает определенным набором особенностей, включая, но не ограничиваясь следующими.
- Портируемость. Это означает, что байт-код Java будет работать на любой платформе с подходящей виртуальной машиной Java.
- Типобезопасность. Типобезопасность тесно связана с безопасностью памяти. Эта особенность языка позволяет избежать ситуаций, когда предпринимается попытка скопировать недопустимый набор битов в заданную область памяти.
- Встроенная безопасность. Модель безопасности Java основывается на «песочнице», в которой код может быть запущен безопасно с минимальными негативными эффектами для базовой платформы.
Как и со всеми технологиями, за эти особенности приходится платить. Однако, как видно из таблицы, стоимость в контексте текущего теста точно не заоблачная.
Отсюда наш второй вывод: за использование ООП приходится платить (скоростью работы программ, их размером и т.п.), но это оправдывается возможностями, которые оно дает.
Расширение теста
Тесты, которые я запускал для этих примеров, очень простые. Более реалистичный тест может использовать объекты, которые читают и пишут в базу данных или отправляют и получают сетевой трафик. Если данные в подобных тестах получают из файлов, это можно использовать для нагрузки приложения операциями ввода-вывода с диска.
Запуск кода
Python может быть запущен из командной строки. Еще удобнее вы можете запустить его из интегрированной среды разработки (IDE), такой как Eclipse. Я предпочитаю использовать IDE из-за увеличения производительности, которую они предлагают: генерация кода, юнит-тестирование, создание пакетов и модулей и пр.
Приступать к работе с Python и Eclipse легко: устанавливаем Eclipse и затем, используя его маркетплейс, устанавливаем плагин PyDev. Далее создаем модуль Python или PyDev, и все готово для написания кода.
Запустить Java код в Eclipse еще проще, поскольку стандартная установка уже включает поддержку Java. И не будем забывать о дополнительном увеличении производительности Java: автодополнение кода, генерация кода (геттеры, сеттеры, конструкторы и т.п.), рефакторинг и т.д.
Вне зависимости от выбранного языка или модели программирования (ООП или же процедурная или функциональная парадигма) нельзя отрицать, что использование современной IDE, такой как Eclipse, обеспечивает большое увеличение производительности. Такой инструмент облегчает гибкую разработку в части генерации кода, рефакторинга и интеграции дополнительных инструментов с помощью плагинов.
Выводы
Объектно-ориентированные языки пользовались недоверием в 1990-е годы. Тогда многие организации предпочитали придерживаться распространенных языков типа С, а не приспосабливаться к новому С++. Далее появилась Java, и я думаю справедливо утверждать, что С++ больше не является объектно-ориентированным языком де-факто.
Сегодня объектно-ориентированные языки часто используются во встроенных платформах, как само собой разумеющееся. Однако все еще имеет место некое сопротивление в использовании ООП в таких языках как Python и PHP. Причиной тому — личные предпочтения программистов.
Интересный аспект в сравнении объектно-ориентированных кодов на разных языках — схожесть этих языков. Объектно-ориентированный код Python существенно не отличается от эквивалентного кода на Java. Это преимущество использования ООП в эру многоязычности, помогающее программистам писать хороший код. Более простой код хорошо воспринимается другими программистами, которые будут поддерживать этот код в дальнейшем.
Как было показано на простых тестах, скорость примерно одинакового кода на Python и Java не сильно различается.
Объектно-ориентированная парадигма в любом языке дает преимущества, одно из которых — простота понимания. Учитывая это и другие реальные и потенциальные выгоды, стоит прекратить избегать ООП.
Комментарии (8)
xlix123
15.12.2015 18:17+4В приведенных выше «бенчмарках» тестируется разве что системный ввод/выод,
но уж никак не ООП.poxu
15.12.2015 18:31Также хотелось бы увидеть, что будет, если в питоне заменить прямое чтение свойств вызовом методов. Предварительно убрав вывод на печать что из него, что их джавы. Также хочется ненавязчиво поинтересоваться у автора как там GIL. Всё ещё на месте?
Ну и, ласт бат но лист, пассаж про то, что С++ де факто не может считаться объектно ориентированным языком. Автор, если я правильно понимаю, из тех, кто говорит, что Убунту нельзя считать Линуксом, а Windows — операционной системой.
knekrasov
15.12.2015 18:19+2Статья ни о чем.
Мерить производительность создания объектов в двух языках — вещь бесполезная. Инициализация объектов происходит примерно одинаково: выделили кусок памяти memset'ом, заполнили метаданные, выполнили присваивания.
Во сколько обходится вызов метода в Python по сравнению с Java? Как происходит поиск метода в случае наследования? Как в Python обстоят дела с инкапсуляцией и изоляцией данных?
SirEdvin
15.12.2015 18:20+1Немного поспорю с копипастой:
Не особо понятно, зачем тут приводится сравнение Python и Java, которое в корне неправильное и разражает (сравнение геттеров/сеттеров с прямым доступом и прочее).
Существуют реальные причины, почему разработчики не хотят использовать ООП в Python, но я сомневаюсь, что они заключены в скорости.
Лучше было бы написать, какие преимущества получит ООП-код над обычным кодом.
Объектно-ориентированный код Python существенно не отличается от эквивалентного кода на Java.
Особеноо абсолютно понятным для любого программиста, не знакомого с языком, является то, что переменная, название которой начинается с _ — приватная. Причем доступится к ней все равно можно, просто некоторые IDE могут вам сказать, что так делать нехорошо.
И так далее.
crazylammer
15.12.2015 18:23+4Товарищ меряет скорость вывода в stdout (он оказался одинаковый в Java и Python — кто бы мог подумать), из чего был сделан глубокомысленный вывод о том, что Python «ожидаемо» превосходит «тяжеловесную» Java.
И при этом человек консультантом работает, книги пишет, а их еще кто-то покупает…
sl4mmer
15.12.2015 18:41>Однако все еще имеет место некое сопротивление в использовании ООП в таких языках как Python и PHP
Даже лет 10 назад это было бы довольно сомнительным высказыванием
Obramko
Если меряете мерялки — запустите тогда уже python-код через pypy.