Python

· Programming language · Computer programming · Python Sink ·

TOC

Datasheet

Paradigm Multi-paradigm: object-oriented, procedural (imperative), functional, structured, reflective
Designed by Guido van Rossum
Developer Python Software Foundation
First appeared 20 February 1991; 30 years ago[2]
Stable release 3.10.2 14 January 2022; 13 days ago
Preview release 3.11.0a4 Edit this on Wikidata / 14 January 2022; 13 days ago
Typing discipline Duck, dynamic, strong typing; gradual (since 3.5, but ignored in CPython)
OS Windows, Linux/UNIX, macOS and more
License Python Software Foundation License
Filename extensions .py, .pyi, .pyc, .pyd, .pyo (prior to 3.5), .pyw, .pyz (since 3.5)
Major implementations CPython, PyPy, Stackless Python, MicroPython, CircuitPython, IronPython, Jython
Dialects Cython, RPython, Starlark
Influenced by ABC, Ada, ALGOL 68, APL, C, C++, CLU, Dylan, Haskell, Icon, Java, Lisp, Modula-3, Perl, Standard ML
Influenced Apache Groovy, Boo, Cobra, CoffeeScript, D, F#, Genie, Go, JavaScript, Julia, Nim, Ring, Ruby, Swift

Description

Python is an interpreted high-level general-purpose programming language. Its design philosophy emphasizes code readability with its use of significant indentation. Its language constructs as well as its object-oriented approach aim to help programmers write clear, logical code for small and large-scale projects.

Python is dynamically-typed and garbage-collected. It supports multiple programming paradigms, including structured (particularly, procedural), object-oriented and functional programming. It is often described as a "batteries included" language due to its comprehensive standard library.

Guido van Rossum began working on Python in the late 1980s, as a successor to the ABC programming language, and first released it in 1991 as Python 0.9.0. Python 2.0 was released in 2000 and introduced new features, such as list comprehensions and a cycle-detecting garbage collection system (in addition to reference counting). Python 3.0 was released in 2008 and was a major revision of the language that is not completely backward-compatible. Python 2 was discontinued with version 2.7.18 in 2020.

Python consistently ranks as one of the most popular programming languages.

Books

Mark Lutz - Learning Python 5th edition

[[Mark Lutz - Learning Python, 5th Edition - 2013.pdf]]

Building Skills in Python

[[Building.Skills.in.Python.Steven.F.Lott.2010.pdf]]

Data types

Strings

String formatting

Inline

this method raise exception if the string does not contain %s

name = "Shaun"
age = 32
string = "Hi, %s!" % name
string = "Hi, %s!" % "Paul"
string = "%s is %d years old" %(name, age)

x = "dance"
print("Nancy can %s and %s too" %(x, "sing"))

print("The value of e is %5.2f" %(24553.718281)) # rounding
Format() method

Docs

print("Welcome to {} world!".format("Python"))

#positional
print("Let's {0} and {1}".format("dance", "chant"))

print("{} called {} to inform that {} will be absent.".format("Luke", "Diana", "John"))
print("{2} called {0} to inform that {1} will be absent.".format("Luke", "Diana", "John"))

print("We {a}, {b} and {c} with you".format(a = "learn", b = "upskill", c = "grow"))

String literals, f-strings
tutorial = "Python"
subject = "programming"
place = "internet"
print(f"Let's learn {tutorial} {subject} from {place}")

n1 = 60
n2 = 12
print (f"The product of {n1} and {n2} is {n1 * n2}")

num = 55
print(f"Is number {num} even? {True if num%2==0 else False}")

# alignment with spaces/zeros, < left, > right
fnum = 2.1239810
name = 'Anthony Crueger Jr.'
print(f'Name: {name:<20}')   # 
print(f'{fnum:<015.05f}')     # leading and trailing zeros rounding to 3 digits
print(f'{fnum:>15.02f}') 

# allows operations like this to fit both name title and name to 20 chars
print(f'{"Name:" + name:<20}')
# or
print(f'{"Name":>10} {":":>5} {name:>25}')    # both aligned with spaces on the left

# formatting numbers
print(f'{fnum:.3f}')                  # float with 3 positions aligned with leading spaces
print(f'{fnum:.010f}')                 # float with 3 pos with aligned trailing zeros

print(f'{fnum:10}')                   # 10 positions aligned with leading spaces
print(f'{fnum:010}')                  # 10 positions aligned with leading zeros

