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 #4


Concatenating Strings

Python allows to concatenate two or more strings using the + operator, or by simply placing the strings adjacent to each other.

>>> a = 'Hello'' ''there!'
>>> a
'Hello there!'
>>> b = 'Hello'    ' '      'there!'
>>> b
'Hello there!'
>>> 
>>> 
>>> a = 'Hello '
>>> b = 'there!'
>>> a + b
'Hello there!'

However, it is not the ideal way to join strings. Best practice is to use the format() function of strings.

>>> "{}{}".format(a, b)
'Hello there!'

The curly braces act as placeholders for the arguments specified in the format() function.


Sending mail with Python

Python provides smtplib & email modules to facilitate sending emails.

# This script uses a gmail account to send the mail. If you
# have your account with another email provider, you will have
# to change the smtp_variable and smtp_port variable accordingly.
 
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
 
# fill in the variables
smtp_server = "smtp.gmail.com"
smtp_port = 587                                 # for smtp.gmail.com
from_address = "from_address_here"              # e.g. username@gmail.com
from_password = "from_address_password_here"    # required by script to login using your username
to_address = "to_address_here"                  # e.g. username2@gmail.com
subject = "Subject_here"               
mail_body = "Body content here"
 
msg = MIMEMultipart()
msg['Subject'] =  subject
msg['To'] = to_address
msg.attach(MIMEText(mail_body))
 
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
server.login(from_address, from_password)
server.sendmail(from_address, to_address, msg.as_string())
server.quit()

If you get a failure message on the interpreter and a mail from your email provider saying that it blocked an attempt to login from a third-party app, you might have to turn on access for less secure apps in the settings of your email account. Doing so makes your email account vulnerable to hackers, so turn this access off once you no longer need it.

To see how to send a mail with attachments, visit this link.


Fetching Last n Elements of an Iterable

In order to fetch the last n items of an iterable, you can use slicing with a negative index. Slicing is a handy way to quickly access parts of an iterable, and is a very fast operation.

Say you have a list of 10 numbers added sequentially, and you need to retrieve the most recently added 5 elements, then you would write listOne[-5:] to serve the purpose. This can be read as "5th from end to end."

>>> listOne = [2, 6, 9, 2, 5, 3, 6, 10]
>>> listOne[-5:]
[2, 5, 3, 6, 10]

To know more about slicing, click here.


Retrieving Most Frequent Elements in an Iterable

The Counter class of builtin module collections creates a dictionary of all elements of a given iterable object, along with corresponding number of occurrences. It provides a few useful methods, one of which is most_common(). This method takes an integer input, say n, and provides a list of tuples of n most common elements with their corresponding number of occurrences.

import collections

words = [
    '!', '@', '#', '$', '%',
    '@', '#', '$', '%',
    '#', '$', '%',
    '$', '%',
     '%',
]

characterCounts = collections.Counter(words)
threeMostFrequentCharacters = characterCounts.most_common(3)
print(threeMostFrequentCharacters)              # [('%', 5), ('$', 4), ('#', 3)]

Returning values from a function: The return keyword

The return statement makes the control exit from a function, with an optional expression.

>>> def calculateArea(length, breadth):
    return length * breadth
 
>>> calculateArea(3, 4)
12
 
 
 
>>> def myMaxFunction(num1, num2):
    if num1 > num2:
        return num1
    else:
        return num2
 
     
>>> myMaxFunction(10, 20)
20
 
>>> maxValue = myMaxFunction(20, 90)
>>> print("Max Value:", maxValue)
Max Value: 90
 
 
>>> def isOdd(number):
    if number % 2 == 1:
        return True
    else:
        return False
 
     
>>> isOdd(5)
True
>>> isOdd(6)
False

A return statement without an expression is as good as return None.

Functions can return anything, from a string to an integer to a Boolean value to lists to sets and so on. In fact, they can even return other functions. I’ll leave that to you to explore.

Functions can return multiple values as well.


Using Generators: the yield keyword

A Generator is an object in Python which returns a sequence of elements, one at a time. A Generator function returns the Generator object. It is characterized by the keyword yield i.e. a function having the yield keyword in its body is a Generator Function. Basic usage example:

