Python デザインパターン サンプルコード Adapter
Mark Summerfield『実践 Python 3』デザインパターンのサンプルコード
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」
(2023-11-16)Python3.11で動作確認済み
◆◆Adapter パターンとは◆◆Renderer HtmlWriter (__subclasshook__) (header, title, start_body, end_body, footer) ↑ TextRenderer HtmlRenderer (header, paragraph, footer) (header, paragraph, footer) ↑ ↑ Page (add_paragraph, render)継承関係はありません。↑は参照関係です。
Plain Text ========== This is a very short plain-text paragraph that demonstrates the simple TextRenderer class. This is another short paragraph just so that we can see two paragraphs in action. <!doctype html> <html> <head><title>HTML</title></head> <body> <p>This is a very short HTML paragraph that demonstrates the simple HtmlRenderer class.</p> <p>This is another short paragraph just so that we can see two paragraphs in action.</p> </body> </html> Expected object of type Renderer, got HtmlWriter
This is a very short HTML paragraph that demonstrates the simple HtmlRenderer class.
This is another short paragraph just so that we can see two paragraphs in action.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
#!/usr/bin/env python3 # Copyright c 2012-13 Qtrac Ltd. All rights reserved. # This program or module is free software: you can redistribute it # and/or modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. It is provided for # educational purposes and is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. import abc import collections import sys import textwrap if sys.version_info[:2] < (3, 2): from xml.sax.saxutils import escape else: from html import escape # Thanks to Nick Coghlan for these! if sys.version_info[:2] >= (3, 3): class Renderer(metaclass=abc.ABCMeta): @classmethod def __subclasshook__(Class, Subclass): if Class is Renderer: attributes = collections.ChainMap(*(Superclass.__dict__ for Superclass in Subclass.__mro__)) methods = ("header", "paragraph", "footer") if all(method in attributes for method in methods): return True return NotImplemented else: class Renderer(metaclass=abc.ABCMeta): @classmethod def __subclasshook__(Class, Subclass): if Class is Renderer: needed = {"header", "paragraph", "footer"} for Superclass in Subclass.__mro__: for meth in needed.copy(): if meth in Superclass.__dict__: needed.discard(meth) if not needed: return True return NotImplemented MESSAGE = """This is a very short {} paragraph that demonstrates the simple {} class.""" def main(): paragraph1 = MESSAGE.format("plain-text", "TextRenderer") paragraph2 = """This is another short paragraph just so that we can see two paragraphs in action.""" title = "Plain Text" textPage = Page(title, TextRenderer(22)) textPage.add_paragraph(paragraph1) textPage.add_paragraph(paragraph2) textPage.render() print() paragraph1 = MESSAGE.format("HTML", "HtmlRenderer") title = "HTML" file = sys.stdout htmlPage = Page(title, HtmlRenderer(HtmlWriter(file))) htmlPage.add_paragraph(paragraph1) htmlPage.add_paragraph(paragraph2) htmlPage.render() try: page = Page(title, HtmlWriter()) page.render() print("ERROR! rendering with an invalid renderer") except TypeError as err: print(err) class Page: def __init__(self, title, renderer): if not isinstance(renderer, Renderer): raise TypeError("Expected object of type Renderer, got {}". format(type(renderer).__name__)) self.title = title self.renderer = renderer self.paragraphs = [] def add_paragraph(self, paragraph): self.paragraphs.append(paragraph) def render(self): self.renderer.header(self.title) for paragraph in self.paragraphs: self.renderer.paragraph(paragraph) self.renderer.footer() class TextRenderer: def __init__(self, width=80, file=sys.stdout): self.width = width self.file = file self.previous = False def header(self, title): self.file.write("{0:^{2}}\n{1:^{2}}\n".format(title, "=" * len(title), self.width)) def paragraph(self, text): if self.previous: self.file.write("\n") self.file.write(textwrap.fill(text, self.width)) self.file.write("\n") self.previous = True def footer(self): pass class HtmlWriter: def __init__(self, file=sys.stdout): self.file = file def header(self): self.file.write("<!doctype html>\n<html>\n") def title(self, title): self.file.write("<head><title>{}</title></head>\n".format( escape(title))) def start_body(self): self.file.write("<body>\n") def body(self, text): self.file.write("<p>{}</p>\n".format(escape(text))) def end_body(self): self.file.write("</body>\n") def footer(self): self.file.write("</html>\n") class HtmlRenderer: def __init__(self, htmlWriter): self.htmlWriter = htmlWriter def header(self, title): self.htmlWriter.header() self.htmlWriter.title(title) self.htmlWriter.start_body() def paragraph(self, text): self.htmlWriter.body(text) def footer(self): self.htmlWriter.end_body() self.htmlWriter.footer() if __name__ == "__main__": main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
#!/usr/bin/env python3 # Copyright c 2012-13 Qtrac Ltd. All rights reserved. # This program or module is free software: you can redistribute it # and/or modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. It is provided for # educational purposes and is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. import abc import sys import textwrap if sys.version_info[:2] < (3, 2): from xml.sax.saxutils import escape else: from html import escape import Qtrac @Qtrac.has_methods("header", "paragraph", "footer") class Renderer(metaclass=abc.ABCMeta): pass MESSAGE = """This is a very short {} paragraph that demonstrates the simple {} class.""" def main(): paragraph1 = MESSAGE.format("plain-text", "TextRenderer") paragraph2 = """This is another short paragraph just so that we can see two paragraphs in action.""" title = "Plain Text" textPage = Page(title, TextRenderer(22)) textPage.add_paragraph(paragraph1) textPage.add_paragraph(paragraph2) textPage.render() print() paragraph1 = MESSAGE.format("HTML", "HtmlRenderer") title = "HTML" file = sys.stdout htmlPage = Page(title, HtmlRenderer(HtmlWriter(file))) htmlPage.add_paragraph(paragraph1) htmlPage.add_paragraph(paragraph2) htmlPage.render() try: page = Page(title, HtmlWriter()) page.render() print("ERROR! rendering with an invalid renderer") except TypeError as err: print(err) class Page: def __init__(self, title, renderer): if not isinstance(renderer, Renderer): raise TypeError("Expected object of type Renderer, got {}". format(type(renderer).__name__)) self.title = title self.renderer = renderer self.paragraphs = [] def add_paragraph(self, paragraph): self.paragraphs.append(paragraph) def render(self): self.renderer.header(self.title) for paragraph in self.paragraphs: self.renderer.paragraph(paragraph) self.renderer.footer() class TextRenderer: def __init__(self, width=80, file=sys.stdout): self.width = width self.file = file self.previous = False def header(self, title): self.file.write("{0:^{2}}\n{1:^{2}}\n".format(title, "=" * len(title), self.width)) def paragraph(self, text): if self.previous: self.file.write("\n") self.file.write(textwrap.fill(text, self.width)) self.file.write("\n") self.previous = True def footer(self): pass class HtmlWriter: def __init__(self, file=sys.stdout): self.file = file def header(self): self.file.write("<!doctype html>\n<html>\n") def title(self, title): self.file.write("<head><title>{}</title></head>\n".format( escape(title))) def start_body(self): self.file.write("<body>\n") def body(self, text): self.file.write("<p>{}</p>\n".format(escape(text))) def end_body(self): self.file.write("</body>\n") def footer(self): self.file.write("</html>\n") class HtmlRenderer: def __init__(self, htmlWriter): self.htmlWriter = htmlWriter def header(self, title): self.htmlWriter.header() self.htmlWriter.title(title) self.htmlWriter.start_body() def paragraph(self, text): self.htmlWriter.body(text) def footer(self): self.htmlWriter.end_body() self.htmlWriter.footer() if __name__ == "__main__": main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
#!/usr/bin/env python3 # Copyright c 2012-13 Qtrac Ltd. All rights reserved. # This program or module is free software: you can redistribute it # and/or modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. It is provided for # educational purposes and is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. import abc import collections import errno import functools import os import sys def coroutine(function): @functools.wraps(function) def wrapper(*args, **kwargs): generator = function(*args, **kwargs) next(generator) return generator return wrapper if sys.version_info[:2] < (3, 3): def remove_if_exists(filename): try: os.remove(filename) except OSError as err: if err.errno != errno.ENOENT: raise else: def remove_if_exists(filename): try: os.remove(filename) except FileNotFoundError: pass # All other exceptions are passed to the caller # Thanks to Nick Coghlan for these! if sys.version_info[:2] >= (3, 3): def has_methods(*methods): def decorator(Base): def __subclasshook__(Class, Subclass): if Class is Base: attributes = collections.ChainMap(*(Superclass.__dict__ for Superclass in Subclass.__mro__)) if all(method in attributes for method in methods): return True return NotImplemented Base.__subclasshook__ = classmethod(__subclasshook__) return Base return decorator else: def has_methods(*methods): def decorator(Base): def __subclasshook__(Class, Subclass): if Class is Base: needed = set(methods) for Superclass in Subclass.__mro__: for meth in needed.copy(): if meth in Superclass.__dict__: needed.discard(meth) if not needed: return True return NotImplemented Base.__subclasshook__ = classmethod(__subclasshook__) return Base return decorator # Thanks to Nick Coghlan for this! class Requirer(metaclass=abc.ABCMeta): # Since we have rules for adding new expected attributes, we *do* # perform the check for subclasses @classmethod def __subclasshook__(Class, Subclass): methods = set() for Superclass in Subclass.__mro__: if hasattr(Superclass, "required_methods"): methods |= set(Superclass.required_methods) attributes = collections.ChainMap(*(Superclass.__dict__ for Superclass in Class.__mro__)) if all(method in attributes for method in methods): return True return NotImplemented def report(message="", error=False): if len(message) >= 70 and not error: message = message[:67] + "..." sys.stdout.write("\r{:70}{}".format(message, "\n" if error else "")) sys.stdout.flush()