print(f'{fnum:010.03f}')              # 10 positions with leading and trailing zeros rounding to 3 digits
print(f'{fnum:<010.03f}')             # 10 positions with leading and trailing zeros aligned to left rounding to 3 digits
print(f'{fnum:>010.03f}')             #
String template class
# template symbol $
from string import Template
a1 = "Python"
a2 = "Internet"
n = Template("Hello, welcome to $n1 programming on the $n2")
print(n.substitute(n1 = a1, n2 = a2))

stu_name = "Long Johnson"
s = Template("Hey, $name! How are you?")
print(s.substitute(name = stu_name))

String operations

Docs

s = "the quick brown fox /jumps 8 over / a lazy dog"
print(s.split(" ")) # list of words
print(s[-10:]) # last 10 symbols
print(' '.join(s.rsplit(' ', 10)[-10:])) # get string of last 10 words
print(s.replace('/','\\'))
before, delim, after = s.partition("/")

import re
print(re.split(r'\d', s))

Lists, tuples, sets, dictionaries

Lists

Mutable arrays

courses = ["History", "Math", "Physics", "CompSci"]
print(len(courses))              # =4
print(courses[0])                # item by index
print(courses[-1])               # last item
print(courses[4])                # exception

print(courses[0:2])              # items from 0 (including) and 2 (excluding)
print(courses[2:])               # items from 2 (including) to the end

courses.append("Art")            # add item to the end
courses.insert(0, "Art")         # insert at position 0

courses2 = ["Art", "Education"]  # new list
courses.insert(0, courses2)      # inserts the list courses2 as a single list element
courses.extend(courses2)         # adds the courses2's values to the end

courses.remove("math")           # removes Math item
popped = courses.pop()           # removes the last value and returns it
popped = courses.pop(0)          # removes the first value and returns it

courses.reverse()                # reverses the item order
courses.sort()                   # sorts alphabetically (if strings) or ascending (if numbers)
courses.sort(reverse=True)       # sorts in revered order

sorted_courses = sorted(courses) # returnes a sorted version without altering the source list

nums = [1, 2, 3, 4, 5]
min(nums)
max(nums)
sum(nums)

courses.index("compSci")         # finds the item's index
"Art" in courses                 # presence check

for item in courses:             # order preserved
	print(item)

for index, course is enumerate(courses):
	print(index, course)

for index, course is enumerate(courses, start=1):
	print(index, course)

courses = ["History", "Math", "Physics", "CompSci"]
course_str = ';'.join(courses)
new_list = course.split(";")

# swap elements
courses[0], courses[1] = courses[1], courses[0]

# slice object, also works for strings
rev: slice = slice(None, None, -1) # equal to [::-1]
numbers = list(range(1, 11))
text = 'Hello, world!'
print(numbers[rev])
print(text[rev])

Tuples

Immutable arrays


# Mutable
list1 = ["History", "Math", "Physics", "CompSci"]
list2 = list1

list1[0] = "Art" # will change the first item for BOTH of list

# Immutable
list1 = ("History", "Math", "Physics", "CompSci")
list2 = list1

list1[0] = "Art" # exception, no item assignment/change allowed

Sets

much more performant than lists and tubles


cs_courses = {"History", "Math", "Physics", "CompSci"}
print(cs_courses)  # returns the items in random order

cs_courses = {"History", "Math", "Physics", "CompSci", "Math"}
print(cs_courses)  # returns the set, but Math is present just once

print("Math" in cs_courses) # True

art_courses = {"History", "Math", "Art", "Design"}
print(cs_cources.intersection(art_courses)) # common items
print(cs_cources.difference(art_courses))   # uncommon items
print(cs_cources.union(art_courses))   # items from both sets, no repeats

set_a = {1, 2, 3, 4, 5}
set_b = {4, 5, 6, 7, 8}
print(set_a | set_b) # Union
print(set_a - set_b) # Subtraction
print(set_a & set_b) # Intersection (shared elements)
print(set_a ^ set_b) # Symmetric difference (unique elements only)

# Empty lists
empty_list = []
empty_list = list()

# Empty tuples
empty_tuple = ()
empty_tuple = tuple()

# Empty sets
empty_set = set() # the only way to create an empty set

empty_dictionary = {} # this creates an empty dictionary instead

Dictionaries

index names are immutable


student = {"name": "John", "age": 25, "courses": ["Math", "CompSci"]}

print(student["name"]) # returns "John"

print(student["phone"]) # throws exception

print(student.get("phone")) # returns None
print(student.get("phone", "Not found")) # returns Not found instead

student["phone"] = "555-5555"

student.update({"name": "jane", "age": 26, "phone": "555-5555"}) # a few chanes in one shot