>>> def generateFamousDetectives():
	print("Famous Detective #1:", end = " ")
	yield "Sherlock Holmes"
	print("Famous Detective #2:", end = " ")
	yield "Hercule Poirot"
	print("Famous Detective #3:", end = " ")
	yield "Nancy Drew"

	
>>> generateFamousDetectives
<function generateFamousDetectives at 0x030303D8>

>>> generateFamousDetectives()
<generator object generateFamousDetectives at 0x030290F8>

>>> generatorObjectOne = generateFamousDetectives()
 
>>> generatorObjectOne.__next__()
Famous Detective #1: 'Sherlock Holmes'
>>> generatorObjectOne.__next__()
Famous Detective #2: 'Hercule Poirot'
>>> generatorObjectOne.__next__()
Famous Detective #3: 'Nancy Drew'
>>> generatorObjectOne.__next__()
Traceback (most recent call last):
    generatorObjectOne.__next__()
StopIteration

The generator function 'generateFamousDetectives' returns a generator object, which we can assign to a variable, such as 'generatorObjectOne'. Once we have this object, there are 3 methods in which we can fetch elements from it, just like iterators:

1. Using the __next__() magic function of the generator object, as done above. The __next__() is calling the builtin next() method and passing itself (i.e. the generator object) to it.
2. Using the builtin next() function explicitly, such as next(generatorObjectOne)
3. Using a for loop, such as for detective in generatorObjectOne: print(detective)

The word 'Generator' can be interpreted in two ways. It can be understood to mean the function that is generating the values one by one i.e. generateFamousDetectives(). And it can also be understood to mean the the generator object that the generator function is returning i.e. generatorObjectOne. The latter is the correct one. You can make the distinction by using the terms generator function and generator.

So, a generator is similar to iterators in the sense that both have __next__() method, both can be passed to the builtin next() function & both can be used in conjunction with a for loop. There is a major difference though. Generators evaluate the generator function till the point they encounter the next yield statement which returns an element, and as a result, they do not store the entire list of elements in memory. Iterators on the other hand, take an iterable as input, store the entire iterable in program memory, and return one element at a time.

For more examples & Generator Expressions, refer to this article.


Using builtin function isinstance()

The builtin function isinstance(obj, cls) returns True if obj is an instance of class cls, else returns False.

>>> help(isinstance)
Help on built-in function isinstance in module builtins:

isinstance(...)
    isinstance(object, class-or-type-or-tuple) -> bool
    
    Return whether an object is an instance of a class or of a subclass thereof.
    With a type as second argument, return whether that is the object's type.
    The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
    isinstance(x, A) or isinstance(x, B) or ... (etc.).

	
### EXAMPLES ###
>>> if isinstance(4, int):
	print("4 is an integer!")
	
4 is an integer!


>>> isinstance(4, str)
False
>>> isinstance(4, int)
True


>>> class Man(object): pass
>>> ethan = Man()
>>> isinstance(ethan, Man)
True	

If you are uncertain about the exact parent class of the object, you can insert all the likely classes in the form of a tuple. The isinstance() function will return True if the object belongs to either of the specified classes.

>>> isinstance(4, (str, int) )
True

Keep in mind that all builtin-classes, data types, user-defined classes inherit from the class called object.


Using builtin function issubclass()

The builtin function issubclass(subClass, superClass) returns True if the class subClass is a subclass of the class superClass, else it returns False.

>>> help(issubclass)
Help on built-in function issubclass in module builtins:

issubclass(...)
    issubclass(C, B) -> bool
    
    Return whether class C is a subclass (i.e., a derived class) of class B.
    When using a tuple as the second argument issubclass(X, (A, B, ...)),
    is a shortcut for issubclass(X, A) or issubclass(X, B) or ... (etc.).
	
### EXAMPLE ###
>>> class Person(object): pass
>>> class Man(Person): pass
>>> class Woman(Person): pass

>>> issubclass(Man, Person)
True

>>> issubclass(Man, object)
True

>>> issubclass(Woman, Man)
False

If you are uncertain about the exact parent class of a subclass, you can insert all the likely classes in the form of a tuple. The issubclass() function will return True if the class specified is a subclass of either of the specified classes.

