diff --git a/4_decorators.py b/4_decorators.py new file mode 100644 index 0000000..b3ce93b --- /dev/null +++ b/4_decorators.py @@ -0,0 +1,40 @@ +""" +Now that we have covered the basics of a function and variables, we will quickly cover decorators. +Decorators are extremely useful and are used to wrap existing functions in another function, you can use this +for many different things, a good example is permission checks. + +Key Concepts: + **wrappers** are used to nest functions in another generalized function, this can be used for various things e.g (logging info) + **args** we use the "*args* paramater to capture all positional based arguements, these come before keyword arguements. + it sounds more complicated than it is, e.g + myfunction(arg1, arg2, arg3) + **kwargs** are arguements defined by a key rather than their position. e.g + myfunction(keyword1="foo", keyword2="bar") + **__name__** is just another magic/dunder method that returns the function's name. +""" + +import time + +def timed(function: any) -> any: #initial function takes in another function as a arguement + + def wrapper(*args: any, **kwargs: any) -> any: # the wrapper, takes in the arguements and keyword arguements from the function + before = time.time() + output = function(*args, **kwargs) + after = time.time() + print(f"{function.__name__} with output of {output} took {after-before}s to execute.") + return output # ensure the output of the function is passed back + + return wrapper + +@timed +def exponential_function(n: int) -> int: # O(n^2) time complexity due to nested loops + output = 0 + for i in range(n): + for j in range(i): + output += j + return output + +# Test the decorated function +print(exponential_function(1000)) # This will take some time to compute +exponential_function(10000) # This will take noticeably more time to compute + diff --git a/5_pandas.py b/5_pandas.py new file mode 100644 index 0000000..e69de29