化零为整:用函数和类构建可复用的 Python 代码
·
你是否曾写过这样的代码?
一段逻辑在程序中到处复制粘贴,一旦需要修改,就必须像打地鼠一样在所有出现的地方进行修改。这样的代码不仅难以维护,更是滋生bug的温床。
解决的黄金法则就是:Don‘t Repeat Yourself (DRY)。 而函数和类,正是实现这一法则的两大利器。它们能将零散的代码块组织成独立的、可复用的逻辑单元,让你的代码从“脚本”升级为“工程”。
本文将带你深入探索如何运用函数和类,为你的代码搭建起坚固而灵活的骨架。
第一部分:函数——代码的基本构建块
函数是一段为了完成特定功能而组织在一起的、可重复使用的代码块。你可以把它想象成一个多功能厨房电器,比如料理机。你投入食材(参数),它进行加工(执行代码),然后输出成果(返回值)。
1. 定义与调用:从“怎么做”到“做什么”
# 定义函数:告诉计算机“怎么做”
def greet(name): # def 关键字,greet 是函数名,name 是参数
"""这是一个问候函数(文档字符串,用于说明)"""
message = f"Hello, {name}!" # 函数体:具体的操作
return message # 返回值:将结果送出函数
# 调用函数:告诉计算机“做什么”,无需关心内部细节
result = greet("Alice") # 将 "Alice" 作为参数传入函数
print(result) # 输出:Hello, Alice!
# 再次调用,实现复用
print(greet("Bob")) # 输出:Hello, Bob!
2. 参数传递的多种方式:让函数更灵活
参数是函数与外界沟通的桥梁。Python提供了非常灵活的参数传递机制。
- 位置参数:最常见的方式,按参数顺序传递。
- 关键字参数:通过参数名指定,顺序无关。
- 默认参数:为参数提供默认值,调用时可省略。
- 可变参数:处理不确定数量的参数。
# 综合示例:定义一个介绍自己的函数
def introduce(name, age, city="未知城市", *hobbies, **info):
"""
name: 位置参数 (必须)
age: 位置参数 (必须)
city: 默认参数,有默认值,可省略
*hobbies: 可变位置参数,接收任意数量的额外位置参数,存入元组
**info: 可变关键字参数,接收任意数量的关键字参数,存入字典
"""
print(f"我叫{name},今年{age}岁,来自{city}。")
if hobbies:
print(f"我的爱好有:{', '.join(hobbies)}。")
if info:
print("其他信息:", info)
# 多种调用方式
introduce("张三", 25) # 只提供必需的位置参数
# 输出:我叫张三,今年25岁,来自未知城市。
introduce("李四", 30, "北京", "读书", "游泳", profession="工程师", wechat="lisi123")
# 输出:
# 我叫李四,今年30岁,来自北京。
# 我的爱好有:读书, 游泳。
# 其他信息: {'profession': '工程师', 'wechat': 'lisi123'}
# 使用关键字参数,顺序可以打乱
introduce(age=22, city="上海", name="王五")
3. 返回值与作用域:数据的进与出
- 返回值:使用
return语句。函数可以返回任意类型的数据,甚至返回多个值(实际上是一个元组)。如果没有return语句,函数默认返回None。
def calculate(x, y):
add = x + y
multiply = x * y
return add, multiply # 返回一个元组 (add, multiply)
result = calculate(3, 4)
print(result) # 输出:(7, 12)
# 可以直接解包
sum_result, product_result = calculate(5, 6)
print(sum_result, product_result) # 输出:11 30
- 作用域:变量生效的范围。
- 局部变量:在函数内部定义,只在函数内有效。
- 全局变量:在函数外定义,在整个文件内有效。在函数内要修改全局变量,需使用
global关键字声明。
count = 0 # 全局变量
def increment():
global count # 声明使用全局变量count,而不是创建新的局部变量
count += 1
local_var = "inside" # 局部变量,函数外无法访问
increment()
print(count) # 输出:1
# print(local_var) # 这行会报错:NameError
第二部分:类与对象——构建更复杂的抽象
当需要描述复杂的事物时,仅靠函数可能不够。比如要描述“学生”,他有名字、年龄、成绩等多个属性(数据),还有学习、吃饭等多种行为(函数)。这时,就需要类来将数据和对数据的操作“捆绑”在一起。
1. 类与对象:蓝图与实物
- 类:是创建对象的蓝图或模板。它定义了一类对象共有的属性和方法。
- 对象:是根据类创建出来的一个具体实例。
# 定义一个“学生”类 (Class)
class Student:
# 构造函数:当创建对象时自动调用的特殊方法
def __init__(self, name, age):
# 初始化对象的属性 (Attribute)
self.name = name # self 代表当前创建的实例本身
self.age = age
self.grades = [] # 可以初始化一个空列表
# 方法 (Method):定义在类中的函数,描述对象的行为
def introduce(self):
print(f"大家好,我是{self.name},今年{self.age}岁。")
def add_grade(self, grade):
self.grades.append(grade)
def get_average_grade(self):
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)
# 根据蓝图(类)创建实物(对象)
student1 = Student("Alice", 20) # 创建第一个学生对象
student2 = Student("Bob", 22) # 创建第二个学生对象
# 每个对象都是独立的,拥有各自的数据
student1.introduce() # 输出:大家好,我是Alice,今年20岁。
student2.introduce() # 输出:大家好,我是Bob,今年22岁。
student1.add_grade(90)
student1.add_grade(80)
student2.add_grade(95)
print(f"{student1.name}的平均分:{student1.get_average_grade()}") # 85.0
print(f"{student2.name}的平均分:{student2.get_average_grade()}") # 95.0
2. 面向对象的三大特性
面向对象编程有三大核心特性,它们极大地增强了代码的组织性和可扩展性。
- 封装:将数据(属性)和行为(方法)包装在类内部,并可以控制外部对内部数据的访问权限。在Python中,通常通过命名约定来实现:
_single_leading_underscore:暗示是内部使用的(如_internal_attr),只是一种约定。__double_leading_underscore:名称修饰,使其难以在类外直接访问,提供一定的私有性。
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 双下划线,将余额“隐藏”起来
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"存款成功,当前余额:{self.__balance}")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"取款成功,当前余额:{self.__balance}")
else:
print("余额不足或金额无效!")
# 提供一个公共方法来安全地查看余额
def get_balance(self):
return self.__balance
account = BankAccount("Alice", 1000)
account.deposit(500) # 通过公共方法操作,是安全的
# print(account.__balance) # 报错!无法直接访问
print(account.get_balance()) # 正确:通过方法访问
- 继承:一个类可以继承另一个类的属性和方法。被继承的类叫父类(基类),继承的类叫子类(派生类)。子类可以“拥有”父类的一切,还可以添加自己特有的内容,或重写父类的方法。
# 父类
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print("动物发出声音...")
# 子类继承父类
class Dog(Animal): # 括号里写父类名
# 可以重写父类方法
def speak(self):
print(f"{self.name}:汪汪汪!")
# 可以添加子类特有的方法
def fetch(self):
print(f"{self.name}正在捡球!")
class Cat(Animal):
def speak(self):
print(f"{self.name}:喵喵喵!")
# 使用
dog = Dog("旺财")
cat = Cat("咪咪")
dog.speak() # 输出:旺财:汪汪汪!
cat.speak() # 输出:咪咪:喵喵喵!
dog.fetch() # 输出:旺财正在捡球!
# cat.fetch() # 报错,Cat类没有fetch方法
- 多态:不同类的对象对同一消息(方法调用)做出不同的响应。这是继承带来的强大特性。上面例子中,
dog.speak()和cat.speak()产生了不同的行为,这就是多态。它允许我们以统一的接口处理不同类型的对象。
def animal_talk(animal): # 参数animal可以是任何Animal或其子类的对象
animal.speak() # 我们不在乎animal具体是什么类型,只要它有speak方法
animal_talk(dog) # 输出:旺财:汪汪汪!
animal_talk(cat) # 输出:咪咪:喵喵喵!
总结:何时使用函数?何时使用类?
- 使用函数:当你的代码主要是一系列独立的操作,或者逻辑相对简单,不需要维护复杂的状态(数据)时。例如:计算工具函数、数据处理流程。
- 使用类:
- 当你需要将数据和对这些数据的操作紧密地绑定在一起时。
- 当你需要创建多个具有相同属性和行为的实例时。
- 当你需要通过继承来构建层次化的模型,实现代码复用时。
- 当你需要模拟现实世界中的实体(如用户、产品、订单)时。
更多推荐


所有评论(0)