Python @ DjangoSpin

50+ Know-How(s) Every Pythonista Must Know

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 21 minutes

Page #8


Using Decorators

Decorators is a Python-specific feature, virtue of which you can define a function called a decorator function. This decorator function takes an object, manipulates it using its own object and returns this latter object. For example, in the event of decorating a function:

def decoratorFunction(inputFunction):
    def manipulateInputFunction():
        capture return value of inputFunction
        return manipulatedReturnValueOfInputFunction
    return manipulateInputFunction
 
@decoratorFunction
def functionToBeDecorated():
    # body of function
    returns an object, say a string
Significance of @ Notation

Any call to functionToBeDecorated() translates to decoratorFunction() with functionToBeDecorated as its argument i.e. functionToBeDecorated() becomes decoratorFunction(functionToBeDecorated)(). For example:

stringOne = functionToBeDecorated()
BECOMES
stringOne = decoratorFunction(functionToBeDecorated)()
Example

Let’s make the output of a function fancier by wrapping its return value with additional text. We will make use of the decorator annotation (@) provided by Python.

def decorateMyFunction(originalFunction):
    '''Decorates a function by wrapping its return value in a pair of HTML paragraph tags.'''
    def addAdditionalText():
        # Obtain string returned by original function
        textFromOriginalFunction = originalFunction()
        # Adding new functionality to the function being decorated
        return "<p>" + textFromOriginalFunction + "</p>"
    return addAdditionalText
 
@decorateMyFunction
def functionToBeDecorated():
    '''A simple function that returns a string.'''
    return "Hi there!"
 
print( functionToBeDecorated() )                  # OUTPUT: <p>Hi there!</p>

Understanding Different Types of Attributes in Object Oriented Python

There are 4 conventions followed while naming variables in Python:

  1. Dunder attributes/Magic attributes such as __doc__, __name__ etc. are named with double underscores wrapped around the attribute name. It is general consensus to use the dunder attributes that Python is shipped with, and not create new ones.
  2. Variables/Attributes intended for private use by the class or module are prefixed with a single underscore e.g. _attr. Note that this is merely a convention, naming an attribute this way does not make it unusable by external classes and modules. It is merely a hint for fellow developers.
  3. Variables/Attributes intended for public use should be named in lower case i.e. variable_name. Note that this is also a convention only, you need not follow it strictly. You can opt for camelCasing as well, like I have done in examples all across the djangoSpin website. The important thing is that you know that this is a popular convention, and while writing professional Python code, you should use lower case variable names with underscores rather than camel casing.
  4. Variables/Attributes that are intended for private use by the class or module and not open for subclassing are named with double underscores in the beginning i.e. __variable_name. These can be accessed using a special syntax as shown below.
>>> class Man(object):
	def __init__(self):
		self.var_one = 1			# public variable
		self._var_two = 2			# private variable, open for subclassing and external use
		self.__var_three = 3		# private variable, not open for subclassing and external use
		self.__var_four__ = 4		# dunder variable

		
>>> ethan = Man()
>>> ethan.__dict__
{'_var_two': 2, '__var_four__': 4, 'var_one': 1, '_Man__var_three': 3}

>>> _Man__var_three
Traceback (most recent call last):
    _Man__var_three
NameError: name '_Man__var_three' is not defined

>>> Man._Man__var_three
Traceback (most recent call last):
    Man._Man__var_three
AttributeError: type object 'Man' has no attribute '_Man__var_three'

>>> ethan._Man__var_three			# correct syntax to access a private variable which is not open for subclassing and external use
3

Finding pathnames matching a Unix-style pattern

The glob standard library helps to find pathnames matching a Unix-style pattern, using its glob() function. You can use Unix wildcards such as *, ? and character ranges with [ ].

# Directory structure of sample Folder
│   1ab.txt
│   a2.txt
│   a2b.txt
│   abc.txt
│   def.txt
│   picture.jpg
│   python.py
│   word.docx
└───dir1
        file1underDir1.txt

>>> import glob

