{"id":9704,"date":"2016-01-04T16:11:06","date_gmt":"2016-01-04T14:11:06","guid":{"rendered":"http:\/\/www.webcodegeeks.com\/?p=9704"},"modified":"2018-01-09T09:51:25","modified_gmt":"2018-01-09T07:51:25","slug":"python-decorator-tutorial","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/","title":{"rendered":"Python Decorator Tutorial"},"content":{"rendered":"<p>Sometimes, we encounter problems that require us to extend the behavior of a function, but we don&#8217;t want to change its implementation.<\/p>\n<p>Some of those problems could be: logging spent time, caching, validating parameters, etc.<\/p>\n<p>All these solutions are often needed in more than one function: you often need to log the spent time of every http connection; you often need to cache more than one data base entity; you often need validation in more than one function.<\/p>\n<p>Today we will solve these 3 mentioned problems with Python decorators:<\/p>\n<ul>\n<li>Spent Time Logging: We&#8217;ll decorate a couple functions to tell us how much time do they take to execute.<\/li>\n<li>Caching: We&#8217;ll add cache to prevent a function from executing when its called several times with the same parameters.<\/li>\n<li>Validating: We&#8217;ll validate a function&#8217;s input to prevent run time errors.<\/li>\n<\/ul>\n<p>[ulp id=&#8217;R7QVpFMZmjosABLC&#8217;]<\/p>\n<h2>1. Understanding Functions<\/h2>\n<p>Before we can jump into decorators, we need to understand how functions actually work.<\/p>\n<p>In essence, functions are procedures that return a value based on some given arguments.<\/p>\n<pre class=\"brush:python\">\r\ndef my_function(my_arg):\r\n    return my_arg + 1\r\n<\/pre>\n<p>In Python, functions are first-class objects.<\/p>\n<p>This means that functions can be assigned to a variable:<\/p>\n<pre class=\"brush:python\">\r\ndef my_function(foo):\r\n    return foo + 1\r\nmy_var = my_function\r\nprint(str(my_var(1))) # prints \"2\"\r\n<\/pre>\n<p>They can be defined inside another functions:<\/p>\n<pre class=\"brush:python\">\r\ndef my_function(foo):\r\n    def my_inner_function():\r\n        return 1\r\n    return foo + my_inner_function()\r\nprint(str(my_function(1))) # still prints \"2\"\r\n<\/pre>\n<p>They can be passed as parameters (higher-order functions):<\/p>\n<pre class=\"brush:python\">\r\ndef my_function(foo, my_parameter_function):\r\n    return foo + my_parameter_function()\r\ndef parameter_function(): return 1\r\nprint(str(my_function(1, parameter_function))) # still prints \"2\"\r\n<\/pre>\n<p>And they can return other functions (also, higher-order functions):<\/p>\n<pre class=\"brush:python\">\r\ndef my_function(constant):\r\n    def inner(foo):\r\n        return foo + constant\r\n    return inner\r\nplus_one = my_function(1)\r\nprint(str(plus_one(1))) # still prints \"2\"\r\n<\/pre>\n<p>Another thing to notice, is that inner functions have access to the outer scope, that&#8217;s why we can use the parameter <code>constant<\/code> in the inner function of the last example. Also, this access is read-only, we can not modify variables from the outer scope within an inner function.<\/p>\n<h2>2. Jumping into decorators<\/h2>\n<p>Python decorators provide a nice and simple syntax to call higher-order functions. By definition, a decorator takes a function as a parameter, and returns a wrapper of that given function to extend its behavior without actually modifying it.<\/p>\n<p>Given this definition we can write somthing like:<\/p>\n<pre class=\"brush:python\">\r\ndef decorator(function_to_decorate):\r\n    def wrapper(value):\r\n        print(\"you are calling {} with '{}' as parameter\".format(function_to_decorate.__name__, value))\r\n        return function_to_decorate(value)\r\n    return wrapper\r\n\r\n\r\ndef replace_commas_with_spaces(value):\r\n    return value.replace(\",\", \" \")\r\n\r\nfunction_to_use = decorator(replace_commas_with_spaces)\r\nprint(function_to_use(\"my,commas,will,be,replaces,with,spaces\"))\r\n<\/pre>\n<p>And after execution, the output will look like:<\/p>\n<pre class=\"brush:bash\">\r\nyou are calling replace_commas_with_spaces with 'my,commas,will,be,replaces,with,spaces' as parameter\r\nmy commas will be replaces with spaces\r\n<\/pre>\n<p>So, what is actually happening here?<\/p>\n<p>We are defining a higher-order function called <code>decorator<\/code> that receives a function as a parameter and returns a wrapper of that function.<\/p>\n<p>The wrapper just prints to the console the name of the called function and the given parameters before executing the wrapped function. And the wrapped functions just replaces the commas with spaces.<\/p>\n<p>Now we have a decorator written here. But it&#8217;s kind of annoying to define the decorator, the function and then assigning the wrapper to another variable to finally be able to use it.<\/p>\n<p>Python provides some sugar syntax to make it easier to write and read, and if we re-write this decorator using it:<\/p>\n<pre class=\"brush:python\">\r\ndef decorator(function_to_decorate):\r\n    def wrapper(value):\r\n        print(\"you are calling {} with '{}' as parameter\".format(function_to_decorate.__name__, value))\r\n        return function_to_decorate(value)\r\n    return wrapper\r\n\r\n\r\n@decorator\r\ndef replace_commas_with_spaces(value):\r\n    return value.replace(\",\", \" \")\r\n\r\nprint(replace_commas_with_spaces.__name__)\r\nprint(replace_commas_with_spaces.__module__)\r\nprint(replace_commas_with_spaces.__doc__)\r\nprint(replace_commas_with_spaces(\"my,commas,will,be,replaces,with,spaces\"))\r\n<\/pre>\n<p>We just annotate the function we want to wrap with the decorator function and that&#8217;s it. That function will be decorated, and the output will look the same.<\/p>\n<pre class=\"brush:bash\">\r\nwrapper\r\n__main__\r\nNone\r\nyou are calling replace_commas_with_spaces with 'my,commas,will,be,replaces,with,spaces' as parameter\r\nmy commas will be replaces with spaces\r\n<\/pre>\n<p>Now, debugging this can be a real pain, as the <code>replace_commas_with_spaces<\/code> function is overridden with the wrapper, so its <code>__name__<\/code>, <code>__doc__<\/code> and <code>__module__<\/code> will also be overridden (as seen in the output).<\/p>\n<p>To avoid this behavior we will use <code>functools.wraps<\/code>, that prevents a wrapper from overriding its inner function properties.<\/p>\n<pre class=\"brush:python\">\r\nfrom functools import wraps\r\n\r\n\r\ndef decorator(function_to_decorate):\r\n    @wraps(function_to_decorate)\r\n    def wrapper(value):\r\n        print(\"you are calling {} with '{}' as parameter\".format(function_to_decorate.__name__, value))\r\n        return function_to_decorate(value)\r\n    return wrapper\r\n\r\n\r\n@decorator\r\ndef replace_commas_with_spaces(value):\r\n    return value.replace(\",\", \" \")\r\n\r\nprint(replace_commas_with_spaces.__name__)\r\nprint(replace_commas_with_spaces.__module__)\r\nprint(replace_commas_with_spaces.__doc__)\r\nprint(replace_commas_with_spaces(\"my,commas,will,be,replaces,with,spaces\"))\r\n<\/pre>\n<p>And now the output will be:<\/p>\n<pre class=\"brush:bash\">\r\nreplace_commas_with_spaces\r\n__main__\r\nNone\r\nyou are calling replace_commas_with_spaces with 'my,commas,will,be,replaces,with,spaces' as parameter\r\nmy commas will be replaces with spaces\r\n<\/pre>\n<p>So, now we know how decorators work in python. Let&#8217;s solve our mentioned problems.<\/p>\n<h2>3. The Practice<\/h2>\n<p>So, we need to implement <em>cache, spent time logging and validations<\/em>.<\/p>\n<p>Let&#8217;s combine them all by solving a bigger problem: <strong>palindromes<\/strong>.<\/p>\n<p>Let&#8217;s make an algorithm that, given a word, will check if it&#8217;s a palindrome. If it isn&#8217;t, it will convert it to palindrome.<\/p>\n<p><span style=\"text-decoration: underline\"><em>palindrome.py<\/em><\/span><\/p>\n<pre class=\"brush:python\">\r\ndef is_palindrome(string_value):\r\n    char_array = list(string_value)\r\n    size = len(char_array)\r\n    half_size = int(size \/ 2)\r\n    for i in range(0, half_size):\r\n        if char_array[i] != char_array[size - i - 1]:\r\n            return False\r\n    return True\r\n\r\n\r\ndef convert_to_palindrome(v):\r\n    def action(string_value, chars):\r\n        chars_to_append = list(string_value)[0:chars]\r\n        chars_to_append.reverse()\r\n        new_value = string_value + \"\".join(chars_to_append)\r\n        if not is_palindrome(new_value):\r\n            new_value = action(string_value, chars + 1)\r\n        return new_value\r\n    return action(v, 0)\r\n\r\nuser_input = input(\"string to convert to palindrome (exit to terminate program): \")\r\nwhile user_input != \"exit\":\r\n    print(str(convert_to_palindrome(user_input)))\r\n    print(\"------------------------------------------------------\")\r\n    user_input = input(\"string to check (exit to terminate program): \")\r\n\r\n<\/pre>\n<p>Here, we have a function called <code>is_palindrome<\/code>, which given an input, returns <code>True<\/code> if its palindrome, or <code>False<\/code> otherwise.<\/p>\n<p>Then, there is a function called <code>convert_to_palindrome<\/code> which, given an input, will add just as many characters (reversed, from the beginning) as necessary to make it palindrome.<\/p>\n<p>Also, there is a while that reads the user input until he inputs <em>&#8220;exit&#8221;<\/em>.<\/p>\n<p>The output looks like:<\/p>\n<pre class=\"brush:bash\">\r\nstring to convert to palindrome (exit to terminate program): anita lava la tina\r\nanita lava la tinanit al aval atina\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): anitalavalatina\r\nanitalavalatina\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): menem\r\nmenem\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): mene\r\nmenem\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): casa\r\ncasac\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): casaca\r\ncasacasac\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): exit\r\n<\/pre>\n<p>As you can see, it works just fine. But we have a couple problems:<\/p>\n<ul>\n<li>I don&#8217;t know how long does it take it to process the input, or if its related to the length of it. (spent time logging)<\/li>\n<li>I don&#8217;t want it to process twice the same input, it&#8217;s not necessary. (cache)<\/li>\n<li>It&#8217;s designed to work with words or numbers, so I don&#8217;t want spaces around. (validation)<\/li>\n<\/ul>\n<p>Let&#8217;s get dirty here, and start with a <em>spent time logging<\/em> decorator.<\/p>\n<p><span style=\"text-decoration: underline\"><em>palindrome.py<\/em><\/span><\/p>\n<pre class=\"brush:python\">\r\nimport datetime\r\nfrom functools import wraps\r\n\r\n\r\ndef spent_time_logging_decorator(function):\r\n    @wraps(function)\r\n    def wrapper(*args):\r\n        start = datetime.datetime.now()\r\n        result = function(*args)\r\n        end = datetime.datetime.now()\r\n        spent_time = end - start\r\n        print(\"spent {} microseconds in {} with arguments {}. Result was: {}\".format(spent_time.microseconds,\r\n                                                                                     function.__name__, str(args),\r\n                                                                                     result))\r\n        return result\r\n\r\n    return wrapper\r\n\r\n\r\ndef is_palindrome(string_value):\r\n    char_array = list(string_value)\r\n    size = len(char_array)\r\n    half_size = int(size \/ 2)\r\n    for i in range(0, half_size):\r\n        if char_array[i] != char_array[size - i - 1]:\r\n            return False\r\n    return True\r\n\r\n\r\n@spent_time_logging_decorator\r\ndef convert_to_palindrome(v):\r\n    def action(string_value, chars):\r\n        chars_to_append = list(string_value)[0:chars]\r\n        chars_to_append.reverse()\r\n        new_value = string_value + \"\".join(chars_to_append)\r\n        if not is_palindrome(new_value):\r\n            new_value = action(string_value, chars + 1)\r\n        return new_value\r\n\r\n    return action(v, 0)\r\n\r\n\r\nuser_input = input(\"string to convert to palindrome (exit to terminate program): \")\r\nwhile user_input != \"exit\":\r\n    print(str(convert_to_palindrome(user_input)))\r\n    print(\"------------------------------------------------------\")\r\n    user_input = input(\"string to check (exit to terminate program): \")\r\n\r\n<\/pre>\n<p>It&#8217;s pretty simple, we wrote a decorator which returns a wrapper that gets the time before and after executor, and then calculates the spent time and logs it, with the called function, parameters and result. The output looks like:<\/p>\n<pre class=\"brush:bash\">\r\nstring to check (exit to terminate program): anitalavalatina\r\nspent 99 microseconds in convert_to_palindrome with arguments ('anitalavalatina',). Result was: anitalavalatina\r\nanitalavalatina\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): exit\r\n<\/pre>\n<p>As you see, there is plenty information in that log line, and our implementation was not changed at all.<\/p>\n<p>Now, let&#8217;s add cache:<\/p>\n<p><span style=\"text-decoration: underline\"><em>palindrome.py<\/em><\/span><\/p>\n<pre class=\"brush:python\">\r\nimport datetime\r\nfrom functools import wraps\r\n\r\n\r\ndef cache_decorator(function):\r\n    cache = {}\r\n\r\n    @wraps(function)\r\n    def wrapper(*args):\r\n        hashed_arguments = hash(str(args))\r\n        if hashed_arguments not in cache:\r\n            print(\"result for args {} was not found in cache...\".format(str(args)))\r\n            cache[hashed_arguments] = function(*args)\r\n        return cache[hashed_arguments]\r\n    return wrapper\r\n\r\n\r\ndef spent_time_logging_decorator(function):\r\n    @wraps(function)\r\n    def wrapper(*args):\r\n        start = datetime.datetime.now()\r\n        result = function(*args)\r\n        end = datetime.datetime.now()\r\n        spent_time = end - start\r\n        print(\"spent {} microseconds in {} with arguments {}. Result was: {}\".format(spent_time.microseconds,\r\n                                                                                     function.__name__, str(args),\r\n                                                                                     result))\r\n        return result\r\n\r\n    return wrapper\r\n\r\n\r\ndef is_palindrome(string_value):\r\n    char_array = list(string_value)\r\n    size = len(char_array)\r\n    half_size = int(size \/ 2)\r\n    for i in range(0, half_size):\r\n        if char_array[i] != char_array[size - i - 1]:\r\n            return False\r\n    return True\r\n\r\n\r\n@spent_time_logging_decorator\r\n@cache_decorator\r\ndef convert_to_palindrome(v):\r\n    def action(string_value, chars):\r\n        chars_to_append = list(string_value)[0:chars]\r\n        chars_to_append.reverse()\r\n        new_value = string_value + \"\".join(chars_to_append)\r\n        if not is_palindrome(new_value):\r\n            new_value = action(string_value, chars + 1)\r\n        return new_value\r\n\r\n    return action(v, 0)\r\n\r\n\r\nuser_input = input(\"string to convert to palindrome (exit to terminate program): \")\r\nwhile user_input != \"exit\":\r\n    print(str(convert_to_palindrome(user_input)))\r\n    print(\"------------------------------------------------------\")\r\n    user_input = input(\"string to check (exit to terminate program): \")\r\n\r\n<\/pre>\n<p>This is a very simple implementation of cache. No TTL, no thread safety. It&#8217;s just a dictionary which keys are the hash of the arguments. If no value with the given key was found, it creates it, then retrieves it. Output:<\/p>\n<pre class=\"brush:bash\">\r\nstring to convert to palindrome (exit to terminate program): anitalavalatina\r\nresult for args ('anitalavalatina',) was not found in cache...\r\nspent 313 microseconds in convert_to_palindrome with arguments ('anitalavalatina',). Result was: anitalavalatina\r\nanitalavalatina\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): anitalavalatina\r\nspent 99 microseconds in convert_to_palindrome with arguments ('anitalavalatina',). Result was: anitalavalatina\r\nanitalavalatina\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): exit\r\n<\/pre>\n<p>There it is. The first execution with <em>&#8220;anitalavalatina&#8221;<\/em> outputs a line informing us that the result for that input was not found. But when we input it again, that line is gone. Awesome! But we still receive spaces, let&#8217;s validate that:<\/p>\n<p><span style=\"text-decoration: underline\"><em>palindrome.py<\/em><\/span><\/p>\n<pre class=\"brush:python\">\r\nimport datetime\r\nfrom functools import wraps\r\n\r\n\r\ndef validation_decorator(validator, if_invalid=None):\r\n    def decorator(function):\r\n        @wraps(function)\r\n        def wrapper(*args):\r\n            if validator(*args):\r\n                return function(*args)\r\n            else:\r\n                return if_invalid\r\n        return wrapper\r\n    return decorator\r\n\r\n\r\ndef cache_decorator(function):\r\n    cache = {}\r\n\r\n    @wraps(function)\r\n    def wrapper(*args):\r\n        hashed_arguments = hash(str(args))\r\n        if hashed_arguments not in cache:\r\n            print(\"result for args {} was not found in cache...\".format(str(args)))\r\n            cache[hashed_arguments] = function(*args)\r\n        return cache[hashed_arguments]\r\n    return wrapper\r\n\r\n\r\ndef spent_time_logging_decorator(function):\r\n    @wraps(function)\r\n    def wrapper(*args):\r\n        start = datetime.datetime.now()\r\n        result = function(*args)\r\n        end = datetime.datetime.now()\r\n        spent_time = end - start\r\n        print(\"spent {} microseconds in {} with arguments {}. Result was: {}\".format(spent_time.microseconds,\r\n                                                                                     function.__name__, str(args),\r\n                                                                                     result))\r\n        return result\r\n\r\n    return wrapper\r\n\r\n\r\ndef is_palindrome(string_value):\r\n    char_array = list(string_value)\r\n    size = len(char_array)\r\n    half_size = int(size \/ 2)\r\n    for i in range(0, half_size):\r\n        if char_array[i] != char_array[size - i - 1]:\r\n            return False\r\n    return True\r\n\r\n\r\ndef should_not_contain_spaces(*args):\r\n    return False not in map(lambda x: \" \" not in str(x), args)\r\n\r\n\r\n@spent_time_logging_decorator\r\n@validation_decorator(should_not_contain_spaces, \"input shouldn't contain spaces.\")\r\n@cache_decorator\r\ndef convert_to_palindrome(v):\r\n    def action(string_value, chars):\r\n        chars_to_append = list(string_value)[0:chars]\r\n        chars_to_append.reverse()\r\n        new_value = string_value + \"\".join(chars_to_append)\r\n        if not is_palindrome(new_value):\r\n            new_value = action(string_value, chars + 1)\r\n        return new_value\r\n\r\n    return action(v, 0)\r\n\r\n\r\nuser_input = input(\"string to convert to palindrome (exit to terminate program): \")\r\nwhile user_input != \"exit\":\r\n    print(str(convert_to_palindrome(user_input)))\r\n    print(\"------------------------------------------------------\")\r\n    user_input = input(\"string to check (exit to terminate program): \")\r\n<\/pre>\n<p>Now, this one is a little tricky. To pass arguments to the decorator we need to wrap it. Yeah, we need a wrapper of the wrapper. Thanks to that, we can pass the validation function and a message if input is invalid. The output looks like:<\/p>\n<pre class=\"brush:bash\">\r\nstring to convert to palindrome (exit to terminate program): anita lava la tina\r\nspent 87 microseconds in convert_to_palindrome with arguments ('anita lava la tina',). Result was: input shouldn't contain spaces.\r\ninput shouldn't contain spaces.\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): anitalavalatina\r\nresult for args ('anitalavalatina',) was not found in cache...\r\nspent 265 microseconds in convert_to_palindrome with arguments ('anitalavalatina',). Result was: anitalavalatina\r\nanitalavalatina\r\n------------------------------------------------------\r\nstring to check (exit to terminate program): exit\r\n<\/pre>\n<h2>3. Download the Code Project<\/h2>\n<p>This was an example of how to write decorators in Python.<\/p>\n<div class=\"download\"><strong>Download<\/strong><br \/>\nYou can download the full source code of this example here: <a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/12\/decorator.zip\"><strong>python-decorator<\/strong><\/a>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Sometimes, we encounter problems that require us to extend the behavior of a function, but we don&#8217;t want to change its implementation. Some of those problems could be: logging spent time, caching, validating parameters, etc. All these solutions are often needed in more than one function: you often need to log the spent time of &hellip;<\/p>\n","protected":false},"author":109,"featured_media":1651,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[53],"tags":[],"class_list":["post-9704","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Python Decorator Tutorial - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"Sometimes, we encounter problems that require us to extend the behavior of a function, but we don&#039;t want to change its implementation.Some of those\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Python Decorator Tutorial - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"Sometimes, we encounter problems that require us to extend the behavior of a function, but we don&#039;t want to change its implementation.Some of those\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/sgvinci\" \/>\n<meta property=\"article:published_time\" content=\"2016-01-04T14:11:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-01-09T07:51:25+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Sebastian Vinci\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@sebastianvinci_\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sebastian Vinci\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\"},\"author\":{\"name\":\"Sebastian Vinci\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/06a43c63e373dff2e159bbc029b405aa\"},\"headline\":\"Python Decorator Tutorial\",\"datePublished\":\"2016-01-04T14:11:06+00:00\",\"dateModified\":\"2018-01-09T07:51:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\"},\"wordCount\":959,\"commentCount\":3,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"articleSection\":[\"Python\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\",\"name\":\"Python Decorator Tutorial - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"datePublished\":\"2016-01-04T14:11:06+00:00\",\"dateModified\":\"2018-01-09T07:51:25+00:00\",\"description\":\"Sometimes, we encounter problems that require us to extend the behavior of a function, but we don't want to change its implementation.Some of those\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Python\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/python\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Python Decorator Tutorial\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/06a43c63e373dff2e159bbc029b405aa\",\"name\":\"Sebastian Vinci\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/12d0233b49dd2a282330a987b16e81c3fbd4a8a8f5d5338348a6edd47cfff99e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/12d0233b49dd2a282330a987b16e81c3fbd4a8a8f5d5338348a6edd47cfff99e?s=96&d=mm&r=g\",\"caption\":\"Sebastian Vinci\"},\"description\":\"Sebastian is a full stack programmer, who has strong experience in Java and Scala enterprise web applications. He is currently studying Computers Science in UBA (University of Buenos Aires) and working a full time job at a .com company as a Semi-Senior developer, involving architectural design, implementation and monitoring. He also worked in automating processes (such as data base backups, building, deploying and monitoring applications).\",\"sameAs\":[\"http:\/\/www.webcodegeeks.com\/\",\"https:\/\/www.facebook.com\/sgvinci\",\"https:\/\/x.com\/sebastianvinci_\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/sebastian-vinci\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Python Decorator Tutorial - Web Code Geeks - 2026","description":"Sometimes, we encounter problems that require us to extend the behavior of a function, but we don't want to change its implementation.Some of those","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/","og_locale":"en_US","og_type":"article","og_title":"Python Decorator Tutorial - Web Code Geeks - 2026","og_description":"Sometimes, we encounter problems that require us to extend the behavior of a function, but we don't want to change its implementation.Some of those","og_url":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_author":"https:\/\/www.facebook.com\/sgvinci","article_published_time":"2016-01-04T14:11:06+00:00","article_modified_time":"2018-01-09T07:51:25+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","type":"image\/jpeg"}],"author":"Sebastian Vinci","twitter_card":"summary_large_image","twitter_creator":"@sebastianvinci_","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Sebastian Vinci","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/"},"author":{"name":"Sebastian Vinci","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/06a43c63e373dff2e159bbc029b405aa"},"headline":"Python Decorator Tutorial","datePublished":"2016-01-04T14:11:06+00:00","dateModified":"2018-01-09T07:51:25+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/"},"wordCount":959,"commentCount":3,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","articleSection":["Python"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/","url":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/","name":"Python Decorator Tutorial - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","datePublished":"2016-01-04T14:11:06+00:00","dateModified":"2018-01-09T07:51:25+00:00","description":"Sometimes, we encounter problems that require us to extend the behavior of a function, but we don't want to change its implementation.Some of those","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/python\/python-decorator-tutorial\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Python","item":"https:\/\/www.webcodegeeks.com\/category\/python\/"},{"@type":"ListItem","position":3,"name":"Python Decorator Tutorial"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/06a43c63e373dff2e159bbc029b405aa","name":"Sebastian Vinci","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/12d0233b49dd2a282330a987b16e81c3fbd4a8a8f5d5338348a6edd47cfff99e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/12d0233b49dd2a282330a987b16e81c3fbd4a8a8f5d5338348a6edd47cfff99e?s=96&d=mm&r=g","caption":"Sebastian Vinci"},"description":"Sebastian is a full stack programmer, who has strong experience in Java and Scala enterprise web applications. He is currently studying Computers Science in UBA (University of Buenos Aires) and working a full time job at a .com company as a Semi-Senior developer, involving architectural design, implementation and monitoring. He also worked in automating processes (such as data base backups, building, deploying and monitoring applications).","sameAs":["http:\/\/www.webcodegeeks.com\/","https:\/\/www.facebook.com\/sgvinci","https:\/\/x.com\/sebastianvinci_"],"url":"https:\/\/www.webcodegeeks.com\/author\/sebastian-vinci\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/9704","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/109"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=9704"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/9704\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/1651"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=9704"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=9704"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=9704"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}