<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Piotr Staszowski, Author at Be on the Right Side of Change</title>
	<atom:link href="https://blog.finxter.com/author/piotrstaszowski/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.finxter.com/author/piotrstaszowski/</link>
	<description></description>
	<lastBuildDate>Sun, 23 Jan 2022 11:22:18 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://blog.finxter.com/wp-content/uploads/2020/08/cropped-cropped-finxter_nobackground-32x32.png</url>
	<title>Piotr Staszowski, Author at Be on the Right Side of Change</title>
	<link>https://blog.finxter.com/author/piotrstaszowski/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>3 Top Design Patterns in Python: Singletons, Decorators, and Iterators</title>
		<link>https://blog.finxter.com/3-top-design-patterns-in-python-singletons-decorators-and-iterators/</link>
		
		<dc:creator><![CDATA[Piotr Staszowski]]></dc:creator>
		<pubDate>Sun, 23 Jan 2022 11:21:25 +0000</pubDate>
				<category><![CDATA[Computer Science]]></category>
		<category><![CDATA[Object Orientation]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=151218</guid>

					<description><![CDATA[<p>Ok, I must admit at the beginning that this topic is a bit of a clickbait &#8211; but if you find it cheating, I have to write in my defense that it was in good faith. If you were starting to write a book, it wouldn&#8217;t cross your mind to ask &#8220;what are the top ... <a title="3 Top Design Patterns in Python: Singletons, Decorators, and Iterators" class="read-more" href="https://blog.finxter.com/3-top-design-patterns-in-python-singletons-decorators-and-iterators/" aria-label="Read more about 3 Top Design Patterns in Python: Singletons, Decorators, and Iterators">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/3-top-design-patterns-in-python-singletons-decorators-and-iterators/">3 Top Design Patterns in Python: Singletons, Decorators, and Iterators</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Ok, I must admit at the beginning that this topic is a bit of a clickbait &#8211; but if you find it cheating, I have to write in my defense that it was in good faith.</p>



<p>If you were starting to write a book, it wouldn&#8217;t cross your mind to ask &#8220;what are the <strong>top plot elements</strong> that I should learn to be able to create an interesting story?&#8221; because you need as much context and life experience as you can assemble.</p>



<h2 class="wp-block-heading">Gangs of Four</h2>



<p>The book <em>&#8220;Design Patterns: Elements of Reusable Object-Oriented Software&#8221;</em> (by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides), thanks to which design patterns gained popularity in computer science, isn&#8217;t about telling you the best ways to do things. </p>



<p>It&#8217;s about teaching your brain to pick up patterns that can be applied to existing code &#8212; to give you the <strong>highest leverage as a developer</strong>. </p>



<p>It&#8217;s a huge toolbox of tools and some of them are used more often than others, but the fact that a tool is frequently used does not mean that you should use it for all your work. </p>



<p>Instead, you should learn as many patterns as possible &#8211; to be able to choose the right one when you notice the possibility of its use.</p>



<p>The book <em><strong>Gangs of Four</strong></em> (that&#8217;s what it is called in the industry) is primarily about patterns for <a rel="noreferrer noopener" href="https://blog.finxter.com/why-should-software-engineers-learn-java/" data-type="post" data-id="30207" target="_blank">Java</a>, and to a lesser extent for C++, but here we are writing in a different language, <a href="https://blog.finxter.com/python-crash-course/" data-type="post" data-id="3951" target="_blank" rel="noreferrer noopener">Python</a>, so in this short article I chose a few design patterns from each category (according to the originally proposed classification) that I found interesting in the context of Python programming. </p>



<p>I sincerely hope that it will inspire you to learn more about this issue on your own, and who knows, maybe there will be more similar articles on the <a href="https://blog.finxter.com/blog" data-type="URL" data-id="https://blog.finxter.com/blog" target="_blank" rel="noreferrer noopener">Finxter website</a> in the future.</p>



<h2 class="wp-block-heading"><a></a>What’s a Software Design Pattern?</h2>



<p>In software design a design pattern is a <strong><em>general, reusable solution to a commonly occurring problem within a given context</em></strong>. </p>



<p>They are like pre-made blueprints that you can customize to solve a problem in your code.</p>



<p>It is not possible to apply a design pattern just like you would use a function from a newly imported <a href="https://blog.finxter.com/the-complete-python-library-guide/" data-type="post" data-id="3414" target="_blank" rel="noreferrer noopener">library</a> (the pattern is not a code snippet, but a general concept that describes how to solve a specific recurring problem). </p>



<p>Instead, you should follow the pattern details and implement a solution that suits the requirements of your program.</p>



<h2 class="wp-block-heading">Classification of Design Patterns</h2>



<p>Initially, there were two basic classifications of design patterns &#8211; based on what problem the pattern solves, and based on whether the pattern concerns classes or objects. Taking into account the first classification, the patterns can be divided into three groups:</p>



<ol class="wp-block-list"><li><strong>Creational</strong> &#8211; provide the capability to create, initialize and configure <a href="https://blog.finxter.com/object-oriented-programming-terminology-cheat-sheet/" data-type="post" data-id="2129" target="_blank" rel="noreferrer noopener">objects</a>, <a href="https://blog.finxter.com/an-introduction-to-python-classes-inheritance-encapsulation-and-polymorphism/" data-type="post" data-id="30977" target="_blank" rel="noreferrer noopener">classes</a>, and data types based on a required criterion and in a controlled way.</li><li><strong>Structural </strong>&#8211; help to organize structures of related objects and classes, providing new functionalities.</li><li><strong>Behavioral </strong>&#8211; are about identifying common communication patterns between objects.</li></ol>



<p>Later, new design patterns appeared, from which another category can be distinguished:</p>



<ol class="wp-block-list"><li><strong>Concurrency </strong>&#8211; those types of design patterns that deal with the multi-threaded programming paradigm.</li></ol>



<h2 class="wp-block-heading">Pattern 1: Singleton</h2>



<p>The Singleton is a creational pattern the purpose of which is to limit the possibility of creating objects of a given class to one instance and to ensure global access to the created object.</p>



<h3 class="wp-block-heading"><a></a>Use Cases</h3>



<ul class="wp-block-list"><li>A class in your program has only a single instance available to all clients such as a single database object shared by different parts of the program.</li><li>You need stricter control over <a href="https://blog.finxter.com/python-globals/" data-type="post" data-id="20680" target="_blank" rel="noreferrer noopener">global</a> variables.</li></ul>



<h3 class="wp-block-heading"><a></a>Code examples</h3>



<p><strong>First naive approach</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class Logger:
   @staticmethod
   def get_instance():
       if '_instance' not in Logger.__dict__:
           Logger._instance = Logger()
       return Logger._instance

   def write_log(self, path):
       pass


if __name__ == "__main__":
   s1 = Logger.get_instance()
   s2 = Logger.get_instance()
   assert s1 is s2
</pre>



<p>What is wrong with this code? </p>



<p>It violates the <a rel="noreferrer noopener" href="https://blog.finxter.com/how-to-create-a-singleton-in-python/" data-type="post" data-id="12803" target="_blank">single responsibility principle</a> and has non-standard class access (you must remember to access instances of the class only by the <code>get_instance()</code> method) &#8211; we try to fix these problems in another code example.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class Singleton:
   _instances = {}

   def __new__(cls, *args, **kwargs):
       if cls not in cls._instances:
           instance = super().__new__(cls)
           cls._instances[cls] = instance
       return cls._instances[cls]


class Logger(Singleton):
   def write_log(self, path):
       pass


if __name__ == "__main__":
   logger1 = Logger()
   logger2 = Logger()
   assert logger1 is logger2
</pre>



<p>So the problems from the previous example have been addressed, but can we take a better approach (without inheritance)? </p>



<p>Let&#8217;s try.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class Singleton(type):
   _instances = {}

   def __call__(cls, *args, **kwargs):
       if cls not in cls._instances:
           instance = super().__call__(*args, **kwargs)
           cls._instances[cls] = instance
       return cls._instances[cls]


class Logger(metaclass=Singleton):
   def write_log(self, path):
       pass


if __name__ == "__main__":
   logger1 = Logger()
   logger2 = Logger()
   assert logger1 is logger2
</pre>



<p>Great, it works, but we should make one more adjustment &#8211; prepare our program to run in a <a href="https://blog.finxter.com/how-to-get-a-thread-id-in-python/" data-type="post" data-id="134202" target="_blank" rel="noreferrer noopener">multi-threaded</a> environment.<br></p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from threading import Lock, Thread


class Singleton(type):
   _instances = {}
   _lock: Lock = Lock()

   def __call__(cls, *args, **kwargs):
       with cls._lock:
           if cls not in cls._instances:
               instance = super().__call__(*args, **kwargs)
               cls._instances[cls] = instance
       return cls._instances[cls]


class Logger(metaclass=Singleton):
   def __init__(self, name):
       self.name = name

   def write_log(self, path):
       pass


def test_logger(name):
   logger = Logger(name)
   print(logger.name)


if __name__ == "__main__":
   process1 = Thread(target=test_logger, args=("FOO",))
   process2 = Thread(target=test_logger, args=("BAR",))
   process1.start()
   process2.start()
</pre>



<p><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">FOO
FOO</pre>



<p>Both processes called <a href="https://blog.finxter.com/python-__new__-magic-method/" data-type="post" data-id="114351" target="_blank" rel="noreferrer noopener">constructors</a> with two different parameters, but only one instance of the <code>Logger</code> class was created &#8211; our hard work is finally over!</p>



<h3 class="wp-block-heading"><a></a>Consequences</h3>



<ul class="wp-block-list"><li>You know that a class has only a single instance;</li><li>You gain a global access point to that instance;</li><li>The singleton is initialized only when requested for the first time;</li><li>Masks bad design to a certain point. For example, when the components of the program know too much about each other. Consequently, many consider it as an <strong><em>anti-pattern</em></strong>.</li></ul>



<h3 class="wp-block-heading"><a></a>Sources</h3>



<ul class="wp-block-list"><li><em>Dive Into Design Patterns</em> by Alexander Shvets</li><li><em>Python Design Patterns Playbook</em> by Gerald Britton (from Pluralsight)</li></ul>



<h2 class="wp-block-heading">Pattern 2: Decorator</h2>



<p>The <a href="https://blog.finxter.com/an-introduction-to-closures-and-decorators-in-python/" data-type="post" data-id="33290" target="_blank" rel="noreferrer noopener">Decorator</a> is a structural pattern the purpose of which is to provide new functionalities to classes/objects at runtime (unlike <a href="https://blog.finxter.com/understanding-inheritance-types-in-python/" data-type="post" data-id="31321" target="_blank" rel="noreferrer noopener">inheritance</a>, which allows you to achieve a similar effect, but at the compilation time). </p>



<p>The decorator is most often an <a rel="noreferrer noopener" href="https://blog.finxter.com/data-abstraction-in-python-simply-explained/" data-type="post" data-id="32045" target="_blank">abstract class</a> that takes an object in the constructor, the functionality of which we want to extend &#8212; but in Python, there is also a built-in decorator mechanism that we can use.</p>



<h3 class="wp-block-heading"><a></a>Use Cases</h3>



<ul class="wp-block-list"><li>You want to assign additional responsibilities to objects at runtime without breaking the code using these objects;</li><li>You cannot extend an object&#8217;s responsibilities through <a rel="noreferrer noopener" href="https://blog.finxter.com/inheritance-in-python-harry-potter-example/" data-type="post" data-id="2179" target="_blank">inheritance</a> for some reason.</li></ul>



<h3 class="wp-block-heading"><a></a>Code examples</h3>



<p>Using <a href="https://blog.finxter.com/closures-and-decorators-in-python/" data-type="post" data-id="11909" target="_blank" rel="noreferrer noopener">decorators</a>, you can wrap objects multiple times because both the target and the decorators implement the same interface. </p>



<p>The resulting object will have the combined and stacked functionality of all wrappers.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from abc import ABC, abstractmethod


class Component(ABC):
   @abstractmethod
   def operation(self):
       pass


class ConcreteComponent(Component):
   def operation(self):
       return "ConcreteComponent"


class Decorator(Component):
   def __init__(self, component):
       self.component = component

   @abstractmethod
   def operation(self):
       pass


class ConcreteDecoratorA(Decorator):
   def operation(self):
       return f"ConcreteDecoratorA({self.component.operation()})"


class ConcreteDecoratorB(Decorator):
   def operation(self):
       return f"ConcreteDecoratorB({self.component.operation()})"


if __name__ == "__main__":
   concreteComponent = ConcreteComponent()
   print(concreteComponent.operation())
   decoratorA = ConcreteDecoratorA(concreteComponent)
   decoratorB = ConcreteDecoratorB(decoratorA)
   print(decoratorB.operation())
</pre>



<p><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ConcreteComponent
ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))</pre>



<p>And a slightly more practical example using the built-in decorator mechanism.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import sys


def memoize(f):
   cache = dict()

   def wrapper(x):
       if x not in cache:
           cache[x] = f(x)
       return cache[x]

   return wrapper


@memoize
def fib(n):
   if n &lt;= 1:
       return n
   else:
       return fib(n - 1) + fib(n - 2)


if __name__ == "__main__":
   sys.setrecursionlimit(2000)
   print(fib(750))
</pre>



<p>Output:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">2461757021582324272166248155313036893697139996697461509576233211000055607912198979704988704446425834042795269603588522245550271050495783935904220352228801000</pre>



<p>Without using the cache decorator for the function (that recursively computes the n-th term of the <a href="https://blog.finxter.com/daily-python-puzzle-fibonacci-series-2/" data-type="post" data-id="219" target="_blank" rel="noreferrer noopener">Fibonacci series</a>), we probably would not have computed a result for value 100 in our lifetime.</p>



<h3 class="wp-block-heading"><a></a>Consequences</h3>



<ul class="wp-block-list"><li>Extend the behavior of an object without creating a <a rel="noreferrer noopener" href="https://blog.finxter.com/python-__init_subclass__-magic-method/" data-type="post" data-id="128830" target="_blank">subclass</a>;</li><li>Add or remove object responsibilities at runtime;</li><li>Combine multiple behaviors by applying multiple decorators to an object;</li><li>Divide a <a rel="noreferrer noopener" href="https://blog.finxter.com/the-unix-philosophy/" data-type="post" data-id="19704" target="_blank">monolithic</a> class that implements many variants of behavior into smaller classes;</li><li>It&#8217;s difficult to take one particular wrapper from the center of the wrappers stack;</li><li>It&#8217;s difficult to implement a decorator in such a way that its behavior does not depend on the order in which the wrapper is stacked.</li></ul>



<h3 class="wp-block-heading"><a></a>Sources</h3>



<ul class="wp-block-list"><li><em>Dive Into Design Patterns</em> by Alexander Shvets</li><li><em>Python. Kurs video.</em> Wzorce czynnościowe i architektoniczne oraz antywzorce by  Karol Kurek</li></ul>



<h2 class="wp-block-heading">Pattern 3: Iterator</h2>



<p><a href="https://blog.finxter.com/iterators-iterables-and-itertools/" data-type="post" data-id="29507" target="_blank" rel="noreferrer noopener">Iterator</a> is a behavioral pattern the purpose of which is to allow you to traverse elements of a collection without exposing its underlying representation. </p>



<p>To implement your iterator in Python, we have two possible options:</p>



<ul class="wp-block-list"><li>Implement the <a rel="noreferrer noopener" href="https://blog.finxter.com/python-__iter__-magic-method/" data-type="post" data-id="81103" target="_blank"><code>__iter__</code></a> and <code><a rel="noreferrer noopener" href="https://blog.finxter.com/python-__next__-magic-method/" data-type="post" data-id="76098" target="_blank">__next__</a></code> special methods in the class.</li><li>Use <a rel="noreferrer noopener" href="https://blog.finxter.com/understanding-generators-in-python/" data-type="post" data-id="33873" target="_blank">generators</a>.</li></ul>



<h3 class="wp-block-heading"><a></a>Use Cases</h3>



<ul class="wp-block-list"><li>The collection has a complicated structure and you want to hide it from the client for convenience or security reasons;</li><li>You want to reduce duplication of the traversal code across your app;</li><li>You want your code to be able to traverse elements of different data structures or when you don&#8217;t know the details of their structure in advance.</li></ul>



<h3 class="wp-block-heading">Code Examples</h3>



<p>In the example below, we will see how we can create a custom collection with an alphabetical order iterator.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from collections.abc import Iterator, Iterable


class AlphabeticalOrderIterator(Iterator):
   _position: int = None
   _reverse: bool = False

   def __init__(self, collection, reverse=False):
       self._collection = sorted(collection)
       self._reverse = reverse
       self._position = -1 if reverse else 0

   def __next__(self):
       try:
           value = self._collection[self._position]
           self._position += -1 if self._reverse else 1
       except IndexError:
           raise StopIteration()
       return value


class WordsCollection(Iterable):
   def __init__(self, collection):
       self._collection = collection

   def __iter__(self):
       return AlphabeticalOrderIterator(self._collection)

   def get_reverse_iterator(self):
       return AlphabeticalOrderIterator(self._collection, True)


if __name__ == "__main__":
   wordsCollection = WordsCollection(["Third", "First", "Second"])
   print(list(wordsCollection))
   print(list(wordsCollection.get_reverse_iterator()))
</pre>



<p><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">['First', 'Second', 'Third']
['Third', 'Second', 'First']</pre>



<p>The next example is for a <a href="https://blog.finxter.com/how-to-iterate-over-a-generator-twice/" data-type="post" data-id="22617" target="_blank" rel="noreferrer noopener">generator</a>, which is a special kind of function that can be paused and resumed from where it was paused. </p>



<p>Based on the stored state, it is possible to return different values during subsequent calls of the generator.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def prime_generator():
   yield 2
   primes = [2]
   to_check = 3
   while True:
       sqrt = to_check ** 0.5
       is_prime = True
       for prime in primes:
           if prime > sqrt:
               break
           if to_check % prime == 0:
               is_prime = False
               break
       if is_prime:
           primes.append(to_check)
           yield to_check
       to_check += 2


generator = prime_generator()
print([next(generator) for _ in range(20)])
</pre>



<p>Output:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]</pre>



<h3 class="wp-block-heading"><a></a>Consequences</h3>



<ul class="wp-block-list"><li>You can clean up the client code and collections by extracting the traversal code into separate classes;</li><li>You can implement new collection types and iterators and pass them into existing code without breaking anything;</li><li>You can iterate the same collection with multiple iterators in parallel because each of them stores information about its iteration state;</li><li>For this reason, you can delay the iteration and continue it as needed;</li><li>The use of this pattern will be overkill if your application only works with simple collections;</li><li>Using an iterator may be less efficient than traversing directly through the items of some specialized collection.</li></ul>



<h3 class="wp-block-heading"><a></a>Sources</h3>



<ul class="wp-block-list"><li><em>Dive Into Design Patterns</em> by Alexander Shvets</li><li><em>Python. Kurs video. Kreacyjne i strukturalne wzorce projektowe</em> by  Karol Kurek</li></ul>



<h2 class="wp-block-heading"><a></a>Conclusion</h2>



<p>The bottom line is even if you never encounter problems that are solved by the design patterns mentioned in the article, knowing patterns is still useful because it teaches you to solve problems using principles of <a href="https://blog.finxter.com/top-python-oop-cheat-sheets/" data-type="post" data-id="20973" target="_blank" rel="noreferrer noopener">object-oriented design</a>.</p>



<hr class="wp-block-separator"/>



<p>The post <a href="https://blog.finxter.com/3-top-design-patterns-in-python-singletons-decorators-and-iterators/">3 Top Design Patterns in Python: Singletons, Decorators, and Iterators</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Minified using Disk

Served from: blog.finxter.com @ 2026-06-04 23:02:59 by W3 Total Cache
-->