Mastering Python Second Edition Release Code
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
Chapter 5, Decorators
|
||||
##############################################################################
|
||||
|
||||
| Enabling Code Reuse by Decorating explains not only how to create your own function/class decorators but also how internal decorators such as property, staticmethod and classmethod function.
|
||||
@@ -0,0 +1,39 @@
|
||||
>>> def decorator(function):
|
||||
... return function
|
||||
|
||||
>>> def add(a, b):
|
||||
... return a + b
|
||||
|
||||
>>> add = decorator(add)
|
||||
|
||||
|
||||
>>> @decorator
|
||||
... def add(a, b):
|
||||
... return a + b
|
||||
|
||||
|
||||
>>> import functools
|
||||
|
||||
>>> def decorator(function):
|
||||
... # This decorator makes sure we mimic the wrapped function
|
||||
... @functools.wraps(function)
|
||||
... def _decorator(a, b):
|
||||
... # Pass the modified arguments to the function
|
||||
... result = function(a, b + 5)
|
||||
...
|
||||
... # Logging the function call
|
||||
... name = function.__name__
|
||||
... print(f'{name}(a={a}, b={b}): {result}')
|
||||
...
|
||||
... # Return a modified result
|
||||
... return result + 4
|
||||
...
|
||||
... return _decorator
|
||||
|
||||
>>> @decorator
|
||||
... def func(a, b):
|
||||
... return a + b
|
||||
|
||||
>>> func(1, 2)
|
||||
func(a=1, b=2): 8
|
||||
12
|
||||
@@ -0,0 +1,82 @@
|
||||
>>> import functools
|
||||
|
||||
>>> def decorator(function):
|
||||
... @functools.wraps(function)
|
||||
... def _decorator(*args, **kwargs):
|
||||
... a, b = args
|
||||
... return function(a, b + 5)
|
||||
...
|
||||
... return _decorator
|
||||
|
||||
>>> @decorator
|
||||
... def func(a, b):
|
||||
... return a + b
|
||||
|
||||
>>> func(1, 2)
|
||||
8
|
||||
|
||||
>>> func(a=1, b=2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: not enough values to unpack (expected 2, got 0)
|
||||
|
||||
|
||||
>>> def add(a, b, /):
|
||||
... return a + b
|
||||
|
||||
>>> add(a=1, b=2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: add() got some positional-only arguments passed ...
|
||||
|
||||
|
||||
|
||||
>>> def add(*, a, b):
|
||||
... return a + b
|
||||
|
||||
>>> add(1, 2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: add() takes 0 positional arguments but 2 were given
|
||||
|
||||
|
||||
|
||||
>>> import inspect
|
||||
>>> import functools
|
||||
|
||||
>>> def decorator(function):
|
||||
... # Use the inspect module to get function signature. More
|
||||
... # about this in the logging chapter
|
||||
... signature = inspect.signature(function)
|
||||
...
|
||||
... @functools.wraps(function)
|
||||
... def _decorator(*args, **kwargs):
|
||||
... # Bind the arguments to the given *args and **kwargs.
|
||||
... # If you want to make arguments optional use
|
||||
... # signature.bind_partial instead.
|
||||
... bound = signature.bind(*args, **kwargs)
|
||||
...
|
||||
... # Apply the defaults so b is always filled
|
||||
... bound.apply_defaults()
|
||||
...
|
||||
... # Extract the filled arguments. If the amount of
|
||||
... # arguments is still expected to be fixed you can use
|
||||
... # tuple unpacking: `a, b = bound.arguments.values()`
|
||||
... a = bound.arguments['a']
|
||||
... b = bound.arguments['b']
|
||||
... return function(a, b + 5)
|
||||
...
|
||||
... return _decorator
|
||||
|
||||
>>> @decorator
|
||||
... def func(a, b=3):
|
||||
... return a + b
|
||||
|
||||
>>> func(1, 2)
|
||||
8
|
||||
|
||||
>>> func(a=1, b=2)
|
||||
8
|
||||
|
||||
>>> func(a=1)
|
||||
9
|
||||
@@ -0,0 +1,43 @@
|
||||
>>> def decorator(function):
|
||||
... def _decorator(*args, **kwargs):
|
||||
... return function(*args, **kwargs)
|
||||
... return _decorator
|
||||
|
||||
>>> @decorator
|
||||
... def add(a, b):
|
||||
... '''Add a and b'''
|
||||
... return a + b
|
||||
|
||||
>>> help(add)
|
||||
Help on function _decorator in module ...:
|
||||
<BLANKLINE>
|
||||
_decorator(*args, **kwargs)
|
||||
<BLANKLINE>
|
||||
|
||||
>>> add.__name__
|
||||
'_decorator'
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> import functools
|
||||
|
||||
>>> def decorator(function):
|
||||
... @functools.wraps(function)
|
||||
... def _decorator(*args, **kwargs):
|
||||
... return function(*args, **kwargs)
|
||||
... return _decorator
|
||||
|
||||
>>> @decorator
|
||||
... def add(a, b):
|
||||
... '''Add a and b'''
|
||||
... return a + b
|
||||
|
||||
>>> help(add)
|
||||
Help on function add in module ...:
|
||||
<BLANKLINE>
|
||||
add(a, b)
|
||||
Add a and b
|
||||
<BLANKLINE>
|
||||
|
||||
>>> add.__name__
|
||||
'add'
|
||||
@@ -0,0 +1,30 @@
|
||||
>>> import functools
|
||||
|
||||
>>> def track(function=None, label=None):
|
||||
... # Trick to add an optional argument to our decorator
|
||||
... if label and not function:
|
||||
... return functools.partial(track, label=label)
|
||||
...
|
||||
... print(f'initializing {label}')
|
||||
...
|
||||
... @functools.wraps(function)
|
||||
... def _track(*args, **kwargs):
|
||||
... print(f'calling {label}')
|
||||
... function(*args, **kwargs)
|
||||
... print(f'called {label}')
|
||||
...
|
||||
... return _track
|
||||
|
||||
>>> @track(label='outer')
|
||||
... @track(label='inner')
|
||||
... def func():
|
||||
... print('func')
|
||||
initializing inner
|
||||
initializing outer
|
||||
|
||||
>>> func()
|
||||
calling outer
|
||||
calling inner
|
||||
func
|
||||
called inner
|
||||
called outer
|
||||
@@ -0,0 +1,35 @@
|
||||
>>> import collections
|
||||
|
||||
|
||||
>>> class EventRegistry:
|
||||
... def __init__(self):
|
||||
... self.registry = collections.defaultdict(list)
|
||||
...
|
||||
... def on(self, *events):
|
||||
... def _on(function):
|
||||
... for event in events:
|
||||
... self.registry[event].append(function)
|
||||
... return function
|
||||
...
|
||||
... return _on
|
||||
...
|
||||
... def fire(self, event, *args, **kwargs):
|
||||
... for function in self.registry[event]:
|
||||
... function(*args, **kwargs)
|
||||
|
||||
>>> events = EventRegistry()
|
||||
|
||||
>>> @events.on('success', 'error')
|
||||
... def teardown(value):
|
||||
... print(f'Tearing down got: {value}')
|
||||
|
||||
>>> @events.on('success')
|
||||
... def success(value):
|
||||
... print(f'Successfully executed: {value}')
|
||||
|
||||
>>> events.fire('non-existing', 'nothing to see here')
|
||||
>>> events.fire('error', 'Oops, some error here')
|
||||
Tearing down got: Oops, some error here
|
||||
>>> events.fire('success', 'Everything is fine')
|
||||
Tearing down got: Everything is fine
|
||||
Successfully executed: Everything is fine
|
||||
@@ -0,0 +1,88 @@
|
||||
>>> import functools
|
||||
|
||||
>>> def memoize(function):
|
||||
... # Store the cache as attribute of the function so we can
|
||||
... # apply the decorator to multiple functions without
|
||||
... # sharing the cache.
|
||||
... function.cache = dict()
|
||||
...
|
||||
... @functools.wraps(function)
|
||||
... def _memoize(*args):
|
||||
... # If the cache is not available, call the function
|
||||
... # Note that all args need to be hashable
|
||||
... if args not in function.cache:
|
||||
... function.cache[args] = function(*args)
|
||||
... return function.cache[args]
|
||||
... return _memoize
|
||||
|
||||
>>> @memoize
|
||||
... def fibonacci(n):
|
||||
... if n < 2:
|
||||
... return n
|
||||
... else:
|
||||
... return fibonacci(n - 1) + fibonacci(n - 2)
|
||||
|
||||
>>> for i in range(1, 7):
|
||||
... print(f'fibonacci {i}: {fibonacci(i)}')
|
||||
fibonacci 1: 1
|
||||
fibonacci 2: 1
|
||||
fibonacci 3: 2
|
||||
fibonacci 4: 3
|
||||
fibonacci 5: 5
|
||||
fibonacci 6: 8
|
||||
|
||||
>>> fibonacci.__wrapped__.cache
|
||||
{(1,): 1, (0,): 0, (2,): 1, (3,): 2, (4,): 3, (5,): 5, (6,): 8}
|
||||
|
||||
# It breaks keyword arguments:
|
||||
|
||||
>>> fibonacci(n=2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ... got an unexpected keyword argument 'n'
|
||||
|
||||
# Unhashable types don't work as dict keys:
|
||||
|
||||
>>> fibonacci([123])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unhashable type: 'list'
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> import functools
|
||||
|
||||
# Create a simple call counting decorator
|
||||
|
||||
>>> def counter(function):
|
||||
... function.calls = 0
|
||||
... @functools.wraps(function)
|
||||
... def _counter(*args, **kwargs):
|
||||
... function.calls += 1
|
||||
... return function(*args, **kwargs)
|
||||
... return _counter
|
||||
|
||||
# Create a LRU cache with size 3
|
||||
|
||||
>>> @functools.lru_cache(maxsize=3)
|
||||
... @counter
|
||||
... def fibonacci(n):
|
||||
... if n < 2:
|
||||
... return n
|
||||
... else:
|
||||
... return fibonacci(n - 1) + fibonacci(n - 2)
|
||||
|
||||
>>> fibonacci(100)
|
||||
354224848179261915075
|
||||
|
||||
# The LRU cache offers some useful statistics
|
||||
|
||||
>>> fibonacci.cache_info()
|
||||
CacheInfo(hits=98, misses=101, maxsize=3, currsize=3)
|
||||
|
||||
# The result from our counter function which is now wrapped both by
|
||||
our counter and the cache
|
||||
|
||||
>>> fibonacci.__wrapped__.__wrapped__.calls
|
||||
101
|
||||
@@ -0,0 +1,36 @@
|
||||
>>> import functools
|
||||
|
||||
>>> def add(function=None, add_n=0):
|
||||
... # function is not callable so it's probably `add_n`
|
||||
... if not callable(function):
|
||||
... # Test to make sure we don't pass `None` as `add_n`
|
||||
... if function is not None:
|
||||
... add_n = function
|
||||
... return functools.partial(add, add_n=add_n)
|
||||
...
|
||||
... @functools.wraps(function)
|
||||
... def _add(n):
|
||||
... return function(n) + add_n
|
||||
...
|
||||
... return _add
|
||||
|
||||
>>> @add
|
||||
... def add_zero(n):
|
||||
... return n
|
||||
|
||||
>>> @add(1)
|
||||
... def add_one(n):
|
||||
... return n
|
||||
|
||||
>>> @add(add_n=2)
|
||||
... def add_two(n):
|
||||
... return n
|
||||
|
||||
>>> add_zero(5)
|
||||
5
|
||||
|
||||
>>> add_one(5)
|
||||
6
|
||||
|
||||
>>> add_two(5)
|
||||
7
|
||||
@@ -0,0 +1,25 @@
|
||||
>>> import functools
|
||||
|
||||
>>> class Debug(object):
|
||||
...
|
||||
... def __init__(self, function):
|
||||
... self.function = function
|
||||
... # functools.wraps for classes
|
||||
... functools.update_wrapper(self, function)
|
||||
...
|
||||
... def __call__(self, *args, **kwargs):
|
||||
... output = self.function(*args, **kwargs)
|
||||
... name = self.function.__name__
|
||||
... print(f'{name}({args!r}, {kwargs!r}): {output!r}')
|
||||
... return output
|
||||
|
||||
|
||||
>>> @Debug
|
||||
... def add(a, b=0):
|
||||
... return a + b
|
||||
...
|
||||
>>> output = add(3)
|
||||
add((3,), {}): 3
|
||||
|
||||
>>> output = add(a=4, b=2)
|
||||
add((), {'a': 4, 'b': 2}): 6
|
||||
@@ -0,0 +1,21 @@
|
||||
>>> import functools
|
||||
|
||||
|
||||
>>> def plus_one(function):
|
||||
... @functools.wraps(function)
|
||||
... def _plus_one(self, n, *args):
|
||||
... return function(self, n + 1, *args)
|
||||
... return _plus_one
|
||||
|
||||
|
||||
>>> class Adder(object):
|
||||
... @plus_one
|
||||
... def add(self, a, b=0):
|
||||
... return a + b
|
||||
|
||||
|
||||
>>> adder = Adder()
|
||||
>>> adder.add(0)
|
||||
1
|
||||
>>> adder.add(3, 4)
|
||||
8
|
||||
@@ -0,0 +1,146 @@
|
||||
>>> import pprint
|
||||
|
||||
|
||||
>>> class Spam(object):
|
||||
...
|
||||
... def some_instancemethod(self, *args, **kwargs):
|
||||
... pprint.pprint(locals(), width=60)
|
||||
...
|
||||
... @classmethod
|
||||
... def some_classmethod(cls, *args, **kwargs):
|
||||
... pprint.pprint(locals(), width=60)
|
||||
...
|
||||
... @staticmethod
|
||||
... def some_staticmethod(*args, **kwargs):
|
||||
... pprint.pprint(locals(), width=60)
|
||||
|
||||
# Create an instance so we can compare the difference between
|
||||
executions with and without instances easily
|
||||
|
||||
>>> spam = Spam()
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
# With an instance (note the lowercase spam)
|
||||
|
||||
>>> spam.some_instancemethod(1, 2, a=3, b=4)
|
||||
{'args': (1, 2),
|
||||
'kwargs': {'a': 3, 'b': 4},
|
||||
'self': <__main__.Spam object at ...>}
|
||||
|
||||
# Without an instance (note the capitalized Spam)
|
||||
|
||||
>>> Spam.some_instancemethod()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ...some_instancemethod() missing ... argument: 'self'
|
||||
|
||||
# But what if we add parameters? Be very careful with these!
|
||||
Our first argument is now used as an argument, this can give
|
||||
very strange and unexpected errors
|
||||
|
||||
>>> Spam.some_instancemethod(1, 2, a=3, b=4)
|
||||
{'args': (2,), 'kwargs': {'a': 3, 'b': 4}, 'self': 1}
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
# Classmethods are expectedly identical
|
||||
|
||||
>>> spam.some_classmethod(1, 2, a=3, b=4)
|
||||
{'args': (1, 2),
|
||||
'cls': <class '__main__.Spam'>,
|
||||
'kwargs': {'a': 3, 'b': 4}}
|
||||
|
||||
>>> Spam.some_classmethod()
|
||||
{'args': (), 'cls': <class '__main__.Spam'>, 'kwargs': {}}
|
||||
|
||||
>>> Spam.some_classmethod(1, 2, a=3, b=4)
|
||||
{'args': (1, 2),
|
||||
'cls': <class '__main__.Spam'>,
|
||||
'kwargs': {'a': 3, 'b': 4}}
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
# Staticmethods are also identical
|
||||
|
||||
>>> spam.some_staticmethod(1, 2, a=3, b=4)
|
||||
{'args': (1, 2), 'kwargs': {'a': 3, 'b': 4}}
|
||||
|
||||
>>> Spam.some_staticmethod()
|
||||
{'args': (), 'kwargs': {}}
|
||||
|
||||
>>> Spam.some_staticmethod(1, 2, a=3, b=4)
|
||||
{'args': (1, 2), 'kwargs': {'a': 3, 'b': 4}}
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> class Spam:
|
||||
...
|
||||
... def __init__(self, spam=1):
|
||||
... self.spam = spam
|
||||
...
|
||||
... def __get__(self, instance, cls):
|
||||
... return self.spam + instance.eggs
|
||||
...
|
||||
... def __set__(self, instance, value):
|
||||
... instance.eggs = value - self.spam
|
||||
|
||||
>>> class Sandwich:
|
||||
...
|
||||
... spam = Spam(5)
|
||||
...
|
||||
... def __init__(self, eggs):
|
||||
... self.eggs = eggs
|
||||
|
||||
>>> sandwich = Sandwich(1)
|
||||
>>> sandwich.eggs
|
||||
1
|
||||
>>> sandwich.spam
|
||||
6
|
||||
|
||||
>>> sandwich.eggs = 10
|
||||
>>> sandwich.spam
|
||||
15
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> import functools
|
||||
|
||||
>>> class ClassMethod(object):
|
||||
... def __init__(self, method):
|
||||
... self.method = method
|
||||
...
|
||||
... def __get__(self, instance, cls):
|
||||
... @functools.wraps(self.method)
|
||||
... def method(*args, **kwargs):
|
||||
... return self.method(cls, *args, **kwargs)
|
||||
... return method
|
||||
|
||||
>>> class StaticMethod(object):
|
||||
... def __init__(self, method):
|
||||
... self.method = method
|
||||
...
|
||||
... def __get__(self, instance, cls):
|
||||
... return self.method
|
||||
|
||||
>>> class Sandwich:
|
||||
... spam = 'class'
|
||||
...
|
||||
... def __init__(self, spam):
|
||||
... self.spam = spam
|
||||
...
|
||||
... @ClassMethod
|
||||
... def some_classmethod(cls, arg):
|
||||
... return cls.spam, arg
|
||||
...
|
||||
... @StaticMethod
|
||||
... def some_staticmethod(arg):
|
||||
... return Sandwich.spam, arg
|
||||
|
||||
>>> sandwich = Sandwich('instance')
|
||||
>>> sandwich.spam
|
||||
'instance'
|
||||
>>> sandwich.some_classmethod('argument')
|
||||
('class', 'argument')
|
||||
>>> sandwich.some_staticmethod('argument')
|
||||
('class', 'argument')
|
||||
@@ -0,0 +1,137 @@
|
||||
>>> import functools
|
||||
|
||||
>>> class Sandwich:
|
||||
... def get_eggs(self):
|
||||
... print('getting eggs')
|
||||
... return self._eggs
|
||||
...
|
||||
... def set_eggs(self, eggs):
|
||||
... print('setting eggs to %s' % eggs)
|
||||
... self._eggs = eggs
|
||||
...
|
||||
... def delete_eggs(self):
|
||||
... print('deleting eggs')
|
||||
... del self._eggs
|
||||
...
|
||||
... eggs = property(get_eggs, set_eggs, delete_eggs)
|
||||
...
|
||||
... @property
|
||||
... def spam(self):
|
||||
... print('getting spam')
|
||||
... return self._spam
|
||||
...
|
||||
... @spam.setter
|
||||
... def spam(self, spam):
|
||||
... print('setting spam to %s' % spam)
|
||||
... self._spam = spam
|
||||
...
|
||||
... @spam.deleter
|
||||
... def spam(self):
|
||||
... print('deleting spam')
|
||||
... del self._spam
|
||||
...
|
||||
... @functools.cached_property
|
||||
... def bacon(self):
|
||||
... print('getting bacon')
|
||||
... return 'bacon!'
|
||||
|
||||
>>> sandwich = Sandwich()
|
||||
>>> sandwich.eggs = 123
|
||||
setting eggs to 123
|
||||
>>> sandwich.eggs
|
||||
getting eggs
|
||||
123
|
||||
>>> del sandwich.eggs
|
||||
deleting eggs
|
||||
>>> sandwich.bacon
|
||||
getting bacon
|
||||
'bacon!'
|
||||
>>> sandwich.bacon
|
||||
'bacon!'
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> class Property(object):
|
||||
... def __init__(self, fget=None, fset=None, fdel=None):
|
||||
... self.fget = fget
|
||||
... self.fset = fset
|
||||
... self.fdel = fdel
|
||||
...
|
||||
... def __get__(self, instance, cls):
|
||||
... if instance is None:
|
||||
... # Redirect class (not instance) properties to self
|
||||
... return self
|
||||
... elif self.fget:
|
||||
... return self.fget(instance)
|
||||
...
|
||||
... def __set__(self, instance, value):
|
||||
... self.fset(instance, value)
|
||||
...
|
||||
... def __delete__(self, instance):
|
||||
... self.fdel(instance)
|
||||
...
|
||||
... def getter(self, fget):
|
||||
... return Property(fget, self.fset, self.fdel)
|
||||
...
|
||||
... def setter(self, fset):
|
||||
... return Property(self.fget, fset, self.fdel)
|
||||
...
|
||||
... def deleter(self, fdel):
|
||||
... return Property(self.fget, self.fset, fdel)
|
||||
|
||||
>>> class Sandwich:
|
||||
... @Property
|
||||
... def eggs(self):
|
||||
... return self._eggs
|
||||
...
|
||||
... @eggs.setter
|
||||
... def eggs(self, value):
|
||||
... self._eggs = value
|
||||
...
|
||||
... @eggs.deleter
|
||||
... def eggs(self):
|
||||
... del self._eggs
|
||||
|
||||
>>> sandwich = Sandwich()
|
||||
>>> sandwich.eggs = 5
|
||||
>>> sandwich.eggs
|
||||
5
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> class Sandwich(object):
|
||||
... def __init__(self):
|
||||
... self.registry = {}
|
||||
...
|
||||
... def __getattr__(self, key):
|
||||
... print('Getting %r' % key)
|
||||
... return self.registry.get(key, 'Undefined')
|
||||
...
|
||||
... def __setattr__(self, key, value):
|
||||
... if key == 'registry':
|
||||
... object.__setattr__(self, key, value)
|
||||
... else:
|
||||
... print('Setting %r to %r' % (key, value))
|
||||
... self.registry[key] = value
|
||||
...
|
||||
... def __delattr__(self, key):
|
||||
... print('Deleting %r' % key)
|
||||
... del self.registry[key]
|
||||
|
||||
|
||||
>>> sandwich = Sandwich()
|
||||
|
||||
>>> sandwich.a
|
||||
Getting 'a'
|
||||
'Undefined'
|
||||
|
||||
>>> sandwich.a = 1
|
||||
Setting 'a' to 1
|
||||
|
||||
>>> sandwich.a
|
||||
Getting 'a'
|
||||
1
|
||||
|
||||
>>> del sandwich.a
|
||||
Deleting 'a'
|
||||
@@ -0,0 +1,26 @@
|
||||
>>> import functools
|
||||
|
||||
>>> def singleton(cls):
|
||||
... instances = dict()
|
||||
... @functools.wraps(cls)
|
||||
... def _singleton(*args, **kwargs):
|
||||
... if cls not in instances:
|
||||
... instances[cls] = cls(*args, **kwargs)
|
||||
... return instances[cls]
|
||||
... return _singleton
|
||||
|
||||
>>> @singleton
|
||||
... class SomeSingleton(object):
|
||||
... def __init__(self):
|
||||
... print('Executing init')
|
||||
|
||||
>>> a = SomeSingleton()
|
||||
Executing init
|
||||
>>> b = SomeSingleton()
|
||||
|
||||
>>> a is b
|
||||
True
|
||||
|
||||
>>> a.x = 123
|
||||
>>> b.x
|
||||
123
|
||||
@@ -0,0 +1,93 @@
|
||||
>>> import functools
|
||||
|
||||
>>> class Value(object):
|
||||
... def __init__(self, value):
|
||||
... self.value = value
|
||||
...
|
||||
... def __repr__(self):
|
||||
... return f'<{self.__class__.__name__} {self.value}>'
|
||||
|
||||
|
||||
>>> class Spam(Value):
|
||||
... def __gt__(self, other):
|
||||
... return self.value > other.value
|
||||
...
|
||||
... def __ge__(self, other):
|
||||
... return self.value >= other.value
|
||||
...
|
||||
... def __lt__(self, other):
|
||||
... return self.value < other.value
|
||||
...
|
||||
... def __le__(self, other):
|
||||
... return self.value <= other.value
|
||||
...
|
||||
... def __eq__(self, other):
|
||||
... return self.value == other.value
|
||||
|
||||
>>> @functools.total_ordering
|
||||
... class Egg(Value):
|
||||
... def __lt__(self, other):
|
||||
... return self.value < other.value
|
||||
...
|
||||
... def __eq__(self, other):
|
||||
... return self.value == other.value
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
>>> numbers = [4, 2, 3, 4]
|
||||
>>> spams = [Spam(n) for n in numbers]
|
||||
>>> eggs = [Egg(n) for n in numbers]
|
||||
|
||||
>>> spams
|
||||
[<Spam 4>, <Spam 2>, <Spam 3>, <Spam 4>]
|
||||
|
||||
>>> eggs
|
||||
[<Egg 4>, <Egg 2>, <Egg 3>, <Egg 4>]
|
||||
|
||||
>>> sorted(spams)
|
||||
[<Spam 2>, <Spam 3>, <Spam 4>, <Spam 4>]
|
||||
|
||||
>>> sorted(eggs)
|
||||
[<Egg 2>, <Egg 3>, <Egg 4>, <Egg 4>]
|
||||
|
||||
# Sorting using key is of course still possible and in this case
|
||||
perhaps just as easy:
|
||||
|
||||
>>> values = [Value(n) for n in numbers]
|
||||
>>> values
|
||||
[<Value 4>, <Value 2>, <Value 3>, <Value 4>]
|
||||
|
||||
>>> sorted(values, key=lambda v: v.value)
|
||||
[<Value 2>, <Value 3>, <Value 4>, <Value 4>]
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> def sort_by_attribute(attr, keyfunc=getattr):
|
||||
... def _sort_by_attribute(cls):
|
||||
... def __lt__(self, other):
|
||||
... return getattr(self, attr) < getattr(other, attr)
|
||||
...
|
||||
... def __eq__(self, other):
|
||||
... return getattr(self, attr) <= getattr(other, attr)
|
||||
...
|
||||
... cls.__lt__ = __lt__
|
||||
... cls.__eq__ = __eq__
|
||||
...
|
||||
... return functools.total_ordering(cls)
|
||||
... return _sort_by_attribute
|
||||
|
||||
>>> class Value(object):
|
||||
... def __init__(self, value):
|
||||
... self.value = value
|
||||
...
|
||||
... def __repr__(self):
|
||||
... return f'<{self.__class__.__name__} {self.value}>'
|
||||
|
||||
>>> @sort_by_attribute('value')
|
||||
... class Spam(Value):
|
||||
... pass
|
||||
|
||||
>>> numbers = [4, 2, 3, 4]
|
||||
>>> spams = [Spam(n) for n in numbers]
|
||||
>>> sorted(spams)
|
||||
[<Spam 2>, <Spam 3>, <Spam 4>, <Spam 4>]
|
||||
@@ -0,0 +1,89 @@
|
||||
>>> import functools
|
||||
|
||||
>>> @functools.singledispatch
|
||||
... def show_type(argument):
|
||||
... print(f'argument: {argument}')
|
||||
|
||||
>>> @show_type.register(int)
|
||||
... def show_int(argument):
|
||||
... print(f'int argument: {argument}')
|
||||
|
||||
>>> @show_type.register
|
||||
... def show_float(argument: float):
|
||||
... print(f'float argument: {argument}')
|
||||
|
||||
>>> show_type('abc')
|
||||
argument: abc
|
||||
|
||||
>>> show_type(123)
|
||||
int argument: 123
|
||||
|
||||
>>> show_type(1.23)
|
||||
float argument: 1.23
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
>>> import functools
|
||||
|
||||
>>> registry = dict()
|
||||
|
||||
>>> def register(function):
|
||||
... # Fetch the first type from the type annotation but be
|
||||
... # careful not to overwrite the `type` function
|
||||
... type_ = next(iter(function.__annotations__.values()))
|
||||
... # Emulate the Python 3.10+ inspect.get_annotations()
|
||||
... if isinstance(type_, str):
|
||||
... type_ = eval(type_)
|
||||
... registry[type_] = function
|
||||
...
|
||||
... @functools.wraps(function)
|
||||
... def _register(argument):
|
||||
... # Fetch the function using the type of argument, and
|
||||
... # fall back to the main function
|
||||
... new_function = registry.get(type(argument), function)
|
||||
... return new_function(argument)
|
||||
...
|
||||
... return _register
|
||||
|
||||
>>> @register
|
||||
... def show_type(argument: any):
|
||||
... print(f'argument: {argument}')
|
||||
|
||||
>>> @register
|
||||
... def show_int(argument: int):
|
||||
... print(f'int argument: {argument}')
|
||||
|
||||
>>> show_type('abc')
|
||||
argument: abc
|
||||
|
||||
>>> show_type(123)
|
||||
int argument: 123
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
>>> import json
|
||||
>>> import functools
|
||||
|
||||
|
||||
>>> @functools.singledispatch
|
||||
... def write_as_json(file, data):
|
||||
... json.dump(data, file)
|
||||
|
||||
|
||||
>>> @write_as_json.register(str)
|
||||
... @write_as_json.register(bytes)
|
||||
... def write_as_json_filename(file, data):
|
||||
... with open(file, 'w') as fh:
|
||||
... write_as_json(fh, data)
|
||||
|
||||
|
||||
>>> data = dict(a=1, b=2, c=3)
|
||||
>>> write_as_json('test1.json', data)
|
||||
>>> write_as_json(b'test2.json', 'w')
|
||||
>>> with open('test3.json', 'w') as fh:
|
||||
... write_as_json(fh, data)
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
>>> write_as_json.registry.keys() == set((bytes, object, str))
|
||||
True
|
||||
@@ -0,0 +1,55 @@
|
||||
>>> class Open:
|
||||
... def __init__(self, filename, mode):
|
||||
... self.filename = filename
|
||||
... self.mode = mode
|
||||
...
|
||||
... def __enter__(self):
|
||||
... self.handle = open(self.filename, self.mode)
|
||||
... return self.handle
|
||||
...
|
||||
... def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
... self.handle.close()
|
||||
|
||||
|
||||
>>> with Open('test.txt', 'w') as fh:
|
||||
... print('Our test is complete!', file=fh)
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
>>> import contextlib
|
||||
|
||||
|
||||
>>> @contextlib.contextmanager
|
||||
... def open_context_manager(filename, mode='r'):
|
||||
... fh = open(filename, mode)
|
||||
... yield fh
|
||||
... fh.close()
|
||||
|
||||
|
||||
>>> with open_context_manager('test.txt', 'w') as fh:
|
||||
... print('Our test is complete!', file=fh)
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> import contextlib
|
||||
|
||||
>>> with contextlib.closing(open('test.txt', 'a')) as fh:
|
||||
... print('Yet another test', file=fh)
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> @contextlib.contextmanager
|
||||
... def debug(name):
|
||||
... print(f'Debugging {name}:')
|
||||
... yield
|
||||
... print(f'Finished debugging {name}')
|
||||
|
||||
|
||||
>>> @debug('spam')
|
||||
... def spam():
|
||||
... print('This is the inside of our spam function')
|
||||
|
||||
>>> spam()
|
||||
Debugging spam:
|
||||
This is the inside of our spam function
|
||||
Finished debugging spam
|
||||
@@ -0,0 +1,40 @@
|
||||
>>> def sandwich(bacon: float, eggs: int):
|
||||
... pass
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
>>> import inspect
|
||||
>>> import functools
|
||||
|
||||
>>> def enforce_type_hints(function):
|
||||
... # Construct the signature from the function which contains
|
||||
... # the type annotations
|
||||
... signature = inspect.signature(function)
|
||||
...
|
||||
... @functools.wraps(function)
|
||||
... def _enforce_type_hints(*args, **kwargs):
|
||||
... # Bind the arguments and apply the default values
|
||||
... bound = signature.bind(*args, **kwargs)
|
||||
... bound.apply_defaults()
|
||||
...
|
||||
... for key, value in bound.arguments.items():
|
||||
... param = signature.parameters[key]
|
||||
... # The annotation should be a callable
|
||||
... # type/function so we can cast as validation
|
||||
... if param.annotation:
|
||||
... bound.arguments[key] = param.annotation(value)
|
||||
...
|
||||
... return function(*bound.args, **bound.kwargs)
|
||||
...
|
||||
... return _enforce_type_hints
|
||||
|
||||
>>> @enforce_type_hints
|
||||
... def sandwich(bacon: float, eggs: int):
|
||||
... print(f'bacon: {bacon!r}, eggs: {eggs!r}')
|
||||
|
||||
>>> sandwich(1, 2)
|
||||
bacon: 1.0, eggs: 2
|
||||
>>> sandwich(3, 'abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: invalid literal for int() with base 10: 'abc'
|
||||
@@ -0,0 +1,38 @@
|
||||
>>> import warnings
|
||||
>>> import functools
|
||||
|
||||
>>> def ignore_warning(warning, count=None):
|
||||
... def _ignore_warning(function):
|
||||
... @functools.wraps(function)
|
||||
... def __ignore_warning(*args, **kwargs):
|
||||
... # Execute the code while catching all warnings
|
||||
... with warnings.catch_warnings(record=True) as ws:
|
||||
... # Catch all warnings of the given type
|
||||
... warnings.simplefilter('always', warning)
|
||||
... # Execute the function
|
||||
... result = function(*args, **kwargs)
|
||||
...
|
||||
... # Re-warn all warnings beyond the expected count
|
||||
... if count is not None:
|
||||
... for w in ws[count:]:
|
||||
... warnings.warn(w.message)
|
||||
...
|
||||
... return result
|
||||
... return __ignore_warning
|
||||
... return _ignore_warning
|
||||
|
||||
>>> @ignore_warning(DeprecationWarning, count=1)
|
||||
... def spam():
|
||||
... warnings.warn('deprecation 1', DeprecationWarning)
|
||||
... warnings.warn('deprecation 2', DeprecationWarning)
|
||||
|
||||
|
||||
# Note, we use catch_warnings here because doctests normally
|
||||
capture the warnings quietly
|
||||
|
||||
>>> with warnings.catch_warnings(record=True) as ws:
|
||||
... spam()
|
||||
...
|
||||
... for i, w in enumerate(ws):
|
||||
... print(w.message)
|
||||
deprecation 2
|
||||
Reference in New Issue
Block a user