Third Example. Creating advices.

As we have seen before, it is needed to pass a function to the set_moment (before, after…) methods. Those methods define advices that will be used to add the new functionallity.

During this example, we will explain what those functions look like.

REMEMBER: Trying to be as clean as possible, we strongly recomend you to create a new aspects.py file and add the Aspects to this file.

Library import

First of all, we need to import to the Aspects file the libraries and modules that will be used. In this case, random.

from aspectify.aop import Aspect
from random import Random

Additionally, we need to define the get_classes method.

def get_classes():
    return [element for element in list(globals().items())]

advice definition

In this example, we will define an Aspect called test_aspect, and we will modify it behaviour during the example.

test_aspect = Aspect()

As previously seen, we can add a before advice, just using the set_before method.

test_aspect.set_before(lambda *a, **k: print("catched!"))

As we can see, the used lambda has two input parameters, *a and **k, representing all the parameters that the original method can have.

REMEMBER: We can access to the parameters used in the original call of the catched method.

In this case, those parameters are ignored, because the method just print a message.

*a and **k input parameters will be used in all the advices definitions except in the definition of the method after_throwing, where we must recieve a third input parameter e: the raised exception. For example:

test_aspect.set_after_throwing(lambda e, *a, **k: print(e, " was raised."))

We can add these new functionallity to some methods in random, for example those starting with the letter ‘c’.

test_aspect.create_pointcut(get_classes(), ".*\.Random\.c.*")
Captured method: random.Random.choice
Captured method: random.Random.choices

Results

Now, we can call the choice method, which will return a random object inside an Iterable (something that can be iterated).

r = Random()
r.choice([1, 5, 10])
catched!
1

We can also produce an exception in this method:

r.choice([])
catched!
Cannot choose from an empty sequence  was raised.
IndexError: Cannot choose from an empty sequence

choice cannot take an empty Iterable. As you can see, we obtain the “catched!” message (before) and then we obtain the “e was raised” message.

Since the exception is raised anyway, the messages are at the top of the cell output, which can be a little weird for us programmers.

PointCut’s cannot be deactivated

We have noted this before: applying a PointCut is not a reversible action, as the method behaviour is dynamically modified.

However, we can apply several PointCuts to the same “catched” method (even in the same moment, using distinct Aspects or in a sequential declaration).

Let us define the following method. We could use a lambda expresion, but this is to notice that we can also use named functions.

def magnifying_glass(*args, **kwargs): # remember, we get as input the *a and the **k parameters
    print("Input parameters: ",  args)

Now, we can modify the before moment, adding this new method. Then, we create the PointCut.

test_aspect.set_before(magnifying_glass)

test_aspect.create_pointcut(get_classes(), ".*\.Random\.c.*")
Captured method: random.Random.choice
Captured method: random.Random.choices

We have captured the same two methods. However, those methods had already been modified, so they have two new behaviours: - print “catched!”, and - print the input parameters.

It is important to note that, in Python, AOP is defined as a LIFO chain: the first defined Aspects are the last to be executed (because the new ones wrap the old ones).

r.choice([1, 5, 10])
Input parameters:  ([1, 5, 10],)
catched!
5

Conclusion

During this example we have explained how to define the methods (or lambdas) used to define advices: they need the *a and the **k input parameters. Using the after_throwing moment, we will need the e parameter too (the raised exception). We have also noted something important: PointCuts cannot be deactivated and, if we define several of them, they will be applied in a LIFO chain.

In the next example we will see how to modify input parameters.