Using the Make_call_* Constructors

To use the library constructors, add this import line:

import dectools

Use a constructor as decorator on your function. Your function must have the correct function signature: the first argument must be “function” and the second and third arguments are probably “args” and “kwargs”. Put any additional arguments after these required arguments.

TODO: Make these arguments optional, because only @make_call_instead really needs them.

For example:

@make_call_before
def my_new_call_before_decorator(function, args, kwargs, some_other_argument):
     # do stuff
     ....

@my_new_call_before_decorator("foo")  # or (some_other_argument = "foo")
def my_newly_decorated_function(other_args):
    pass

We will call “my_new_call_before_decorator” the decorator and “my_newly_decorated_function” the decorated function.

Quick Reference

Construtor Required Parameters Used For
@make_call_once function registering with frameworks, global resource creation
@make_call_before funciton, args, kwargs requiring login, lazy instantiation
@make_call_if function, args, kwargs checking priviledges, skipping if overloaded
@make_call_after function, args, kwargs converting return values to exceptions, redrawing the screen
@make_call_instead function, args, kwargs everything else. logs, caches, locking, resource handling.

@make_call_once

Requires: (function, ...)

Call the decorator once when the decorator is applied, but does not affect the decorated function.

Used for registering the function with frameworks, global resource creation, or enforcing proprietary licensing restrictions.

@make_call_before

Requires: (function, args, kwargs, ...)

Call the decorator before the decorated function, then call the decorated function. If the decorator throws an exception, then the decorated function is not called.

Used for requiring users to be logged in first, lazy instantiation of resources, and setting preconditions.

@make_call_after

Requires: (function, args, kwargs, ...)

Call decorator after the decorated function. The decorator’s return value is returned.

Used for converting errors from return values into exceptions, double checking that resources were released, and redrawing of the screen.

@make_call_if

Requires: (function, args, kwargs, ...)

Call decorator first. If decorator returns a True value, then call the decorated function.

Used for checking authorization, skipping decluttering during peak times, and enforcing some proprietary licensing.

@make_call_instead

Requires: (function, args, kwargs, ...)

Call the decorator. Do not call the decorated function. The decorator will usually call the function with the statement return_value = function(*args, **kwargs).

Used for everything, including logging, caching, acquiring and releasing locks, acquiring and releasing other resources, comparing results between an optimized algorithm and a slow but reliable one, and more.

How the Constructors Really Work

TODO: Make the constructors really work this way instead of with a couple extra levels of functino calls. The concept is about right.

This follows through one example to show how it works. Walking through it might demystify decorators, might make you think I am an idiot, and might put you to sleep.

The example

@make_call_instead   # Step 1
def notice_me(function, args, kwargs, message = "I see you"):
    print message + ": " + function.__name__
    return function(args, kwargs)

@notice_me("Watching you")  # Step 2, Parts A and B.
def hello(name):
    print "Hello", name

hello("Charles")  # Step 3.

The example (unwrapped)

This is the same example, except we do some extra assignements to temporary or differently-named variables. It also moves the @ operation to after the def statement, which is where it actually occurs.

def notice_me(function, args, kwargs, message = "I see you"):
    print message + ": " + function.__name__
    return function(args, kwargs)
widget = make_call_instead(notice_me)  # Step 1
notice_me = widget


def hello(name):
    print "Hello", name
gizmo = widget("Watching you")  # Step 2 Part A
hello_out = gizmo(hello)        # Step 2 Part B
hello = hello_out

hello("Charles")                # Step 3

The explanation

Step 1

The constructor, @make_call_instead, is a concrete decorator because it takes one function as input. The function passed in, e.g., notice_me():

  • has “function, args, and kwargs” as first three arguments. In the current version, this is asserted. In future versions, it might not be required.
  • has zero or more additional arguments. The additional arguments may or may not have default values.

The return value, named widget here, has some properties:

  • It has a signature of only the additional parameters. For this case, it has a signature of taking only the message argument.
  • It has the __name__ metadata of notice_me().
  • It has decorator data describing that “the function signature was changed” and mentions “make_call_instead()” as the culprit.
  • It can be used in step 2.

Step 2, Part A

Step 2 is divided into two parts, because two separate operations occur in the line @notice_me(“Watching you”). notice_me(“Watching you”) resolves into an intermediate value and the ‘@’ operation is then applied.

For Part A., gizmo = widget(“Watching you”) takes an argument.

  • The argument corresponds to the additional arguments after “function, args, kwargs”.
  • The signature of widget is “def widget(message):” and widget(“watching you”) is equivalent to widget(message = "watching you"). Your IDE will help get the arguments correct.
  • Widget is a complex decorator, taking arguments and yielding a decorator.
  • dectools makes complex decorators out of functions. “Function” is used interchangably with “method”.
  • The output, gizmo(), is a concrete decorator for use in Step 2, Part B. gizmo() takes one argument, a callable function, and returns a callable function with the additional functionality of notice_me(). This makes gizmo() a concrete decorator.
  • When you use the decorator again, like @notice_me(“Still watching”), or, equivalently, gizmo_2 = widget(“Still_watching”) both gizmo and gizmo_2 will have the same __code__ attribute but different __closure__ attributes. If that made no sense, don’t worry. There will not be a quiz. Just recognize that you will use notice_me with different input parameters.
  • the output, gizmo, is used in Step 2, Part B.

Step 2, Part B

This line, “hello_out = gizmo(hello)” decorates hello.

  • gizmo() takes exactly one argument, hello, and returns the modified function. For the example, we call it hello_out for clarity. Right after this line, hello_out is assigned to hello.
  • hello_out has the same signature and __name__ as hello. These are not related to the signature or name of notice_me or gizmo. That is, using the decorator does not change the signature: one of the magic points of dectools.
  • hello is expected to be a function. The library might support hello being a class in the future if I can make a case for it being helpful. It works and is helpful for @make_call_once().
  • hello_out does get additional metadata to record that it was decorated.

Step 3

When the line hello(“charles”) is called these things happen:

  1. hello(“charles”) is called. When it was decorated, the actual code for hello was changed to call a dectools function. Let’s call this function “call_through()” for this example.

  2. “call_through()” then calls notice_me() with the original hello function and additonal decorator parameters. In this case, it’s called:

    notice_me(function= hello, args = ("charles"), kwargs = {}, message = "Watching you")
    
  3. notice_me() then prints “Watching you: hello”, then calls hello. I wonder if anyone reads this documentation? Email me if you do.

  4. The return value of hello() is returned to notice_me() which is returned to call_through() which is returned to the main program, where it is ignored.