Как должен быть структурирован модуль Python?

#python

#python

Вопрос:

Это моя первая публикация в stack overflow, поэтому я прошу прощения, если я делаю что-то не так.

Я пытаюсь понять наилучший способ структурирования модуля Python. В качестве примера я создал резервный модуль, который синхронизирует источник и место назначения, копируя файлы только в том случае, если есть различия между источником и местом назначения. Модуль резервного копирования содержит только класс с именем Backup .

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

 """Class that represents a backup event."""

import hashlib
import os
import shutil

class Backup:
    def __init__(self, source, destination):
        self.source = source
        self.destination = destination

    def sync(self):
        """Synchronizes root of source and destination paths."""

        sroot = os.path.normpath(self.source)
        droot = os.path.normpath(self.destination)   '/'   os.path.basename(sroot)

        if os.path.isdir(sroot) and os.path.isdir(droot):
            Backup.sync_helper(sroot, droot)
        elif os.path.isfile(sroot) and os.path.isfile(droot):
            if not Backup.compare(sroot, droot):
                Backup.copy(sroot, droot)
        else:
            Backup.copy(sroot, droot)

    def sync_helper(source, destination):
        """Synchronizes source and destination."""

        slist = os.listdir(source)
        dlist = os.listdir(destination)

        for s in slist:
            scurr = source   '/'   s
            dcurr = destination   '/'   s

            if os.path.isdir(scurr) and os.path.isdir(dcurr):
                Backup.sync_helper(scurr, dcurr)
            elif os.path.isfile(scurr) and os.path.isfile(dcurr):
                if not Backup.compare(scurr, dcurr):
                    Backup.copy(scurr, dcurr)
            else:
                Backup.copy(scurr, dcurr)

        for d in dlist:
            if d not in slist:
                Backup.remove(destination   '/'   d)

    def copy(source, destination):
        """Copies source file, directory, or symlink to destination"""

        if os.path.isdir(source):
            shutil.copytree(source, destination, symlinks=True)
        else:
            shutil.copy2(source, destination)

    def remove(path):
        """Removes file, directory, or symlink located at path"""

        if os.path.isdir(path):
            shutil.rmtree(path)
        else:
            os.unlink(path)

    def compare(source, destination):
        """Compares the SHA512 hash of source and destination."""

        blocksize = 65536
        shasher = hashlib.sha512()
        dhasher = hashlib.sha512()

        while open(source, 'rb') as sfile:
            buf = sfile.read(blocksize)
            while len(buf) > 0:
                shasher.update(buf)
                buf = sfile.read(blocksize)

        while open(destination, 'rb') as dfile:
            buf = dfile.read(blocksize)
            while len(buf) > 0:
                dhasher.update(buf)
                buf = dfile.read(blocksize)

        if shasher.digest() == dhasher.digest():
            return True
        else:
            return False
  

Я думаю, что это действительно не имеет смысла как класс, поскольку единственным методом является sync. С другой стороны, резервная копия — это объект реального мира. Это действительно меня смущает.

Как некоторые побочные вопросы. Мой метод синхронизации и функция sync_helper кажутся очень похожими, и, вероятно, их можно как-то свернуть (я оставлю это как упражнение для себя), но так ли это вообще, как это делается при использовании рекурсивной функции, для которой требуется определенное начальное состояние. То есть, можно ли что-то делать в одной функции для достижения определенного состояния, а затем вызывать рекурсивную функцию, которая выполняет фактическую работу. Это кажется беспорядочным.

Наконец, у меня есть куча служебных функций, которые на самом деле не являются частью объекта, но используются sync. Было бы разумнее разбить их на подмодуль утилиты или что-то в этом роде, чтобы не вызывать путаницы?

Структурирование моих программ — самая запутанная вещь для меня прямо сейчас, любая помощь была бы очень признательна.

Комментарии:

1. Братан -> codereview.stackexchange.com разместите сообщение здесь

2. Python — это мультипарадигма, а не ООП. Некоторые из самых приятных функций в Python более функциональны, чем объектно-ориентированные. Нет никаких причин заключать все в класс. @NikhilParmar, вероятно, прав, что вам следует перенести этот вопрос.

3. Прошу прощения, я отправлю вопрос по предоставленной ссылке, спасибо.

Ответ №1:

Этот метод (и несколько других) неверен:

 def copy(source, destination):
    """Copies source file, directory, or symlink to destination"""
  

Он работает так, как он использовался ( Backup.copy(scurr, dcurr) ) , но он не работает при использовании в экземпляре.

Все методы в Python должны принимать self в качестве первого позиционного аргумента: def copy(self, source, destination) , или должны быть превращены в staticmethod (или перемещены из класса).

Статический метод объявляется с помощью staticmethod декоратора:

 @staticmethod
def copy(source, destination):
    """Copies source file, directory, or symlink to destination"""
  

Но в данном случае, source и destination на самом деле являются атрибутами Backup экземпляров, поэтому, вероятно, его следует изменить для использования атрибутов:

 def copy(self):
    # use self.source and self.destination
  

Комментарии:

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

2. @ruengemoog Если это статический метод, используйте @staticmethod (я обновил ответ на этом примере). Если это также «частный» метод, назовите его _copy (с подчеркиванием). Это общее соглашение об именовании (см. PEP-8 ). В конце концов, что касается выбора между статическим методом и функцией вне класса, я бы переместил функции из класса, только если разумно использовать их из других мест, а не только из этого класса.

3. Имеет смысл. Спасибо за комментарий.