Python Decorators

Table of Contents

  • What are decorators in Python?
  • Prerequisites for learning Decorators
  • Getting back to Decorators
  • Decorating Functions with Parameters
  • Chaining Decorators in Python

What are decorators in Python?

Python has an interesting feature called decorators to add functionality to an existing code.

This is also called metaprogramming as a part of the program tries to modify another part of the program at compile time.


Prerequisites for learning decorators

In order to understand about decorators, we must first know a few basic things in Python.

We must be comfortable with the fact that, everything in Python (Yes! Even classes), are objects. Names that we define are simply identifiers bound to these objects. Functions are no exceptions, they are objects too (with attributes). Various different names can be bound to the same function object.

Here is an example.

def first(msg):
   print(msg)

first("Hello")
second = first
second("Hello")

When you run the code, both functions first and second gives same output. Here, the names first and second refer to the same function object.

Now things start getting weirder.

Functions can be passed as arguments to another function.

If you have used functions like mapfilter and reduce in Python, then you already know about this.

Such function that take other functions as arguments are also called higher order functions. Here is an example of such a function.

def inc(x):
     return x + 1

def dec(x):
     return x - 1

def operate(func, x):
    result = func(x)
    return result

We invoke the function as follows.

>>> operate(inc,3)
4
>>> operate(dec,3)
2

Furthermore, a function can return another function.

def is_called():
     def is_returned():
            print("Hello")
     return is_returned

new = is_called()

new()
#Outputs "Hello"

Here, is_returned() is a nested function which is defined and returned, each time we call is_called().

Note: Finally, we must know about closures in Python.


Getting back to Decorators

Functions and methods are called callable as they can be called.

In fact, any object which implements the special method __call__() is termed callable. So, in the most basic sense, a decorator is a callable that returns a callable.

Basically, a decorator takes in a function, adds some functionality and returns it.

def make_pretty(func):
      def inner():
           print("I got decorated")
           func()
      return inner

def ordinary():
    print("I am ordinary")
When you run the following codes in shell,
>>> ordinary()
I am ordinary

>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary

In the example shown above, make_pretty() is a decorator. In the assignment step.

pretty = make_pretty(ordinary)

The function ordinary() got decorated and the returned function was given the name pretty.

We can see that the decorator function added some new functionality to the original function. This is similar to packing a gift. The decorator acts as a wrapper. The nature of the object that got decorated (actual gift inside) does not alter. But now, it looks pretty (since it got decorated).

Generally, we decorate a function and reassign it as,

ordinary = make_pretty(ordinary).

This is a common construct and for this reason, Python has a syntax to simplify this.

We can use the @ symbol along with the name of the decorator function and place it above the definition of the function to be decorated. For example,

@make_pretty
def ordinary():
    print("I am ordinary")

is equivalent to

def ordinary():
    print("I am ordinary")
ordinary = make_pretty(ordinary)

This is just a syntactic sugar to implement decorators.


Decorating Functions with Parameters

The above decorator was simple and it only worked with functions that did not have any parameters. What if we had functions that took in parameters like below?

def divide(a, b):
    return a/b

This function has two parameters, a and b. We know, it will give error if we pass in b as 0.

>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

Now let’s make a decorator to check for this case that will cause the error.

def smart_divide(func):
      def inner(a,b):
             print("I am going to divide",a,"and",b)
             if b == 0:
                   print("Whoops! cannot divide")
                   return
            return func(a,b)
       return inner

@smart_divide
def divide(a,b):
    return a/b

This new implementation will return None if the error condition arises.

>>> divide(2,5)
I am going to divide 2 and 5
0.4

>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide

In this manner we can decorate functions that take parameters.

A keen observer will notice that parameters of the nested inner() function inside the decorator is same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameter.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such decorator will be.

def works_for_all(func):
    def inner(*args, **kwargs):
        print("I can decorate any function")
        return func(*args, **kwargs)
    return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

def star(func):
        def inner(*args, **kwargs):
               print("*" * 30)
               func(*args, **kwargs)
               print("*" * 30)
         return inner

def percent(func):
        def inner(*args, **kwargs):
               print("%" * 30)
               func(*args, **kwargs)
               print("%" * 30)
         return inner

@star
@percent
def printer(msg):
     print(msg)
printer("Hello")

This will give the output.

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

