Python functions are a lot more flexible than you would think. They are much more than just code generation specifications for a compiler. They are full-blown objects stored in pieces of memory as such that they can be freely passed around a program and called indirectly. They also support operations that have little to do with calls at all like Attribute storage and Annotation.
“First Class” objects: Indirect function calls
Python functions fundamentally follow the python object model. It really means we can pass functions to other functions, embedded into other data structures, return a function as if they were basic data types like strings, numbers etc. Function objects also happen to support a special operation: they can be called by listing arguments in parentheses after a function expression. This is usually called a First Class Object Model. It is ubiquitous in python and a necessary part of functional programming.
For example:
Let us try to understand a following piece of code here. def statement will define a variable name used as if it had appeared on the left of an ‘=’ sign. After def runs, the function name is simply a reference to an object. It can be reassigned to other names freely and call it through any reference. In the below code, ‘p’ references the function name printmessage. We can call object through name by adding ().
>>> def printmessage(msg):
… print(msg)
…
>>> printmessage(‘Hello World: Direct Call’)
Hello World : Direct Call
>>> p = printmessage
>>> p(‘Hello world : Indirect Call’)
Hello World : Indirect Call
>>>
We can even place the function objects into data structures, as though they were integers or strings. The following code snippet embeds the function twice in a list of tuples, as a sort of actions table.
>>> def printmessage(msg):
… print(msg)
…
>>> schedule = [ (printmessage, ‘World’), (printmessage,’Welcome’)]
>>> for (func,arg) in schedule:
… func(arg)
…
World
Welcome
>>>
Functions can also be created and returned for use elsewhere – the closure created in this mode also retains the state from the enclosing scope:
>>>
>>> def makelabel(labelname):
… def printlabel(message):
… print(labelname + ‘ : ‘ + message)
… return printlabel
…
>>> l = makelabel(‘Byte’)
>>> l(‘Academy’)
Byte:Academy
>>>
Python’s universal first class object model and lack of type declarations make this language incredible and flexible programming language.
Function Introspection
Because they are objects, we can also process functions with normal object tools. In fact, functions are more flexible than you might expect. We can inspect the function objects as follows, these introspection tools allow us to explore implementation details too – functions have attached code objects, for example, which provide details on aspects such as functions local variables and arguments.
Function Attributes
Function objects are not limited to system-defined attributes, it is possible to attach arbitrary user-defined attributes as shown below.
Python Decorators
One of the applications of python function objects is Python Decorators. What is Python Decorator? Decoration is a way to specify management or augmentation code for functions and classes.
A decorator in python is any callable python object that is used to modify a function or a class.
Python decorators come in 2 related flavors.
- Function Decorators: A reference to a function is passed to decorator and the decorator returns a modified function. The modified functions contain references and calls to the original function.
- Class Decorators: A reference to a class is passed to a decorator and the decorator returns a modified class. The modified classes contain references and calls to the original class.
Function Decorators Example:
def color_text(name):
return “Coloring this ”.format(name)
def ptext_decorate(func):
def ptag_name(name):
return “<p>{0}</p>”.format(func(name))
return ptag_name
mytext = ptext_decorate(color_text)
print(mytext(‘Byte Academy’))
Output:
<p>Coloring this Byte Academy </p>
Python Decorator Syntax – Syntactic Sugar
Python makes creating and using decorators a bit cleaner and nicer for the programmer through syntactic sugar. In the above example, to decorate a text we don’t have to
mytext = ptext_decorate(color_text)
There is a neat shortcut for this. The name of the decorator should be prepended with an @symbol as follows:
def ptext_decorate(func):
def ptag_name(name):
return “<p>{0}</p>”.format(func(name))
return ptag_name
@ptext_decorate
def color_text(name):
return “Coloring this ”.format(name)
print(color_text(‘Byte Academy))
Class Decorators Example: By Decorating Methods
In python, methods are functions that expect their first parameter to be a reference to the current object. We can build decorators for methods the same way, while taking self into consideration in ptag_name function.
def ptext_decorate(func):
def ptag_name(name):
return “<p>{0}</p>”.format(func(name))
return ptag_name
class Person(object):
def __init__(self):
self.name = ‘Dennis’
self.family = ‘Gutridge’
@ptext_decorate
def getfullname(self):
return self.name + self.family
person1 = Person()
print(person1.getfullname())
Applications of Decorators
In general, decorators are ideal for extending the behavior of functions that we don’t want to modify. As structuring tools, decorators naturally foster encapsulation of code, which reduces redundancy and makes future changes easier. The reference page mentioned here has all the decorator code pieces in the form of a central repository. https://wiki.python.org/moin/PythonDecoratorLibrary
Liked what you read? Checkout Byte Academy's Software Development and Intro to Python courses.