# The * denotes one or more characters
>>> glob.glob('*.txt')										# matches files with extension 'txt'
['1ab.txt', 'a2.txt', 'a2b.txt', 'abc.txt', 'def.txt']
>>> glob.glob('*.doc*')										# matches files with extension having the string 'doc' in it.
['word.docx']


# The ? denotes a single character
>>> glob.glob('??.txt')										# matches text files with only two letters in its name.
['a2.txt']


# [character-range] matches any character lying in the said range.
>>> glob.glob('[a-z][0-9].txt')								# matches two-character named text files with an alphabetic first character & numeric second character.
['a2.txt']
>>> glob.glob('[a-z][0-9][a-z].txt')						# matches three-character named text files with an alphabetic first character, numeric second character & an alphabetic third character.
['a2b.txt']


# The '**' pattern matches any file, directory or subdirectory.
>>> glob.glob('**')
['1ab.txt', 'a2.txt', 'a2b.txt', 'abc.txt', 'def.txt', 'dir1', 'picture.jpg', 'python.py', 'word.docx']

Defining Class Methods in Python: The @classmethod decorator

In Object Oriented Python, an instance method perceives its argument as the object on which the method is being called. It can operate on class attributes as well as instance attributes. Here's an example:

>>> class Toy:
	'''Toy class'''
	count = 0

	def __init__(self, name, color):
		'''sets instance attributes to provided values; increments counter and prints it.'''
		self.name = name
		self.color = color
		Toy.count += 1
		print("Toys manufactured so far:", Toy.count)

		
>>> woody = Toy('Woody', 'Brown')
Toys manufactured so far: 1
>>> buzz = Toy('Buzz Lightyear', 'White & Purple')
Toys manufactured so far: 2

@classmethod: A method following the @classmethod decorator will perceive its first argument to be the class, and not the instance. The @classmethod decorator denotes a method that operates on the class attributes rather than instance attributes. Let's segregate the initializiation and count increment operation into two different methods to demonstrate this.

>>> class Toy:
	'''Toy class'''
	count = 0

	def __init__(self, name, color):
		'''sets instance attributes to provided values; increments counter and prints it.'''
		self.name = name
		self.color = color
		self.incrementCount()
		
	@classmethod
	def incrementCount(cls):
		cls.count += 1
		print("Toys manufactured so far:", cls.count)

		
>>> woody = Toy('Woody', 'Brown')
Toys manufactured so far: 1
>>> buzz = Toy('Buzz Lightyear', 'White & Purple')
Toys manufactured so far: 2

Note that the class method can be invoked by the object as well. We can call it using the Toy.incrementCount() notation as well, but that would defeat the purpose of the example. Also, the argument can be called anything apart from 'cls', it makes more sense to call it something that is synonymous to the word 'class' (remember, class is a keyword).


Defining Static Methods in Python: The @staticmethod decorator

In Object Oriented Python, an instance method perceives its argument as the object on which the method is being called. It can operate on class attributes as well as instance attributes. I'll cite the example I used while demonstrating the @classmethod decorator.

>>> class Toy:
	'''Toy class'''
	count = 0

	def __init__(self, name, color):
		'''sets instance attributes to provided values; increments counter and prints it.'''
		self.name = name
		self.color = color
		Toy.count += 1
		print("Toys manufactured so far:", Toy.count)

		
>>> woody = Toy('Woody', 'Brown')
Toys manufactured so far: 1
>>> buzz = Toy('Buzz Lightyear', 'White & Purple')
Toys manufactured so far: 2

@staticmethod: A method following the @staticmethod decorator will NOT perceive its first argument to be the class or the instance. Rather, it will take the first argument to mean a regular positional argument. This is used to denote utility methods, which belong in the class code, but don't operate on the instance or the class. In the below example, checkForName() performs a validation, something like a utility method. It does not operate the instance or the class, and hence neither of these needs to be passes as an argument to it.

