《python编程从入门到实践》8-9章 函数、类
8 函数

8.1 定义函数
def greet_user(): # def 定义一个函数
"""显示简单的问候语""" # 文档字符串(docstring)的注释
print("Hello!")
greet_user()
第一行代码使用关键字 def 来告诉 Python,你要定义一个函数。这是函数定义。
这些字符串通常前后分别用三个双引号引起,能够包含多行。
8.2 传递实参
8.2.2 关键字实参
关键字实参是传递给函数的名值对。这样会直接在实参中将名称和值关联起来,因此向函数传递实参时就不会混淆了
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(animal_type='hamster', pet_name='harry')
8.2.3 默认值
在编写函数时,可以给每个形参指定默认值。如果在调用函数中给形参提供了实参,
Python 将使用指定的实参值;否则,将使用形参的默认值。
def describe_pet(pet_name, animal_type='dog'):
"""显示宠物的信息"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(pet_name='willie')
# 可以简单使用 describe_pet('willie')
8.2.4 等效的函数调用
位置可以混乱
# 一条名为 Willie 的小狗
describe_pet('willie')
describe_pet(pet_name='willie')
# 一只名为 Harry 的仓鼠
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
8.3 返回值
8.3.1 返回简单的值
def get_formatted_name(first_name, last_name):
"""返回标准格式的姓名"""
full_name = f"{first_name} {last_name}"
return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician)
8.3.2 让实参变成可选的
有时候,需要让实参变成可选的,以便使用函数的人只在必要时才提供额外的信息。可以使用默认值来让实参变成可选的。
def get_formatted_name(first_name, last_name, middle_name=""):
"""返回标准格式的姓名"""
if middle_name:
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 Hooker Lee
8.4 传递列表
你经常会发现,向函数传递列表很有用,可能是名字列表、数值列表或更复杂的对象列表(如字典)。将列表传递给函数后,函数就能直接访问其内容。下面使用函数来提高处理列表的效率
def greet_users(names):
"""向列表中的每个用户发出简单的问候"""
for name in names:
msg = f"Hello, {name.title()}!"
print(msg)
usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)
输出
Hello, Hannah!
Hello, Ty!
Hello, Margot!
8.4.1 在函数中修改列表
来看一家为用户提交的设计制作 3D 打印模型的公司。需要打印的设计事先存储在一个列表中,打印后将被移到另一个列表中。下面是在不使用函数的情况下模拟这个过程的代码:
# 首先创建一个列表,其中包含一些要打印的设计
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
# 模拟打印每个设计,直到没有未打印的设计为止
# 打印每个设计后,都将其移到列表 completed_models 中
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
# 显示打印好的所有模型
print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
输出
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case
8.4.2 禁止函数修改列表
如果不想让原列表被修改,可以在调用时传递列表的副本:function_name(list_name[:])。
function_name(list_name[:])
8.5 传递任意数量的实参
| 特性 | *args |
**kwargs |
|---|---|---|
| 全称及含义 | "arguments" 的缩写,用于接收任意数量的位置参数 | "keyword arguments" 的缩写,用于接收任意数量的关键字参数 |
| 参数类型 | 接收多个位置参数,将其打包成一个元组(tuple) | 接收多个关键字参数,将其打包成一个字典(dict) |
| 调用方式示例 | function(1, 2, 3) |
function(a=1, b=2, c=3) |
| 函数定义示例 | def function(*args):<br> print(args) |
def function(**kwargs):<br> print(kwargs) |
| 在函数内部的使用 | 像使用元组一样访问和操作这些参数,如通过索引 args[0] 访问第一个参数,使用循环遍历等 |
像使用字典一样访问和操作这些参数,如通过键 kwargs['key'] 获取对应的值,使用 kwargs.items() 遍历键值对 |
| 参数顺序要求 | 位置参数必须放在关键字参数之前,如果函数同时有普通位置参数,*args 要放在普通位置参数之后 |
必须放在 *args 和普通参数之后,在函数定义中位置参数、*args、**kwargs 的顺序通常为:def func(普通参数, *args, **kwargs): |
| 应用场景 | 适用于不知道会传入多少个位置参数的情况,例如计算多个数字的总和等 | 适用于不知道会传入多少个关键字参数的情况,比如传递用户的多个属性信息、配置参数等 |
| 示例代码 | def sum_numbers(*nums):result = sum(nums)return resultprint(sum_numbers(1, 2, 3, 4)) |
def print_user_info(**user):for key, value in user.items():print(f"{key}: {value}")print_user_info(name="Alice", age=25, city="New York") |
8.5.1 结合使用位置实参和任意数量的实参
你预先不知道函数需要接受多少个实参,好在 Python 允许函数从调用语句中收集任意数量的实参。
例如一个制作比萨的函数,它需要接受很多配料,但无法预先确定顾客要点多少种配料。下面的函数只有一个形参 *toppings,不管调用语句提供了多少实参,这个形参都会将其收入囊中:
def make_pizza(*toppings):
"""概述要制作的比萨"""
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese') # 返回值是一个元组
输出
Making a pizza with the following toppings:
- pepperoni
Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
函数定义时使用
*参数名(如*toppings)的语法会将传入的多个位置参数打包成一个元组(tuple)。
8.5.2 使用任意数量的关键字实参
有时候,你需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键值对——调用语句提供了多少就接受多少。
在 Python 中,
**kwargs是一个特殊的参数语法,用于接收任意数量的关键字参数(keyword arguments),并将它们作为一个 字典(dict) 传递给函数
def build_profile(first, last, **user_info):
"""创建一个字典,其中包含我们知道的有关用户的一切"""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)
输出
{'location': 'princeton', 'field': 'physics',
'first_name': 'albert', 'last_name': 'einstein'}
8.6 将函数存储在模块中
为了隐藏代码细节、聚焦高层逻辑并实现复用,你可以将函数存储在称为模块的独立 .py 文件中,再将其导入主程序。
导入整个模块
假设你有一个名为 pizza.py 的模块,里面定义了 make_pizza() 函数:
# 在主程序中
import pizza
pizza.make_pizza('pepperoni')
导入特定的函数
from pizza import make_pizza
make_pizza('pepperoni') # 直接调用,无需加模块名前缀
使用别名
如果函数名或模块名可能冲突,或者太长,可使用 as 指定别名:
# 给函数指定别名
from pizza import make_pizza as mp
mp('pepperoni')
# 给模块指定别名
import pizza as p
p.make_pizza('pepperoni')
导入模块中的所有函数
使用星号
*可导入模块中所有函数,但由于容易引发名称冲突,不建议在大型项目中使用:
from pizza import *
make_pizza('pepperoni')
9 类

9.1. 创建和使用类
类让你能够模拟几乎任何东西。编写类时,你定义的是一大类对象的通用特征。
9.1.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!")
核心要点:
__init__()是一个特殊方法,每当根据类创建新实例时,Python都会自动运行它。self是必不可少的参数,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。- 以
self为前缀的变量(如self.name)可供类中的所有方法使用,称为属性。
9.1.2 根据类创建实例
my_dog = Dog('Willie', 6)
print(f"My dog's name is {my_dog.name}.") # 访问属性
my_dog.sit() # 调用方法
9.2. 使用类和实例
类编写好后,大部分时间将花在使用实例上。你经常需要修改实例的属性。
9.2.1 给属性指定默认值
有些属性无须通过形参传入,可以直接在 __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.")
9.2.2 修改属性的值
有三种方式可以修改属性的值:
① 直接通过实例修改
my_new_car = Car('audi', 'a4', 2024)
my_new_car.odometer_reading = 23 # 直接修改
② 通过方法修改
class Car:
# --snip--
def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage
my_new_car.update_odometer(23)
③ 通过方法递增(增加特定的值)
class Car:
# --snip--
def increment_odometer(self, miles):
"""将里程表读数增加指定的量"""
self.odometer_reading += miles
9.3. 继承
如果要编写的类是另一个既有类的特殊版本,可使用继承。子类将自动获得父类的所有属性和方法,同时还可以定义自己的属性和方法。
9.3.1 子类的 __init__() 方法
在子类中,必须调用父类的 __init__() 方法来初始化父类中定义的属性:
class ElectricCar(Car): # 括号内指定父类名
"""电动汽车的独特之处"""
def __init__(self, make, model, year):
"""先初始化父类的属性,再初始化电动汽车特有的属性"""
super().__init__(make, model, year) # 调用父类的方法
self.battery_size = 40 # 子类特有的属性
9.3.2 重写父类中的方法
如果父类方法不符合子类模拟实物的行为,可以在子类中定义一个同名方法来重写它:
class ElectricCar(Car):
# --snip--
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This car doesn't have a gas tank!")
9.3.3 将实例用作属性(组合)
当类变得庞大复杂时,可以将一部分提取出来作为独立的类,并将其作为另一个类的属性,这种方法称为组合:
class Battery:
"""一次模拟电动汽车电池的简单尝试"""
def __init__(self, battery_size=40):
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() # 将 Battery 实例用作属性
my_leaf = ElectricCar('nissan', 'leaf', 2024)
my_leaf.battery.describe_battery() # 通过属性调用 Battery 类的方法
9.4. 导入类
随着类的增多,应将类存储在模块中,然后在主程序中导入,以保持文件整洁。
假设将 Car 类存储在了 car.py 模块中:
① 导入单个类
from car import Car
my_mustang = Car('ford', 'mustang', 2024)
② 从一个模块中导入多个类
from car import Car, ElectricCar
③ 导入整个模块
import car
my_mustang = car.Car('ford', 'mustang', 2024)
④ 使用别名
from car import ElectricCar as EC
my_leaf = EC('nissan', 'leaf', 2024)
9.5. Python 标准库
Python标准库是一组预先写好的模块。其中 random 模块在模拟很多现实情况时很有用:
randint(a, b):返回 a 和 b 之间(含)的随机整数。choice(seq):从列表或元组中随机返回一个元素。
from random import randint, choice
print(randint(1, 6)) # 掷骰子
players = ['charles', 'martina', 'michael']
print(choice(players)) # 随机挑选首发球员

浙公网安备 33010602011771号