Online Book Reader

Home Category

Learning Python - Mark Lutz [555]

By Root 1652 0
undecorated function directly. Assuming this is a debugging tool only, you can use this flag to optimize your program for production use:

C:\misc> C:\python30\python –O devtools_test.py

False

Bob Smith is 45 years old

birthday = 5/31/1963

110000

231000

Generalizing for Keywords and Defaults, Too

The prior version illustrates the basics we need to employ, but it’s fairly limited—it supports validating arguments passed by position only, and it does not validate keyword arguments (in fact, it assumes that no keywords are passed in a way that makes argument position numbers incorrect). Additionally, it does nothing about arguments with defaults that may be omitted in a given call. That’s fine if all your arguments are passed by position and never defaulted, but less than ideal in a general tool. Python supports much more flexible argument-passing modes, which we’re not yet addressing.

The mutation of our example shown next does better. By matching the wrapped function’s expected arguments against the actual arguments passed in a call, it supports range validations for arguments passed by either position or keyword name, and it skips testing for default arguments omitted in the call. In short, arguments to be validated are specified by keyword arguments to the decorator, which later steps through both the *pargs positionals tuple and the **kargs keywords dictionary to validate.

"""

File devtools.py: function decorator that performs range-test

validation for passed arguments. Arguments are specified by

keyword to the decorator. In the actual call, arguments may

be passed by position or keyword, and defaults may be omitted.

See devtools_test.py for example use cases.

"""

trace = True

def rangetest(**argchecks): # Validate ranges for both+defaults

def onDecorator(func): # onCall remembers func and argchecks

if not __debug__: # True if "python –O main.py args..."

return func # Wrap if debugging; else use original

else:

import sys

code = func.__code__

allargs = code.co_varnames[:code.co_argcount]

funcname = func.__name__

def onCall(*pargs, **kargs):

# All pargs match first N expected args by position

# The rest must be in kargs or be omitted defaults

positionals = list(allargs)

positionals = positionals[:len(pargs)]

for (argname, (low, high)) in argchecks.items():

# For all args to be checked

if argname in kargs:

# Was passed by name

if kargs[argname] < low or kargs[argname] > high:

errmsg = '{0} argument "{1}" not in {2}..{3}'

errmsg = errmsg.format(funcname, argname, low, high)

raise TypeError(errmsg)

elif argname in positionals:

# Was passed by position

position = positionals.index(argname)

if pargs[position] < low or pargs[position] > high:

errmsg = '{0} argument "{1}" not in {2}..{3}'

errmsg = errmsg.format(funcname, argname, low, high)

raise TypeError(errmsg)

else:

# Assume not passed: default

if trace:

print('Argument "{0}" defaulted'.format(argname))

return func(*pargs, **kargs) # OK: run original call

return onCall

return onDecorator

The following test script shows how the decorator is used—arguments to be validated are given by keyword decorator arguments, and at actual calls we can pass by name or position and omit arguments with defaults even if they are to be validated otherwise:

# File devtools_test.py

# Comment lines raise TypeError unless "python –O" used on shell command line

from devtools import rangetest

# Test functions, positional and keyword

@rangetest(age=(0, 120)) # persinfo = rangetest(...)(persinfo)

def persinfo(name, age):

print('%s is %s years old' % (name, age))

@rangetest(M=(1, 12), D=(1, 31), Y=(0, 2009))

def birthday(M, D, Y):

print('birthday = {0}/{1}/{2}'.format(M, D, Y))

persinfo('Bob', 40)

persinfo(age=40, name='Bob')

birthday(5, D=1, Y=1963)

#persinfo('Bob', 150)

#persinfo(age=150, name='Bob')

#birthday(5, D=40, Y=1963)

# Test methods, positional and keyword

class Person:

def __init__(self, name, job, pay):

self.job = job

self.pay = pay

# giveRaise = rangetest(...)(giveRaise)

@rangetest(percent=(0.0,

Return Main Page Previous Page Next Page

®Online Book Reader