你是否曾写过这样的代码?

一段逻辑在程序中到处复制粘贴,一旦需要修改,就必须像打地鼠一样在所有出现的地方进行修改。这样的代码不仅难以维护,更是滋生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) # 输出:咪咪:喵喵喵!
总结:何时使用函数?何时使用类?
  • 使用函数:当你的代码主要是一系列独立的操作,或者逻辑相对简单,不需要维护复杂的状态(数据)时。例如:计算工具函数、数据处理流程。
  • 使用类
    • 当你需要将数据和对这些数据的操作紧密地绑定在一起时。
    • 当你需要创建多个具有相同属性和行为的实例时。
    • 当你需要通过继承来构建层次化的模型,实现代码复用时。
    • 当你需要模拟现实世界中的实体(如用户、产品、订单)时。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