>>> class Toy:
	'''Toy class'''
	count = 0

	def __init__(self, name, color):
		'''sets instance attributes to provided values; increments counter and prints it.'''
		self.name = name
		self.color = color
		self.checkForName(self.name)

	@staticmethod
	def checkForName(name):
		if name.startswith('w') or name.startswith('W'):
			print("Hey! This is a random check to see if your name begins with 'W', and it does!")
		else:
			print("Oh no! Your name does not start with W, you just missed out on a goodie!")

			
>>> woody = Toy('Woody', 'Brown')
Hey! This is a random check to see if your name begins with 'W', and it does!
>>> buzz = Toy('Buzz Lightyear', 'White & Purple')
Oh no! Your name does not start with W, you just missed out on a goodie!

Enforcing Encapsulation: The property class

Getters and setters in Python can be used to set and get attributes. However, they don't strictly impose encapsulation, since attributes can be get and set without these getter-setter methods as well. Pythonic convention is that the users are trusted to access the attributes via getters-setters rather than mangling them directly. It is the Pythonic way.

An anti-Pythonic way to enforce encapsulation is to use the builtin property class and its decorator @property. The property class returns a property object. It is used to manage attributes. Its constructor takes four arguments:

  1. getter method of attribute x (keyword argument 'fget', defaults to None)
  2. setter method of attribute x (keyword argument 'fset', defaults to None)
  3. deleter method of attribute x (keyword argument 'fdel', defaults to None)
  4. docstring (help text) of attribute x (keyword argument 'doc', defaults to None)

The help(property) reveals a handy example. Consider a class C with an attribute 'x':

>>> class C(object):
	def getx(self): return self._x
	def setx(self, value): self._x = value
	def delx(self): del self._x
	x = property(getx, setx, delx, "I'm the 'x' property.")

	
>>> c = C()
>>> c.x = 5					# invokes the setter setx()
>>> c.x						# invokes the getter getx()
5
>>> del c.x					# invokes the deleter delx()
>>> help(C)
# Among other help information
 |  x
 |      I'm the 'x' property.

The @property decorator offers an easy-on-the-eye way of achieving the above functionality:

>>> class C(object):
	@property
	def x(self):
		"I am the 'x' property."
		return self._x
		
	@x.setter
	def x(self, value):
		self._x = value
		
	@x.deleter
	def x(self):
		del self._x
		
# @property implies that the function declared below (x) is passed to the property constructor as its first argument, return value (a property object) of which is assigned to the function itself i.e. x = property(fget = x). @x.setter translates to x = property(fset = x), the x on the right is the function declared after the @x.setter notation. Similarly, @x.deleter translates to x = property(fdel = x). 

# The 'doc' keyword argument of the property constructor gets set to the docstring of the function after the @property notation. So, in actuality, the @property gets translated to x = property(fget = x, fset = None, fdel = None, doc = "I am the 'x' property.")

Don't get confused by the attribute x and the property object x. The example in the documentation is ambiguous. The property object provides us with a handle to the attribute x. Here's a much cleaner example:

>>> class C(object):
	@property
	def propertyObjectOfAttrX(self):
		"I'm the 'x' property."
		return self._x
	@propertyObjectOfAttrX.setter
	def propertyObjectOfAttrX(self, value):
		self._x = value
	@propertyObjectOfAttrX.deleter
	def propertyObjectOfAttrX(self):
		del self._x

		
>>> c = C()
>>> c.propertyObjectOfAttrX = 5
>>> c.propertyObjectOfAttrX
5
>>> del c.propertyObjectOfAttrX

Note that in order for the @property decorator to work, the names of additional functions (setter and deleter) must be the same as the name of the property object i.e. the getter propertyObjectOfAttrX. Otherwise, you'll get an Attribute Error(s). I'll leave that to you to explore.

Read-only attribute

To make an attribute read-only, only specify the getter method of the attribute.

>>> class C(object):
	def __init__(self):
		self._x = 2
	@property
	def propertyObjectOfAttrX(self):
		"I'm the 'x' property."
		return self._x

