Python Features

Python Features

Nooks and Crannies

Knowing a framework does not necessarily equate to knowing the language in itself. Case in point, knowing Django does not explicitly mean that you already know python, unless you learned the specific language prior then you are at an advantage. This is the main reason why people advocate for learning the basics of the language first. Although even if you do, frameworks are collections of modules that allow developers to write applications. In this case, you do not have to worry about low-level details. Since you do not use the language constantly you may need a refresher of the language from time to time.

If you are practicing data structures and algorithms and it's kicking your ass, don't worry, me too. Whenever you are up for it to we could have a virtual talk over ice cream because ice cream always makes everything better.

I was on and off last year with my learning of data structures and algorithms using Python of course, but I started getting serious later in the year hence still going on strong this year and why we are here at this moment.

When going through some of the Leetcode problems for the first time I could not help but notice that I had some gaps in my python knowledge. So I went through everything I could find on fundamentals, I was on programiz, geeks for geeks, and of course sololearn. My timeline for all this was 10 days inconsistently. If you are consistent you can cover all this in less time.

This article is meant to act as a python refresher and the new things I picked up. It is also the first article on my Data structures and Algorithms using Python Series because to tell a good story you need to start from the beginning.

Let's get started.

Floor division // returns absolute numbers.

print(5 // 2) 
#output 2

The abs() function returns absolute values of given numbers.

print("The absolute value of -13.5 is :", abs(-13.5))
#output 13.5

If the number is a complex number, abs() returns its magnitude.

Type Casting is a process in which we convert a literal of one type to another.

#integer
v = 54

#float
f = float(v)
print(f)
print(type(f))

#string
s = str(v)
print(s)
print(type(s))

#int
i = int(v)
print(i)
print(type(i))

""" output
54.0
<class 'float'>
54
<class 'str'>
54
<class 'int'>
"""

In place operators allow you to write code like v = v + 6 more compact as
v += 6. These operators can be used for any numerical operations such as +, -, *, /, %, **, //.

Walrus Operator

Walrus operator := allows you to assign values to variables within an expression including variables that do not exist yet.

 print(character:=str(input("Enter a string:")))

Comparison Operators

Comparison operators are also known as relational operators. The key difference between assignment = operator and comparison operator == is the number of equal signs.

If statements can be used to check if a certain condition holds. If it does some statements are carried out else they are not carried out.

num = 6
#if expression:
if num > 0:
    #statements to run if it is true
    print(num, "is a positive number.")
    #if the statement does not meet  the 1st condition
else:
    print(num, "is a non-negative number.")

Boolean Logic

Boolean logic is used to make more complicated conditions for if statements that rely on more than one condition. Boolean operators in Python are: and, or and not

The and operator takes two arguments and evaluates as True if both of its arguments are True else it is False.

x = 12
print(x > 3 and x < 10)
#evaluates to false because only one statement is true

The or operator takes two arguments and evaluates to True if either argument is true or if both arguments are true else evaluates to False if both are False.

x = 12
print(x > 3 or x < 10)
#evaluates true

The not operator reverses the result, returns False if the result is True.

x = 12
print(not(x > 3 or x < 10))
#evaluates false

Python Identity Operators

Python identity operators include is and is not. Identity operators compare objects not if they are equal but if they are actually the same object with the same memory location. is returns True if both variables are the same object is not returns True of both variables are not the same object.

v = ["London", "Dublin"]
e = ["London", "Dublin"]
l = v

print(v is not l)

# returns False because l is the same object as v

print(v is not e)

# returns True because v is not the same object as e, even if they have the same content

print(v != e)

# to demo the difference between "is not" and "!=": this comparison returns False because v is equal to e

Python Membership Operators

Python membership operators include in and not in. Python membership operators test if a sequence is presented in an object.

#in
v = ["New York", "Los Angeles"]

print("Los Angeles" in v)
# returns True because a sequence with the value "Los Angeles" is in the list

#not in 
v = ["New York", " Los Angeles"]

print("Las Vegas" not in v)

# returns True because a sequence with the value "Las Vegas" is not in the list

Lists

Lists are used to store multiple items in a single variable. A list is created using square brackets [ ] with commas separating items. In typical situations, lists may contain items of a single item type but it is also possible to include several different types. Lists can be nested with other lists to represent 2D and 3D grids such as matrices. A one-dimensional array is an array that has a single argument that you index to access a specific value. A two-dimensional array is simply an array of arrays. You give two arguments to access a single value.

empty_list = [ ]
# one-dimensional array, see what I did there used the walrus operator

print(array := [1,2,3,4,5,6,7])
#accesing values by indexing
print(array[2]) #3

# Two dimensional array
print(two_d_array := [[1,2],[3,4],[5,6],[7,8]])
print(two_d_array[1][1]) #4

#Three dimenisonal array
print(three_d_array := 
[ [ [1,2,3,4], [5,6,7,8]],[[9,10,11,12],[13,14,15,16] ] ])
print(three_d_array[1][1][1]) #14

Items at an index can be reassigned.

print(array := [1,2,3,4,5,6,7])
#accesing values by indexing
print(array[2]) #3
#replacing the value of 3 to 9
array[2] = 9
print(array[2]) #9

len() allows you to get the length of items in a list.

array = [1,2,3,4,5,6,7]
print(len(array)) #7

To append items to the end of a list use .append()

array = [1,2,3,4,5,6,7]
array.append(8)
print(array)

The insert method is similar to append, except that it allows you to insert a new item at any position in the list, as opposed to just at the end. Items that are after the inserted item, are shifted to the right.

array = [1,2,3,4,5,6,7]
array.insert(2, 9)
print(array) #[1, 2, 9, 3, 4, 5, 6, 7]

In case the item being indexed is not in the list, ValueError is raised.

Other functions and methods for lists include:

  • max(list): Returns the list item with the maximum value
  • min(list): Returns the list item with the minimum value
  • list.count(item): Returns a count of how many times an item occurs in a list
  • list.remove(item): Removes an object from a list
  • list.reverse(): Reverses items in a list.

Loops

A while loop is used to repeat a block of code multiple times. The while loop is used in cases when the number of iterations is not known and depends on some calculations and conditions in the code block of the loop. For example, if we wanted to calculate the sum of natural numbers to a point highlighted by the user, we could do:

# user input
user = int(input("Enter number: "))
# initialize sum and counter
sum = 0
i = 1
#iterate
while i <= user:
    sum += i
    # update counter
    i += 1   
print("The sum is", sum)

Break Statement

To end loops prematurely you can use break to avoid running an infinite loop. Break statements are always used inside a loop to avoid errors. In case the break statement is used in nested loops break breaks the innermost loop.

for v in "velda":
    if v == "l":
        break
    print(v)

print("Break statement")

Continue Statement

A continue statement jumps to the top of the loop. It stops the current iteration and continues to the next.

for v in "velda":
    if v == "l":
        continue
    print(v)

print("Break statement")

For loop is used to iterate in situations where the iterations are fixed or known.


#sum of numbers

# List of numbers
numbers = [1,2,3,4,5]

# variable to store the sum
sum = 0

# iterate over the list
for v in numbers:
    sum = sum+v

print("The sum is", sum)

For and While loops can be used to achieve the same results though the for loop is preferred since it has a cleaner and shorter syntax.

Range

The range() function returns a sequence of numbers. By default, it starts from 0, increments by 1 and stops before the specified number.

Syntax of range:

  • range(stop)
  • range(start, stop[, step])

range() takes three arguments having the same use in both definitions:

  • Start: integer starting from which the sequence of integers is to be returned
  • Stop: integer before which the sequence of integers is to be returned(stop-1)
  • Step (Optional) - integer value which determines the increment between each integer in the sequence
# empty range
print(list(range(0)))

# using range(stop)
print(list(range(10)))

# using range(start, stop)
print(list(range(1, 10)))

# using range(start, stop, step)
print(list(range(0, 10, 2)))

Functions

Create functions using the def statement

def salutation(name):
     """greetings"""
    print("Ahoy, " + name + " Good day!")

#calling func
salutation('Velda')

Function arguments like name can be used as variables inside the function definition. However, they cannot be referenced outside of the function's definition. This also applies to other variables created inside a function.

Documentation string which is commonly in short known as docstring is used to explain what a function does. These comments are retained throughout the runtime of the program for the programmer to inspect comments at run time.

Modules

Modules are pieces of code that other people have written to fulfill common tasks, such as generating random numbers, performing mathematical operations, etc.

from math import pi
radius= 7
print( area:= pi * radius * radius)
print("The value of pi is", pi)

Trying to import a module that isn't available causes an ImportError.

Pandas is a python library used to analyze data. A Pandas Series is like a column in a table. It is a one-dimensional array holding data of any type. You can import a module or object under a different name using the as keyword. This is mainly used when a module or object has a long or confusing name such as pandas.

import pandas as pd

v = [1, 2, 4]

var = pd.Series(v)

print(var)

Types of Modules

  • Those you write yourself
  • Those from external sources
  • Those that are preinstalled with Python

Common Exceptions

  • ImportError: an import fails;
  • IndexError: a list is indexed with an out-of-range number;
  • NameError: an unknown variable is used;
  • SyntaxError: the code can't be parsed properly;
  • TypeError: a function is called on a value of an inappropriate type;
  • ValueError: a function is called on a value of the correct type, but with an inappropriate value.

Exception Handling

To handle exceptions, and to call code when an exception occurs, you can use a try/except statement.

The try block lets you test a block of code for errors.

The except block lets you handle the error.

The else block lets you execute code when there is no error.

The finally block lets you execute code, regardless of the result of the try- and except blocks.

try:
  print(v)
except:
  print("something is wrong")
finally:
  print("The 'try except' is dooooone")

Assertion

An assertion is a sanity-check that you can turn on or off when you have finished testing the program. An expression is tested, and if the result comes up false, an exception is raised. Assertions are carried out through the use of the assert statement.

def average(result):
    assert len(result) != 0,"List is empty."
    return sum(result)/len(result)

result2 = [76,88,80,90,96]
print("Average of mark2:",average(result2))

result1 = []
print("Average of mark1:",average(result1))

The assert statement is used as debugging tool as it halts the program at the point where an error occurs.

Reading and Writing Files

To read a file you need to open it first. To open a file use:

myfile = open("filename.txt")

To read the file apply a second argument to the open function. Read is the default mode. python myfile = open("filename.txt", "r")

To add other functions use the following additional modes:

  • "w" means write mode, for rewriting the contents of a file.
  • "a" means append mode, for adding new content to the end of the file.
  • "b" opens it in binary mode, which is used for non-text files (such as image and sound files).
  • "wb" binary write mode

It is good practice to avoid wasting resources by making sure that files are always closed after they have been used. You can close the files through:

myfile.close()

To retrieve each line in a file, you can use the .readlines() method to return a list in which each element is a line in the file.

Dictionaries

Dictionaries are an unordered collection of items. They are data structures used to map arbitrary keys to values. Dictionaries are optimized to retrieve values when the key is known.

# empty dictionary
my_dict = {}

# dictionary with integer keys
my_dict = {1: "orange", 2: "banana''}

Dictionaries can be indexed in the same way as lists, using square brackets containing keys.

my_dict = {1: 'banana', 2: 'orange'}

#add item
my_dict['3'] = 'pineapple'

print(my_dict)

Indexing a key that is not part of the dictionary returns a KeyError.

A useful dictionary method is .get(). It does the same thing as indexing, but if the key is not found in the dictionary it returns another specified value instead ('None', by default).

person = {'name': 'Tony', 'age': 45}

print('Name: ', person.get('name'))

print('Age: ', person.get('age'))

# value is not provided
print('Salary: ', person.get('salary')) 

# value is provided
print('Salary: ', person.get('salary', 0.0))

Tuples

Tuples are very similar to lists, except that they are immutable (they cannot be changed). They are created using parenthesis (). To access the values in the tuple we use indexing just like in lists. Reassigning a value in a tuple causes a TypeError. Tuples are faster than lists but cannot be changed.

# Empty tuple
my_tuple = ()
print(my_tuple)

# Tuple having integers
my_tuple = (2, 5, 4)
print(my_tuple)

# tuple with mixed datatypes
my_tuple = (2, "Hiya", 5.4)
print(my_tuple)

# nested tuple
my_tuple = ("here", [1, 2, 3], (4, 5, 6))
print(my_tuple)

#indexing
print(my_tuple[0])
#here

Slicing

Slicing provides an easy way of retrieving values from a list. Slicing format:[start:stop:step]

  • Start is the index of the list where slicing starts.
  • Stop is the index of the list where slicing ends.
  • Step allows you to select an nth item within the range from start to stop.
#get all items
my_list = [1, 2, 3, 4, 5]
print(my_list[:])

#get items to a specific position, starting from the end
print(my_list[2:])  #[3, 4, 5]

#get items before specific pos, starting from the start
print(my_list[:2]) #[1, 2]

#from one pos to another
print(my_list[2:4]) #[3, 4]

#get specified intervals
print(my_list[1:4:2]) #[2,4]

#get items at specified intervals
print(my_list[::2]) #[1,3,5]

#negative slicing
print(my_list[:-2]) #[1, 2, 3]

#indexing to start from the last item
print(my_list[::-2]) #[5,3,1]

Slice is also an idiomatic way to reverse a list.

List Comprehensions

List comprehensions are a useful way of quickly creating lists whose contents obey a simple rule.

For example, we can do the following:

#iterating using list comprehensions
k_letters = [ letter for letter in 'Velda' ]
print( k_letters) 
 #['V', 'e', 'l', 'd', 'a']

A list comprehension can also contain an if statement to enforce a condition on values in the list.

number_list = [ v for v in range(20) if v % 2 == 0]
print(number_list)

Creating a list in an extensive range will result in MemoryError.

String Formatting

String formatting is a way to embed non-strings within strings. String formatting uses a string's format method to substitute a number of arguments in the string

Each argument of the format function is placed in the string at the corresponding position, which is determined using the curly braces{ }.

print("{0}{1}{0}".format("abra", "cad"))
#abracadabra

Built-in Functions and Methods for Tasks

  • join() : joins a list of strings with another string as a separator.
  • replace() : replaces one substring in a string with another.
  • startswith() and endswith() : determine if there is a substring at the start and end of a string, respectively.
  • To change the case of a string, you can use lower() and upper().
  • split() breaks a string at the specified separator and returns a list of strings.
  • max() and min() to get maximum and minimum values
  • To round a number to a certain number of decimal places, use round( number, n_digits). Where number is what is being rounded, n_digits is the number up to which the given number is rounded. The default is 0.
    from math import pi
    print(round(pi, 2)) #3.14
    
  • enumerate() is used to iterate through the values of a list simultaneously.
food = ['rice', 'noodles', 'spaghetti']

for item in enumerate(food):
  print(item)

# changing default start value
for count, item in enumerate(food, 100):
  print(count, item)

Lambdas

Lambdas are anonymous functions. They are not as powerful as named functions. They can only do things that require a single expression - usually equivalent to a single line of code. Lambda functions can be assigned to variables and used like normal functions.

# Program to show the use of lambda functions
double = lambda v: v * 2

print(double(4))

# Program to filter out only the even items from a list
my_list = [1, 5, 4, 6, 8, 11, 3, 12]

new_list = list(filter(lambda v: (v % 2 == 0) , my_list))

print(new_list)

# Program to double each item in a list using map()

my_list = [1, 5, 4, 6, 8, 11, 3, 12]

new_list = list(map(lambda v: v * 2 , my_list))

print(new_list) 
#8, [4, 6, 8, 12],[2, 10, 8, 12, 16, 22, 6, 24]

Map and filter functions are higher-order functions that operate on lists. map() takes a function and returns an iterable with the function applied to each argument. filter() removes items that do not match a predicate. The results need to be converted explicitly to list in order to print.

Sets

A set is an unordered collection of items. Every set element is unique and must be immutable. They are created using curly braces { }, or the set function set(). They share some functionality with lists, such as the use of in to check whether they contain a particular item.

To create an empty set, you must use set(), as {} creates an empty dictionary.

# empty set
print(set())
print(my_set:={})

Differences between Sets and Lists

  • Sets are unordered meaning that they can't be indexed.
  • Sets do not contain duplicates.
  • It is faster to check whether an item is in a set than in a list due to the way sets are stored.
  • Sets use add to add items

General use cases of sets include membership testing and elimination of duplicate entries.

Mathematical Operations on Sets

The union operator |combines two sets to form a new one containing items in either.

The intersection operator& gets items only in both.

The difference operator - gets items in the first set but not in the second.

The symmetric difference operator ^ gets items in either set, but not both.

#initializing sets
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

#union operator
print(A | B) 
#{1, 2, 3, 4, 5, 6, 7, 8}

#intersection
print(A & B)
# {4, 5}

#difference
print(A - B)
# {1, 2, 3}

#symmetric  difference
print(A ^ B)
#{1, 2, 3, 6, 7, 8}

Nice to Know

When to use a dictionary:

  • When you need a logical association between a key: value pair.
  • When you need a fast lookup for your data, based on a custom key.
  • When your data is being constantly modified. Dictionaries are mutable.

When to use the other types:

  • Use lists if you have a collection of data that does not need random access.
  • Use a set if you need uniqueness for the elements.
  • Use tuples when your data cannot change.

Generators

Python generators are a simple way of creating iterators. They can be created using functions and yield statements. The yield statement is used to define a generator, replacing the return of a function to provide a result to its caller without destroying local variables.

def reverse_str(my_str):
    length = len(my_str)
    for i in range(length - 1, -1, -1):
        yield my_str[i]


# For loop to reverse the string
for char in reverse_str("hannah"):
    print(char)

Generators result in improved performance due to lower memory usage. Additionally, we do not have to wait until all elements are generated before we start to use them.

Decorators

Decorators provide a way to modify functions using other functions. This is ideal when you need to extend the functionality of functions that you don't want to modify. Python provides support to wrap a function in a decorator by pre-pending the function definition with a decorator name and the @ symbol.

def uppercase_decorator(function):
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase

    return wrapper 

@uppercase_decorator
def say_hi():
    return 'hello there'

print(say_hi())

Python has a lot more paradigms that I may have not covered in detail in this article like Object Oriented Programming (OOP) and Functional Programming. With a refresher of the most basic stuff and new concepts like the Walrus operator, I am confident that you can write a basic algorithm exploring different data structures.

This is just the beginning of a beautiful journey.

Thanks for reading, cheers!