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

Page #2

Using the builtin function range()

In a lot of situations, you may feel the need to obtain a list of numbers, such as in for loop. The builtin function range(number) provides a list of integers from 0 to one less than the number provided. When used in the form range(lowerBound, upperBound), it returns a list of integers from lowerBound to one less than upperBound. Lastly, when used in the form range(lowerBound, upperBound, stepSize), it provides a list of integers from lowerBound to upperBound with the supplied step-size.

```>>> range(4)
[0, 1, 2, 3]
>>> range(2, 7)
[2, 3, 4, 5, 6]
>>> range(0, 11, 2)
[0, 2, 4, 6, 8, 10]
>>> range(10, 1, -1)
[10, 9, 8, 7, 6, 5, 4, 3, 2]

>>> myString = 'rendezvous'
>>> for characterPosition in range( len(myString) ):
print( "Letter at index {} of {} is {}".format( characterPosition, myString, myString[characterPosition] ) )

Letter at index 0 of rendezvous is r
Letter at index 1 of rendezvous is e
Letter at index 2 of rendezvous is n
Letter at index 3 of rendezvous is d
Letter at index 4 of rendezvous is e
Letter at index 5 of rendezvous is z
Letter at index 6 of rendezvous is v
Letter at index 7 of rendezvous is o
Letter at index 8 of rendezvous is u
Letter at index 9 of rendezvous is s
```

Storing keyword arguments dynamically

We can utilize the builtin dictionary of keyword arguments of each object (stored in __dict__ attribute of the object) to store the keyword arguments supplied to it during its creation.

```>>> class Person(object):
def __init__(self, **kwargs):
for argument in kwargs.keys():
self.__setattr__(argument, kwargs[argument])

>>> ethan = Person(name = 'Ethan', age = 23, height = 170)
>>> ethan.__dict__
{'age': 23, 'name': 'Ethan', 'height': 170}
```

Enumerating with enumerate()

It is sometimes needed to have an index counter for elements of an iterable object. Python's builtin enumerate class enables us to do just this. The constructor of builtin enumerate class accepts an iterable object and returns an iterator of tuples in the form (index, value).

```>>> help(enumerate)
...
class enumerate(object)
|  enumerate(iterable[, start]) -> iterator for index, value of iterable
|
|  Return an enumerate object.  iterable must be another object that supports
|  iteration.  The enumerate object yields pairs containing a count (from
|  start, which defaults to zero) and a value yielded by the iterable argument.
|  enumerate is useful for obtaining an indexed list:
|      (0, seq[0]), (1, seq[1]), (2, seq[2]), ...
...

### EXAMPLE ###
>>> listOne = [ 'apple', 'ball', 'cat', 'dog' ]
>>> enumeratedObject = enumerate(listOne)
>>> next(enumeratedObject)
(0, 'apple')
>>> next(enumeratedObject)
(1, 'ball')

# Using a for loop to iterate over the iterator of tuples
>>> listOne = [ 'apple', 'ball', 'cat', 'dog' ]
>>> for pair in enumerate(listOne):
print(pair)

(0, 'apple')
(1, 'ball')
(2, 'cat')
(3, 'dog')

# Unpacking the tuples
>>> listOne = [ 'apple', 'ball', 'cat', 'dog' ]
>>> for index, element in enumerate(listOne):
print(index, ": ", element)

0 :  apple
1 :  ball
2 :  cat
3 :  dog
```

By default, the indexes start from 0. To alter this, Python provides us with an optional second argument, which tells the enumerate class to begin indexes from the specified value.

```>>> listOne = [ 'apple', 'ball', 'cat', 'dog' ]
>>> for index, element in enumerate(listOne, start = 1):
print(index, ": ", element)

1 :  apple
2 :  ball
3 :  cat
4 :  dog
```

Implementing FTP using ftplib module

Python's standard library module ftplib enables users to carry out operations on a remote system using File Transfer Protocol. You can transfer files, fetch directory listing, create directories etc. on the connected system. Expand the following snippet for a few chosen operations you can perform, you can view the complete list of functionality that the ftplib offers by visiting its documentation