>>> c = C()
>>> c.propertyObjectOfAttrX
2
>>> c.propertyObjectOfAttrX = 10
Traceback (most recent call last):
    c.propertyObjectOfAttrX = 10
AttributeError: can't set attribute

This happens because the @property decorator call translates to propertyObjectOfAttrX = property(fget = propertyObjectOfAttrX, fset = None, fdel = None, doc = "I'm the 'x' property."). Unless the user knows the variable name( i.e. _x), he/she cannot set it. This is how the property objects enforce encapsulation.


Interchanging Data Structures: list(), tuple(), dict(), set(), frozenset()

You can make any data structure from any other data structure using the builtin constructors list(), tuple(), dict(), set(), frozenset() of their respective classes. This interchanging operation is incredibly helpful in certain situations e.g. while reversing a tuple, while removing duplicates from a list, extracting keys and values from a dictionary, making a set immutable by initializing a frozenset from it etc. Expand the following for examples.

# list to tuple
# transforming a mutable list into an immutable tuple
>>> myList = [number for number in range(1, 11)]
>>> myTuple = tuple( myList )
>>> print("Type: ", type( myTuple ), ";", "Contents: ", myTuple)
Type:  <class 'tuple'> ; Contents:  (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
 
# tuple to list
# sorting a tuple
>>> tupleOfNumbers = (5, 4, 2, 3, 1)
>>> listOfNumbers = list(tupleOfNumbers)       
>>> listOfNumbers.sort()
>>> listOfNumbers
[1, 2, 3, 4, 5]
>>> newTupleOfNumbers = tuple(listOfNumbers)  
>>> newTupleOfNumbers
(1, 2, 3, 4, 5)
 
 
# list to set
# removing duplicates
>>> myList = [number for number in range(1, 11)]
>>> myList2 = [number for number in range(5, 16)]
>>> myList + myList2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> mySet = set( myList + myList2 )
>>> mySet
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
 
# set to frozenset
# making a set immutable
>>> mySet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
>>> mySet.add(16)
>>> mySet
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
>>> myImmutableSet = frozenset( mySet )
>>> myImmutableSet.add(17)
# Traceback information
AttributeError: 'frozenset' object has no attribute 'add'
 
# dict to list
# initializing a new dictionary from the keys of an exisitng dictionary
>>> myDictionary = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}
>>> myListOfKeys = list( myDictionary.keys() )
>>> myListOfKeys
['one', 'four', 'five', 'three', 'two']
>>> myNewDictionary = dict.fromkeys( myListOfKeys )
>>> myNewDictionary
{'one': None, 'four': None, 'two': None, 'three': None, 'five': None}


Using builtin chr() & ord() functions

The ord() function takes a single character (special ones too) and returns its Unicode value. Unicode is a collection of codes for more than 1,20,000 characters covering letters, numbers in a variety of languages, including symbols. Unicode values for letters a through z are 97 to 122 & A through Z are 65 to 90. It is inverse of the builtin chr() function.

The chr() function takes a Unicode value and returns the symbol or letter represented by the provided code in the Unicode dictionary. It is inverse of the builtin ord() function.

>>> ord('a')       
97     
>>> chr(97)       
'a'    

Sorting an iterable: sorted()

Python's builtin sorted() function sorts the elements an iterable object in ascending order and returns a list of the ordered elements.

>>> anUnsortedList = [3, 6, 1, 2]
>>> sorted(anUnsortedList)
[1, 2, 3, 6]

>>> anUnsortedString = 'bca'
>>> sorted(anUnsortedString)
['a', 'b', 'c']

This is a rudimentary application of the sorted() function. It has two optional keyword arguments: reverse and key. To know more about them, read this piece on the sorted() function.


Raising your own Exceptions: the raise keyword

The raise keyword is used to explicitly raise an exception. This is in contrast to when you make a mistake in your code, because then the exception is raised implicitly. Here, you are deliberately raising an exception. The raise keyword is useful in 2 scenarios, one is for reraising the exception, and other is for using exception for error-checking. Expand the following sourcecode for more on these 2 scenarios and other details related to the raise keyword.