del student["age"]
popped = student.pop("age")

student = {"name": "John", "age": 25, "courses": ["Math", "CompSci"]}
print(len(student))
print(student.keys())    # returns dict_keys(["name", "age", "courses"])
print(student.values())  # returns dict_values(["John", 25], ["Math", "ComSci"]])
print(student.items())   # returns dict_tems([("name", "John"), ("age", 25), ...])

for key, value in student.items():
	print(key, value)

# sort dictionary
print(dict(sorted(student.items())))

Scopes

LEGB rule
Local >> Enclosing >> Global >> Built-in

Global and local

x = "global x"

def test():
	x = "local x"
	print(x) # local x

test()
print(x) # still global x

def test():
	global x # x may not be defined earlier, not used often
	x = "local x"
	print(x) # local x

test()
print(x) # global x overwritten to local
def test(z):  # z is in local scope
	x = "local x"
	print(z) # local x

test("local z")
print(z) # exception! no such global var

Built-in

import builtins
print(dir(builtins))

Enclosing

nested functions

def outer():
	x = "outer x"

	def inner():
		x = "inner x"
		print(x) # prints inner x

	inner()
	print(x) # prints outer x
	
outer()
print(x) # exception!

Conditionals

# if
language = "Python"

if language == "Python":
	print("Language is Python")
elif language == "java":
	print("Language is Java")
elif language == "Pascal":
	print("Language is Pascal")
else:
	print("No match")

# is
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b) # True
print(a is b) # False, not the same object
print(id(a))  
print(id(b))  # ids are different 

b = a
print(a is b) # true, now the same object

Evaluation to True/False

Evaluates to False

Inline if

a = 4
b = "yes" if a == 4 else "no"

Loops

nums = [1, 2, 3, 4, 5]
for num in nums:
	if num == 3:
		print("Found")
		break
	print(num)

nums = [1, 2, 3, 4, 5, 3]
for num in nums:
	if num == 3:
		print("Found")
		continue
	print(num)

for i in range(10): # not including 10
	print(i)

for i in range(1, 11): # start, end, step
	print(i)

x = 0

while x < 10:
	print(x)
	x += 1

Functions

def hello_func(greeting, name = "You"): # You is the default value, keyword argument
	return "{}, {}". format(greeting, name)

print(hello_func("Hi"))

print(hello_func("Hi", name="Keerah"))


def student_info(*args, **kwargs):  # arbitrary number of arguments
	print(args)
	print(kwargs)

student_info("Math", "Art", name="John", age=22)
# returns
# ("Math", "Art")
# {"name": "John", "age": 22}

courses = ["Math", "Art"]
info = {"name": "John", "age": 22}

student_info(courses, info)
# not equal to the previous use, it returns:
# (["Math", "Art"], {"name": "John", "age": 22})
# {}

student_info(*courses, **info)
# now it upacks the arguments and passes them

First-class functions

First-class functions are treated as first-class citizens.
First-class citizen (aka first-class object) is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, and assigned as a variable.
Higher order functions are the functions that can receive functions as arguments and return functions.

def square(x):
	return x * x

f = square(5)
print(square) # returns <function ssquare at 0x...>
print(f)      # returns 25

f = square   # asign function to a variable
print(f)     # now returns same <function ssquare at 0x...>
print(f(5))  # returns 25
def square(x):
	return x * x

def my_map(func, arg_list)
	result = []
	for in in arg_list:
		result.append(func(i))
	return result

squares = my_map(square, [1, 2, 3, 4, 5]) # the square function name without (), cause parenthesis execute the fucntion, but we have to pass it as an argument
def logger(msg):

	def log_message():
		print("Log:", msg)
	
	return log_message # returning the function, no parenthesis = no execution

log_hi = logger("hi!")
print(log_hi.__name__) # name of the returned function
log_hi()

html tag wrapping example

def html_tag(tag):

	def wrap_text(msg):
		print("<{0}>{1}<{0}>".format(tag, msg))

	return wrap_text

print_h1 = html_tag("h1")
print_h1("Test headline!")

print_p = htma_tag("p")    
print_p("Test paragraph")

Closures

A closure is a record storing a function together with an environment: a mapping associating each free variable of the function with the value or storage location to which the name was bound when the closure was created.
A closure, unlike a plain function, allows the function to access those captured variables through the closure's reference to them, even when the function is invoked outside their scope.

#import logging
#logging.basicConfig(filename="c:\temp\example.log", level=logging.INFO)