>>> issubclass( Man, (Person, Woman) )
True
>>> issubclass( Person, (Man, Woman) )
False

Keep in mind that all builtin-classes, data types, user-defined classes inherit from the class called object.


Making an object global: global keyword

There will be instances where you are declaring an object inside a confined piece of code, and you will need the object outside it as well. You can use the global keyword for this.

## BEFORE
>>> def storeMyName():
        name = "Ethan"
  
>>> storeMyName()
>>> name
Traceback (most recent call last):
  # Traceback Info
    name
NameError: name 'name' is not defined
 
## AFTER
>>> def storeMyName():
        global name
        name = "Ethan"
  
>>> storeMyName()
>>> name
'Ethan'

Using the nonlocal keyword

The nonlocal keyword is used when you are dealing with variables with same name but in different scopes. Consider a situation where you have a variable 'scope' in a function 'outerFunction'. This function contains another function inside it 'innerFunction', which also has a variable by the name of scope. Any assignment inside the innerFunction will alter the variable of the innerFunction and not of the outerFunction, unless you are using the nonlocal keyword. The nonlocal keyword, used inside the innerFunction tells Python that any assignment to the 'scope' variable should alter the variable in the scope immediately outside of the local scope.

Here is a code example to illustrate the behavior of nonlocal keyword with 2 scopes. The 2 scopes have a variable by the same name. The keyword is used in the inner scope. Outcome is that variable with the same name in the nearest scope (scope 1 in this case) is accessed and assigned a new value.

# Behavior without the nonlocal keyword
>>> def outerFunction():
	scope = "Scope 1"
	print("Locals in outerFunction:", locals())
	def innerFunction():
		scope = "Scope 2"
		print("Locals in innerFunction:", locals())
		print(scope)
	innerFunction()
	print(scope)

>>> outerFunction()
Locals in outerFunction: {'scope': 'Scope 1'}
Locals in innerFunction: {'scope': 'Scope 2'}
Scope 2
Scope 1


# Behavior with the nonlocal keyword
>>> def outerFunction():
	scope = "Scope 1"
	print("Locals in outerFunction:", locals())
	def innerFunction():
		nonlocal scope
		scope = "Scope 2"
		print("Locals in innerFunction:", locals())
		print(scope)
	innerFunction()
	print(scope)

	
>>> outerFunction()
Locals in outerFunction: {'scope': 'Scope 1'}
Locals in innerFunction: {'scope': 'Scope 2'}
Scope 2
Scope 2


# Code example to illustrate the behavior of nonlocal keyword with 3 scopes. All 3 scopes have a variable by the same name. The keyword is used in the innermost scope. Outcome is that variable with the same name in the nearest scope (scope 2 in this case) is accessed and assigned a new value.

# Behavior without the nonlocal keyword
>>> def outerFunction():
	scope = "Scope 1"
	print("Locals in outerFunction:", locals())
	def interimFunction():
		scope = "Scope 2"
		print("Locals in interimFunction:", locals())
		def innerFunction():
			scope = "Scope 3"
			print("Locals in innerFunction:", locals())
			print(scope)
		innerFunction()
		print(scope)
	interimFunction()
	print(scope)

	
>>> outerFunction()
Locals in outerFunction: {'scope': 'Scope 1'}
Locals in interimFunction: {'scope': 'Scope 2'}
Locals in innerFunction: {'scope': 'Scope 3'}
Scope 3
Scope 2
Scope 1

# Behavior without the nonlocal keyword
>>> def outerFunction():
	scope = "Scope 1"
	print("Locals in outerFunction:", locals())
	def interimFunction():
		scope = "Scope 2"
		print("Locals in interimFunction:", locals())
		def innerFunction():
			nonlocal scope
			scope = "Scope 3"
			print("Locals in innerFunction:", locals())
			print(scope)
		innerFunction()
		print(scope)
	interimFunction()
	print(scope)

	
>>> outerFunction()
Locals in outerFunction: {'scope': 'Scope 1'}
Locals in interimFunction: {'scope': 'Scope 2'}
Locals in innerFunction: {'scope': 'Scope 3'}
Scope 3
Scope 3
Scope 1

# CONCLUSION: The variable with the same name in the nearest scope (scope 2 in this case) is accessed and assigned a new value.

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