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

8 函数

image

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 result
print(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 类

image

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))  # 随机挑选首发球员

posted @ 2026-04-06 20:21  【唐】三三  阅读(38)  评论(0)    收藏  举报