######### RERAISING AN EXCEPTION: raise ###########
# The raise keyword in this situation is found in the except clause, as a standalone keyword. This is used when you want to do some processing in the except clause before you re-raise the most recently caught exception, also known as active exception.

>>> import io
>>> try:
    fh = open('someExistentFile.txt', 'r')
    fh.write("Attempting to write to a file opened in read-only mode.")
except io.UnsupportedOperation:
    fh.close()
    print("File closed?", fh.closed)
    raise
 
File closed? True
Traceback (most recent call last):
  File "<pyshell#292>", line 3, in <module>
    fh.write("Attempting to write to a file opened in read-only mode.")
io.UnsupportedOperation: not writable
 

######### ERROR CHECKING: raise ExceptionName(“Optional message string.”) ###########
# You can intentionally raise errors as part of error-checking. The raise keyword is followed by the Exception to be thrown if a particular check fails. You can even pass a custom message to provide a situation-specific explanation to the user.

>>> temperature = 450
>>> if temperature > 400:
    raise ValueError()
 
Traceback (most recent call last):
  File "<pyshell#257>", line 2, in <module>
    raise ValueError()
ValueError
>>>
>>>
>>>
>>> if temperature > 400:
    raise ValueError("Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!")
 
Traceback (most recent call last):
  File "<pyshell#262>", line 2, in <module>
    raise ValueError("Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!")
ValueError: Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!

# You can choose from common builtin Exceptions and just supply a custom message string, as we did in the above example. And if there is no suitable builtin Exception, you can make your own exception in the following fashion:



############ CREATING AN EXCEPTION OBJECT FROM AN EXISTING EXCEPTION TYPE #############
 
>>> TemperatureError = ValueError()
>>> raise TemperatureError
Traceback (most recent call last):
  File "<pyshell#220>", line 1, in <module>
    raise TemperatureError
ValueError
 
 
 
>>> TemperatureError = ValueError("Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!")
>>> raise TemperatureError
Traceback (most recent call last):
  File "<pyshell#246>", line 1, in <module>
    raise TemperatureError
ValueError: Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!
 
 
 
 
>>> def check_temperature(temperature):
    if temperature < 45:
        raise TemperatureError
    print("Temperature under control.")
 
     
>>> check_temperature(450)
Traceback (most recent call last):
  File "<pyshell#249>", line 1, in <module>
    check_temperature(450)
  File "<pyshell#248>", line 3, in check_temperature
    raise TemperatureError
  File "C:\Python34\lib\idlelib\run.py", line 353, in runcode
    exec(code, self.locals)
  File "<pyshell#246>", line 1, in <module>
    raise TemperatureError
ValueError: Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!

# Note that even though you are raising TemperatureError, the exception that is raised is ValueError. This is because all Exceptions are actually classes(parent of all these is the class called Exception), and classes can be instantiated i.e. objects can be made out of them. The very first line of the example (TemperatureError = ValueError()) is creating an instance/object called TemperatureError of the class ValueError. If you wish to create your own Exception class, you can do that in the following way. It is a brief write-up, which can be extended based on your knowledge of object-oriented concepts.

############# CREATING CUSTOM EXCEPTION CLASS DERIVED FROM THE PARENT CLASS OF ALL EXCEPTION CLASSES i.e. Exception. ##########
>>> class TemperatureError(Exception): pass
 
>>> myCustomExceptionObject = TemperatureError("Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!")
>>> def check_temperature(temperature):
    if temperature > 400:
        raise myCustomExceptionObject
    print("Temperature under control.")
 
     
>>> check_temperature(450)
Traceback (most recent call last):
  File "<pyshell#309>", line 1, in <module>
    check_temperature(450)
  File "<pyshell#308>", line 3, in check_temperature
    raise myCustomExceptionObject
TemperatureError: Temperature greater than 400 degrees!!! CODE GREEN! CODE GREEN!

# Note that some other languages like Java use the keyword throw instead of raise for the same functionality.

See also: 50+ Tips & Tricks for Python Developers


Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply