Так как функции являются объектами Python, то можно заменить значения ключей словаря функциями и возвращать их, если ключи совпадают. Другими словами, использовать функцию в качестве значения ключа словаря в Python.
Подробнее о паттерне проектирования "Factory".
Пример своеобразной фабрики функций:
# словарь с функциями calc = { "plus": lambda x, y: x + y, "minus": lambda x, y: x - y, "division": lambda x, y: x / y, # в качестве значения можно использовать # встроенную функцию pow() или любую # пользовательскую функцию "power": pow } def action(match, dictionary, default="NO CALC"): """шаблон фабрики функций""" if match in dictionary: return dictionary[match] return lambda *x: default >>> plus = action('plus', calc) >>> minus = action('minus', calc) >>> power = action('power', calc) >>> square = action('square', calc) >>> plus(5, 4) # 9 >>> minus(5, 4) # 1 >>> power(3, 3) # 27 >>> square(1, 1) 'NO CALC' >>> square(1) 'NO CALC'
Обратите внимание, что значениями ключей может быть обычная функция, определенная при помощи оператора def functuon(): ...
.
Если лень создавать шаблон Фабрики, то можно использовать словарь с функциями напрямую:
# словарь с функциями calc = { "plus": lambda x, y: x + y, "minus": lambda x, y: x - y, "division": lambda x, y: x / y, "power": pow } # функция, которая будет вызываться при ошибке def err(*args): return print(f"Ошибка calc{args}") >>> calc.get('plus', err)(5, 4) # 9 >>> calc.get('minus', err)(5, 4) # 1 >>> calc.get('division', err)(5, 4) # 3.0 >>> calc.get('pwr', err)(5, 4) # Ошибка calc(5, 4)
Представьте ситуацию, что есть некий код, который по условию if/elif/else
как-то обрабатывает данные, например, преобразует их в формат JSON или YAML или в список кортежей или делает что-то еще. Обратите внимание, что код обработки данных в каждом блоке if
или elif
может быть огромным. Как результат, он плохо читается и его трудно поддерживать, а также его нельзя использовать повторно. В таких ситуациях приходит на помощь фабрика функций.
Например есть такой код:
# test.py import json import xml.etree.ElementTree as et # например поступают данные в виде словаря book = [ {"book_id": "1", "title": "Книга-1", "autor": "Автор-1"}, {"book_id": "2", "title": "Книга-2", "autor": "Автор-2"}, ] # и они обрабатываются следующим образом def convertation(book, format): if format == 'JSON': # в каждом блоке может быть очень много кода for item in book: song_info = { 'id': item['book_id'], 'title': item['title'], 'autor': item['autor'] } print(json.dumps(song_info)) elif format == 'XML': for item in book: autor_info = et.Element('book', attrib={'id': item['book_id']}) title = et.SubElement(autor_info, 'title') title.text = item['title'] autor = et.SubElement(autor_info, 'autor') autor.text = item['autor'] print(et.tostring(autor_info, encoding='unicode')) else: raise ValueError(format) # запускаем код в интерактивном режиме # $ python3 -i test.py >>> convertation(book, 'XML')
Согласно паттерна проектирования "Фабрика", сложный ветвящийся код необходимо разбить на части. А именно, вынести каждый блок, который преобразовывает данные определенным образом в отдельную функцию, и вызывать их например из словаря где ключами будут форматы, а значениями соответствующие функции.
Смотрим как это можно сделать:
# test.py import json import xml.etree.ElementTree as et def to_json(book): """Преобразование в JSON""" for item in book: song_info = { 'id': item['book_id'], 'title': item['title'], 'autor': item['autor'] } print(json.dumps(song_info)) def to_xml(book): """Преобразование в XML""" for item in book: autor_info = et.Element('book', attrib={'id': item['book_id']}) title = et.SubElement(autor_info, 'title') title.text = item['title'] autor = et.SubElement(autor_info, 'autor') autor.text = item['autor'] print(et.tostring(autor_info, encoding='unicode')) def err(*args): """Сообщение об ошибке""" print('Формат не поддерживается.') # словарь-фабрики функций convertation = { 'JSON': to_json, 'XML': to_xml } # поступают данные в виде словаря book = [ {"book_id": "1", "title": "Книга-1", "autor": "Автор-1"}, {"book_id": "2", "title": "Книга-2", "autor": "Автор-2"}, ] # запускаем код в интерактивном режиме # $ python3 -i test.py # и работаем с фабрикой >>> convertation.get('JSON', err)(book) >>> convertation.get('XML', err)(book) >>> convertation.get('YAML', err)(book)
Теперь код легче читается, а функции, которые конвертируют данные можно использовать повторно. Также эти функции можно вынести в отдельный модуль и импортировать по необходимости и следовательно кода станет меньше и он станет проще для понимания.
Обратите внимание, что теперь добавление новых конверторов данных не составит труда. Стоит определить функцию и добавить ее в "Словарь-Фабрику" и все!
Но "словарь-фабрика" имеет свои недостатки, например, не видно какая произошла ошибка, чем она вызвана и т.д. Для нивелирования всех недостатков, связанных с применением словарей Python в качестве фабрик, лучше использовать функцию-интерфейс:
# test.py import json import xml.etree.ElementTree as et def to_json(book): """Преобразование в JSON""" for item in book: song_info = { 'id': item['book_id'], 'title': item['title'], 'autor': item['autor'] } print(json.dumps(song_info)) def to_xml(book): """Преобразование в XML""" for item in book: autor_info = et.Element('book', attrib={'id': item['book_id']}) title = et.SubElement(autor_info, 'title') title.text = item['title'] autor = et.SubElement(autor_info, 'autor') autor.text = item['autor'] print(et.tostring(autor_info, encoding='unicode')) def convertation(song, format): """Интерфейс - принимает данные `book` и возвращает строковое представление согласно `format` """ if format == 'JSON': return to_json(song) elif format == 'XML': return to_xml(song) else: raise ValueError(format) # поступают данные в виде словаря book = [ {"book_id": "1", "title": "Книга-1", "autor": "Автор-1"}, {"book_id": "2", "title": "Книга-2", "autor": "Автор-2"}, ] # запускаем код в интерактивном режиме # $ python3 -i test.py # и работаем с интерфейсом >>> convertation(book, 'JSON') >>> convertation(book, 'XML') >>> convertation(book, 'YAML')