def logger(func):
	def log_func(*args):
		#logging.info("Running ""{}"" with arguments {}".format(func.__name__, args))
		print("fake log:", "Running \"{}\" with arguments {}".format(func.__name__, args))
		print(func(*args))
	return log_func

def add(x, y):
	return x+y

def sub(x, y):
	return x-y

add_logger = logger(add) # this is a closure
sub_logger = logger(sub)

add_logger(3, 3)
sub_logger(10, 5)

Decorators

def decorator_function(original_function):
	def wrapper_function():
		return original_function()
	return wrapper_function

def display():
	print("display function ran")

decorated_display = decorator_function(display)
decorated_display()

# or another more common syntax

@decorator_function
def display():
	print("display function ran")

display()

and more confusion with class decorators and multiple decorators at https://youtu.be/FsAPt_9Bf3U

Classes

Instance variables

contain data that is unique to each instance of the class


# empty class
class Employee:
	pass

emp1 = Employee()
emp2 = Employee()

emp1.first = "Corey"
emp1.last = "Schafer"
emp1.email = "Corey.Schafer@company.com"

print(emp1.email)

# the next usage is equal but creates variables at init
class Employee:

	# class constructor
	# self is an instance of the class
	def __init__(self, first, last, pay):
		# there's no need to give them same names, but it's more consistent
		self.first = first
		self.last = last
		self.pay = pay
		self.email = first + "." + last + "@company.com"
	
	# additional method to return full name
	def fullname(self):
		return "{} {}".format(self.first, self.last)

# self variable is passed automatically 
# init method is run automatically
emp1 = Employee("Corey", "Schafer", 50000)

print(emp1.email)

# parenthesis used for methods, otherwise it's an attribute
# instance argument self is passed automatically
print(emp1.fullname())

# in backfround it get trsnsform into the next call
# self instance is explicit in this one
print(Employee.fullname(emp1))

Class variables

are the same in each instance

class Employee:

	# Class variable
	raise_amount = 1.04

	def __init__(self, first, last, pay):
		# there's no need to give them same names, but it's more consistent
		self.first = first
		self.last = last
		self.pay = pay
		self.email = first + "." + last + "@company.com"
	
	def fullname(self):
		return "{} {}".format(self.first, self.last)
	
	def apply_raise(self):
		self.pay = int(self.pay * Employee.raise_amount)
		# or instance also can access it
		self.pay = int(self.pay * self.raise_amount)

emp1 = Employee("Corey", "Schafer", 50000)
emp1.apply_raise()
print(emp1.pay)

# both class and instance contain this variable
print(Employee.raise_amount)
print(emp1.raise_amount)

# get the namespace of emp1
print(emp1.__dict__)
# the result is {"first": "Corey", "pay: 50000", "email": "Corey.Schafer@company.com", "last": "Schafer"}
# no raise anount in this list

# this one contains raise_amount
print(Employee.__dict__)

# this creates a new variable for this instance only
# now emp1 namespace contains its own raise_amount
emp1.raise_amount = 1.05

# now the following ones will work differently
self.pay = int(self.pay * Employee.raise_amount) # uses the class attribute
self.pay = int(self.pay * self.raise_amount) # uses raise_amount from the emp1 namespace
class Employee:
	
	numof = 0

	def __init__(self, name):
		# runs everytime a new employee added
		Employee.numof += 1

emp1 = Employee("Corey Schafer")
emp2 = Employee("Test User")

print(Employee.numof)
# returns 2

Methods

Class methods


class Employee:
	
	numof = 0
	raise_amount = 1.04

	def __init__(self, name, pay):
		self.pay = pay
		Employee.numof += 1 # runs everytime a new employee added

	# inside this we work with the class instead of the instance
	# and using cls instead of self
	@classmethod # class method decorator
	def set_raise_amount(cls, amount):
		cls.raise_amount = amount

emp1 = Employee("Corey Schafer", 50000)
emp2 = Employee("Test User", 50000)

# no self variable is passed, cause it addresses the class method
Employee.set_raise_amount(1.05)

# equals to
Employee.raise_amount = 1.05

# calling this method from an instance still changes the class attribute for the class and for the instances 
emp1.set_raise_amount(1.05)

Class methods as alternative constructors
mostly for various methods of creating instances

class Employee:
	
	numof = 0
	raise_amount = 1.04

	def __init__(self, name, pay):
		self.pay = pay
		Employee.numof += 1 

	@classmethod
	def set_raise_amount(cls, amount):
		cls.raise_amount = amount

	# as alternative constructor
	@classmethod 
	def from_string(cls.employee_str):
		name, pay = employee_str.split(";")
		return cls(name, pay) # calls the actual constructor and returns a new instance

