Форум сайта python.su
0
скажите пожалуйста, всегда ли нужно соблюдать принцип единой ответственности? я стараюсь писать код так чтобы класс решал одну задачу и по возможности лежал в отдельном модуле
проблема в том, что код, который содержит основную логику приложения, и который управляет описанными выше классами по-моему в принципе не может подчиняться принципу единой ответственности - то есть решать одну задачу.
вот посмотрите пожалуйста как выглядит мой код с основной логикой приложения:
#!/usr/bin/env python3 import config from parser import Parser from db_connection import DB_connection from logger import Logger if __name__ == '__main__': logger = Logger() with DB_connection(config.dbname, config.user, config.password, config.host, logger) as db_connection: try: db_cursor = db_connection.cursor() except Exception as e: msg = 'Database ' + str(config.dbname) + ' connection is failed ' + str(e) logger.write_to_log(msg) try: parser = Parser(config.headers, logger) html = parser.get_page_html(config.url) links_to_inner = parser.get_links_to_inner(html) except Exception as e: msg = 'Parse operations for ' + str(config.url) + ' is failed ' + str(e) logger.write_to_log(msg) if links_to_inner: for link in links_to_inner: html = parser.get_page_html(link) content_page_inner = parser.get_content_page_inner(html) try: req = 'INSERT INTO parse_results2 (public_date, position, company, pay, description) VALUES (%s,%s,%s,%s,%s)' vals = ( content_page_inner['public_date'], content_page_inner['position'], content_page_inner['company'], content_page_inner['pay'], content_page_inner['desc'] ) db_cursor.execute(req, vals); except Exception as e: msg = 'Write to table is failed: ' + str(e) logger.write_to_log(msg) if 'db_cursor' in dir(): db_cursor.close()
Офлайн
186
> я стараюсь писать код так чтобы класс решал одну задачу и по возможности лежал в отдельном модуле
С чего ты взял что класс должен лежать в отдельном модуле?
> проблема в том, что код, который содержит основную логику приложения, и который управляет описанными выше классами по-моему в принципе не может подчиняться принципу единой ответственности - то есть решать одну задачу.
Твой код получает список ссылок и сохраняет их в БД. Т.е. он решает 2 задачи. Разбей его на функцию которая возвращает список ссылок и на функцию которая записывает их в БД.
Офлайн
0
Rodegast
> я стараюсь писать код так чтобы класс решал одну задачу и по возможности лежал в отдельном модуле С чего ты взял что класс должен лежать в отдельном модуле?> проблема в том, что код, который содержит основную логику приложения, и который управляет описанными выше классами по-моему в принципе не может подчиняться принципу единой ответственности - то есть решать одну задачу.Твой код получает список ссылок и сохраняет их в БД. Т.е. он решает 2 задачи. Разбей его на функцию которая возвращает список ссылок и на функцию которая записывает их в БД.
import config from classes.parser import Parser from classes.db_connection import DB_connection from classes.logger import Logger from classes.sql_requester import Sql_requester if __name__ == '__main__': def get_links(): try: parser = Parser(config.headers, logger) html = parser.get_page_html(config.url) links_to_inner = parser.get_links_to_inner(html) return { 'links_to_inner': links_to_inner, 'parser': parser } except Exception as e: msg = 'Parse operations for ' + str(config.url) + ' is failed ' + str(e) logger.write_to_log(msg) return False def parse_links(links, sql_requester): for link in links['links_to_inner']: html = links['parser'].get_page_html(link) content_page_inner = links['parser'].get_content_page_inner(html) sql_requester.fill_table(content_page_inner) logger = Logger() with DB_connection(config.dbname, config.user, config.password, config.host, logger) as db_connection: try: db_cursor = db_connection.cursor() sql_requester = Sql_requester(db_cursor, logger) except Exception as e: logger.write_to_log('Connection is failed') else: if sql_requester.clear_table(): links = get_links() if links: parse_links(links, sql_requester) finally: db_cursor.close()
Офлайн
568
zlodiakЧто на вас такое нашло то? Почему вы вернулись к процедурному стилю? Было же все хорошо, паттерны изучали, и тут вдруг опять какие-то глупости. Как вы к этому коду будете ранее изученые паттерны применять?
так
if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
Офлайн
186
> так?
Нет, примерно вот так:
import config from parser import Parser from db_connection import DB_connection from logger import Logger def get_links(logger): parser = Parser(config.headers, logger) html = parser.get_page_html(config.url) res = [] for x in parser.get_links_to_inner(html): res.append(parser.get_content_page_inner(parser.get_page_html(x))) return res def writer(data, logger): if data: with DB_connection(config.dbname, config.user, config.password, config.host, logger) as db_connection: db_cursor = db_connection.cursor() for x in data: db_cursor.execute( "INSERT INTO parse_results2 (public_date, position, company, pay, description) VALUES (%s,%s,%s,%s,%s)", (x['public_date'], x['position'], x['company'], x['pay'], x['desc']) ) db_cursor.close() if __name__ == '__main__': logger = Logger() writer(get_links(logger), logger)
Отредактировано Rodegast (Апрель 9, 2019 10:47:26)
Офлайн
0
FishHookкак оказалось, паттерны изучать намного проще чем применять) даже такой простой парсер мне пока сложно написать. я решил упростить задачу: временно отказаться от логгера и блоков try/except. посмотрите пожалуйста в правильном ли направлении я двигаюсь.
if __name__ == '__main__': Main(config, credentials, Parser, DB_connection, Sql_requester)
import config import credentials from classes.parser import Parser from classes.db_connection import DB_connection from classes.sql_requester import Sql_requester class Main: def __init__(self):
Отредактировано zlodiak (Апрель 9, 2019 12:24:42)
Офлайн
568
zlodiak
которые можно было просто импортировать, например так:
Офлайн
0
FishHook
Старайтесь не импортировать объекты, а создавать их по месту необходимости
if __name__ == '__main__': Main(config, credentials, Parser, DB_connection, Sql_requester, classes.exceptions, Logger)
Отредактировано zlodiak (Апрель 9, 2019 18:56:24)
Офлайн
568
zlodiakА откуда вы их собрались передавать? Main - это ваша точка входа, она кроме аргументов командной строки вообще ничего принимать не должна.
Но как в этом случае быть с большим количеством передаваемых аргументов
if __name__ == '__main__': config = Config("production_settings.ini") parser = Parser(file=sys.argv[1], root="root") db = DbConnection("db_settings.ini") app = App() app.setConfig(config) app.setParser(parser) app.setDb(db) app.run()
import unittest class TestAppCase(unittest.TestCase): def setUp(self): config = Config("test_settings.ini") parser = Parser(file="test_data.html", root="root") db = DbConnection("test_db_settings.ini") app = App() app.setConfig(config) app.setParser(parser) app.setDb(db) self.app = app def test_result(self): self.app.run() self.assertEqual(self.app.result, {"count": 100}, 'incorrect result')
Офлайн
0
FishHook
Я вам не просто так рекомендую написать модульные тесты. Как только вы попробуете это сделать, принцип единичной ответственности войдет в ваш код автоматически. Нужно протестировать каждый публичный метод по возможности используя не более одного assert на тест.
class Main: def __init__(self, exceptions): self.exceptions = exceptions self.content = [] def set_db_connection(self, db_connection): self.db_connection = db_connection def set_parser(self, parser): self.parser = parser def set_logger(self, logger): self.logger = logger def set_Sql_requester(self, Sql_requester): self.Sql_requester = Sql_requester def run(self): try: self.get_content() self.save_content() except (self.exceptions.ErrorGetContent, self.exceptions.ErrorConnectDB, self.exceptions.ErrorClearTable, self.exceptions.ErrorSaveContent) as e: msg = e self.logger.write_to_log(msg) print('E::', msg) # debug info def get_content(self): self.content = self.parser.get_content() def save_content(self): with self.db_connection as db_connection: db_cursor = db_connection.cursor() try: sql_requester = self.Sql_requester(db_cursor, self.exceptions) sql_requester.clear_table() sql_requester.fill_table(self.content) except (self.exceptions.ErrorClearTable, self.exceptions.ErrorTableInsert) as e: msg = 'Save content is failed. ' + str(e) self.logger.write_to_log(msg) raise self.exceptions.ErrorSaveContent(msg)
import unittest import config import credentials import classes.exceptions from classes.parser import Parser from classes.db_connection import DB_connection from classes.sql_requester import Sql_requester from classes.logger import * from classes.main import Main class TestMainCase(unittest.TestCase): def setUp(self): logger = Logger(HumanFormatStrategy) db_connection = DB_connection(credentials, classes.exceptions, logger) parser = Parser(config, classes.exceptions, logger) main = Main(classes.exceptions) main.set_db_connection(db_connection) main.set_parser(parser) main.set_logger(logger) main.set_Sql_requester(Sql_requester) self.main = main def test_content(self): self.main.run() self.assertNotEqual(len(self.main.content), 0) if __name__ == '__main__': unittest.main()
Офлайн