суббота, 1 марта 2008 г.

ООП как способ сделать мир понятнее. Часть 1.

На python.org.ua один из пользователей посетовал на то что не может понять ООП.
Я решил немного помочь ему в этом, а так же всем начинающим питонщикам с той же проблемой.
Надеюсь хоть кому-то пригодится.

Глава 1 или надо же как-то начинать

Допустим мы хотим написать программу для работы с файлами разных типов. Соответственно в зависимости от типа файла будет добавляться разный функционал, но какой мы пока не знаем.
Что известно точно это те операции что будут производится над файлом в любом случае, в не зависимости от типа. Например такие как "удаление" "копирование" "перенос" а также получение общих атрибутов файла.(размер, имя, тип и т.п)
При написании этой части программы без использования ООП пришлось бы писать просто функцию для каждого действия, просто передавая им путь к файлу. Или использовать библиотечные (входящие в поставку python).
Например так:


import os
import shutil
import filecmp

# path - путь к файлу который мы будем использовать
path = "exemple.txt"

# операции из стандартних библиотек os и shutil
os.rename(src_path,dst_path) # переименовать
shutil.copy(src_path,dst_path) # копировать
shutil.move(src_path,dst_path) # переместить
os.remove(path) # удалить
size = os.path.getsize(path) # размер файла в байтах
filecmp.cmp(path, path2) # сравнить файлы

# теперь немного сложнее
def GetModifyTime(path):
""" функция полученя времни
последнего изменения файла """
from datetime import datetime # для преобразования даты в приемлемый формат
return datetime.fromtimestamp(os.path.getmtime(path))

Все вроде бы просто, и вполне понятно и юзабельно. А теперь попробуем сделать практически тоже само но используя ООП-подход.
Для начала создадим некий класс FileBase - этот класс будет представлять наш файл на диске. Что это значит? Это значит что мы привяжем его к некоторому файлу, и все манипуляции будем производить над классом (точнее над одним из его экземпляров).
Про класс следует думать как про нечто, что представляет файл. Как про юриста что представляет человека :) То есть, как про что-то реальное.

Вот так:

import os
import shutil
from datetime import datetime

# self - это ссылка внутри класса, обозначает
# сам класс. То есть ссылка на самого себя.
class FileBase(object):
def __init__(self, path):
""" этот метод (функция класса) вызывается
при создании копии (экземпляра) класса.
В нем мы сразу привязываем класс к файлу.
"""
self.path = path # self.path - это атрибут (параметр) нашего класса в котором записан путь к файлу

def __str__(self):
"""Возвращаем путь к файлу
при вызове экземпляра класса """
return self.path

def rename(self, newname):
os.rename(self.path, os.path.join(os.path.dirname(self.path), newname))

def copy(self, path):
shutil.copy(self.path,path)
return FileBase(path) # возвращаем новый объект для файла.

def move(self,path):
shutil.move(self.path, path)
self.path = path

def delete(self):
""" удаляем файл """
os.remove(self.path)
self.path = None

def size(self):
"""Возвращаем размер файла"""
return os.path.getsize(self.path)

def __cmp__(self, filobject):
"""сравниваем два файла
если не равны - возвращаем разницу размеров
"""
if filecmp.cmp(self.path, filobject):
return 0
else:
return self.size - filobject.size

def getModifyTime(self):
"""возвращаем время последней модификации файла"""
return datetime.fromtimestamp(os.path.getmtime(self.path))

Сложнее немного не так ли? Но на самом деле это кажущаяся сложность, и почему мы увидим дальше.
Хочу обратить внимание, что rename в классе несколько не тот что в стандартной функции.

os.rename(src_path,dst_path)

При вызове этой функции переименованный файл будет перемещён в папку dst_path под новым именем. В случае же с классом то вызов метода rename не переместит файл, а просто переименует его в той папке где он находится.
С функцией сравнения тоже есть отличия, я расскажу о них дальше.

Вот теперь давайте посмотрим как произвести одни и те же операции используя наш класс и без него:

print "без использования ООП"
# для начала возьмем файл
path = "c:\\test\\test2\\example.txt"
print os.path.getsize(path) # выводим размер файла
print GetModifyTime(path) # выводим дату создания
# где сейчас файл?
print path
# перемещаем файл в другую папку:
dst = "c:\\test\\"+os.path.basename(path) # путь к новому файлу
shutil.move(path,dst)
# где теперь?
print dst
# копируем обратно
shutil.copy(dst,path) # копировать
# где теперь файлы?
print "Файл - " + fileobj.path
print "Копия файла" + copy_fileobj.path
print GetModifyTime(path) # выводим дату последней модификации копии
# сравниваем файлы:
if filecmp.cmp(path, dst):
print "одинаковые"
else:
print "разные"

отлично. Теперь то же но с использованием нашего класса:

fileobj = FileBase("c:\\test\\test2\\example.txt") # теперь fileobj - наш файл
print fileobj.size() # выводим размер файла
print fileobj.getModifyTime() # выводим дату последней модификации
# где сейчас файл?
print fileobj
# перемещаем в другую папку
fileobj.move("c:\\test\\"+os.path.basename(fileobj))
# где теперь?
print fileobj
# ок, копируем обратно одновременно получая объект копии файла
copy_fileobj = fileobj.copy("c:\\test\\test2\\example.txt")
# гд теперь файлы?
print "Файл - " + fileobj
print "Копия файла" + copy_fileobj
# можно посмотреть дату последней подификации копии
print copy_fileobj.getModifyTime()
# сравним файл с копией
if fileobj == copy_fileobj:
print "одинаковые"
else:
print "разные"

Обратите внимание что оба файла сравниваются как обычные переменные. Можно также узнать какой из них больше, а какой меньше, наитивно, как у переменных. В не-ООП стиле пришлось бы писать и явно вызывать спец. функцию.

Я думаю разница уже заметна. Мы делали одни и те же вещи, но в случае с ООП мы оперируем некоторым объектом который представляет файл. Он его представляет и манипулирует им, порождая новые объекты-представители при копировании, изменяя собственные свойства при переносе и т.д. Так если бы мы делали это в файловом менеджере, например.
Без ооп - мы манипулируем файлами, относясь к нему непосредственно через пути, что несколько запутывает саму программу. Разница пока невелика, но стало немного проще.
Самое интересное что улучшение понятности программы при правильном использовании - не самое главное преимущество ООП. В следующем посте я расскажу про преимущество несколько более важное.
Я расскажу про наследование.