八、函数
1.定义函数
def greet_user():
"""显示简单的问候语"""
print("Hello! ")
greet_user()
Hello!
使用关键字 def 来告诉python,要定义一个函数。
def 函数名():
() 内可以有信息,但是 () 必不可少。
定义以冒号结尾。
紧跟在函数后面的所有缩进进行构成了函数体。
“”" “”" 中的文本称为文档字符串( docstring )的注释,描述了函数是做什么的。
- 在函数定义中,变量是一个形参( parameter ),即函数完成工作所需的信息;
- 值是一个实参( argument ),即调用函数时传递给函数的信息。
2.传递实参
函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。
位置实参:要求实参的顺序与形参的顺序相同
关键字实参:每个实参都由变量名和值组成
使用列表和字典
2.1位置实参
def describe_pet(animal_type, pet_name):
"""显示宠物信息"""
print(f"I have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
I have a hamster.
My hamster’s name is Harry.
2.2关键字实参
def describe_pet(animal_type, pet_name):
"""显示宠物信息"""
print(f"I have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster') # 不需要顺序也可
I have a hamster.
My hamster’s name is Harry.
I have a hamster.
My hamster’s name is Harry.
3.默认值
编写函数时,可给每一个形参指定默认值。在调用函数中给形参提供了实参时,python将使用指定的实参值;否则,将使用默认值。
使用默认值时,必须先在形参列表中列出没有默认值的形参,再列出有默认值的形参。这让python依然能够正确地解读位置实参。
def describe_pet(pet_name, animal_type='dog'):
"""显示宠物信息"""
print(f"I have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('harry')
describe_pet(pet_name='Lynn', animal_type='hamster')
# 或者 describe_pet('Lynn', 'hamster')
I have a dog.
My dog’s name is Harry.
I have a hamster.
My hamster’s name is Lynn.
4.返回值
函数可以处理一些数据,并返回一个或一组值。函数返回的值称为返回值。在函数中,可使用 return 语句返回到调用函数的代码行。
4.1返回简单值
def get_formatted_name(first_name, last_name, middle_name=''):
"""返回整洁的名字"""
if middle_name: # python将非空的字符串解读为True
full_name = f"{first_name} {middle_name} {last_name}"
else:
full_name = f"{first_name} {last_name}"
return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician)
musician = get_formatted_name('john', 'hooker', 'lee')
print(musician)
Jimi Hendrix
John Lee Hooker
4.2返回字典
函数可返回任何类型的值,包括列表和字典等复杂的数据结果
def build_person(first_name, last_name, age=None):
"""返回一个字典,其中包含有关一个人的信息"""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person
musician = build_person('jami', 'hendrix', 27)
print(musician)
{‘first’: ‘jami’, ‘last’: ‘hendrix’, ‘age’: 27}
在函数定义中,新增了一个可选的形参 age ,并将其默认值设置为特殊值 None (表示变量没有值)。可将 None 视为占位值。在测试条件中,None 相当于 False。
5.传递列表
5.1在函数中修改列表
在函数中对这个列表所做的任何修改都是永久性的。
def print_models(unprinted_designs, completed_models):
"""模拟打印每个设计,直到没有未打印的设计为止
打印每个设计后,都将其移到列表completed_models中"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
def show_completed_models(completed_models):
"""显示打印好的所有模型"""
print("\nThe following models have been printed:")
for model in completed_models:
print(model)
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
print(unprinted_designs) #检验是否被修改
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case
[]
5.2禁止函数修改列表
有时候需要禁止函数修改列表,可向函数传递列表的副本而非原件。
形如:function_name(List_name[:])
除非有充分的理由,否则还是应该将原始列表传递给函数。这是因为让函数使用现成的列表可避免花时间和内存创建副本,从而提高效。
6.任意数量的实参
6.1使用任意数量的位置实参
下面的函数只有一个形参 *toppings
,但不管调用语句提供了多少实参,这个形参会将它们都收入囊中。
形参名*toppings
中的星号让 python 创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中,即函数只收到一个值。
def make_pizza(*toppings):
"""打印顾客点的所有配料"""
print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
(‘pepperoni’,)
(‘mushrooms’, ‘green peppers’, ‘extra cheese’)
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。
def make_pizza(size, *toppings):
"""打印顾客点的所有配料"""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"-{topping}")
make_pizza(15, 'pepperoni')
make_pizza(20, 'mushrooms', 'green peppers', 'extra cheese')
Making a 15-inch pizza with the following toppings:
-pepperoni
Making a 20-inch pizza with the following toppings:
-mushrooms
-green peppers
-extra cheese
6.2使用任意数量的关键字实参
形参**user_info
中两个星号让 python 创建一个名为 user_info 的空字典,并将收到的所有名称值对都放在这个字典中。
def build_profile(first, last, **user_info):
"""创建一个空字典,包括用户的一切"""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user = build_profile('albert', 'einstein',
location='princeton', field='physics')
print(user)
{‘location’: ‘princeton’, ‘field’: ‘physics’, ‘first_name’: ‘albert’, ‘last_name’: ‘einstein’}
7.将函数存储在模块中
将模块存储在称为模块的独立文件中,再将模块导入到主程序中。import 语句允许在当前运行的程序文件中使用模块中的代码。
7.1导入整个模块
模块是扩展名为 .py 的文件,包含要导入到程序中的代码。
在同一个目录下,创建 pizza.py 和 making_pizzas.py 两个文件, pizza.py 作为模块, making_pizzas.py 作为主函数。
pizza.py 文件
def make_pizza(size, *toppings):
"""打印顾客点的所有配料"""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"-{topping}")
making_pizzas.py 文件
import pizza
pizza.make_pizza(15, 'pepperoni')
pizza.make_pizza(20, 'mushrooms', 'green peppers', 'extra cheese')
Making a 15-inch pizza with the following toppings:
-pepperoni
Making a 20-inch pizza with the following toppings:
-mushrooms
-green peppers
-extra cheese
代码行 import pizza
让 python 打开文件 pizza.py ,并将其中的所有函数都复制到这个函数中(看不到复制的代码)。
调用被导入模块中的函数,可指定被导入模块的名称和函数名,并用句点分隔。
7.2导入特定的函数
导入模块中的特定函数
from module_name import function_name
通过用逗号分隔,导入任意数量的函数
from module_name import function_0, function_1, function_2
使用上述语法时,调用函数无需使用句点,指定名称即可。
7.3使用as
给模块/函数指定别名
模块:
import pizza as p
p.make_pizza(……)
函数:
from pizza import make_pizze as mp
mp(……)
7.4导入模块中所有函数
使用星号(*)运算符可导入模块中所有函数。
import pizza *
由于导入了每个函数,可通过名称来调用每一个函数,而无需使用句点表示法。
导入所有函数可能会导致遇到多个名称相同的函数或变量,进而覆盖函数,产生错误。
7.5函数编写指南
每个函数都注释,并使用文档字符串格式
形参指定默认值/调用关键字实参时,等号两边不要有空格
形参列表过长,在函数定义输入左括号后打回车键,并在下一行按两次 tab 键
相邻函数用两行空行分开
九、类
1.创建 Dog 类
class Dog:
""""一次模拟小狗的简单测试"""
def __init__(self, name, age):
"""初始化属性 name 和 age"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗收到命令蹲下"""
print(f"{self.name} is now sitting.")
def roll_over(self):
"""模拟小狗收到命令时打滚"""
print(f"{self.name} rolled over!")
在python中,首字母大写的名称指的是类。类中的函数称为方法。
__init__()
是一个特殊的方法,每当根据 Dog 类创建新实例时,python都会自动运行它。在这个方法中,开头和末尾各有两个下划线,这是一种约定,旨在避免python默认方法与普通方法发生名称冲突。
在这个方法定义中,形参 self 必不可少,而且必须位于其他形参的前面。因为python调用这个方法来创建 Dog 实例时,将自动传入实参 self 。每个与实例相关联的方法调用都自动传递实参 self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
以 self 为前缀的变量(self.name
)可供类中的所有方法使用,可以通过类的任何实例来访问。像这样可通过实例访问的变量称为属性。
2.根据类创建实例
通常认为,首字母大写的名称指的是类,而小写的名称指的是根据类创建的实例。
class Dog:
#内容同上,省略#
my_dog = Dog('willie', 6)
print(f"My dog's name is {my_dog.name}") # 访问属性
print(f"My dog is {my_dog.age} years old.")
my_dog.sit() # 调用方法
my_dog.roll_over()
My dog’s name is willie.
My dog is 6 years old.
willie is now sitting.
willie rolled over!
2.1访问属性
要使用实例的属性,可使用句点表示法 my_dog.name
。在这里,python先找到实例 my_dog ,再查找与该实例相关的属性 name 。
2.2调用方法
要调用方法,可指定实例的名称和要调用的方法,并用句点分隔。形如 my_dog.sit()
。
3.使用类和实例
3.1给属性指定默认值
class Car:
"""模拟汽车"""
def __init__(self, make, model, year):
"""初始化描述汽车属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0 # 给属性指定默认值
def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""打印一条汽车里程的信息"""
print(f"This car has {self.odometer_reading} miles on it.")
my_new_car = Car('audi', 'a4', 2020)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
2020 Audi A4
This car has 0 miles on it.
3.2修改属性的值
3.2.1直接修改属性的值
通过实例直接访问它。
my_new_car = Car('audi', 'a4', 2020)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 25 # 直接修改属性的值
my_new_car.read_odometer()
2020 Audi A4
This car has 25 miles on it.
3.2.2 通过方法修改属性的值
class Car:
--snip--
def update_odometer(self, mileage):
"""将里程表读数设定为指定的值"""
self.odometer_reading = mileage
my_new_car = Car('audi', 'a4', 2020)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(27) # 通过方法修改属性的值
my_new_car.read_odometer()
2020 Audi A4
This car has 27 miles on it.
可对方法 update_odometer()
进行扩展,使其在修改里程表读数时做些额外的工作。
class Car:
--snip--
def update_odometer(self, mileage):
"""
将里程表读数设定为指定的值。
禁止将里程表的数据往回调。
"""
if mileage >self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
3.3.3 通过方法对属性的值进行增值
class Car:
--snip--
def increment_odometer(self, miles):
"""将里程表读书增加指定的量"""
self.odometer_reading += miles
4.继承
一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。
4.1子类的方法__init__()
在既有类的基础上编写新类时,通常要调用父类的方法 __init()__
。这将初始化在父类 __init()__
方法中定义的属性,从而让子类包含这些属性。
class Car:
"""模拟汽车"""
def __init__(self, make, model, year):
"""初始化描述汽车属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0 # 给属性指定默认值
def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""打印一条汽车里程的信息"""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""
将里程表读数设定为指定的值。
禁止将里程表的数据往回调。
"""
if mileage > self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""将里程表读书增加指定的量"""
self.odometer_reading += miles
# 子类
class ElectricCar(Car):
"""电动汽车的独特之处"""
def __init__(self, make, model, year):
"""初始化父类的的属性"""
super().__init__(make, model, year)
my_tesla = ElectricCar('tesla', 's', 2018)
print(my_tesla.get_descriptive_name())
2018 Tesla S
定义子类时,必须在圆括号内指定父类的名称。方法 __init()__
接受创建 Car 实例所需的信息。
super()
是一个特殊函数,让你能够调用父类的方法。super().__init__(make, model, year)
这行代码python调用父类的方法 __init()__
,让 ElectricCar 实例包括这个方法定义的所有属性。父类也称为超类(superclass)。
4.2给子类定义属性和方法
让一个类继承另一个类后,就可以添加独属于子类的新属性和新方法了。
class Car:
--snip--
class ElectricCar(Car):
"""电动汽车的独特之处"""
def __init__(self, make, model, year):
"""
初始化父类的的属性
在初始化电动汽车独有属性
"""
super().__init__(make, model, year)
self.battery_size = 75 # 独属子类的属性
def describe_battery(self): # 独属子类的方法
"""打印一条描述电瓶容量的信息"""
print(f"This car has a {self.battery_size}-kwh battery.")
my_tesla = ElectricCar('tesla', 's', 2018)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
2018 Tesla S
This car has a 75-kwh battery.
4.3重新父类的方法
对于父类的方法,只要它不符合子类模拟的实物行为,都可以进行重写。为此,可在子类中定义一个与要重写的父类方法同名的方法。这样,python将不会考虑这个父类方法,而是只关注在子类中定义的相应方法。
4.4将类用作另一个类的属性
当自己给类添加的细节越来越多时,可能需要将类的一部分提取出来,作为一个单独的类。
class Car:
--snip--
class Battery:
"""描述电瓶信息"""
def __init__(self, battery_size=75): # 给予默认值75
"""初始化电瓶属性"""
self.battery_size = battery_size
def describe_battery(self):
"""打印一条描述电瓶容量的信息"""
print(f"This car has a {self.battery_size}-kwh battery.")
class ElectricCar(Car):
"""电动汽车的独特之处"""
def __init__(self, make, model, year):
"""
初始化父类的的属性
在初始化电动汽车独有属性
"""
super().__init__(make, model, year)
self.battery = Battery() # 将类用作另一个类的属性
my_tesla = ElectricCar('tesla', 's', 2018)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
2018 Tesla S
This car has a 75-kwh battery.
5.导入类
将 Car 类存储在一个名为 car.py 的模块中(只包含类的相关内容),在文件开头包含一个模块级的文档字符串。
- 从一个模块中导入多个类
from car import Car, ElectricCar
- 导入整个模块
import car
此做法调用函数时需要用句点式,如car.Car()
访问 Car 类。 - 导入模块中所有类(不建议)
from car import *
可能会出现名称方面冲突的情况。 - 在模块中导入另一个模块
当一个模块中的类依赖与另一个模块中的类,可在前一个模块中导入必要的类。
from car import Car
6.python标准库
模块 random 的两个函数 random()
和 choie()
。
- 函数
random()
它将两个整数作为参数,并随机返回一个位于这两个整数之间(含)的整数。
>>> from random import randint
>>> randint(1,6)
3
- 函数
choie()
它将一个列表或元组作为参数,并随机返回其中的一个元素。
>>> from random import choice
>>> palyers = [‘s’, ‘a’, ‘r’, ‘h’, ‘p’]
>>> choice(palyers)
‘s’
十、文件与异常
1. 从文件中读取数据
1.1读取整个文件
首先创建一个文件( pi_digits.txt
),然后在程序中使用 open()
函数。
3.1415926535
8979323846
2643383279
with open('pi_digits.txt') as f:
contents = f.read()
print(contents)
函数 open()
接受一个参数,即要打印的文件的名称,返回一个表示文件的对象。
python 在当前执行的文件所在的目录中查找指定的文件。
关键字 with 在不再需要访问文件后将其关闭。
结果:
3.1415926535
8979323846
2643383279
相比与原始文件,该输出唯一不同的地方是末尾多了一个空行。因为 read()
到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除空行,可在函数调用 print()
中使用 rstrip()
。
print(contents.rstrip())
1.2文件路径
程序文件存储在了文件夹 python_work 中,而在该文件夹中有一个名为 text_files 的文件夹用于存储程序文件操作的文本文件。
with open('text_files/filename.py') as file_object
这行代码可让 python 在文件夹 python_work 下的文件夹 text_files 中去查找指定的 .txt 文件。
注意:显示文件路径时,Windows 系统使用反斜杠(\)而不是斜杠(/),但在代码中依然可以使用斜杠。
将文件在计算机中的准确位置告诉 python ,这样就不用关心当前运行的程序存储在什么地方了,这称为绝对文件路径。通过使用绝对路径,可读取系统中任何地方的文件。
注意:如果在文件路径中使用反斜杠,将可能引发错误,因为反斜杠用于对字符串中的字符进行转义。
1.3逐行读取
with open('pi_digits.txt') as f:
for line in f:
print(line)
3.1415926535
8979323846
2643383279
打印每一行时,发现空白行更多了。因为在这个文件中,每行末尾都有一个看不见的换行符,而函数 print()
也会加上一个换行符,相当与有两个换行符。要消除多余的空白行,可在函数调用 print()
中使用 rstrip()
。
print(line.rstrip())
3.1415926535
8979323846
2643383279
1.4创建一个包含文件各行内容的列表
使用关键字 with 时,open()
返回的文件对象只在 with 代码块内可用。如果要在代码块外使用,可将文件各行存储在一个列表中。
方法 readlines()
从文件中读取每一行,并将其存储在一个列表中。
filename = 'pi_digits.txt'
with open(filename) as f:
lines = f.readlines()
for line in lines:
print(line.rstrip())
1.5使用文件的内容
filename = 'pi_digits.txt'
with open(filename) as f:
lines = f.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string)
print(len(pi_string))
3.141592653589793238462643383279
32
读取文本文件时,python 将其中的所有文本都解读为字符串。如果读的是数,并要将其作为数值使用,就必须使用函数
int()
将其转换为整数或使用函数float()
将其转换为浮点数。
2. 写入文件
2.1写入空文件
filename = 'programming.txt'
with open(filename, 'w') as f:
f.write("I love programming.")
本例中,调用 open()
时提供两个参数。第一个实参是文件名称,第二个实参(‘w’)告诉python要以写入模式打开这个文件。
如果文件不存在,函数
open()
将自动创建它。
如果文件已存在,python 将在返回文件对象前清空该文件的内容。
模式 | 字母 |
---|---|
读取模式 | r |
写入模式 | w |
附加模式 | a |
读写模式 | r+ |
如果省略了模式实参,将以默认的只读模式打开文件。
2.2写入多行
函数 write()
不会在写入的文本末尾添加换行符,需要在方法调用时包含换行符。
filename = 'programming.txt'
with open(filename, 'w') as f:
f.write("I love programming.\n")
f.write("I love creating new games.\n")
在文件 programming.txt 查看
I love programming.
I love creating new games.
2.3附加到文件
如果要给文件添加内容,而不是覆盖原有的内容,可以以附加模式打开文件。如果指定的文件不存在,将自动创建一个空文件。
filename = 'programming.txt'
with open(filename, 'a') as f:
f.write("I also love finding meaning in large datasets.\n")
f.write("balabala\n")
在文件 programming.txt 查看
I love programming.
I love creating new games.
I also love finding meaning in large datasets.
balabala
3.异常
python 使用称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让 python 不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果未对异常进行处理,程序将停止并显示 traceback ,其中包含有关异常的报告。
异常是使用 try—except 代码块处理的。 try—except 代码块让 python 执行指定的操作,同时告诉 python 发生异常时怎么办。使用 try—except 代码块时,即便出现异常,程序也将继续运行。
3.1处理异常
- ZeroDivisionError 异常
print(5/0)
Traceback (most recent call last):
File "D:\work_python\text.py", line 1, in <module>
print(5/0)
ZeroDivisionError: division by zero
- FileNotFoundError 异常
filename = "alice.txt"
with open(filename) as f:
content = f.read()
Traceback (most recent call last):
File "D:\work_python\text.py", line 3, in <module>
with open(filename) as f:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
- 使用 try—except 代码块
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
You can’t divide by zero!
3.2 else 代码块
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("First 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 zero!")
else:
print(answer)
在本例中,如果除法运算成功,就使用 else 代码块来打印结果。如果第二个数输入零,不会出现 trackback 的情况,程序继续运行,避免崩溃。
Give me two numbers, and I’ll divide them.
Enter ‘q’ to quit.
First number:5
Second number:2
2.5
First number:9
Second number:0
You can’t divide by zero!
First number:8
Second number:2
4.0
First number:q
3.3静默失败
有时候希望程序发生异常时保持静默,就像什么都没发生一样继续运行。在 except 代码块中明确告诉python什么都不要做(使用 pass 语句)。
pass 语句还充当了占位符,提醒你在某个地方什么都没做,并且以后也许在这里做些什么。
try:
--snip--
except FileNotFoundError:
pass
4.存储数据
用户关闭程序时,几乎总是要保持他们提供的信息。一种简单的方式是使用 json 来存储数据。
JSON(JavaScript Object Notation)格式最初是为 JavaScript 开发的,但随后成了一种常见格式,被包括 Python 在内的众多语言采用。
4.1 使用 json.dump()
和json.load()
函数 json.dump()
接受两个实参:要存储的数据,以及可用于存储数据的文件对象。
通常使用的**文件扩展名 .json **来指出文件存储的数据为 JSON 格式。
import json
numbers = [1, 2, 3, 5]
filename = 'numbers.json'
with open(filename, 'w') as f:
json.dump(numbers, f)
这个程序没有输出,但可以打开 numbers.json 文件查看内容。
数据的存储格式与 Python 中一样。
[1, 2, 3, 5]
下面再编写一个函数,使用 json.load()
将列表读取到内存中:
import json
filename = 'numbers.json'
with open(filename) as f:
numbers = json.load(f)
print(numbers)
[1, 2, 3, 5]
这是一种在程序之间共享数据的简单方式。
4.2 保存和读取用户生成的数据
import json
# 如果以前存储了用户名,就加载它,并用 else 语句打印出来;
# 否则,提示用户输入用户名并存储它。
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}")
else:
print(f"Welcome back, {username}")
无论是执行的是 except 还是 else 代码块,都会显示用户名和合适的问候语。
用户名没存储过:
What is your name? json
We’ll remember you when you come back, json
用户名存储过了:(这是程序至少运行过一次时的输出)
Welcome back, json
4.3 重构
代码能够正确地运行,但通过将其划分为一系列完成具体工作的函数,还可以改进。这样的过程称为重构。重构让代码更清晰、更易于理解、更容易扩展。
在这个最终版中,每一个函数都执行单一而清晰的任务。
import json
filename = 'username.json'
def get_stored_username():
"""如果存储了用户名,就获取它"""
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
return None # 用户名不存在返回None
else:
return username
def get_new_username():
"""提示用户输入用户名"""
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
return username
def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username() # 获取文件 username.json 中的数据
if username: # 用户存储过
print(f"Welcome back, {username}")
else: # 用户没存储过
username = get_new_username() # 调用函数录入用户名
print(f"We'll remember you when you come back, {username}")
greet_user()