Так как функции являются объектами 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')