The above syntax of,

@star
@percent
def printer(msg):
    print(msg)

is equivalent to

def printer(msg):
    print(msg)
printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

@percent
@star
def printer(msg):
    print(msg)

The execution would take place as,

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Advertisements

What does an empty return in Python mean?

Example program:

 if hasattr(self, 'moved_away'):
        return        # and here code continues

Solution 1:

It means it will return None. You could remove the return and it would still return None because all functions that don’t specify a return value in python will by default return None.

In this particular case it means the code will go no further if the object has the attribute 'moved_away', without the return any code below would be evaluated even if the if statement evaluated to True.

So you can think of it as being similar to a break statement in a loop when you have a condition you want to exit the loop on, without the break the code would continue to be evaluated.

Solution 2:

return exits the current methods.

So, here it will stop the execution & return None.

Property in python

Table of Contents

  • An Example To Begin With
  • Using Getters and Setters
  • The Power of @property
  • Digging Deeper into Property

Python has a great concept called property which makes the life of an object oriented programmer much simpler.

Before defining and going into details of what @property is, let us first build an intuition on why it would be needed in the first place.


An Example To Begin With

Let us assume that you decide to make a class that could store the temperature in degree Celsius. It would also implement a method to convert the temperature into degree Fahrenheit. One way of doing this is as follows.

# program
class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

We could make objects out of this class and manipulate the attribute temperature as we wished. Try these on Python shell.

>>> # create new object
>>> man = Celsius()

>>> # set temperature
>>> man.temperature = 37

>>> # get temperature
>>> man.temperature
37

>>> # get degrees Fahrenheit
>>> man.to_fahrenheit()
98.60000000000001

The extra decimal places when converting into Fahrenheit is due to the floating point arithmetic error (try 1.1 + 2.2 in the Python interpreter).

Whenever we assign or retrieve any object attribute like temperature, as show above, Python searches it in the object’s __dict__ dictionary.

>>> man.__dict__
{'temperature': 37}

Therefore, man.temperature internally becomes man.__dict__['temperature'].

Now, let’s further assume that our class got popular among clients and they started using it in their programs. They did all kinds of assignments to the object.

One fateful day, a trusted client came to us and suggested that temperatures cannot go below -273 degree Celsius (students of thermodynamics might argue that it’s actually -273.15), also called the absolute zero. He further asked us to implement this value constraint. Being a company that strive for customer satisfaction, we happily heeded the suggestion and released version 1.01 (an upgrade of our existing class).


Using Getters and Setters

An obvious solution to the above constraint will be to hide the attribute temperature (make it private) and define new getter and setter interfaces to manipulate it. This can be done as follows.

# program
class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)
   def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32
   # new update
   def get_temperature(self):
      return self._temperature
   def set_temperature(self, value):
      if value < 273:
          raise ValueError(“Temperature below -273 is not possible”)
      self._temperature = value
We can see above that new methods get_temperature() and set_temperature() were defined and furthermore, temperature was replaced with _temperature. An underscore (_) at the beginning is used to denote private variables in Python.
>>> c = Celsius(-277)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)

>>> c.set_temperature(-300)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

This update successfully implemented the new restriction. We are no longer allowed to set temperature below -273.

Please note that private variables don’t exist in Python. There are simply norms to be followed. The language itself don’t apply any restrictions.

>>> c._temperature = -300
>>> c.get_temperature()
-300

But this is not of great concern. The big problem with the above update is that, all the clients who implemented our previous class in their program have to modify their code from obj.temperature to obj.get_temperature() and all assignments like obj.temperature = val to obj.set_temperature(val).

This refactoring can cause headaches to the clients with hundreds of thousands of lines of codes.

All in all, our new update was not backward compatible. This is where property comes to rescue.


The Power of @property

The pythonic way to deal with the above problem is to use property. Here is how we could have achieved it.

# program
class Celsius:
  def __init__(self, temperature = 0):
      self.temperature = temperature
  def to_fahrenheit(self):
      return (self.temperature * 1.8) + 32
  def get_temperature(self):
     print(“Getting value”)
     return self._temperature
  def set_temperature(self, value):
     if value < 273:
         raise ValueError(“Temperature below -273 is not possible”)
     print(“Setting value”)
     self._temperature = value
  temperature = property(get_temperature,set_temperature)

