Files and Exceptions¶
Now that you’ve mastered the basic skills you need to write organized programs that are easy to use, it’s time to think about making your programs even more relevant and usable. In this chapter you’ll learn to work with files so your programs can quickly analyze lots of data. You’ll learn to handle errors so your programs don’t crash when they encounter unexpected situations. You’ll learn about exceptions, which are special objects Python creates to manage errors that arise while a program is running. You’ll also learn about the json module, which allows you to save user data so it isn’t lost when your program stops running.
Learning to work with files and save data will make your programs easier for people to use. Users will be able to choose what data to enter and when to enter it. People can run your program, do some work, and then close the program and pick up where they left off later. Learning to handle exceptions will help you deal with situations in which files don’t exist and deal with other problems that can cause your programs to crash. This will make your programs more robust when they encounter bad data, whether it comes from innocent mistakes or from malicious attempts to break your programs. With the skills you’ll learn in this chapter, you’ll make your programs more applicable, usable, and stable.
READING FROM A FILE¶
An incredible amount of data is available in text files. Text files can contain weather data, traffic data, socioeconomic data, literary works, and more. Reading from a file is particularly useful in data analysis applications, but it’s also applicable to any situation in which you want to analyze or modify information stored in a file. For example, you can write a program that reads in the contents of a text file and rewrites the file with formatting that allows a browser to display it.
When you want to work with the information in a text file, the first step is to read the file into memory. You can read the entire contents of a file, or you can work through the file one line at a time.
Reading an Entire File¶
To begin, we need a file with a few lines of text in it. Let’s start with a file that contains pi to 30 decimal places, with 10 decimal places per line:
pi_digits.txt
3.1415926535
8979323846
2643383279
All the files used in this lecture can be found under the data folder.
Here’s a program that opens this file, reads it, and prints the contents of the file to the screen:
Difference between relative file path and absolute file path¶
'data/pi_digits.txt' this file path is so called relative file path, which means this file path is relative to this notebook location. Under the same folder. For the software development, you should use the relative directories. Don't use abslute directories
"C:\Users\zyang\OneDrive\桌面\ee.txt" this is an absolute file path, which mean directly from your computer. Windows use '/' in file path. Mac and Linux uses "" in file path. Personal information, and the absolute file path different between different computer
with open('data/pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
3.1415926535 8979323846 2643383279
Python default path is the linux and mac file path, it should be use '/' instead of '' (special character) . Compare below two cell. You need to insert 'r' before a windows path file. "//" also work
with open(r'C:\Users\zyang\OneDrive\桌面\ee.txt') as file_object:
contents = file_object.read()
print(contents)
eeeeee
with open('C:/Users/zyang/OneDrive/桌面/ee.txt') as file_object:
contents = file_object.read()
print(contents)
eeeeee
The first line of this program has a lot going on. Let’s start by looking at the open() function. To do any work with a file, even just printing its contents, you first need to open the file to access it. The open() function needs one argument: the name of the file you want to open. Python looks for this file in the directory where the program that’s currently being executed is stored. In this example, file_reader.py is currently running, so Python looks for pi_digits.txt in the directory where file_reader.py is stored. The open() function returns an object representing the file. Here, open('pi_digits.txt') returns an object representing pi_digits.txt. Python assigns this object to file_object, which we’ll work with later in the program.
The keyword with closes the file once access to it is no longer needed. Notice how we call open() in this program but not close(). You could open and close the file by calling open() and close(), but if a bug in your program prevents the close() method from being executed, the file may never close. This may seem trivial, but improperly closed files can cause data to be lost or corrupted. And if you call close() too early in your program, you’ll find yourself trying to work with a closed file (a file you can’t access), which leads to more errors. It’s not always easy to know exactly when you should close a file, but with the structure shown here, Python will figure that out for you. All you have to do is open the file and work with it as desired, trusting that Python will close it automatically when the with block finishes execution.
Once we have a file object representing pi_digits.txt, we use the read() method in the second line of our program to read the entire contents of the file and store it as one long string in contents. When we print the value of contents, we get the entire text file back.
The only difference between this output and the original file is the extra blank line at the end of the output. The blank line appears because read() returns an empty string when it reaches the end of the file; this empty string shows up as a blank line. If you want to remove the extra blank line, you can use rstrip() in the call to print():
with open('data/pi_digits.txt') as file_object:
contents = file_object.read()
print(contents.rstrip())
3.1415926535 8979323846 2643383279
If you choose to read file, and after finish coding you need to close the file
f =open('data/pi_digits.txt')
f.read()
'3.1415926535 \n 8979323846 \n 2643383279\n'
f.close()
File Paths¶
When you pass a simple filename like pi_digits.txt to the open() function, Python looks in the directory where the file that’s currently being executed (that is, your .py program file) is stored.
Sometimes, depending on how you organize your work, the file you want to open won’t be in the same directory as your program file. For example, you might store your program files in a folder called python_work; inside python_work, you might have another folder called text_files to distinguish your program files from the text files they’re manipulating. Even though text_files is in python_work, just passing open() the name of a file in text_files won’t work, because Python will only look in python_work and stop there; it won’t go on and look in text_files. To get Python to open files from a directory other than the one where your program file is stored, you need to provide a file path, which tells Python to look in a specific location on your system.
Because text_files is inside python_work, you could use a relative file path to open a file from text_files. A relative file path tells Python to look for a given location relative to the directory where the currently running program file is stored. For example, you’d write:
with open('text_files/filename.txt') as file_object:
This line tells Python to look for the desired .txt file in the folder text_files and assumes that text_files is located inside python_work (which it is).
Windows systems use a backslash () instead of a forward slash (/) when displaying file paths, but you can still *use forward slashes in your code.\
You can also tell Python exactly where the file is on your computer regardless of where the program that’s being executed is stored. This is called an absolute file path. You use an absolute path if a relative path doesn’t work. For instance, if you’ve put text_files in some folder other than python_work—say, a folder called other_files—then just passing open() the path 'text_files/filename.txt' won’t work because Python will only look for that location inside python_work. You’ll need to write out a full path to clarify where you want Python to look.
Absolute paths are usually longer than relative paths, so it’s helpful to assign them to a variable and then pass that variable to open():
file_path = '/home/ehmatthes/other_files/text_files/filename.txt'
with open(file_path) as file_object:
Using absolute paths, you can read files from any location on your system. For now it’s easiest to store files in the same directory as your program files or in a folder such as text_files within the directory that stores your program files.
If you try to use backslashes in a file path, you’ll get an error because the backslash is used to escape characters in strings. For example, in the path "C:\path\to\file.txt", the sequence \t is interpreted as a tab. If you need to use backslashes, you can escape each one in the path, like this: "C:\\path\\to\\file.txt".
Reading Line by Line¶
When you’re reading a file, you’ll often want to examine each line of the file. You might be looking for certain information in the file, or you might want to modify the text in the file in some way. For example, you might want to read through a file of weather data and work with any line that includes the word sunny in the description of that day’s weather. In a news report, you might look for any line with the tag <headline>
and rewrite that line with a specific kind of formatting.
You can use a for loop on the file object to examine each line from a file one at a time:
filename = 'data/pi_digits.txt'
with open(filename) as file_object:
for line in file_object:
print(line)
3.1415926535 8979323846 2643383279
➊ filename = 'pi_digits.txt'
➋ with open(filename) as file_object:
➌ for line in file_object:
print(line)
At ➊ we assign the name of the file we’re reading from to the variable filename. This is a common convention when working with files. Because the variable filename doesn’t represent the actual file—it’s just a string telling Python where to find the file—you can easily swap out 'pi_digits.txt' for the name of another file you want to work with. After we call open(), an object representing the file and its contents is assigned to the variable file_object ➋. We again use the with syntax to let Python open and close the file properly. To examine the file’s contents, we work through each line in the file by looping over the file object ➌.
When we print each line, we find even more blank lines. These blank lines appear because an invisible newline character is at the end of each line in the text file. The print function adds its own newline each time we call it, so we end up with two newline characters at the end of each line: one from the file and one from print(). Using rstrip() on each line in the print() call eliminates these extra blank lines:
filename = 'data/pi_digits.txt'
with open(filename) as file_object:
for line in file_object:
print(line.rstrip()) # .rstrip() eliminate the special character at the end of each line
3.1415926535 8979323846 2643383279
filename= "C:/Users/zyang/OneDrive/桌面/gift_government.txt"
with open(filename) as file_object:
for line in file_object:
print(line.rstrip())
hhhh hu78 chen98 hua0920
Making a List of Lines from a File¶
When you use with, the file object returned by open() is only available inside the with block that contains it. If you want to retain access to a file’s contents outside the with block, you can store the file’s lines in a list inside the block and then work with that list. You can process parts of the file immediately and postpone some processing for later in the program.
The following example stores the lines of pi_digits.txt in a list inside the with block and then prints the lines outside the with block:
filename = 'data/pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
3.1415926535 8979323846 2643383279
filename = 'data/pi_digits.txt'
with open(filename) as file_object:
➊ lines = file_object.readlines()
➋ for line in lines:
print(line.rstrip())
At ➊ the readlines() method takes each line from the file and stores it in a list. This list is then assigned to lines, which we can continue to work with after the with block ends. At ➋ we use a simple for loop to print each line from lines. Because each item in lines corresponds to each line in the file, the output matches the contents of the file exactly.
Working with a File’s Contents¶
After you’ve read a file into memory, you can do whatever you want with that data, so let’s briefly explore the digits of pi. First, we’ll attempt to build a single string containing all the digits in the file with no whitespace in it:
filename = 'data/pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.rstrip() #eliminate the empty line
print(pi_string)
print(len(pi_string))
3.1415926535 8979323846 2643383279 36
filename = 'data/pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip() #only keep string, eliminate all empty
print(pi_string)
print(len(pi_string))
3.141592653589793238462643383279 32
Note: When Python reads from a text file, it interprets all text in the file as a string. If you read in a number and want to work with that value in a numerical context, you’ll have to convert it to an integer using the int() function or convert it to a float using the float() function.
Large Files: One Million Digits¶
So far we’ve focused on analyzing a text file that contains only three lines, but the code in these examples would work just as well on much larger files. If we start with a text file that contains pi to 1,000,000 decimal places instead of just 30, we can create a single string containing all these digits. We don’t need to change our program at all except to pass it a different file. We’ll also print just the first 50 decimal places, so we don’t have to watch a million digits scroll by in the terminal:
filename = 'data/pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(f"{pi_string[:52]}...")
print(len(pi_string))
3.14159265358979323846264338327950288419716939937510... 1000002
Python has no inherent limit to how much data you can work with; you can work with as much data as your system’s memory can handle.
Is Your Birthday Contained in Pi?¶
I’ve always been curious to know if my birthday appears anywhere in the digits of pi. Let’s use the program we just wrote to find out if someone’s birthday appears anywhere in the first million digits of pi. We can do this by expressing each birthday as a string of digits and seeing if that string appears anywhere in pi_string:
filename = 'data/pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")
Enter your birthday, in the form mmddyy: 901031 Your birthday appears in the first million digits of pi!
filename = 'data/pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
prompt = 'Enter your birthday, in the form mmddyy:'
prompt += '\n(Enter quit when you finish)'
while True:
birthday = input(prompt)
if birthday == 'quit':
break
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")
Enter your birthday, in the form mmddyy: (Enter quit when you finish) 032001 Your birthday appears in the first million digits of pi! Enter your birthday, in the form mmddyy: (Enter quit when you finish) quit
WRITING TO A FILE ¶
One of the simplest ways to save data is to write it to a file. When you write text to a file, the output will still be available after you close the terminal containing your program’s output. You can examine output after a program finishes running, and you can share the output files with others as well. You can also write programs that read the text back into memory and work with it again later.
Writing to an Empty File¶
To write text to a file, you need to call open() with a second argument telling Python that you want to write to the file. To see how this works, let’s write a simple message and store it in a file instead of printing it to the screen:
filename = 'data/programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
Note: Be careful when you try to use w mode to write a file. When you try to do that make sure there is no file with a same name. If it already exist, the python program will delete the exisiting one, and create a new one with just you write.
➊ with open(filename, 'w') as file_object:
➋ file_object.write("I love programming.")
The call to open() in this example has two arguments ➊. The first argument is still the name of the file we want to open. The second argument, 'w', tells Python that we want to open the file in write mode. You can open a file in read mode ('r'), write mode ('w'), append mode ('a'), or a mode that allows you to read and write to the file ('r+'). If you omit the mode argument, Python opens the file in read-only mode by default.
The open() function automatically creates the file you’re writing to if it doesn’t already exist. However, be careful opening a file in write mode ('w') because if the file does exist, Python will erase the contents of the file before returning the file object.
At ➋ we use the write() method on the file object to write a string to the file. This program has no terminal output, but if you open the file programming.txt, you’ll see one line:
NOTE: Python can only write strings to a text file. If you want to store numerical data in a text file, you’ll have to convert the data to string format first using the str() function.
Writing Multiple Lines¶
The write() function doesn’t add any newlines to the text you write. So if you write more than one line without including newline characters, your file may not look the way you want it to:
filename = 'data/programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
file_object.write("I love creating new games.")
If you open programming.txt, you’ll see the two lines squished together:
I love programming.I love creating new games.
Including newlines in your calls to write() makes each string appear on its own line:
filename = 'data/programmings.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.\n")
file_object.write("I love creating new games.")
The output now appears on separate lines:
I love programming.
I love creating new games.
You can also use spaces, tab characters, and blank lines to format your output, just as you’ve been doing with terminal-based output.
Note: check the different of the two code above. The first one create all the content in the same line, because in programming, if you don't specify at what part you want to separate the senetences, it will automatically creates into one line. The second one using '\n' to separate the line
filename = 'data/programmingss.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.\t\t\t I love creating new games.")
Note: /t using as empty space to separate the two sentences in one line.
Appending to a File¶
If you want to add content to a file instead of writing over existing content, you can open the file in append mode. When you open a file in append mode, Python doesn’t erase the contents of the file before returning the file object. Any lines you write to the file will be added at the end of the file. If the file doesn’t exist yet, Python will create an empty file for you.
Let’s modify the program by adding some new reasons we love programming to the existing file programming.txt:
filename = 'data/programming.txt'
with open(filename, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in a browser.\n")
Note: When try to use the append function ('a' mode), if you don't specify you want a new line using the "\n" at the beginning of the line, it will utomatically added after the last sentence without creating a new line.
leafmap usage¶
using leafmap download file function to download files
- it can be anyfiles
- from anywhere on the Internet (github, google drive)
import leafmap
leafmap.download_file("https://raw.githubusercontent.com/zyang91/GEOG-510/main/docs/python/data/pi_digits.txt")
Downloading... From: https://raw.githubusercontent.com/zyang91/GEOG-510/main/docs/python/data/pi_digits.txt To: C:\Users\zyang\GEOG-510\python_code\pi_digits.txt 71%|███████████████████████████████████████████████████████▊ | 41.0/58.0 [00:00<00:00, 20.6kB/s]
'C:\\Users\\zyang\\GEOG-510\\python_code\\pi_digits.txt'
#It can download anyfile
leafmap.download_file("https://raw.githubusercontent.com/opengeos/leafmap/master/examples/data/us_regions.geojson")
Downloading... From: https://raw.githubusercontent.com/opengeos/leafmap/master/examples/data/us_regions.geojson To: C:\Users\zyang\GEOG-510\python_code\us_regions.geojson 208kB [00:00, 895kB/s]
'C:\\Users\\zyang\\GEOG-510\\python_code\\us_regions.geojson'
EXCEPTIONS ¶
Python uses special objects called exceptions to manage errors that arise during a program’s execution. Whenever an error occurs that makes Python unsure what to do next, it creates an exception object. If you write code that handles the exception, the program will continue running. If you don’t handle the exception, the program will halt and show a traceback, which includes a report of the exception that was raised.
Exceptions are handled with try-except blocks. A try-except block asks Python to do something, but it also tells Python what to do if an exception is raised. When you use try-except blocks, your programs will continue running even if things start to go wrong. Instead of tracebacks, which can be confusing for users to read, users will see friendly error messages that you write.
Using try-except Blocks¶
When you think an error may occur, you can write a try-except block to handle the exception that might be raised. You tell Python to try running some code, and you tell it what to do if the code results in a particular kind of exception.
Here’s what a try-except block for handling the ZeroDivisionError exception looks like:
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
You can't divide by zero!
5/0 #division by zero
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[3], line 1 ----> 1 5/0 #division by zero ZeroDivisionError: division by zero
We put print(5/0), the line that caused the error, inside a try block. If the code in a try block works, Python skips over the except block. If the code in the try block causes an error, Python looks for an except block whose error matches the one that was raised and runs the code in that block.
In this example, the code in the try block produces a ZeroDivisionError, so Python looks for an except block telling it how to respond. Python then runs the code in that block, and the user sees a friendly error message instead of a traceback:
You can't divide by zero!
If more code followed the try-except block, the program would continue running because we told Python how to handle the error. Let’s look at an example where catching an error can allow a program to continue running.
Using Exceptions to Prevent Crashes¶
Handling errors correctly is especially important when the program has more work to do after the error occurs. This happens often in programs that prompt users for input. If the program responds to invalid input appropriately, it can prompt for more valid input instead of crashing.
Let’s create a simple calculator that does only division:
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
try:
answer = int(first_number) / int(second_number)
except ZeroDivisionError:
print("You can't divide by 0!")
else:
print(answer)
Give me two numbers, and I'll divide them. Enter 'q' to quit. First number: 2 Second number: 3 0.6666666666666666 First number: 1 Second number: 3 0.3333333333333333 First number: 1 Second number: 0 You can't divide by 0! First number: q
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
try:
answer = int(first_number) / int(second_number)
except ValueError:
print('You must enter a mumber')
except ZeroDivisionError:
print("You can't divide by 0!")
else:
print(answer)
Give me two numbers, and I'll divide them. Enter 'q' to quit. First number: ggg Second number: gg You must enter a mumber First number: q
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
try:
answer = int(first_number) / int(second_number)
except:
print("Somethong went wrong")
else:
print(answer)
Give me two numbers, and I'll divide them. Enter 'q' to quit. First number: g Second number: g Somethong went wrong First number: q
#know the error type
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
try:
answer = int(first_number) / int(second_number)
except Exception as e:
print(e)
else:
print(answer)
Give me two numbers, and I'll divide them. Enter 'q' to quit. First number: 78 Second number: 9 8.666666666666666 First number: 89 Second number: 0 division by zero First number: 88 Second number: hh invalid literal for int() with base 10: 'hh' First number: q
The try-except-else block works like this: Python attempts to run the code in the try block. The only code that should go in a try block is code that might cause an exception to be raised. Sometimes you’ll have additional code that should run only if the try block was successful; this code goes in the else block. The except block tells Python what to do in case a certain exception arises when it tries to run the code in the try block. By anticipating likely sources of errors, you can write robust programs that continue to run even when they encounter invalid data and missing resources. Your code will be resistant to innocent user mistakes and malicious attacks.
Handling the FileNotFoundError Exception¶
One common issue when working with files is handling missing files. The file you’re looking for might be in a different location, the filename may be misspelled, or the file may not exist at all. You can handle all of these situations in a straightforward way with a try-except block.
Let’s try to read a file that doesn’t exist. The following program tries to read in the contents of Alice in Wonderland, but I haven’t saved the file alice.txt in the same directory as alice.py:
filename = 'alice.txt'
with open(filename, encoding='utf-8') as f:
contents = f.read()
--------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) Cell In[8], line 3 1 filename = 'alice.txt' ----> 3 with open(filename, encoding='utf-8') as f: 4 contents = f.read() File ~\miniconda3\envs\geos\lib\site-packages\IPython\core\interactiveshell.py:282, in _modified_open(file, *args, **kwargs) 275 if file in {0, 1, 2}: 276 raise ValueError( 277 f"IPython won't let you open fd={file} by default " 278 "as it is likely to crash IPython. If you know what you are doing, " 279 "you can use builtins' open." 280 ) --> 282 return io_open(file, *args, **kwargs) FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
Sorry, the file alice.txt does not exist.
Analyzing Text¶
You can analyze text files containing entire books. Many classic works of literature are available as simple text files because they are in the public domain. The texts used in this section come from Project Gutenberg (http://gutenberg.org/). Project Gutenberg maintains a collection of literary works that are available in the public domain, and it’s a great resource if you’re interested in working with literary texts in your programming projects.
Let’s pull in the text of Alice in Wonderland and try to count the number of words in the text. We’ll use the string method split(), which can build a list of words from a string. Here’s what split() does with a string containing just the title "Alice in Wonderland":
title = "Alice in Wonderland"
title.split()
['Alice', 'in', 'Wonderland']
The split() method separates a string into parts wherever it finds a space and stores all the parts of the string in a list. The result is a list of words from the string, although some punctuation may also appear with some of the words. To count the number of words in Alice in Wonderland, we’ll use split() on the entire text. Then we’ll count the items in the list to get a rough idea of the number of words in the text:
filename = 'data/alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
# Count the approximate number of words in the file.
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
The file data/alice.txt has about 29465 words.
filename = 'data/alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
# Count the approximate number of words in the file.
words = contents.split()
num_words = len(set(words)) #unique words
print(f"The file {filename} has about {num_words} words.")
The file data/alice.txt has about 6018 words.
Finish¶
References¶
- Matthes, Eric (2022). Python Crash Course, 3rd Edition: A Hands-On, Project-Based Introduction to Programming. No Starch Press. ISBN: 978-1593279288. (Publisher website)
- Dr.Qiusheng Wu online lecture