Python
introduction
- Don't need to explicitly compile and run your code like C.
- Use interpreter, a program to read and run your code.
- Less code, more work.
- A good ecosystem with lots of libraries to solve problems which have been solved by others.
Types
bool
, int
, float
and str
are types in python.
Use input
to get str from your keyboard
Note that you can perform
str
by+
and*
.+
performs string concatenation and*
performs repetition.
Run the code and we get following result. The variable type that input
returns is str
explains the reason why output is 12, i.e. '1' + '2'
type()
can be used to tell you the type of a value.
In these results, the word “class” is used in the sense of a category; a type is a category of values.
String
Use index
to choose the char you want in the sequence.
Use slice
to select a segment of a string.
>>> s = "hello, world"
>>> s[1]
'e'
>>> s[0:5]
'hello'
>>> s[:7]
'hello, '
>>> s[7:]
'world'
>>> s[:]
'hello, world'
[n:m]
returns the part of the string from the “n-eth” character to the “m-eth” character, including the first but excluding the last.
str
is immutable.
Conditions
- Indentation is important in python
if x < y:
print("x is less than y")
elif x > y:
print("x is greater than y")
else:
print("x is equal to y")
Object-Oriented Programming
Some functions are built into objectives themselves. These functions called method
comes with some data types, like a string.
s = input("Do you agree? ")
# method for string to convert it to lowercase
s = s.lower()
if s in ["y", "yes"]:
print("Agreed")
elif s in ["n", "no"]:
print("Not agreed")
Click here for official documentation
Class
A programmer-defined type is also called a class. We can define a class like this:
To create a Point class, you can call Point
as if it were a function. Creating a new object is called instantiation, and the object is an instance of the class.
You can assign values to named elements of an object using dot notation. These elements are called attributes
Actually, when creating an object or instance of a class, the built-in method __init__
will initialize the content of the object for us. We have an additional parameter in the defination of __init__
called self
. It refers to the object itself and provides us a way to store values in the object.
class Student:
def __init__(self, name, house):
self.name = name
self.house = house
def get_student():
name = input("Name: ")
house = input("House: ")
student = Student(name, house)
return student
__str__
is also a function called when the object needed to be treated as a str
, such as print(student)
. When we define __str__
, the string ver of the object is just the return value of __str__
.
class Student:
def __init__(self, name, house):
self.name = name
self.house = house
def __str__(self):
return f"{self.name} from {self.house}"
Decorator
@
is called a decorator to decorate functions in class.
For example, a getter function is a function called when we want to get the attribute of an object, a setter function is a function called when we want to set the attribute of an object.
We can define getter function with @property
and setter function with @[attribute_name].setter
.
class Student:
def __init__(self, name, house):
self.name = name
self.house = house
def __str__(self):
return f"{self.name} from {self.house}"
@property
def name(self):
return self._name
@name.setter
def name(self, name):
if not name:
raise ValueError("Missing name")
self._name = name
@property
def house(self):
return self._house
@house.setter
def house(self, house):
if house not in ["A", "B", "C", "D"]:
raise ValueError("Invalid house")
self._house = house
def get_student():
name = input("Name: ")
house = input("House: ")
student = Student(name, house)
return student
self._house
or self._name
. That's because our attibutes can't have the same name with functions. So we add a underscore to distinguish them. In __init__
function, we call those setter functions so we don't use underscore.
Tragically, you can still set the attribute invalidly without raising error by assigning self._house
directly. So the convention is don't change attributes begin with _
.
Classmethod
The function decorated by @Classmethod
can't access attributes of self
. But it can access class variables. A classmethod can be directly called without creating an instance.
Inheritance
When we want to define two classes that shares some same attributes or method, we can define a third class and pass that class to other two classes as a super class, which is called inheritance.
In the following example, Wizard
is a parent
, or super
class of Student
and Professor
. super().[function_name]
means calling the function of its super class.
class Wizard:
def __init__(self, name):
if not name:
raise ValueError("Missing name")
self.name = name
class Student(Wizard):
def __init__(self, name, house):
super().__init__(name)
self.house = house
class Professor(Wizard):
def __init__(self, name, subject):
super().__init__(name)
self.subject = subject
Oprator Overloading
You can define the distinct usage of specific operator which concatenates the class.
For example, if you want to use +
to operate your class, you can define __add__
function.
class Vault:
def __init__(self, galleons=0, sickles=0, knuts=0):
self.galleons = galleons
self.sickles = sickles
self.knuts = knuts
def __str__(self):
return f"{self.galleons} Galleons, {self.sickles} Sickles, {self.knuts} Knuts"
def __add__(self, other):
galleons = self.galleons + other.galleons
sickles = self.sickles + other.sickles
knuts = self.knuts + other.knuts
return Vault(galleons, sickles, knuts)
potter = Vault(100, 50, 25)
harry = Vault(25, 50, 100)
total = potter + harry
Objects are mutable.
Copying an object is often used. You can use copy
module to do that. There are two types of copy
-
Shallow copy:
copy.copy()
copies the object and any references it contains, but not the embedded objects. -
Deep copy:
copy.deepcopy()
copies not only the object but also the objects it refers to, and the objects they refer to, and so on.
Loop
A forever loop
True and False are capitalized in Python
You can loop over almost anything that is iterable in Python, such as string.
before = input("Before: ")
print("After: ", end="")
for c in before:
print(c.upper(), end="")
# end is a second parameter here telling print to insert nothing instead of a new line after every output
# otherwise each letter will be outputed in seperate lines
print() # move cursor to next line
do-while
loop, just put the loop body into a forever loop and jump out when something happens.
In Python, even a for loop can have a else
clause.
names = ["Cat", "Dog", "Bird"]
name = input("Name: ")
for n in names:
if name == n:
print("Found")
break
else:
print("Not Found")
break
, then the code in else
will execute.
This is just an example to explain for-else
. Actually it can be solved below
names = ["Cat", "Dog", "Bird"]
name = input("Name: ")
if name in names:
print("Found")
else:
print("Not Found")
Function
Practically, we want the main part of a program at the top of the file so that we can dive right in and know what the file is doing. Let's look at the following way.
If we run the code, we will find a traceback.Traceback (most recent call last):
File "C:\Users\86156\desktop\meow.py", line 2, in <module>
meow()
^^^^
NameError: name 'meow' is not defined
meow()
is called before its defination.
To solve this problem, we need to define a function called main
and takes no arguments.
Note that the last line is necessary since the beginning only define the main
function and it will be executed only when we call it.
Now, let's add some parameter so that we can control the times the loop will run.
Flow of execution
The order statements run in is called flow of execution. Execution always begin at the first statement of the program. Statements are run one at a time from top to bottom. Function definition don't alter the flow of execution of the program, but remember that statements inside the function don’t run until the function is called. A function call is like a detour in the flow of execution. Instead of going to the next statement, the flow jumps to the body of the function, runs the statements there, and then comes back to pick up where it left off.
Higher-Order Functions
Functions as Arguments
Consider you want to compute the sum of a series. Here are two examples:
def sum_naturals(n):
total, k = 0, 1
while k <= n:
total, k = total + k, k + 1
return total
def sum_cubes(n):
total, k = 0, 1
while k <= n:
total, k = total + k*k*k, k + 1
return total
term
, which is a function, into the sum function so that we don't need to write a paricular function for every series.
Let's define our function in the following way:
def summation(n, term):
total, k = 0, 1
while k <= n:
total, k = total + term(k), k + 1
return total
def cube(x):
return x*x*x
def identity(x):
return x
sum_cube = summation(n, cube)
sum_natural = summation(n, identity)
term
function to get the sum of more series.
Functions as General Methods
Consider the following function to compute the golden ratio.
def improve(update, close, guess = 1):
while not close(guess):
guess = update(guess)
return guess
def golden_update(guess):
return 1 / guess + 1
def square_close_to_successor(guess):
return approx_eq(guess * guess, guess + 1)
def approx_eq(x, y, tolerance = 1e-3):
return abs(x - y) < tolerance
phi = improve(golden_update, square_close_to_successor)
improve
function is asgeneral expression of repetitive refinement. It doesn't specify what problem is being solved: those details are left to the update
and close
functions passed in as arguments. approx_eq
returns True when its arguments are close to each other.
This example illustrates two related big ideas in computer science.
-
Naming and functions allow us to abstract away a vast amount of complexity. While each function definition has been trivial, the computational process set in motion by our evaluation procedure is quite intricate.
-
It is only by virtue of the fact that we have an extremely general evaluation procedure for the Python language that small components can be composed into complex processes. Understanding the procedure of interpreting programs allows us to validate and inspect the process we have created.
Nested Definitions
The above examples demonstrate how the ability to pass functions as arguments significantly enhances the expressive power of our programming language. Each general concept or equation maps onto its own short function. One negative consequence of this approach is that the global frame becomes cluttered with names of small functions, which must all be unique. Another problem is that we are constrained by particular function signatures: the update
argument to improve
must take exactly one argument. Nested function definitions address both of these problems, but require us to enrich our environment model.
The following program compute a square root of a number.
def sqrt(a):
def sqrt_update(x):
return average(x, a/x)
def sqrt_close(x):
return approx_eq(x * x, a)
return improve(sqrt_update, sqrt_close)
def
statements only affect the current local frame. These functions are only in scope while sqrt
is being evaluated. Consistent with our evaluation procedure, these local def
statements don't even get evaluated until sqrt
is called.
-
The names of a local function do not interfere with names external to the function in which it is defined, because the local function name will be bound in the current local environment in which it was defined, rather than the global environment.
-
A local function can access the environment of the enclosing function, because the body of the local function is evaluated in an environment that extends the evaluation environment in which it was defined.
The sqrt_update
function carries with it some data: the value for a referenced in the environment in which it was defined. Because they "enclose" information in this way, locally defined functions are often called closures.
Functions as Returned Values
We can define function composition like \(h(x)=f(g(x))\) using our existing tools:
def square(x):
return x * x
def successor(x):
return x + 1
def composel(f, g):
def h(x):
return f(g(x))
return h
square_successor = composel(square, successor)
result = square_successor(12)
Lambda Expressions
lambda
expression is an unnamed function, reture a single expression as its body.
s = lambda x, y : x * y # input x and y, return x*y
t = lambda : None # no input and always output None
u = lambda *args: sum(args) # input arbitary arguments, reutrn their sum
v = lambda **kwargs: 1 # input arbitary key-value arguments, return 1
s
, t
, u
and v
are all functions
Exceptions
In C, we often return some distinct value to signifies the function failed to achieve its goal. In Python, we can use exception
to handle it.
def main():
x = get_int("x: ")
y = get_int("y: ")
print(x + y)
def get_int(prompt):
return int(input(prompt))
main()
get_int()
function works well when we input some numbers. But a exception happens when we input other things.
python .\calculator.py
x: cat
Traceback (most recent call last):
File "C:\Users\86156\desktop\calculator.py", line 10, in <module>
main()
File "C:\Users\86156\desktop\calculator.py", line 2, in main
x = get_int("x: ")
^^^^^^^^^^^^^^
File "C:\Users\86156\desktop\calculator.py", line 8, in get_int
return int(input(prompt))
^^^^^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'cat'
ValueError
tells us what kind of exception we have. We can revise it in this way:
def get_int(prompt):
try:
return int(input(prompt))
except ValueError:
print("Not an interger")
try
and except
to tell the interpreter we try to do something and if ValueError
exception happens, we will see "Not an integer" instead of traceback.
Only try
and except
are not enough. We need to add a loop so that the function will loop again and again until get a valid input.
def get_int(prompt):
while True:
try:
return int(input(prompt))
except ValueError:
print("Not an interger")
else
as part of try-except block. The statement in else
execute when the except
not happened.
while True:
try:
x = int(input("What's x? "))
except ValueError:
print("x is not an integer")
else:
break
print(f"x is {x}")
You can also use raise
to raise an exception. Combine it with try-except
can catch the error.
class Student:
def __init__(self, name, house):
if not name:
raise ValueError("Missing name")
if house not in ["G", "H", "R", "S"]
raise ValueError("Invalid house")
self.name = name
self.house = house
def get_student():
name = input("Name: ")
house = input("House: ")
try:
student = Student(name, house)
except ValueError:
print("Please input the correct name and house")
return None
else:
return student
Unit Tests
Assume we have a program calculator.py
to calculate the square of the input. We want to write a program to automaticaly test whether our program is right. We can use assert
to assert the boolean expression following it is True. If True, nothing will happen. But if not, AssertionError
will appear on screen
from calculator import square
def main():
test_square()
def test_square():
try:
assert square(2) == 4
except AssertionError:
print("2 squared was not 4")
try:
assert square(3) == 9
except AssertionError:
print("3 squared was not 9")
try:
assert square(0) == 0
except AssertionError:
print("0 squared was not 0")
try:
assert square(-2) == 4
except AssertionError:
print("-2 squared was not 4")
if __name__ == "__main__":
main()
pytest
You can also use pytest
, is a third-party library that allows you to unit test your program.
Run pytest test_calculator.py
in your command line, which is shown below, pytest will automatically test it for you.
from calculator import square
def test_positive():
assert square(2) == 4
assert square(3) == 9
def test_negative():
assert square(-2) == 4
assert square(-3) == 9
def test_positive():
assert square(0) == 0
Let's add some bug in calculator.py
, rerun the command and see what will happen.
def main():
x = int(input("What's x? "))
print("x squared is", square(x))
def square(n):
return n + n
if __name__ == "__main__":
main()
- Unit testing code using multiple tests is so common that you have the ability to run a whole folder of tests with a single command.
- Add a folder called
test
with test programs in it. An additional file__init__.py
is necessary. Leave it empty andpytest
is informed that the whole folder containing__init__.py
has tests that can be run. - Run
pytest test
for testing.
list
List is something like array but their memory is automatically handled for you. An array is about having contiguously in memory. In Python, a list is more like a linked list. It will allocate memory for you and you don't have to know about pointers and nodes.
scores = [72, 73, 33] # List using square brackets
# use the bulid-in functions to get the sum and length of the list
average = sum(scores) / len(scores)
Traverse
You can use following ways to traverse a list
names = ['adam', 'bob', 'tony']
for name in names:
print(name)
for i in range(len(names)):
print(names[i])
Opeartions
You can use +
and *
to manipulate lists
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a + b
[1, 2, 3, 4, 5, 6]
>>> a * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
Method
append
add a element to the end of a list
extend
takes a list as an argument and append its element to the end of the origin list
sort
sort the elements from low to high
>>> a = [1, 2, 3]
>>> a.append(6)
>>> a
[1, 2, 3, 6]
>>> b = [5, 7]
>>> b.extend(a)
>>> b
[5, 7, 1, 2, 3, 6]
>>> b.sort()
>>> b
[1, 2, 3, 5, 6, 7]
pop
delete the element with given index in the list and return that element. If no index is provided, delete the last one.
remove
delete the element with given value and return nothing
del
operator can remove more than one element with slice
>>> x = b.pop(2)
>>> x
3
>>> b
[1, 2, 5, 6, 7]
>>> b.remove(7)
>>> b
[1, 2, 5, 6]
>>> del b[1:3]
>>> b
[1, 6]
dist
Dictionary is essentially a hash tabel, a collection of key-value pairs.
people = [
{"name": "Carter", "number": "12345"}, # dist uses curly brace
{"name": "David", "number": "12335"},
{"name": "John", "number": "16345"},
]
name = input("Name: ")
for person in people:
if person["name"] == name:
number = person["number"]
print(f"Found {number}")
break
else:
print("Not Found")
people = {
"Carter": "12345",
"David": "12335",
"John": "16345",
}
name = input("Name: ")
if name in people: # Python will look for the name among the keys in the dist
number = people[name]
print(f"Found {number}")
else:
print("Not Found")
len
function returns the number of key-value pairs.
in
operator tells whether something appears as a key in the dist.
values
method returns a collection of values.
>>> dist = {"apple":"red", "banana":"yellow", "potato":"brown"}
>>> len(dist)
3
>>> "apple" in dist
True
>>> "red" in dist
False
>>> value = dist.values()
>>> "red" in value
True
tuple
Tuples are immutable, you can't modify the elements. But you can replace one tuple with another.
Note that one value in parentheses without a comma is not a tuple
Assignment
tuple assignment is more elegant for you can implement in the following way.
The left side is a tuple of variables; the right side is a tuple of expressions. Each value is assigned to its respective variable. All the expressions on the right side are evaluated before any of the assignments.
Tuples as return values
Actually a tuple is a list of value separated by comma and we can omit the parentheses although not recommand.
In some functions, return values are often separated by comma, which are actually tuples.
Variable-length argument tuples
Functions can take a variable number of arguments. A parameter name that begins with a * gathers arguments into a tuple. For example, the following print out arguments as a tuple.
The complement of gather is scatter. If you have a sequence of values and you want to pass it to a function as multiple arguments, you can use the * operator. For example:
>>> t = (1, 2)
>>> divmod(t)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: divmod expected 2 arguments, got 1
>>> divmod(*t)
(0, 1)
Lists and tuples
zip()
is a built-in function that takes two or more sequences and interleaves them.
The result is a zip object that knows how to iterate through the pairs. The most common use of zip
is in a for loop:
>>> l = 'abc'
>>> n = [1, 2, 3]
>>> z = zip(l ,n)
>>> for pair in z:
... print(pair)
...
('a', 1)
('b', 2)
('c', 3)
A zip object is a kind of iterator, which is any object that iterates through a sequence. Iterators are similar to lists in some ways, but unlike lists, you can’t use an index to select an element from an iterator.
If you want to use list operators and methods, you can use a zip object to make a list:
If you need to traverse the elements of a sequence and their indices, you can use the built-in function enumerate
:
>>> list(zip(l, n))
[('a', 1), ('b', 2), ('c', 3)]
>>> for index, element in enumerate('abc'):
... print(index, element)
...
0 a
1 b
2 c
Dicts and tuples
Dictionary have a method called items
returns a sequence of tuples with key-value pairs. The result is a dist_items
object, which is also an iterator.
Combining dict
with zip
yields a concise way to create a dictionary
Files
- transient program: data disappears when programs end
- persistent program: some of their data is kept in permanent storage.
- reading and writing text files or store the state of program in a database are basic way to maintain data.
Reading and writing
The built-in function open
takes the name of the file as a parameter and returns a file object.
readline
reads a line each time and returns the result as a string. Or you can use readlines
to return all lines in a list
You can also use a file object as part of a for loop
fin = open('demo.txt')
for line in fin:
print(line.strip()) # strip() removes '\n' in the end of a str
Open a file with mode w
as a second parameter to write a file. If the file already exists, opening it in write mode clears out the old data and starts fresh. You can use mode a
if you don't want to erase old thing and just append.
write
write str into the file. Return value is the number of characters that were written.
>>> fout = open('demo.txt', 'w')
>>> line1 = "This is a title.\n"
>>> fout.write(line1)
17
>>> line2 = "this is a p"
>>> fout.write(line2)
11
Close the file when operating is completed
To avoid forgetting to close the file, you can use the keyword with
to do that for you. The following code assign open("names.txt", "a")
to file
and close it when the statement in with is done.
csv
csv
stands for Comma-Separated Values. It's a very common convention to store multiple pieces of information that are related in the same file. See the following example.
We can use functions in csv
module to handle this case. csv.reader
act similar to above.
import csv
with open("mygo.csv") as file:
reader = csv.reader(file)
for row in reader:
print(f"{row[0]} is {row[1]}")
Now we insert some hints as to what these columns are in the first row and use csv.DistReader
to handle it.
It makes our code more robust by associating our data with its column title
We can also write to csv
.
DictWriter
takes two parameters: the file
being written to and the fieldnames
to write. Further, notice how the writerow
function takes a dictionary as its parameter. Quite literally, we are telling the compiler to write a row with two fields called name and home.
Filenames and paths
os
module provides functions for working with files and directories.
os.getcwd
returns the name of the current directory
os.path
provides other functions for working with filenames and paths.
>>> os.path.abspath('demo.txt') # find absolute path
'C:\\Users\\86156\\Desktop\\demo.txt'
>>> os.path.exists('demo.txt') # check is the file exist
True
>>> os.path.isdir('demo.txt') # check is it a directory
False
Database
A database is a file that is organized for storing data. Many databases are organized like a dictionary in the sense that they map from keys to values, and it is on disk.
The module dbm
provides an interface for creating and updating database files.
Mode c
means that database should be created if it doesn’t already exist. The result is a database object that can be used (for most operations) like a dictionary.
The result of access is bytes object.
You can iterate it with a for loop
Close it when you are done
A limitation of dbm
is that the keys and values have to be strings or bytes.
You can use pickle
module to transilate any type of object into a string, and then translate the string back into objects.
pickle.dumps
takes an object and returns a string representation.pickle.loads
reconstitues the object.
>>> import pickle
>>> t = [1, 2 ,3]
>>> t1 = pickle.dumps(t)
>>> t1
b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00]\x94(K\x01K\x02K\x03e.'
>>> t2 = pickle.loads(t1)
>>> t2
[1, 2, 3]
Writing modules
Any file that contains Python code can be imported as a module.
Suppose you have a file like this
You can import it like this.
Now you have a module object demo
and you can use function in it.
The test code in the module will run when you import it, but normally we only want the defination of functions. We can add the following idiom:
__name__
is a built-in variable that is set when the program starts. If the program is running as a script, __name__
has the value '__main__'
; in that case, the test code runs. Otherwise, if the module is being imported, the test code is skipped.
Other
Some operators
Floor division and modulus
The division sign /
divides two numbers to a float, while floor division //
returns an integer rounded down by that float. The modulus operator %
divides two numbers and return s the remainder.
Logical operator
Python use and
, or
and not
these three keywords to do logical operation.
truncation, imprecision and overflow
Python won't truncate the result if it has fractional component. It will convert type automatically.
Run the code We can use f string to show more digits after the decimal point The result below shows that the floating point imprecision problem remains in Python In Python, integer won't overflow no matter how big it is because Python will reserve more and more memeory for that integer to fit it.sys
The sys library has system-related functionality.
In C, we got access to command-line arguments with main()
, argc
and argv
.
You can do command-line arguments in Python with the help of sys
hello, world
. If two, then different.
Note that the command python
is ignored from argv
You can also exit the program with sys
import sys
if len(sys.argv) != 2:
print("Missing command-line argument")
sys.exit(1)
print(f"hello, {sys.argv[1]}")
sys.exit(0)
pip
Using pip
to install third-party libraries.