 Cool. Thank you. Well, thank you Jason for inviting me. Hello Dev Nation. I'm gonna share my screen here real quick Whoa, we just had an inception moment. Okay Already can everyone see my slides Everything was good. Cool. Great. Thank you so Hello Dev Nation, my name is Pandy Knight and I'm the automation Panda and I am a big Python fan just like y'all Currently, I'm the lead software engineer and test for precision lender a Q2 and Today, I'm going to be talking with you about Decorators and Python. So have you ever seen those at tags on top of Python functions? Maybe you've seen them on top of methods and classes. Those are decorators one of Python's niftiest language features Decorators are essentially wrappers. They wrap additional code around existing definitions When used right, they can clean up your code better than OxyClean Today, let's learn how to use them. So here's a regular old hello world function When we run it It prints hello world Nothing fancy here. Now. Let's take that function and BAM! add a decorator Using this at sign. We just added a decorator named tracer to the hello world function So what is this decorator? Tracer is just another function But it's special because it takes in another function as an argument that named funk Since tracer decorates hello world the hello world function is passed into tracer as that argument. Wow So what's inside tracer? This decorator has an inner function named wrapper Can you even do that? Well with Python? Yes, you can The wrapper function prints entering calls the function originally passed into the decorator and then prints exiting When tracer decorates hello world that means hello world will be the will be wrapped by entering an exiting print statements Finally, the decorator returns the new wrapper function Anytime the decorated function is called it will effectively be replaced by this new wrapper function So when we call hello world the trace statements are now printed, too Wow, that's amazing That's how decorators work Decorators wrap functions around functions Think about them like candy bars The decorator is like this foil wrapper and the decorated function is the chocolate bar inside But how is this even possible? That decorator code looks confusing Decorators are possible because in Python functions are objects In fancy language, we say functions are first-order values Since functions are just objects We can pass them into other functions as arguments Define new functions inside existing functions and Return a function from a function This is all part of a paradigm called functional programming Python supports functional programming because functions can be treated like objects and that's awesome So using functions as objects decorators change how functions are called Decorators create an outer decorator function around an inner decorated function Remember the outer function is like the foil wrapper and the inner function is like the chocolate Creating an outer function lets you add new code around the inner function Some people call this advice You can add advice before or after an inner function You could even skip the inner function entirely The best part is decorators can be applied to any function. They make sharing code easy So you don't need to repeat yourself Decorators are reminiscent of a paradigm called aspect oriented programming in which code can be cleverly inserted before and after points of execution neat So remember decorators wrap functions around functions like candy bars. Hold on now We have a problem in that Python code. We just saw if the wrapper function effectively replaces hello world Then what identity does hello world report its name is wrapper and its help is also wrapper That's not right in Python we use name and help to kind of figure out what functions do and if they're misnamed that can become very confusing Never fear. There's an easy solution to this The funk tools module provides a decorator named wraps Put funk tools dot wraps on the wrapper function and it passes the inner function object and Decorated functions once again show the right identity That's really cool But wait, we have another problem. How do decorators work with inputs and outputs? What if we decorate a function with parameters and a return value if we applied the current tracer decorator? We'll get an error the arguments broke it crap That's okay. We can fix that too First we need to add star args and star star kw args to the wrapper functions parameters And then pass them through to the inner function This will make sure all arguments go through to the decorator in the decorated function then more mods We captured the inner functions return value and return it from the wrapper function This makes sure return values also pass through If the inner function has no return value, don't worry. The decorator will pass through a none value Pretty nifty thing about Python. So now when we call the function with the updated tracer We'll see the tracing is now once again successful And when we check that return value It's exactly what we expected it works pass through is awesome. Yeah, that's awesome But wait, there's more You can write a decorator to call a function twice Start with the decorator template that we had before and Now simply call the inner function twice it has a return value make sure to return the final value for continuity Bam, it works, but wait, there's more You could write a timer decorator Start with that template we had before Add the inner function and surround it with timestamps using the time module Bam now you can time any function, but wait, there's more You can also add more than one decorator to a function This is what we call nesting Be careful because order matters Decorators are executed in the order of closeness to the inner function so in this case Call twice is applied first and then timer is applied even though timer is the one on top If these decorators are reversed in order Then each inner function is called with timed cool You guessed it. Wait, there's more You can scrub and validate functional arguments Check out these two simple math functions add and subtract Create a decorator to scrub and validate the inputs as integers Add the wrapper function and make sure it has positional args Then cast all arguments as integers before passing them into the inner function What we see here on that line of new args is what we call a list Comprehension it makes processing list-based data really efficient in Python now when we call those math functions add and subtract All numbers are integers even if we pass them in as float values Using non-numeric inputs also raises a value error such as if you were to pass in ABC in This we can somewhat protect and coax our input arguments for safety, but wait, there's more You can create decorators with parameters. What? Yeah, I mean in Python. You can basically do anything Here's a decorator that will repeat a function five times The repeat function is a little different Instead of taking the inner function object It takes in the parameter which is the number of times to repeat the inner function Inside there's a repeat decorator function that has a parameter for the inner function The repeat function returns the repeat decorator function Inside repeat decorator is the wrapper function that we remember from our typical template It uses func tools wraps and passes through all the arguments using star args and star star KW args Repeat decorator returns wrapper Finally wrapper contains the logic for calling the inner function multiple times according to the repeat decorators parameter value So you can see we have a for loop for blank in a range. We can discard the iterator variable and Inside this for loop. We just call that function however many times are in the range Now hello world what runs five times? That's really nifty if we wanted to run it four times we say repeat four We run it want to run it 57 times we repeat 57 Even though this decorator is a bit more complicated and seems like inception upon inception it all still works That's cool. But wait, there's more Decorators can be used to save state Here's a decorator that will count the number of times the function is called Count calls has that standard decorator format but Little differently outside the wrapper a count attribute is initialized to zero This attribute is added to the wrapper function objects We can do this in Python because remember functions are first-order values and treated as objects and You can dynamically add attributes in Python to any object. So this is perfectly valid Inside the wrapper the count is incremented before calling the inner function The count value because it's attached to the wrapper object will persist across multiple calls Initially the hello world count value is zero But after calling hello world twice That value goes up and it persists together with that original function. That's really awesome But wait, there's more Decorators can be used in classes. They're not just limited to functions. You can do this on classes Here the inner decorator is applied to the hello method Or a timer. I'm sorry. I can't even read my own text. I'm sorry. Happy Wednesday, everybody Here the timer decorator is applied to hello As long as parameters and return values are set up correctly Decorators can be applied equally to functions as well as methods Decorators can also be applied directly to the class When a decorator is applied to a class it wraps the constructor Note that it does not wrap each method in the class. That tends to be something people misunderstand They think if you put a decorator on the class level then every single method would be wrapped But that's not the case Since decorators can wrap classes and methods in addition to functions It would technically be more correct say that decorators wrap callables around callables in Python a callable is literally anything that can be called Function is a callable a method is a callable and technically a class can even be a callable So in in broader terms, we would say decorators wrap functions around functions But we could also say more broadly decorators wrap callables around callables So that's all great But can decorators be tested? Good code must arguably be testable code Well today's your lucky day because yes, you can test decorators and as the automation Panda I'm gonna show you how to do it Testing decorators can be a challenge We should always try to test the code we write but decorators given the level of inception We saw can be a bit tricky. Here's some advice First separate tests for decorator functions from the decorated functions For decorator functions focus on the intended outcomes Try to focus on the wrapper instead of the inner function Because remember decorators can be applied to any callable. So cover the parts that make decorators unique Decorated functions should have their own separate unit tests Second apply decorators to fake functions used only for testing These functions can be simple or they could be mocked That way unit tests won't have dependencies on existing functions that could change Testers will also be or tests will also be simpler if they use slimmed down decorated functions third Make sure decorators have test coverage for every possible way it could be used Cover decorated parameters Decorated function arguments and return values Make sure the name and the help are correct as we saw before Check any side effects like saving state such as when we did the count Try it on methods and classes as well as functions With decorators most failures happen due to overlooked edge cases So let's take a look at a few short decorator tests We'll use that count calls decorator from earlier There are two decorated functions to use for testing The first one here is a no operation or no op function that literally does nothing Just pass these get out of there It has no parameters or returns The second one is a function that takes in one argument and returns it Notice that both of these are very simple But they represent two separate equivalence classes of decoratable functions The test cases will verify outcomes of using the decorator For count calls that means tests will focus on the count attribute added to the decorated functions The first test case here verifies that the initial count value for any function is zero We could add a second test that calls the function three times and verifies that the count is three The third test exercises the same function to make sure arguments and return values work correctly It calls the same function Asserts the return value and asserts the count value as well This collection of tests is by no means complete It simply shows how to start writing tests for decorators It also shows that you don't need to overthink unit tests for decorators either As we all know in python the zen of python goes simple is better than complex Up to this point, we've covered how to write your own decorators However, python has several decorators available in the language and in various modules that you can use The best part is they're absolutely free Decorators like class method static method and property can apply to methods in a class Frameworks like flask and pie tests have even more decorators Let's take a closer look at some of these Let's start by comparing the class method and static method decorators We'll revisit the greeter class we saw before The class method decorator will turn any method into a class method instead of an instance method That means this hello method can be called directly from the class itself instead of from an object of the class This decorator will pass a reference to the class into the method So the method has some context of the class Here the reference is named cls instead of what we typically see with self for instance methods And the method uses it to get the class's name This method can be called using greeter.hello Wow The static method decorator works almost the same as the class method decorator Except that it does not pass a reference to the class into the method Notice there's no cls And notice as well nothing needs to be passed in Methods can still be called from the class here with greeter goodbye You could say that static method is just a simpler version of class method Now let's take a look at another decorator property To show how to use it we'll create a class called accumulator to keep a count of a tally Accumulator's init method initializes a count attribute to zero An add method adds an amount to the count So far here there's nothing unusual Now let's add a property This count method has the property decorator on it This means that count will be callable as an attribute instead of a method Meaning that it won't need those parentheses It is effectively a getter The calls to count in the init and add methods will actually call this property instead of a raw variable Inside the count property the method returns an attribute named underscore count The underscore means that this variable should be private However, this class hasn't set that variable yet That variable is set in the setter method Setters are optional for properties Here the setter validates that the value is set to something not negative The value is good then it sets underscore count If the value is negative then it raises a value error Under score count is handled internally while count is handled publicly as the property The getter and setter controls Added by the property decorator let you control how the property is handled In this class the setter protects the property against bad values So let's use this class when an accumulator object is constructed its initial count is zero After adding an amount to the object the count goes up Its count can be directly set to a non-negative value But attempting to set count directly in a negative value raises an exception as expected Protection like this is great Python packages also frequently contained Frequently contained decorators For example, Flask is a very popular web micro framework that enables you to write web APIs with very little python code Here's an example Hello world flask app taken directly from the flash docs online It imports the flask module Creates the app And defines a single endpoint at the root path that returns the string. Hello world Flask's app.root decorator can turn any function into a web API endpoint That's awesome Just these five lines here is a fully sufficient flask app Another popular python package with decorators is PyTest Python's most popular test framework One of PyTest's best features is the ability to parameterize test functions to run for multiple input combinations Test parameters empower data-driven testing for wider coverage To show how this works I will use a simple test for basic arithmetic test addition It asserts that a plus b equals c The values for a b and c must come from a list of tuples For example, one plus two equals three zero plus one equals one and so forth The PyTest.mark.parameterize decorator connects the list of test values to the test function It runs the test once for each tuple in the list And it injects the tuple values into the test case as function arguments The test case would run four times Test parameters are a great way to rerun test logic without repeating test code Don't repeat yourself So act now before it's too late When should you use decorators in your python code? Use decorators for aspects An aspect is a special cross-cutting concern There are things that happen in many parts of code and they frequently require repetitive calls Think about something like logging If you want to add logging statements to different parts of the code Then you'll need to write multiple logging calls in all those places Logging itself is one concern But it cross-cuts the whole code base One solution for logging could be to use decorators Much like we saw earlier with the tracer decorator Good use cases for decorators include logging profiling input validation retries and registries These are things that typically require lots of extra calls inserted in duplicative ways So ask yourself this Should the code you're writing wrap something else? If yes, then you have a good candidate for a decorator However, decorators aren't good for all circumstances You should avoid decorators for what we would consider main behaviors Because those should probably be put directly in the body of the decorated function Avoid logic that's complicated or has heavy conditionals too Because simple is better than complex You should also try to avoid completely side-stepping the decorated function because that could really confuse people Ask yourself this is the code you want to write the wrapper or the candy bar itself Rappers make good decorators, but candy bars do not So I hope you found this infomercial about decorators useful If you want to learn more Then check out this real python tutorial named primer on python decorators It covers everything i've shown here plus more So thank you very much for listening again. My name is panty knight and i'm the automation panda If you like my talk then please read my blog at automation panda dot com and follow me on twitter at automation panda Finally if you want to hear more talks like this then definitely please come to a python conference Their conference is happening all around the world all the time And due to the covid pandemic they've basically all gone virtual. So there's no reason not to join The python community would love to welcome you