```## CREATING AN FTP CONNECTION
>>> import ftplib
>>> ftpConnection = ftplib.FTP()
>>> ftpConnection.connect('ftp.yourDomainHere.com')

# The connect() method returns a welcome message from FTP server with status code 220 if connection successful. If unsuccessful, it raises socket.gaierror saying '[Errno 11001] getaddrinfo failed'.
# The login() method returns a success message with status code 230 if credentials are correct. If unsuccessful, it raises ftplib.error_perm saying '530 Login incorrect'

# Alternatively, you can provide this information to the constructor of the FTP class, in order to reduce method calls.

## GETTING PRESENT WORKING DIRECTORY
>>> ftpConnection.pwd()

## RETRIEVING DIRECTORY LISTING
>>> ftpConnection.dir()
# The dir() method gives long form of constituents of the current directory with information such as file permissions, created by, creation date & time etc.

# Alternatively, you can use th nlst() method.
>>> ftpConnection.nlst()
# The nlst() method returns a list of directories & files in the current directory. To view contents of a subdirectory, enter the name of subdirectory as an argument e.g. ftpConnection.nlst('subDirectoryOne').

# Alternatively, you can use the retrlines() method. The retrlines() method lists directories with either 'NLST' or 'LIST' as its argument.
>>> ftpConnection.retrlines('LIST')         # output similar to dir()
>>> ftpConnection.retrlines('NLST')         # output similar to nlst()

## CHANGING CURRENT WORKING DIRECTORY
>>> ftpConnection.cwd('subDirectoryOne')

# To move up a directory, enter two dots in the arguments.
>>> ftpConnection.cwd('..')

## DELETING A FILE
>>> ftpConnection.delete('fileName')

## CREATING A DIRECTORY
>>> ftpConnection.mkd('subDirectoryOneUnderSubDirectoryOne')

## REMOVING A DIRECTORY
ftpConnection.rmd('subDirectoryOneUnderSubDirectoryOne')

# The above works fine if the directory is empty. If it is not empty, you will receive an error saying the directory is not empty. In such a case, use the following code snippet to remove a non-empty directory.

## EMPTYING A NON-EMPTY DIRECTORY

# Navigate to the directory containing the non-empty directory, then call the following function.
def removeNonEmptyDirectory():
files = ftpConnection.nlst()

for file in files:
try:										# If file is not a directory
ftpConnection.delete(file)
except:										# If file is a directory
try:									# if directory is empty
ftpConnection.rmd(file)
except:									# if directory is not empty
ftpConnection.cwd(file)
removeNonEmptyDirectory()
ftpConnection.cwd('..')
ftpConnection.rmd(file)

# Now that the directory is empty, call the rmd() method on it to delete it.

## RENAMING A FILE/DIRECTORY
>>> ftpConnection.rename('subDirectoryOne', 'subDirectoryTwo')

# Navigate to directory on remote system where you file is placed. Then, execute the following to download the file.
>>> ftpConnection.retrbinary('RETR fileNameOnRemoteSystem.txt', open(r'fullPathToNewFileOnLocalSystemIncludingNewFileName', 'wb'))

# Navigate to directory where you want to place the file. Then execute the following.
>>> ftpConnection.storbinary('STOR fileNameOnRemoteSystem', open(r'fullPathToFileOnLocalSystemIncludingFileName', 'rb'))

# Contents of dirOne
|subDirOne
|--subDirOneFileOne.txt
|--subDirOneFileTwo.txt
|subDirTwo
|fileOne.txt
|fileTwo.txt

directoryToMove = r'pathToDirectoryOnLocalSystem'

import os
files = os.listdir(pathOfDirectoryToMove)				# ['fileOne.txt', 'fileTwo.txt', 'subDirOne', 'subDirTwo']
os.chdir(pathOfDirectoryToMove)											# changes current working directory to pathTo_dirOne

for file in files:
# if file is not a directory, then transfer it.
if os.path.isfile(pathOfDirectoryToMove + r'\{}'.format(file)):
fileHandler = open(file, 'rb')
ftpConnection.storbinary('STOR {}'.format(file), fileHandler)
fileHandler.close()
# if file is a directory, make a directory by the same name on the remote system, access it, and call uploadDirectory() again to process its contents.
elif os.path.isdir(pathOfDirectoryToMove + r'\{}'.format(file)):
ftpConnection.mkd(file)
ftpConnection.cwd(file)

ftpConnection.cwd('..')
os.chdir('..')											# go back to parent directory

## EXECUTING FTP COMMANDS
# The sendcmd() method allows you to execute FTP commands and receive a response string from the server. A list of valid FTP commands can be viewed from https://en.wikipedia.org/wiki/List_of_FTP_commands or by executing ftpConnection.sendcmd('HELP'). A couple of usage examples:
>>> ftpConnection.sendcmd('PWD')
>>> ftpConnection.sendcmd('CDUP')
>>> ftpConnection.sendcmd('QUIT')

## CLOSING AN FTP CONNECTION
>>> ftpConnection.close()
```

Working with binary files in Python

Not all files are text files. Others contain information in the form of bits and bytes, like images, formatted text files (Microsoft Word), audio-video files etc. These are called binary files and cannot be read in the same way as Python reads text files. Check this post out an elaborated discussion on working with binary files in Python.