And, issue the following code in shell once you run it.

>>> c = Celsius()

We added a print() function inside get_temperature() and set_temperature() to clearly observe that they are being executed.

The last line of the code, makes a property object temperature. Simply put, property attaches some code (get_temperature and set_temperature) to the member attribute accesses (temperature).

Any code that retrieves the value of temperature will automatically call get_temperature()instead of a dictionary (__dict__) look-up. Similarly, any code that assigns a value to temperature will automatically call set_temperature(). This is one cool feature in Python.

We can see above that set_temperature() was called even when we created an object.

Can you guess why?

The reason is that when an object is created, __init__() method gets called. This method has the line self.temperature = temperature. This assignment automatically called set_temperature().

>>> c.temperature
Getting value
0

Similarly, any access like c.temperature automatically calls get_temperature(). This is what property does. Here are a few more examples.

>>> c.temperature = 37
Setting value

>>> c.to_fahrenheit()
Getting value
98.60000000000001

By using property, we can see that, we modified our class and implemented the value constraint without any change required to the client code. Thus our implementation was backward compatible and everybody is happy.

Finally note that, the actual temperature value is stored in the private variable _temperature. The attribute temperature is a property object which provides interface to this private variable.


Digging Deeper into Property

In Python, property() is a built-in function that creates and returns a property object. The signature of this function is

property(fget=None, fset=None, fdel=None, doc=None)

where, fget is function to get value of the attribute, fset is function to set value of the attribute, fdel is function to delete the attribute and doc is a string (like a comment). As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

>>> property()
<property object at 0x0000000003239B38>

A property object has three methods, getter()setter(), and delete() to specify fgetfset and fdel at a later point. This means, the line

temperature = property(get_temperature,set_temperature)

could have been broken down as

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

These two pieces of codes are equivalent.

Programmers familiar with decorators in Python can recognize that the above construct can be implemented as decorators.

We can further go on and not define names get_temperature and set_temperature as they are unnecessary and pollute the class namespace. For this, we reuse the name temperaturewhile defining our getter and setter functions. This is how it can be done.

# program
class Celsius:
    def __init__(self, temperature = 0):
        self._temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    @property
    def temperature(self):
        print(“Getting value”)
        return self._temperature
   @temperature.setter
    def temperature(self, value):
        if value < 273:
             raise ValueError(“Temperature below -273 is not possible”)
       print(“Setting value”)
       self._temperature = value
The above implementation is both, simple and recommended way to make properties. You will most likely encounter these types of constructs when looking for property in Python.

How can you Get the Google cache age of any URL or web page?

Ans: Use the following URL format:

http://webcache.googleusercontent.com/search?q=cache:URLGOESHERE

Be sure to replace “URLGOESHERE” with the proper web address of the page or site whose cache you want to retrieve and see the time for. For example, to check the Google Webcache age of edureka.co you’d use the following URL:

http://webcache.googleusercontent.com/search?q=cache:edureka.co

What is monkey patching in Python?

Ans: In Python, the term monkey patch only refers to dynamic modifications of a class or module at run-time.

Consider the below example:

# m.py
class MyClass:
   def f(self):
      print "f()"

We can then run the monkey-patch testing like this:

import m
def monkey_f(self):
    print "monkey_f()"

m.MyClass.f = monkey_f
obj = m.MyClass()
obj.f()

The output will be as below:

monkey_f()

As we can see, we did make some changes in the behavior of f() in MyClass using the function we defined, monkey_f(), outside of the module m.

Copy in Python (Deep Copy and Shallow Copy)

Python defines a module which allows to deep copy or shallow copy mutable object using the inbuilt functions present in the module “copy“.

Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.

Deep copy
Deep

In case of deep copy, a copy of object is copied in other object. It means that any changesmade to a copy of object do not reflect in the original object.
In python, this is implemented using “deepcopy()” function.

# Python code to demonstrate copy operations
# importing "copy" for copy operations
import copy
# initializing list 1
li1 = [1, 2, [3,5], 4]
# using deepcopy to deep copy
li2 = copy.deepcopy(li1)
# original elements of list
print ("The original elements before deep copying")
for i in range(0,len(li1)):
print (li1[i],end=" ")
# adding and element to new list
li2[2][0] = 7
# Change is reflected in l2
print ("The new list of elements after deep copying ")
for i in range(0,len( li1)):
print (li2[i],end=" ")
# Change is NOT reflected in original list
# as it is a deep copy
print ("The original elements after deep copying")
for i in range(0,len( li1)):
print (li1[i],end=" ")

Output:

The original elements before deep copying
1 2 [3, 5] 4 
The new list of elements after deep copying 
1 2 [7, 5] 4 
The original elements after deep copying
1 2 [3, 5] 4 

In the above example, the change made in the list did not effect in other list, indicating the list is deep copied.

 

Shallow copy
Shallow

In case of shallow copy, a reference of object is copied in other object. It means that any changes made to a copy of object do reflect in the original object.
In python, this is implemented using “copy()” function.

# Python code to demonstrate copy operations
# importing "copy" for copy operations
import copy
# initializing list 1
li1 = [1, 2, [3,5], 4]
# using copy to shallow copy
li2 = copy.copy(li1)
# original elements of list
print ("The original elements before shallow copying")
for i in range(0,len(li1)):
    print (li1[i],end=" ")
# adding and element to new list
li2[2][0] = 7
# checking if change is reflected
print ("The original elements after shallow copying")
for i in range(0,len( li1)):
    print (li1[i],end=" ")

Output:

The original elements before shallow copying
1 2 [3, 5] 4 
The original elements after shallow copying
1 2 [7, 5] 4 

In the above example, the change made in the list did effect in other list, indicating the list is shallow copied.

Important Points:

The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):

  • A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
  • A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

Python memory management 

In this page we will discuss various topics like different type of scope of variables and how python interpreter uses run-time stack and heap.

Scope

Scope refers to a “part of program” where a collection of identifiers are visible. A scope is just the range in which a variable can be modified or change in
The identifier must be defined using id = … 
In previous chapters we explored about variables, assignment operator, but now we have to explore little bit more.
if i write, for instance
k = 9 
You would say k equals to 9. It is careless saying. The meaning of above expression is that k is a reference that points to an object that contains 9 inside it. In simple words references have names; and objects are pointed to by references.

Local scope

Consider the below program, contain several scopes. The light yellow region is called the Local scope. What is local scope ? The local scope is the scope of function that the Python interpreter is currently executing. When you reference an identifier in your code, Python interpreter first examines the local scope to if the identifier is defined there. See the below code, While executing line 7, the reference k is used, if Python interpreter finds k in the local scope, it looks up the corresponding value and retrieves it.

Scope of variable

Source code:
k = 4

def main():
	list1 = []
	
	def add():
		for x in xrange(k):
			list1.append(x)
		print list1
	
	add()
	
main()

Enclosing Scope

If Python interpreter does not find the k within the local scope, it will examine the Enclosing Scope to see if it can get k there. While excuting line 7, the enclosing scope is the light green. The identifier defined in this enclosing scope include list1. While executing line 4 the light green region is the local scope. Every function definition defines a different scope.

Note: It must be clear that the scope does not include the function name itself, but it includes the function’s parameter and body

Global scope

The scope outside of all functions is called the global scope. In above figure k is defined in global scope.

Buit-In scope

The last scope in Python is the Built-In scope. The Global scope is limited with in the module. Consider if an identifier is not found within any of the scopes including Global scope within the modules, then Python interpeter will examine the built-in identifier to check if it is defined there. For a moment, consider the identifier str() If you were to write the following:

z = str(34)
Python would first examine in the local scope to check if str were defined. If str is not found within the local scope, Python would go into all enclosing scopes. If str is not found in the enclosing scope then Python would examine the Buit-In function, where it would surly find the str function or class.
Note: Now you know about scope, it should now be clear, that’s why it is always said don’t use identifiers that is already exists in the buit-in scope

The Run-time stack and the Heap

As we learned about scopes. Local scope of function, contained parameters and variables, must be stored someplace in the RAM of Computer. Python divided the RAM into tow parts called Run-time stack and the Heap.
The activation record is a “chunk of memory” (a bunch of buckets) which contains all the information necessary to keep track of a “function call”. The run-time stack is a stack of Activation Records. When a function is called the Python interpreter pushes the activation record of function onto the run-time stack. When a function returns, its corresponding activation record is popped from the run-time stack.
The Heap is area of RAM where all values (objects) are stored. The run-time stack never contains the object. The run-time stack store only references pointed to corresponding objects in the heap.

                                   Python Run-time stack and heap