emp1 = Employee("Corey Schafer", 50000)
emp2 = Employee.from_string("Test User;50000")

Static methods

regular functions, they do not pass class cls or instance self variables

class Employee:
	
	numof = 0
	raise_amount = 1.04

	def __init__(self, name, pay):
		self.pay = pay
		Employee.numof += 1 

	@classmethod
	def set_raise_amount(cls, amount):
		cls.raise_amount = amount

	@classmethod 
	def from_string(cls.employee_str):
		name, pay = employee_str.split(";")
		return cls(name, pay) 

	# static method to establish a workday
	@staticmethod
	def is_workday(day)
		if day.weekday() == 5 or day.weekday() == 6:
			return False
		return True

import datetime
my_date = datetime.date(2016, 7, 11)
print(Employee.is_workday(my_date))

Inheritance

Inherit attributes

class Emloyee:
	raise_amt = 1.04
	
	def __init__(self, first, last, pay):
		self.first = first
		self.last = last
		self.email = first + "." + last + "@email.com"
		self.pay = pay
		
	def fullname(self):
		return "{ } { }".format(self.first, self.last)
		
	def apply_raise(self):
		self.pay = int(self.pay * self.raise_amt)

# new class inherits the Employee class
class Developer(Employee):
	#overriding the value for the subclass
	raise_amt = 1.10

dev1 = Employee('Corey', 'Schafer', 50000)
# using the iherited
dev2 = Developer('Test' ,' Employee', 60000)

print(help(Developer)) # to see the method resolution order

dev2.apply_raise() # the amount is from the developer class

Inherit methods

class Emloyee:
	raise_amt = 1.04
	
	def __init__(self, first, last, pay):
		self.first = first
		self.last = last
		self.email = first + "." + last + "@email.com"
		self.pay = pay
		
	def fullname(self):
		return "{ } { }".format(self.first, self.last)
		
	def apply_raise(self):
		self.pay = int(self.pay * self.raise_amt)

# new class inherits the Employee class
class Developer(Employee):
	#overriding the value for the subclass
	raise_amt = 1.10

	def __init__(self, first, last, pay, prog_lang):
		super().__init__(first, last, pay) # letting the parent class handle these
		# or
		Employee.__init__(self, first, last, pay) # more suitable for multiple inheritance
		self.prog_lang = prog_lang


dev1 = Developer('Corey', 'Schafer', 50000, "Python")
dev2 = Developer('Test' ,' Employee', 60000", "Java")

print(dev1.email)
print(dev1.prog_lang)
class Emloyee:
	raise_amt = 1.04
	
	def __init__(self, first, last, pay):
		self.first = first
		self.last = last
		self.email = first + "." + last + "@email.com"
		self.pay = pay
		
	def fullname(self):
		return "{ } { }".format(self.first, self.last)
		
	def apply_raise(self):
		self.pay = int(self.pay * self.raise_amt)

# new class inherits the Employee class
class Developer(Employee):
	#overriding the value for the subclass
	raise_amt = 1.10

	def __init__(self, first, last, pay, prog_lang):
		super().__init__(first, last, pay) # letting the parent class handle these
		self.prog_lang = prog_lang

class Manager(Employee):

	def __init__(self, first, last, pay, employees=None):
		super().__init__(first, last, pay) 
		if employees is None:
			self.employees = []
		else:
			self.employees = employees

	def add_emp(self, emp):
		if emp is not in self.employees:
			self.employees.append(emp)

	def remove_emp(self, emp):
		if emp is not in self.employees:
			self.employees.remove(emp)

	def print_emp(self):
		for emp in self.employees:
			print("-->", emp.fullname())

dev1 = Developer('Corey', 'Schafer', 50000, "Python")
dev2 = Developer('Test' ,' Employee', 60000", "Java")

mgr1 = Manager("Sue", "Smith", 90000, [dev1])
print(mgr1.email)

mgr1.print_emps() # returns --> Corey Schafer

mgr1.add_emp(dev2)
mgr1.remove_emp(dev1)
mgr1.print_emps() # returns --> Test Employee
print(isinstance(mgr1, Manager) # True cause mgr1 is an instance of class Manager
print(isinstance(mgr1, Employee) # True cause mgr1 is an instance of class Employee
print(isinstance(mgr1, Developer) # False cause mgr1 is an instance of (sub)class Employee >> Manager not of subclass Developer

print(issubclass(Developer, Employee)) # True
print(issubclass(manager, Developer)) # False