[Jan-2015] As part of a recent project, I extended an arguments type-checking function+method decorator that appears in Chapter 39 of Learning Python, 5th Edition, to also perform return type testing, and use 3.X function attributes instead of decorator arguments. That makes it 3.X only, but it's simple to translate this code to use 2.X-compatible decorator arguments instead, as shown in the book.
Fetch the decorator module here:
See the decorator module's self-test code for usage examples, and the book for the logic underlying the decorator itself (its origins go back to this post from 2008/2009).
As another use case—and an example of bytes and bit-level processing—I used this decorator to verify that type annotations coded in simple file obfuscation functions were being respected. Here's a partial excerpt from this (not yet published) client:
"""
[Scheme 2 of 4]
>>> data = do_encode2(b'spam', ord('x'))
120 b'spam'
bytearray(b'\x00\xeb\x00\xe8\x00\xd9\x00\xe5')
>>> data = do_decode2(bytes(data), ord('x')) # need bytes() for decorator only!
120 bytearray(b'\x00\xeb\x00\xe8\x00\xd9\x00\xe5')
bytearray(b'spam')
"""
from debugtypes import debugtypes
@debugtypes
def do_encode2(data: bytes, adder: int) -> bytearray:
trace(adder, data[:4])
newdata = bytearray()
for byte in data: # bytes => int (or via bytes[i])
word = byte + adder # add scrambler
byte1 = (word & 0xFF00) >> 8 # upper byte
byte2 = (word & 0X00FF) # lower byte
newdata.extend([byte1, byte2]) # 2 bytes for 1, to binary file
trace(newdata[:8]) # int => bytes (or bytes([int]))
return newdata
@debugtypes
def do_decode2(data: bytes, adder: int) -> bytearray:
trace(adder, data[:8])
newdata = bytearray()
ix = 0
while ix < len(data):
byte1, byte2 = data[ix], data[ix+1] # bytes => int
word = (byte1 << 8) + byte2 # 1 word to 2 bytes
word -= adder # remove scrambler
newdata.append(word) # retain low byte
ix += 2
trace(newdata[:4])
return newdata
...
from encoder import *
encode, decode = do_encode2, do_decode2 # choose your weapon
data = open(filename, 'rb').read() # load from original name
data = encode(data, adder) # scramble byte data
newname = filename + encext # write to new enc name
with open(newname, 'wb') as newfile: # guarantee closes
newfile.write(data)
This decorator might be useful during development to ensure coding-time expectations, but has some major downsides:
Update Apr-2015: A similar model is being proposed as standard type declarations for Python 3.5; it threatens to escalate the same caveats to best practice. For an arguably better use case for decorators, see the Private/Public attributes class decorator example from LP5E.