Getting current date

The date class of Python's builtin datetime module helps us to retrieve current date.

```>>> import datetime

>>> thisDay = datetime.date.today()
>>> type(thisDay)
<class 'datetime.date'>

>>> thisDay
datetime.date(2016, 12, 24)
>>> print(thisDay)
2016-12-24
>>> thisDay.__repr__()
'datetime.date(2016, 12, 24)'
>>> thisDay.__str__()
'2016-12-24'

# In order to convert the date object generated by today() function into string, use the strftime() function. You will need to specify the format of your date with the help of directives. The following format uses three standard directives, namely %d, %m and %y for day, month and year without century, respectively.
>>> thisDay.strftime("%d/%m/%y")
'24/12/16'

# The following format uses
# %A: full name of the weekday e.g Sunday, Monday etc. Use %a for the abbreviated versions like Sun, Mon etc.
# %d: the date number e.g 01, 02, 22 etc.
# %B: full name of month e.g. January, February. Use %b for abbreviated versions like Jan, Feb etc.
# %Y: year with century e.g. 2015, 2016. Use %y as in the earlier format, for year without century.
>>> thisDay.strftime("%A %d %B %Y")
'Saturday 24 December 2016'
```

You can find the full list of directives on the official Python datetime documentation.

Prettifying output with the builtin pprint module

The builtin pprint module enables to prettify the output while printing lists, tuples & dictionaries. This is extremely useful while having a closer look at these data structures.

```</p>
>>> years = {
'1960s': [1961, 1962, 1963], '1970s': [1971, 1972, 1973],
'1980s': [1981, 1982, 1983]
}
>>> years
{'1970s': [1971, 1972, 1973], '1980s': [1981, 1982, 1983], '1960s': [1961, 1962, 1963]}

>>> import pprint
>>> pprint.pprint(years)
{'1960s': [1961, 1962, 1963],
'1970s': [1971, 1972, 1973],
'1980s': [1981, 1982, 1983]}

>>> pprint.pprint(years, indent = 8)
{       '1960s': [1961, 1962, 1963],
'1970s': [1971, 1972, 1973],
'1980s': [1981, 1982, 1983]}

>>> pprint.pprint(years, width = 20)
{'1960s': [1961,
1962,
1963],
'1970s': [1971,
1972,
1973],
'1980s': [1981,
1982,
1983]}

>>> pprint.pprint(years, depth = 1)
{'1960s': [...], '1970s': [...], '1980s': [...]}
```

Using Comprehensions to create lists, sets & dictionaries

Comprehensions are an excellent Python feature to create lists, dictionaries & sets in an alternative way. For example, to create a list of numbers 0 to 9, you would normally write the following code:

```>>> listOfNumbers = []
>>> for number in range(0, 10):
listOfNumbers.append(number)

>>> listOfNumbers                       # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

To create the same list, use the expression num for num in range(0, 10) in pair of square brackets.

```>>> listOfNumbers = [ number for number in range(0, 10) ]	# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

Collecting arguments passed to a Python script

The builtin sys module exposes objects used or maintained by the Python interpreter. One of these objects is argv, which is a list that captures the arguments passed to a Python script while calling it.

```# anyPythonFile.py
import sys
print(sys.argv)

# From the command prompt
\$ python path_to_anyPythonFile.py hello there !

# OUTPUT
['anyPythonFile.py', 'hello', 'there', '!']
```

Note that argv[0] is the name of file itself.

Generating random elements with the random module

The standard library called random helps the user to inject simulation into your Python programs. Its randint(x, y) function gives a random integer in the range marked by its arguments, inclusive of both end points. If you do not want the upper bound to be inclusive in the range, you can use the randrange(x, b) function, which even offers a step argument just like range().

```>>> import random
>>> random.randint(0, 5)			# random number from 1-5
3
>>> random.randint(0, 5)
5
>>> random.randint(0, 5)
3

>>> random.randrange(0, 5)			# random number from 1-4
1
>>> random.randrange(0, 5)
4
>>> random.randrange(0, 5)
0

>>> random.randrange(0, 5, 2)		# random number from 1-4 & step = 2
2
>>> random.randrange(0, 5, 2)
0
>>> random.randrange(0, 5, 2)
2
>>> random.randrange(0, 5, 2)
4
```

To generate random elements other than integers, we have a method called choice(), which accepts an iterable and returns a random element from it.

```>>> random.choice( ['a', 'b'] )
'a'
>>> random.choice( ['a', 'b'] )
'a'
>>> random.choice( ['a', 'b'] )
'b'
>>> random.choice( ['a', 'b'] )
'a'
>>> random.choice( ['a', 'b'] )
'b'
```