Python Run-time stack and heap

When Python executing lines 7,8 and 9 of the code, the run-time stack looks as shown in figure above. There are three activation records on run-time stack. Python Interpreter first pushed the module Activation Record, While executing module the Python interpreter went through top to bottom and put variable definition of module scope into the activation record of module. Activation record of module consisted of the reference k to the value 4.

Note:
When you write k = 4 it means K is reference in stack, binds to object containing 4, stored in heap. When you use del()built-in function to delete k it deletes the binding. Object containing 4 on the heap is also reclaimed by the garbage collector because there are no references pointing at it anymore.

At the end of module the main function is called. When main function was called python interpreter pushed the activation record of main function. The main function activation record consisted of Variable, list1, defined in the main function. At the end of main function the add function is called, then activation record of add function is pushed onto the stack.

How To Use String Formatters in Python 2 and 3

Introduction

Python’s str.format() method of the string class allows you to do variable substitutions and value formatting. This lets you concatenate elements together within a string through positional formatting.

This tutorial will guide you through some of the common uses of formatters in Python, which can help make your code and program more readable and user friendly.

Using Formatters

Formatters work by putting in one or more replacement fields or placeholders — defined by a pair of curly braces {} — into a string and calling the str.format() method. You’ll pass into the method the value you want to concatenate with the string. This value will be passed through in the same place that your placeholder is positioned when you run the program.

Let’s print out a string that uses a formatter:

print("Sammy has {} balloons.".format(5))
Output
Sammy has 5 balloons.

In the example above, we constructed a string with a pair of curly braces as a placeholder:

"Sammy has {} balloons."

We then added the str.format() method and passed the value of the integer 5 to that method. This places the value of 5 into the string where the curly braces were:

Sammy has 5 balloons.

We can also assign a variable to be equal to the value of a string that has formatter placeholders:

open_string = "Sammy loves {}."
print(open_string.format("open source"))
Output
Sammy loves open source.

In this second example, we concatenated the string "open source" with the larger string, replacing the curly braces in the original string.

Formatters in Python allow you to use curly braces as placeholders for values that you’ll pass through with the str.format() method.

Using Formatters with Multiple Placeholders

You can use multiple pairs of curly braces when using formatters. If we’d like to add another variable substitution to the sentence above, we can do so by adding a second pair of curly braces and passing a second value into the method:

new_open_string = "Sammy loves {} {}."                      #2 {} placeholders
print(new_open_string.format("open-source", "software"))    #Pass 2 strings into method, separated by a comma
Output
Sammy loves open-source software.

To add another substitution, we added a second pair of curly braces into the original string. Then, we passed two strings into the str.format() method, separating them by a comma.

Following the same syntax, we can add additional substitutions:

sammy_string = "Sammy loves {} {}, and has {} {}."                      #4 {} placeholders
print(sammy_string.format("open-source", "software", 5, "balloons"))    #Pass 4 strings into method
Output
Sammy loves open-source software, and has 5 balloons.

In sammy_string we added 4 pairs of curly braces as placeholders for variable substitution. We then passed 4 values into the str.format() method, mixing string and integer data types. Each of these values are separated by a comma.

Reordering Formatters with Positional and Keyword Arguments

When we leave curly braces empty without any parameters, Python will replace the values passed through the str.format() method in order. As we have seen, so far, a formatter construction with two empty curly braces with two values passed through will look like this:

print("Sammy the {} has a pet {}!".format("shark", "pilot fish"))
Output
Sammy the shark has a pet pilot fish!

The first pair of curly braces is substituted with the string value of "shark", and the second pair is substituted with the string value of "pilot fish".

The values that exist within the method look like this:

("shark", "pilot fish")

They are essentially the tuple data type and each individual value contained in the tuple can be called by its index number, which starts with the index number 0.

We can pass these index numbers into the curly braces that serve as the placeholders in the original string:

print("Sammy the {0} has a pet {1}!".format("shark", "pilot fish"))

In the above example, the output will be what we get without passing index numbers into the braces as we are calling the values in the tuple in order:

Output
Sammy the shark has a pet pilot fish!

But, if we reverse the index numbers with the parameters of the placeholders we can reverse the values being passed into the string:

print("Sammy the {1} has a pet {0}!".format("shark", "pilot fish"))
Output
Sammy the pilot fish has a pet shark!

If you call an index number of 2 in a tuple that has values at index positions 0 and 1, then you are calling on a value that is out of range. When you call an index number that is out of range, you’ll receive an error message:

print("Sammy the {2} has a pet {1}!".format("shark", "pilot fish"))
Output
IndexError: tuple index out of range

The error message we see refers to the tuple only having values at index numbers 0 and 1, therefore placing index number 2 out of range.

Let’s add a few more placeholders and a few more values to pass to them, so we can understand how we can reorder formatters a little better. First, here is a new string with four placeholders:

print("Sammy is a {}, {}, and {} {}!".format("happy", "smiling", "blue", "shark"))
Output
Sammy is a happy, smiling and blue shark!

Without parameters, the values that are passed into the str.format() method are concatenated into the string in order.

The string values contained in the tuple correspond to the following index numbers:

“happy” “smiling” “blue” “shark”
0 1 2 3

Let’s use the index numbers of the values to change the order that they appear in the string:

print("Sammy is a {3}, {2}, and {1} {0}!".format("happy", "smiling", "blue", "shark"))
Output
Sammy is a shark, blue, and smiling happy!

Since we started with index number 3, we called the last value of "shark" first. The other index numbers included as parameters change the order of how the words appear within the original string.

In addition to positional arguments, we can also introduce keyword arguments that are called by their keyword name:

print("Sammy the {0} {1} a {pr}.".format("shark", "made", pr = "pull request"))
Output
Sammy the shark made a pull request.

This example shows the use of a keyword argument being used with positional arguments. We can fill in the keyword argument pr alongside positional arguments, and can move these arguments around to change the resulting string:

print("Sammy the {pr} {1} a {0}.".format("shark", "made", pr = "pull request"))
Output
Sammy the pull request made a shark.

Positional and keyword arguments used with string formatters give us more control over manipulating our original strings through reordering.

Specifying Type

We can include more parameters within the curly braces of our syntax. We’ll use the format code syntax {field_name:conversion}, where field_name specifies the index number of the argument to the str.format() method that we went through in the reordering section, and conversion refers to the conversion code of the data type that you’re using with the formatter.

The conversion type refers to the the single-character type code that Python uses. The codes that we’ll be using here are s for string, d to display decimal integers (10-base), and f which we’ll use to display floats with decimal places. You can read more about the Format-Specification Mini-Language through Python 3’s official documentation.

Let’s look at an example where we have an integer passed through the method, but want to display it as a float by adding the f conversion type argument:

print("Sammy ate {0:f} percent of a {1}!".format(75, "pizza"))
Output
Sammy ate 75.000000 percent of a pizza!

We used the syntax of {field_name:conversion} for the first curly brace replacement field to output a float. The second curly braces only uses the first parameter {field_name}.

In the example above, there are a lot of numbers displaying after the decimal point, but you can limit those. When you are specifying f for float values, you can additionally specify the precision of that value by including a full stop . followed by the number of digits after the decimal you would like to include.

If Sammy ate 75.765367% of the pizza, but we don’t need to have a high level of accuracy, we can limit the places after the decimal to 3 by adding .3 before the conversion type f:

print("Sammy ate {0:.3f} percent of a pizza!".format(75.765367))
Output
Sammy ate 75.765 percent of a pizza!

If we just want one decimal place, we can rewrite the string and method like so:

print("Sammy ate {0:.1f} percent of a pizza!".format(75.765367))
Output
Sammy ate 75.8 percent of a pizza!

Note that modifying precision will cause the number to be rounded.

Although we display a number with no decimal places as a float, if we try to change the float to an integer by using the d conversion type, we will receive an error:

print("Sammy ate {0:d} percent of a pizza!".format(75.765367))
Output
ValueError: Unknown format code 'd' for object of type 'float'

If you would like no decimal places to be shown, you can write your formatter like so:

print("Sammy ate {0:.0f} percent of a pizza!".format(75.765367))
Output
Sammy ate 75 percent of a pizza!

This will not convert your float to an integer, but instead limit the number of places shown after the decimal point.

Padding Variable Substitutions

Because the placeholders are replacement fields, you can pad or create space around an element by increasing field size through additional parameters. This can be useful when we need to organize a lot of data visually.

