Как реорганизовать Python «класс бога»?

10

проблема

Я работаю над проектом Python, основным классом которого является « Объект Бога ». Существует так много чертовых атрибутов и методов!

Я хочу изменить класс.

Уже…

Для первого шага я хочу сделать что-то относительно простое; но когда я попробовал самый простой подход, он сломал некоторые тесты и существующие примеры.

По сути, у класса есть довольно длинный список атрибутов, но я могу четко просмотреть их и подумать: «Эти 5 атрибутов связаны ... Эти 8 также связаны ... а затем есть остальные».

GetAttr

Я просто хотел сгруппировать связанные атрибуты в вспомогательный класс, похожий на dict. У меня было ощущение, __getattr__что это идеально подходит для работы. Поэтому я переместил атрибуты в отдельный класс и, конечно же, __getattr__отлично справился со своей магией ...

Во первых .

Но затем я попытался запустить один из примеров. Подкласс примера пытается установить один из этих атрибутов напрямую (на уровне класса ). Но так как атрибут больше не был «физически расположен» в родительском классе, я получил сообщение о том, что атрибут не существует.

@имущество

Я тогда прочитал о @propertyдекораторе. Но потом я также прочитал, что это создает проблемы для подклассов, которые хотят делать, self.x = blahкогда xэто свойство родительского класса.

Желаемый

  • Пусть весь клиентский код продолжает работать self.whatever, даже если whateverсвойство родителя не «физически расположено» в самом классе (или экземпляре).
  • Сгруппируйте связанные атрибуты в похожие на dict контейнеры.
  • Уменьшите чрезмерную шумность кода в основном классе.

Например, я не просто хочу изменить это:

larry = 2
curly = 'abcd'
moe   = self.doh()

В это:

larry = something_else('larry')
curly = something_else('curly')
moe   = yet_another_thing.moe()

... потому что это все еще шумно. Хотя это успешно превращает простой атрибут во что-то, что может управлять данными, оригинал имел 3 переменные, а измененная версия все еще имеет 3 переменные.

Тем не менее, я был бы в порядке с чем-то вроде этого:

stooges = Stooges()

И если поиск self.larryне удастся, что-то проверит stoogesи посмотрит, есть ли larryтам. (Но это также должно работать, если подкласс пытается сделать это larry = 'blah'на уровне класса.)

Резюме

  • Хотите заменить связанные группы атрибутов в родительском классе одним атрибутом, который хранит все данные в другом месте
  • Хотите работать с существующим клиентским кодом, который использует (например) larry = 'blah'на уровне класса
  • Хотите продолжать разрешать подклассам расширять, переопределять и изменять эти измененные атрибуты, не зная, что что-то изменилось


Это возможно? Или я лаю не на том дереве?

Zearin
источник
6
Вам не хватает половины преимуществ, если вы настаиваете на том, чтобы по-прежнему иметь этот огромный богоподобный интерфейс, даже если вы разделяете части реализации. Вы можете предоставить ярлыки, но просто поместив переменные в разные пространства имен и полностью перенаправив их, вы получите очень мало, если вообще что-нибудь.
1
@delnan: Хорошо, так что бы вы порекомендовали вместо этого?
Zearin

Ответы:

9

Написав, а затем рефакторинг питона "объект Бога", я сочувствую. Я разбил оригинальный объект на подразделы, основанные на методах. Например, оригинал выглядел как этот псевдокод:

method A():
    self.bla += 1

method B():
    self.bla += 1

do stuff():
    self.bla = 1
    method A()
    method B()
    print self.bla

Метод материала - это автономная «единица» работы. Я перенес его в новый класс, который создается в оригинале. Это также вытянуло необходимые свойства. Некоторые использовались только подклассом и могли двигаться прямо через дорогу. Другие были поделены, и были переведены в общий класс.

«Объект Бога» создает новую копию общего класса при запуске, и каждый из новых подклассов принимает указатель как часть их метода init. Например, вот урезанная версия почтовой программы:

