Table of Contents
What are decorators? Decorators in Python are a way to wrap functions and methods, enhancing their capabilities without having to modify them directly. Decorators are an advanced feature of Python that can help you write your code more elegantly and efficiently. However, writing decorators can be tricky, because they have their own syntax and sometimes it’s hard to tell what you should do in certain cases. A decorator can be used as an attribute or a function; the syntax varies depending on what you’re trying to accomplish with your decorator. Here are 10 ways to implement and use types of decorators in python! Python’s decorators are an extremely powerful feature and can be used to make your code clear, readable, and elegant. In this article, we will go over the following points and show you how to implement and use types of decorators in python.
1) Decorator Constructors
Constructors are those functions that get called by default when we create an object. A decorator constructor takes another function as a parameter, which is being decorated. It then returns a new function, where all it’s doing is calling our decorated function and returning its result. Let’s see what it looks like The above example will print out Hello World every time we call our hello_world() function. Pretty cool, right? We can also add arguments to our original function and they will be passed along to whatever function we pass into @hello_world. For example, This time around, our hello_world() function takes one argument (the name) and prints out Hello {0}. You can use whatever syntax you want for your arguments; just make sure you match up your parameters with your decorator constructor’s parameters. Here’s another example: This time around, instead of printing out Hello World, it prints out Goodbye Cruel World!. Neat! You can also use a decorator constructor to add default arguments. For example, This will print out I am your father every time we call our hello_world() function. So what does that look like? Let’s take a look at an example: >>> def say_hello(name=World): … print(Hello {}.format(name)) … >>> @say_hello(John) … def say_goodbye(): … print(Goodbye cruel {}.format(name)) … >>> say_goodbye() # John Hello John Goodbye cruel John >>> say_goodbye() # World Hello World Goodbye cruel world You can even have multiple types of decorators in python.
Ready to take your python skills to the next level? Sign up for a free demo today!
2) Using Multiple Calls to @decore
1: Which of the following data structures allows elements to be added and removed in a Last-In, First-Out (LIFO) order?
You can apply @decore as many times as you want, but each decorator must return a wrapped function that takes exactly one parameter. In our example, we have 3 instances of @decore: One decorates another decorator that does nothing but log which function was called (you could do something more complex here!). This second decorator wraps a simple hello() function that returns Hello World when invoked. When we call HelloFunction(), it logs both that it was called and what its parameters were. Finally, we call @hello_world on itself, passing in Python User Guide as an argument. It logs that it was called with Python User Guide as an argument and then returns Hello World. The result is three lines logged to console: Calling Hello Function, Hello Function Called with PYTHON USER GUIDE, Hello World. We’ve created a chain of decorators! Because of how multiple calls to @decore work, however, there are some limitations. Namely: Since every decorator in your chain has access to its caller’s decorated function arguments, any decorator can modify or replace those arguments before they reach your original function. This means that any decorator can actually change what happens when your original function is called—and it will be called immediately after each decoration runs (in order). There are cases where you might want to do something like logging before calling your original decorated function so you know what arguments were passed into it.
3) Adding Default Arguments with @decore
If you have a function that takes several arguments but always passes it certain default values, decorating it with @decorator makes it easy to add those default values. This is especially useful if you want to be able to use both optional and required arguments. For example The first parameter is required (must be passed), while only foo is optional (can be passed or not). To make sure that all functions take a value for foo, we can decorate it like so:
This will automatically set all functions decorated with @app(name=) to take an argument called name. This can then be used as follows: @decore(‘name’, ‘foo’) def hello_world(): print(Hello, world!) hello_world() # Hello, world! hello_world(‘Ringo’) # Hello, Ringo! To make things more flexible, you could also use a kwarg like so:
@decore(‘name’, ‘foo’, **kwargs) def hello_world(*args): print(Hello, world!) hello_world() # Hello, world! hello_world(‘Ringo’) # Hello, Ringo! @decore has some other useful features too.
4) Letting Users Call the Decorator Directly
In many cases, your users may know how a function works and don’t need an explanation. In those cases, it can be useful to allow your users to call a decorator on their own. That way, they can create their own versions of a decorator or pass arguments directly into it when they call it. That’s why most decorators will have an __init__ method that takes arguments: those arguments will be passed directly into its corresponding functions by default. The user can always override that behavior, but letting them do so is often useful if you expect certain defaults (especially if you want some control over how they use your decorator). For example, we could define our @decorator function as follows: def __init__(self, name): self.name = name And then we could pass it either a string or another function object: >>> @decorator(‘My Name’) … def spam(): … print(‘Hello!’) >>> @decorator(spam) … def eggs(): … print(‘Scrambled’) >>> spam() Hello! >>> eggs() Scrambled Of course, you could also make these parameters optional. It all depends on what you need for your specific decorator implementation. Allowing Users to Specify Arguments When Calling Your Decorator: If you don’t want your users calling a decorator without any arguments, you can simply not specify any parameters in its __init__ method. Doing so will cause an error when they try to call it without passing anything into it first. However, if you want to support calling a decorator with multiple arguments, simply list them inside parentheses after your decorated function: def __init__(self, fn): # Initialize instance variables here. … # Store references to argument values here.
5) Creating Stackable Decorators
One of my favorite features of decorators is that they can be stacked. This means that you can have one decorator perform a basic task, like logging, and then use another decorator to add some additional functionality, such as gzipping. The following example is based on a common use case: logging something before it’s sent over HTTP. The result is cleaner-looking code with a minimum of repeated code . To create stackable decorators, just define your log function inside a wrapper function, which returns another function. That inner function should take two arguments: what to log and what value to return (None). It doesn’t need any special arguments because all arguments will be passed through from outer functions. Finally, make sure your wrapper returns None so that none of its inner functionality is executed when called directly by other parts of your program—that only happens when calling through outer layers of decoration. If your decorator needs to do some extra work after logging, put it in an inner function. Here’s an example: def logged(func): def wrapper(*args, **kwargs): print(‘Calling %r’ % func.__name__) res = func(*args, **kwargs) print(‘Returning from %r’ % func.__name__) return res return wrapper @logged def get_data(): # … do stuff … # Call decorated function @logged def post_data(): # … do stuff … # Same thing here! post_data() get_data()
Get hands-on with our python course – sign up for a free demo!
6) Checking Whether an Instance was Created by a Particular Decorator
Suppose you have a class with a decorator: @decorator class C(object): pass After some time, you may want to check whether an instance was created by that decorator. It would be nice if there were a way of doing that without simply checking self.__class__ . The following idiom allows you to do exactly that: def decorated_by(cls, wrapped): cls = type(cls) return (wrapped is cls or type(wrapped) is cls) Here’s how it works: We start by defining a function decorated_by() which takes as its only argument another function. This function then uses reflection to determine whether or not an object was created by a particular decorator. In order for it to work, we need to define our decorator using functools.partial() , like so: from functools import partial @functools.partial def my_decorator(f): … If we put all of these pieces together, we get something like what follows: from functools import partial from inspect import getsourcefile from inspect import getsourcelines def decorated_by(cls, wrapped): cls = type(cls) return (wrapped is cls or type(wrapped) is cls) @my_decorator # This tells us where our source code came from!
7) Regulating Argument Usage with Docstrings
Docstrings can be used to tell you what arguments a function takes, as well as what it returns. The built-in inspect module makes it easy to view these docstrings for most objects. You can even use your own docstrings for functions that you’ve written. Any time you have an opportunity to make your code more self-documenting, don’t hesitate! It might seem like overkill at first, but it can become second nature very quickly, reducing errors in your code and helping you learn faster. Just think of how useful having docs on all of your native functions would be! And because Python is so readable, writing clear documentation isn’t hard—you’ll find yourself writing useful comments while learning anyway. So go ahead and add some docstrings; they’re worth it. Here are a few ways to get started: ~~~python def hello_world(): This is my first function. print(Hello World!) hello_world() # prints Hello World! ~~~ ~~~python def factorial(n): Compute n!, given n >= 0. if n == 0: return 1 else: return n * factorial(n – 1) print(factorial(5)) # prints 120 ~~~ ~~~python def get_array(lst): Return a list with elements from lst return [elem for elem in lst] print(get_array([1, 2, 3])) # prints [1, 2, 3] ~~~ __all__ = [‘hello’, ‘goodbye’] def hello(): Say hello. print(‘Hello’) goodbye() # Doesn’t do anything…yet. But wait! 🙂 __all__ = [‘hi’, ‘bye’] hi = hello bye = goodbye hi() bye() # Say goodbye twice…just because we can! See?
8) Ensuring Type Compatibility with Type Annotations or Instance Checks
If you want your decorated function to work on more than one kind of object, decorate it with a type annotation: def f(cls, num): print(‘do something with an instance of’, cls) return num This ensures that if a programmer calls f() on an instance of Cls , Python will know that you expect that Cls is similar enough to int that you can call f() successfully. You’re allowed to leave off annotations when they’re obvious. For example, there’s no point annotating your function as returning float if it returns every possible float value. But be careful—Python may not be able to infer types from context alone. The following two functions are equivalent: def g(x): return x + 1 def h(x): return x + 1.0 In both cases, Python knows that x must be a number because we explicitly said so by returning it without changing its value. However, we don’t have any information about what x might be! It could be an integer or a floating-point number; even worse, it could be some completely unrelated object like a string or class instance! When writing decorators, make sure you include annotations for all arguments. That way, Python won’t have to rely on outside information to determine how to process your function. Be sure that all inputs (including default values) are valid before calling a decorated function: def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Do nothing return func(*args, **kwargs) return wrapper @my_decorator def spam(): print(‘Hello world’) spam() # Hello world Unfortunately, our custom wrapper isn’t checking anything about args or kwargs . As long as they’re passed in correctly (and aren’t None ), our wrapper will happily accept them and pass them along unchanged. That’s dangerous behavior!
Ready to take your python skills to the next level? Sign up for a free demo today!
9) Combining Different Levels of Interposition at Runtime
One of python’s biggest advantages is also one of its biggest disadvantages. The dynamic nature of python allows for rapid development, but that dynamism can be problematic when you need to use a third-party library that has been designed to work only with a specific version of python or under certain versions of operating systems. To address these compatibility issues, you have two basic options: try to make your existing libraries compatible with other versions or isolate incompatible components so they won’t break code outside your scope. One way you can isolate incompatible components is by using decorators. In practice, decorators allow you to run a function call at different levels on a class without modifying either class involved in that function call. For example, if you want to add logging functionality to an existing class without altering its source code, you could do so by adding a logging decorator. This would essentially wrap all functions inside that class with logging calls. While useful in some cases, there are times when it may not be feasible to change your entire system just because an external component doesn’t play well with others—and because of python’s dynamic nature, it may not even be possible to make those changes at all!
10) Using Mocks as Decoration Targets
Mocks are objects used to simulate how real objects behave. They’re also used as decoration targets, allowing you to pass mocks into methods as functions or classes so that you can use them during testing. This is useful when a class or function relies on services that can’t be easily replaced with another object, such as an external server. Instead of having a live instance of that object, mocking it lets you simulate its behavior within your code during tests. Some mocking frameworks even give you tools for automatically adding decorator behavior (as well as other kinds of mock behavior) for more seamless integration with testing suites. It’s important to note that mock objects don’t actually replace any dependencies they might have; they just make it easier to test your code by controlling their behavior. In addition, not all mocking frameworks support decorators, so check before using them if you need support for these features. If you do want to use them, however, here’s what you need to know: Creating a Mock Class from Another Class: To create a mock class from an existing one, first create an empty class file called something like mymockclass.py . Next, import your original class file—in our case it would be myoriginalclass.py —and write your own empty subclass inside of it that subclasses whatever your original was based on. For example: from myoriginalclass import MyOriginalClass # Write out subclass definition here.
Conclusion
This is a list of ten different ways you can use decorators in your code. Try them out! All of these examples are real codes, so they should work exactly as described. Hopefully, that gives you a better idea of how Python’s decorator syntax works. For even more information on decorators, be sure to check out our guide here at Pythonista. Happy coding! The above was generated by a program written using NLTK-Natural Language Toolkit, which takes text like Ten Ways to and outputs it as Ten Ways To. It then goes through an additional step of choosing words from all available synonyms before generating text. If you are interested to learn new coding skills, the Entri App will help you to acquire them very easily. Entri App is following a structural study plan so that the students can learn very easily. If you don’t have a coding background, it won’t be any problem. You can download the Entri App from the google play store and enroll in your favorite course.
Experience the power of our Python course with a free demo – Enroll Now!