We can add a number to indicate field size (in terms of characters) after the colon : in the curly braces of our syntax:

print("Sammy has {0:4} red {1:16}!".format(5, "balloons"))
Output
Sammy has    5 red balloons        !

In the example above, we gave the number 5 a character field size of 4, and the string balloons a character field size of 16 (because it is a long string).

As we see, by default strings are left-justified within the field, and numbers are right-justified. You can modify this by placing an alignment code just following the colon. < will left-align the text in a field, ^ will center the text in the field, and > will right-align it.

Let’s left-align the number and center the string:

print("Sammy has {0:<4} red {1:^16}!".format(5, "balloons"))
Output
Sammy has 5    red     balloons    !

Now we see that 5 is left-aligned, providing space in the field before red, and balloons is centered in its field with space to the left and right of it.

By default, when we make a field larger with formatters, Python will fill the field with whitespace characters. We can modify that to be a different character by specifying the character we want it to be directly following the colon:

print("{:*^20s}".format("Sammy"))
Output
*******Sammy********

We are accepting the string being passed to str.format() in the index position of 0 since we did not specify otherwise, including the colon, and specifying that we will use * instead of space to fill up the field. We’re centering the string with ^, specifying that the field is 20 characters in size, and also indicating that we are working with a string conversion type by including s.

We can combine these parameters with other parameters we’ve used before:

print("Sammy ate {0:5.0f} percent of a pizza!".format(75.765367))
Output
Sammy ate    76 percent of a pizza!

In the parameters within the curly braces, we specified the index field number of the float and included the colon, indicated the size of the field number and included the full stop, wrote in the number of places after the decimal place, and then specified the conversion type of f.

Using Variables

So far, we have passed integers, floats, and strings into the str.format() method, but we can also pass variables through the method. This works just like any other variable.

nBalloons = 8
print("Sammy has {} balloons today!".format(nBalloons))
Output
Sammy has 8 balloons today!

We can use variables for both the original string and what is passed into the method :

sammy = "Sammy has {} balloons today!"
nBalloons = 8
print(sammy.format(nBalloons))
Output
Sammy has 8 balloons today!

Variables can be easily substituted for each part of our formatter syntax construction. This makes it easier to work with when we are taking in user-generated input and assigning those values to variables.

Using Formatters to Organize Data

Formatters can be seen in their best light when they are being used to organize a lot of data in a visual way. If we are showing databases to users, using formatters to increase field size and modify alignment can make your output more readable.

Let’s look at a typical for loop in Python that will print out ii*i, and i*i*i in the range from 3 to 12:

for i in range(3,13):
    print(i, i*i, i*i*i)
Output
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
11 121 1331
12 144 1728

While the output is organized in a way, the numbers overflow into each other’s columns, making the bottom of the output less readable. If you are working with a bigger data set with many small and big numbers, this can pose a problem.

Let’s use formatters to give more space to these numbers:

for i in range(3,13):
    print("{:3d} {:4d} {:5d}".format(i, i*i, i*i*i))

Here, in our curly braces, we didn’t add the field name for index number and started with the colon, followed by the number for the field size, and a d conversion type since we’re working with integers. In this example, we accommodated for the size of each expected output, giving 2 extra character spaces for each, depending on the maximum possible number size, so our output looks like this:

Output
  3    9    27
  4   16    64
  5   25   125
  6   36   216
  7   49   343
  8   64   512
  9   81   729
 10  100  1000
 11  121  1331
 12  144  1728

We can specify a consistent field size number in order to have even columns, making sure that we accommodate the larger numbers:

for i in range(3,13):
    print("{:6d} {:6d} {:6d}".format(i, i*i, i*i*i))
Output
     3      9     27
     4     16     64
     5     25    125
     6     36    216
     7     49    343
     8     64    512
     9     81    729
    10    100   1000
    11    121   1331
    12    144   1728

We can also manipulate the alignment of the columns by adding <^, and > for text alignment, change dto f to add decimal places, change field name index numbers, and more to ensure that we are displaying the data as we would like.

Conclusion

Using formatters for variable substitution can be effective way to concatenate strings and organize values and data. Formatters represent a simple but non-descriptive way for passing variable substitutions into a string, and are useful for making sure output is readable and user friendly.