repr
methodThe Python __repr__()
method is used to tell Python what the string representation of the class should be. It can only have one parameter, self
, and it should return a string.
class Employee:def __init__(self, name):self.name = namedef __repr__(self):return self.namejohn = Employee('John')print(john) # John
In Python, methods are functions that are defined as part of a class. It is common practice that the first argument of any method that is part of a class is the actual object calling the method. This argument is usually called self.
# Dog classclass Dog:# Method of the classdef bark(self):print("Ham-Ham")# Create a new instancecharlie = Dog()# Call the methodcharlie.bark()# This will output "Ham-Ham"
In Python, a class needs to be instantiated before use.
As an analogy, a class can be thought of as a blueprint (Car), and an instance is an actual implementation of the blueprint (Ferrari).
class Car:"This is an empty class"pass# Class Instantiationferrari = Car()
In Python, class variables are defined outside of all methods and have the same value for every instance of the class.
Class variables are accessed with the instance.variable
or class_name.variable
syntaxes.
class my_class:class_variable = "I am a Class Variable!"x = my_class()y = my_class()print(x.class_variable) #I am a Class Variable!print(y.class_variable) #I am a Class Variable!
In Python, the .__init__()
method is used to initialize a newly created object. It is called every time the class is instantiated.
class Animal:def __init__(self, voice):self.voice = voice# When a class instance is created, the instance variable# 'voice' is created and set to the input value.cat = Animal('Meow')print(cat.voice) # Output: Meowdog = Animal('Woof')print(dog.voice) # Output: Woof
The Python type()
function returns the data type of the argument passed to it.
a = 1print(type(a)) # <class 'int'>a = 1.1print(type(a)) # <class 'float'>a = 'b'print(type(a)) # <class 'str'>a = Noneprint(type(a)) # <class 'NoneType'>
In Python, a class is a template for a data type. A class can be defined using the class
keyword.
# Defining a classclass Animal:def __init__(self, name, number_of_legs):self.name = nameself.number_of_legs = number_of_legs
In Python, the built-in dir()
function, without any argument, returns a list of all the attributes in the current scope.
With an object as argument, dir()
tries to return all valid object attributes.
class Employee:def __init__(self, name):self.name = namedef print_name(self):print("Hi, I'm " + self.name)print(dir())# ['Employee', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'new_employee']print(dir(Employee))# ['__doc__', '__init__', '__module__', 'print_name']
__main__
in PythonIn Python, __main__
is an identifier used to reference the current file context. When a module is read from standard input, a script, or from an interactive prompt, its __name__
is set equal to __main__
.
Suppose we create an instance of a class called CoolClass
. Printing the type()
of the instance will result in:
<class '__main__.CoolClass'>
This means that the class CoolClass
was defined in the current script file.
Functional programming is a programming paradigm that adheres to the declarative style of programming.
In the declarative style of programming, the programmer describes what must be done as opposed to how it must be done.
nums = [9, 6, 5, 2, 3]nums.sort() # Sorting the list declaratively using the sorting algorithm provided by the Python langaugedef custom_sort(list):# Custom sorting algorithm defined herecustom_sort(nums) # Sorting the list non-declarativley by using a custom built sorting algorithm
In functional programming, a function is expected to be “pure”, meaning it should have no side effects!
A side effect is when a function alters the state of an external variable.
A function can read an external variable, but it should not change it!
nums = [1, 3, 5, 9]# This function has side effects because it alters the nums list!def square1():for i in range(len(nums)):nums[i] = nums[i]**2# This function does not have side effects because it does not alter the nums list!def square2(lst):new_list = []for i in lst:new_list.append(i)return i# Note: squre2 should loop using recursion. The for-loop is used instead for simplicity!
When storing data that contains multiple properties, using a namedtuple
is more efficient than using a regular tuple. It allows you to store and reference the properties of a data entry by their name.
The accompanying code shows a data entry for a student which contains information about the student’s age, eye color, and gender.
from collections import namedtuple as namedtuple# Record for student Peter stored in a regular tuplepeter = (16, blue, "male") # This is error-prone because you are forced to remember what each entry means.student = namedtuple("student", ["age", "eye_color", "gender"])peter = student(16, "blue", "male")# This is more efficient and less error-prone as the student's data can be accssed like so: peter.age, peter, eye_color, peter.gender
We use lazy iteration in functional programming to be more efficient with memory. With lazy iteration, the iterator is triggered only when the next value is needed.
In the example given, evens
is intended to be a collection of the even numbers in nums
.
The next even number in evens
will be obtained by calling next(evens)
and should only be done when the next even number is needed!
nums = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)evens = filter(lambda x: x % 2 == 0, nums)print(evens) # This will not output a tuple of the even numbers in nums because the iterator has not yet been triggered!print(next(evens)) # This will output 2
The higher-order functions map()
, filter()
and reduce()
can be used together to execute a task that would otherwise require many loops neatly.
from functools import reducenums = (2, 4, 6, 8, 10, 12, 14, 16)# The following adds 1 to all numbers greater than 8 (in nums) and sums them all upsum = reduce(lambda x, y: x + y, map(lambda x: x+1, filter(lambda x: x > 8, nums)))
Functional programming is widely used to process data stored in CSV files or JSON files. Since the files could contain a large amount of data, lazy iteration plays an essential role. Data is imported when needed instead of occupying too much memory by loading it all at once.
We can represent data records stored in CSV files using a namedtuple
. In the code block shown, the map()
function is used to read in a record of data and represent it as a namedtuple
.
import csvfrom collections import namedtuplefrom functools import reducetree = namedtuple("tree", ["index", "girth", "height", "volume"])with open('trees.csv', newline = '') as csvfile:reader = csv.reader(csvfile, delimiter=',', quotechar='|')fields = next(reader)# This will return an iterator that will be triggerd when the next tree is needed!trees = map(lambda x: tree(x[0], x[1], x[2], x[3]), reader)
Setting the logging level for the logger object will allow all messages at and above that level to be produced in the output stream. The logging level can be set by using setLevel()
. If no level is manually set, the default level logging.WARNING
is used.
import logginglogger.setLevel(logging.INFO)
Formatting of logged messages can be changed by using the Formatter
class. Information like timestamps, function names, and line numbers can be included in the logged message.
import loggingformatter = logging.Formatter('[%(asctime)s] %(message)s')
Log messages can be directed to both files and the console by adding FileHandler
and StreamHandler
handler objects to the logger.
While logging to console is helpful if needing to review debugging log messages in real-time, logging to a file allows for the logged messages to exist well after the program execution has occurred.
import loggingimport sysfile_handler = logging.FileHandler("program.log")stream_handler = logging.StreamHandler(sys.stdout)
For a simple, one-liner configuration of the logger, there is a basicConfig()
method that will allow for adding handlers, setting formatting options for the logged messages, and setting the log level for the logger.
import logginglogging.basicConfig(filename='program.log', level=logging.DEBUG, format= '[%(asctime)s] %(levelname)s - %(message)s')
Logging messages and exceptions is important for debugging applications and for logging important information that generates during execution. The proper method for logging these messages is the logging
module.
import logging
There are six logging levels associated with the logging module:
import logginglogging.DEBUGlogging.INFOlogging.WARNINGlogging.ERRORlogging.CRITICALlogging.NOTSET