Page #4
- Concatenating Strings
- Sending mail with Python
- Fetching Last n Elements of an Iterable
- Retrieving Most Frequent Elements in an Iterable
- Returning values from a function: The return keyword
- Using Generators: the yield keyword
- Using builtin function isinstance()
- Using builtin function issubclass()
- Making an object global: global keyword
- Using the nonlocal keyword
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