#!/usr/bin/env python
# -*- coding: ascii -*-
'''Functions for emailing with dirMon.'''

from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
import os
import smtplib
import datetime
import logging

class mailer:
    def __init__(self,SERVER="mail.server.com",FROM="support@server.com"):
        self.server = SERVER
        self.send_from = FROM
        self.logger = logging.getLogger('dirMon.mailer')

    def send_mail(self, send_to, subject, text, files=[]):
        assert type(send_to)==list
        assert type(files)==list
        if self.logger.isEnabledFor(logging.DEBUG):
            self.logger.debug(' '.join(("Sending email to:",' '.join(send_to))))
            self.logger.debug(' '.join(("Subject:",subject)))
            self.logger.debug(' '.join(("Text:",text)))
            self.logger.debug(' '.join(("Files:",' '.join(files))))
        msg = MIMEMultipart()
        msg['From'] = self.send_from
        msg['To'] = COMMASPACE.join(send_to)
        msg['Date'] = formatdate(localtime=True)
        msg['Subject'] = subject
        msg.attach( MIMEText(text) )
        for f in files:
            part = MIMEBase('application', "octet-stream")
            part.set_payload( open(f,"rb").read() )
            Encoders.encode_base64(part)
            part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
            msg.attach(part)
        smtp = smtplib.SMTP(self.server)
        mydict = smtp.sendmail(self.send_from, send_to, msg.as_string())
        if self.logger.isEnabledFor(logging.DEBUG):
            self.logger.debug("Email Successfully Sent!")
        smtp.close()
        return mydict

Он создается один раз и используется разными классами, которым нужны возможности почтовой рассылки.

Поэтому для вас создайте класс larryс нужными вам свойствами и методами. Везде, где клиент говорит larry = blahзаменить его larryObj.larry = blah. Это переносит вещи в подпроекты, не нарушая текущий интерфейс.

Единственное, что нужно сделать, это искать «единицы работы». Если вы собираетесь превратить часть «Объекта Бога» в его собственный метод, сделайте это . Но поместите метод вне этого. Это заставляет вас создавать интерфейс между компонентами.

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

#!/usr/bin/env python
'''This module holds a class to spawn various subprocesses'''
import logging, os, subprocess, time, dateAdditionLib, datetime, re

class spawner:
    def __init__(self, mailer):
        self.logger = logging.getLogger('dirMon.spawner')
        self.myMailer = mailer

Сконцентрируйтесь на наименьшей отдельной возможной единице работы и уберите ее. Это проще сделать и позволяет быстро поиграть с настройкой. Не смотрите на свойства для перемещения вещей, они являются вспомогательными для задач , выполняемых с ними в большинстве случаев. Все, что осталось после того, как вы разберетесь с методами, вероятно, должно остаться в исходном объекте, так как он является частью общего состояния.

Но теперь новые объекты должны принимать нужные им свойства в качестве переменных init, не затрагивая свойство объектов вызывающего. Затем они возвращают любые необходимые значения, которые вызывающий может использовать для обновления общих свойств по мере необходимости. Это помогает отделить объекты и делает систему более надежной.

Спенсер Рэтбун
источник
1
Фантастический ответ, Спенсер. Спасибо! У меня есть несколько дополнительных вопросов, которые являются слишком конкретными по своему характеру, чтобы быть уместными здесь. Могу ли я связаться с вами в частном порядке, чтобы обсудить это?
Zearin
@Zearin уверен, мой профиль имеет мой адрес электронной почты. Хотя это было для проекта компании, и я не могу дать вам полную копию репозитория из-за запатентованных вещей там. Если бы у меня было достаточно времени, я мог бы очистить снимки до / после, но я не уверен, насколько это вам поможет.
Спенсер Рэтбун
Я не вижу ни одного адреса электронной почты в вашем профиле. Там всякая информация, но не контактная информация. ☺ Как мне с вами связаться?
Zearin
Понял. Кибермен: «Удалить! Удалять! Удалять!"
Зерин