patternpythonMinor
20+ functions for generating different waveforms
Viewed 0 times
waveformsdifferentgeneratingforfunctions
Problem
I added a guard clause to the functions in
Each function has a unique core and a unique docstring, then some common guards and preconditioning around it:
So I thought I'd factor out the common code to make it more DRY. However I can't find a really elegant way to do it:
The complete file is here. Removed parts to make it less lengthy, while still showing some docstrings and functions with different numbers of arguments:
```
"""The suite of window functions."""
from __future__ import division, print_function, absolute_import
import warnings
import numpy as np
from scipy import fftpack, linalg, special
from scipy._lib.six import string_types
__all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall',
'blackmanharris', 'flattop', 'bartlett', 'hanning', 'barthann',
'hamming', 'kaiser', 'gaussian', 'general_gaussian', 'chebwin',
'slepian', 'cosine', 'hann', 'exponential', 'tukey', 'get_window']
...
def triang(M, sym=True):
"""Return a triangu
scipy.signal.windows, but the way they are currently written means the same 11 lines are now repeated in every function.Each function has a unique core and a unique docstring, then some common guards and preconditioning around it:
if int(M) != M or M < 0:
raise ValueError('Window length M must be a non-negative integer')
if M == 0:
return np.array([])
if M == 1:
return np.ones(1, 'd')
odd = M % 2
if not sym and not odd:
M = M + 1
...
if not sym and not odd:
w = w[:-1]
return wSo I thought I'd factor out the common code to make it more DRY. However I can't find a really elegant way to do it:
- Moving the redundant code into helper functions doesn't work because the early exits don't work nested inside another function. Adding more code to catch the early exits kind of defeats the purpose.
- Wrapping them in a decorator changes the function signature from
blackman(M, sym=True)toblackman(M, *args), since some window functions have different numbers of arguments than others.
- Splitting the unique guts of each function out into their own
_corefunctions would work, but seems a little mangled, with the actual code being separated from the docstrings.
The complete file is here. Removed parts to make it less lengthy, while still showing some docstrings and functions with different numbers of arguments:
```
"""The suite of window functions."""
from __future__ import division, print_function, absolute_import
import warnings
import numpy as np
from scipy import fftpack, linalg, special
from scipy._lib.six import string_types
__all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall',
'blackmanharris', 'flattop', 'bartlett', 'hanning', 'barthann',
'hamming', 'kaiser', 'gaussian', 'general_gaussian', 'chebwin',
'slepian', 'cosine', 'hann', 'exponential', 'tukey', 'get_window']
...
def triang(M, sym=True):
"""Return a triangu
Solution
I do believe that the decorator way is the proper one. There are some tradeoff using it but you can easily overcome your main concern using
The wrapper within the decorator should be aware of both
A first approach could be:
I just modified the boilerplate code to group the two
But this poses the issue of
or
Which, when called naturally using
So I'd go for this second version and keep the current signature of the functions for now, but if you can use Python 3 instead, it will be made much more explicit. I would, however, remove the
But then, there is the case of
So you can
But most importantly:
And the whole module could become:
```
"""The suite of window functions."""
from __future__ import division, print_function, absolute_import
import warnings
from functools import wraps
import numpy as np
from scipy import fftpack, linalg, special
from scipy._lib.six import string_types
__all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall',
'blackmanharris', 'flattop', 'bartlett', 'hanning', 'barthann',
'hamming', 'kaiser', 'gaussian', 'general_gaussian', 'chebwin',
'slepian', 'cosine', 'hann', 'exponential', 'tukey', 'get_window']
...
def argument_checker(func):
@wraps(func)
def wrapper(M, **kwargs):
if int(M) != M or M >> from scipy import signal
>>> from scipy.fftpack import fft, fftshift
>>> import matplotlib.pyplot as plt
>>> window = signal.triang(51)
>>> plt.plot(window)
>>> plt.title("Triangular window")
>>> plt.ylabel("Amplitude")
>>> plt.xlabel("Sample")
>>> plt.figure()
>>> A = fft(window, 2048) / (len(window)/2.0)
>>> freq = np.linspace(-0.5, 0.5, len(A))
>>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max())))
>>> plt.plot(freq, response)
>>> plt.axis([-0.5, 0.5, -120, 0])
>>> plt.title("Frequency response of the triangular window")
>>> plt.ylabel("Normalized magnitude [dB]")
>>> plt.xlabel("Normalized frequency [cycles per sample]")
"""
n = np.arange(1, (M + 1) // 2 + 1)
if M % 2 == 0:
w = (2 * n - 1.0) / M
w = np.r_[w, w[::-1]]
else:
w = 2 * n / (M + 1.0)
functools.wraps: it will reuse the name, docstring and signature of the decorated function for the wrapper.The wrapper within the decorator should be aware of both
M and sym; this is where things can get tricky depending on how you want things to behave.A first approach could be:
from functools import wraps
def argument_checker(func):
@wraps(func)
def wrapper(M, *args, sym=True, **kwargs):
if int(M) != M or M < 0:
raise ValueError('Window length M must be a non-negative integer')
if M == 0:
return np.array([])
if M == 1:
return np.ones(1, 'd')
if not sym and not M % 2:
return func(M + 1, *args, **kwargs)[:-1]
else:
return func(M, *args, **kwargs)
return wrapperI just modified the boilerplate code to group the two
if not sym and not odd into a single if as there is func that abstract the window building. However this solution is Python 3 only as the signature of wrapper will raise a SyntaxError in Python 2, and as regard to your imports from __future__, I'm assuming Python 2 here. So you might want:def argument_checker(func):
@wraps(func)
def wrapper(M, *args, **kwargs):
sym = kwargs.pop('sym', True)
...But this poses the issue of
sym needing to be a keyword argument, and not a positional one. Again, this can be made more explicit using Python 3 syntax for keyword-only arguments:def triang(M, *, sym=True):
...or
def tukey(M, alpha=0.5, *, sym=True):
...Which, when called naturally using
triang(150, False), for instance, will raise TypeError: triang() takes 1 positional argument but 2 were given.So I'd go for this second version and keep the current signature of the functions for now, but if you can use Python 3 instead, it will be made much more explicit. I would, however, remove the
*args parameter from the wrapper to make it accept only keyword arguments.But then, there is the case of
tukey that add an other layer of check into the checks. To handle that properly, it is easier to split the decorator into two, more meaningful, tasks:from functools import wraps
def argument_checker(func):
@wraps(func)
def wrapper(M, **kwargs):
if int(M) != M or M < 0:
raise ValueError('Window length M must be a non-negative integer')
if M == 0:
return np.array([])
if M == 1:
return np.ones(1, 'd')
return func(M, **kwargs)
return wrapper
def parity_handler(func):
@wraps(func)
def wrapper(M, **kwargs):
sym = kwargs.get('sym', True)
if not sym and not M % 2:
return func(M + 1, **kwargs)[:-1]
else:
return func(M, **kwargs)
return wrapperSo you can
@argument_checker
@parity_handler
def triang(M, sym=True):
...But most importantly:
def alpha_handler(func):
@wraps(func)
def wrapper(M, **kwargs):
alpha = kwargs.get('alpha', 0.5)
if alpha = 1.0:
return hann(M, sym=kwargs.get('sym', True))
return func(M, **kwargs)
return wrapper
@argument_checker
@alpha_handler
@parity_handler
def tukey(...And the whole module could become:
```
"""The suite of window functions."""
from __future__ import division, print_function, absolute_import
import warnings
from functools import wraps
import numpy as np
from scipy import fftpack, linalg, special
from scipy._lib.six import string_types
__all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall',
'blackmanharris', 'flattop', 'bartlett', 'hanning', 'barthann',
'hamming', 'kaiser', 'gaussian', 'general_gaussian', 'chebwin',
'slepian', 'cosine', 'hann', 'exponential', 'tukey', 'get_window']
...
def argument_checker(func):
@wraps(func)
def wrapper(M, **kwargs):
if int(M) != M or M >> from scipy import signal
>>> from scipy.fftpack import fft, fftshift
>>> import matplotlib.pyplot as plt
>>> window = signal.triang(51)
>>> plt.plot(window)
>>> plt.title("Triangular window")
>>> plt.ylabel("Amplitude")
>>> plt.xlabel("Sample")
>>> plt.figure()
>>> A = fft(window, 2048) / (len(window)/2.0)
>>> freq = np.linspace(-0.5, 0.5, len(A))
>>> response = 20 * np.log10(np.abs(fftshift(A / abs(A).max())))
>>> plt.plot(freq, response)
>>> plt.axis([-0.5, 0.5, -120, 0])
>>> plt.title("Frequency response of the triangular window")
>>> plt.ylabel("Normalized magnitude [dB]")
>>> plt.xlabel("Normalized frequency [cycles per sample]")
"""
n = np.arange(1, (M + 1) // 2 + 1)
if M % 2 == 0:
w = (2 * n - 1.0) / M
w = np.r_[w, w[::-1]]
else:
w = 2 * n / (M + 1.0)
Code Snippets
from functools import wraps
def argument_checker(func):
@wraps(func)
def wrapper(M, *args, sym=True, **kwargs):
if int(M) != M or M < 0:
raise ValueError('Window length M must be a non-negative integer')
if M == 0:
return np.array([])
if M == 1:
return np.ones(1, 'd')
if not sym and not M % 2:
return func(M + 1, *args, **kwargs)[:-1]
else:
return func(M, *args, **kwargs)
return wrapperdef argument_checker(func):
@wraps(func)
def wrapper(M, *args, **kwargs):
sym = kwargs.pop('sym', True)
...def triang(M, *, sym=True):
...def tukey(M, alpha=0.5, *, sym=True):
...from functools import wraps
def argument_checker(func):
@wraps(func)
def wrapper(M, **kwargs):
if int(M) != M or M < 0:
raise ValueError('Window length M must be a non-negative integer')
if M == 0:
return np.array([])
if M == 1:
return np.ones(1, 'd')
return func(M, **kwargs)
return wrapper
def parity_handler(func):
@wraps(func)
def wrapper(M, **kwargs):
sym = kwargs.get('sym', True)
if not sym and not M % 2:
return func(M + 1, **kwargs)[:-1]
else:
return func(M, **kwargs)
return wrapperContext
StackExchange Code Review Q#135910, answer score: 6
Revisions (0)
No revisions yet.