Python进阶

时间:2024-10-23 16:37:13

面向对象编程(OOP)

1.1 类和对象
  • 是一个模板,用来描述一类对象的属性和行为。通过定义类,你可以创建对象(也称为类的实例)。
  • 对象 是类的实例,通过类创建的具体实例对象。
示例:
# 定义一个类
class Dog:
    # 类的初始化方法
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age

    # 类的方法
    def bark(self):
        print(f"{self.name} 叫了,汪汪!")

# 创建对象(实例)
my_dog = Dog("Lucky", 3)
print(my_dog.name)  # 输出:Lucky
my_dog.bark()  # 输出:Lucky 叫了,汪汪!
1.2 类属性和实例属性
  • 类属性 是属于类的属性,由所有对象共享。
  • 实例属性 是属于对象的属性,由每个对象独立拥有。
示例:
class Dog:
    species = "Canine"  # 类属性

    def __init__(self, name):
        self.name = name  # 实例属性

# 访问类属性
print(Dog.species)  # 输出:Canine

# 访问实例属性
dog1 = Dog("Buddy")
dog2 = Dog("Max")
print(dog1.name)  # 输出:Buddy
print(dog2.name)  # 输出:Max
1.3 子类重写 __init__ 方法
  • 重写 __init__ 方法:子类可以重写父类的 __init__ 方法,从而增加新的实例属性或修改父类已有的属性。在重写时,可以使用 super() 函数调用父类的 __init__ 方法,以确保继承父类的属性。
示例:
class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类的 __init__ 方法
        self.breed = breed  # 新增子类特有的属性

my_dog = Dog("Lucky", "Golden Retriever")
print(my_dog.name)  # 输出:Lucky
print(my_dog.breed)  # 输出:Golden Retriever
  • 在这个例子中,Dog 类重写了 __init__ 方法,并使用 super().__init__(name) 调用了父类的 __init__ 方法,继承了 name 属性,同时新增了 breed 属性。
1.4 子类和父类的类属性相互作用
  • 类属性 是属于类本身的属性,由所有对象共享。子类可以继承父类的类属性,同时也可以修改或定义自己的类属性。
  • 如果子类修改父类的类属性,它会在子类中创建一个新的属性,而不会影响父类的属性。类属性是相对独立的,只有当通过父类访问时,才会保持一致。
示例:
class Animal:
    species = "Animal"

class Dog(Animal):
    species = "Canine"

print(Animal.species)  # 输出:Animal
print(Dog.species)  # 输出:Canine

my_dog = Dog()
print(my_dog.species)  # 输出:Canine
  • 在这个例子中,Dog 类的 species 属性独立于 Animal 类的 species,子类可以拥有自己的类属性,不会影响父类。
1.5 私有属性
  • 在类中,属性名前加 __(双下划线)可以将属性设为私有,仅能在类内部访问。私有属性不会被子类继承。
示例:
class Animal:
    def __init__(self, name):
        self.__name = name  # 私有属性

    def get_name(self):
        return self.__name

animal = Animal("Lucky")
print(animal.get_name())  # 输出:Lucky
# print(animal.__name)  # 会报错,无法直接访问私有属性
1.6 方法重载(Python中的变通方式)
  • 方法重载 是指同一个类中可以有多个同名方法,根据参数的不同执行不同的逻辑。然而,Python 不直接支持传统的函数重载,因为在Python中,函数名相同的后定义的会覆盖先定义的。可以通过可变参数*args**kwargs)来实现类似重载的行为。
示例:
class MathOperations:
    def add(self, *args):
        if len(args) == 1:
            return args[0] + 10
        elif len(args) == 2:
            return args[0] + args[1]
        else:
            return "参数数量不正确"

math = MathOperations()
print(math.add(5))         # 输出:15
print(math.add(5, 10))     # 输出:15
print(math.add(1, 2, 3))   # 输出:参数数量不正确
  • 这里,add 方法通过检查参数的数量来执行不同的逻辑,从而实现类似重载的效果。

2. 迭代器与生成器

2.1 next(iterator) vs iterator.next()
  • 在Python 3中,迭代器使用 next() 函数来获取下一个元素,而不使用 iterator.next()。这是因为 next() 是一种全局函数,适用于任何实现了 __next__() 方法的对象,而 iterator.next() 形式只存在于 Python 2 中。
  • 在Python 3中,迭代器对象的 __next__() 方法已经被封装在全局的 next() 函数中,所以推荐使用 next(iterator)
2.2 生成器的暂停与继续
  • 生成器 是一种特殊的迭代器,由函数生成,并使用 yield 语句来产生值和暂停执行。当你调用 next() 时,生成器会从上次暂停的地方继续运行。
  • 当生成器遇到 yield,它会返回 yield 后的值,并暂停执行函数体。下次调用 next() 时,它会从暂停的地方继续执行,直到再次遇到 yield 或函数结束。
示例解释:
def count_up_to(max):
    count = 1
    while count <= max:
        yield count  # 暂停并返回当前值
        count += 1

counter = count_up_to(3)
print(next(counter))  # 输出:1
print(next(counter))  # 输出:2
print(next(counter))  # 输出:3
  • 在第一次调用 next(counter) 时,生成器运行到 yield count 处,返回 count 的值并暂停执行。
  • 第二次调用 next(counter) 时,生成器从暂停的地方继续执行,将 count 增加 1,然后再次遇到 yield,返回新的 count 值。
  • 这个过程一直持续到生成器函数执行完毕或不再遇到 yield 为止。

3. 模块与包细化

3.1 模块的导入与实例化
  • 当你使用 import mymodule 时,Python解释器会将 mymodule.py 文件中的代码执行一次,并将模块作为对象实例加载到当前作用域中。此后,你可以使用 mymodule 访问模块中的属性和方法,就像访问对象的属性和方法一样。
示例:
import mymodule  # 导入时执行模块中的代码
mymodule.greet("Alice")  # 调用模块中的函数
  • 这意味着模块在被导入后,成为当前程序中的一个对象,可以通过 . 访问其中的内容。
3.2 包结构与 my_package/
  • 是一个包含多个模块的目录,通常是一个文件夹,并且必须包含一个 __init__.py 文件,用于标识该目录是一个包。my_package/ 表示包的目录名称,目录中可以包含其他模块和子包。
示例:
my_package/       # 包目录
    __init__.py   # 包初始化文件
    module1.py    # 包含的模块
    module2.py
  • __init__.py 是必要的,它告诉Python该目录是一个包。目录的名字(即 my_package/)是你用来导入包时使用的名称。包的结构可以*组织,但必须有清晰的层次,方便导入和管理模块。
3.3 目录名字和结构的影响
  • 目录名字决定了导入包时的路径,例如,from my_package import module1 中,my_package 就是包的名字。
  • 如果目录结构混乱,或者 __init__.py 文件缺失,Python可能无法正确识别和导入包中的模块。

4. 文件操作的细化

4.1 文件对象的特性
  • 文件对象 是通过 open() 函数返回的对象,具有多种方法和属性:
    • read(size):读取文件内容,size 是可选参数,表示读取的字节数。
    • readline():读取一行内容。
    • readlines():读取所有行并返回一个列表,每一行作为列表的一个元素。
    • write(content):将字符串写入文件。
    • close():关闭文件,释放资源。
    • seek(offset):移动文件指针到指定位置,offset 表示字节数。
    • tell():返回文件指针当前位置。
    • mode:文件的打开模式,例如 'r''w''rb' 等。
    • name:文件的名称。
4.2 line 对象和 strip() 方法
  • 在文件操作中,line 是一个字符串对象,表示文件中的一行。每一行通常以换行符 \n 结尾。
  • strip() 方法:用于移除字符串开头和结尾的空白字符(包括空格、换行符、制表符等)。
示例:
with open("example.txt", "r") as file:
    for line in file:
        print(line.strip())  # 移除每一行末尾的换行符
  • 这里,strip() 移除了每一行末尾的换行符,使得输出更加整洁。

附录

1. 装饰器和语法糖详解

1.1 什么是语法糖?

  • 语法糖 是指编程语言中提供的特定语法,用来使代码更加易读和简洁,而不引入新的功能。语法糖的存在是为了让程序员更轻松地编写代码。装饰器的 @ 就是一种语法糖,它可以简化函数包装的过程。

1.2 装饰器详解

  • 装饰器 是一种设计模式,用来为函数或方法添加新的功能,而不修改其原始代码。它本质上是一个高阶函数,接收一个函数作为参数并返回一个新的函数。
1.2.1 装饰器的工作原理
  • 装饰器的作用是“包裹”目标函数,添加一些在目标函数执行前后要完成的操作。通常通过在目标函数上方添加 @decorator_name 的方式来应用装饰器。
示例1:最简单的装饰器
def simple_decorator(func):
    def wrapper():
        print("在执行函数之前")
        func()
        print("在执行函数之后")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello, World!")

say_hello()
  • 解析
    1. 定义了一个装饰器函数 simple_decorator(func),它接受一个函数作为参数,并返回一个新函数 wrapper()
    2. wrapper() 函数在执行目标函数前后分别添加了打印操作。
    3. 使用 @simple_decorator 应用装饰器,相当于将 say_hello 函数传递给 simple_decorator,然后用 wrapper 替换原始的 say_hello 函数。
    4. say_hello() 被调用时,实际执行的是 wrapper() 函数,添加了额外的逻辑。
1.2.2 装饰器的等价写法
  • 使用 @decorator_name 只是一个语法糖,等价于以下写法:
def say_hello():
    print("Hello, World!")

say_hello = simple_decorator(say_hello)
say_hello()

1.3 带参数的装饰器

  • 有时我们希望装饰器本身能够接受参数。这时可以通过在装饰器外再定义一层包装函数来实现。
示例2:带参数的装饰器
def repeat(num_times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")
  • 解析
    1. repeat(num_times) 是一个工厂函数,返回真正的装饰器 decorator
    2. decorator(func) 是接收函数并返回包装函数 wrapper 的实际装饰器。
    3. wrapper(*args, **kwargs) 可以接收任意数量和类型的参数,重复执行目标函数 num_times 次。

1.4 装饰器与函数参数(*args**kwargs

  • 在示例2中,*args**kwargs 用于接收目标函数的所有参数。*args 接收任意数量的非关键字参数,**kwargs 接收任意数量的关键字参数。这使得装饰器更通用,可以适用于各种函数。

2. 可变参数(*args**kwargs

2.1 *args 的用法

  • *args 用于接收不定数量的位置参数,生成一个元组。可以在函数定义中使用 *args 来允许传递任意数量的参数。
示例1:使用 *args 的函数
def sum_numbers(*args):
    total = 0
    for num in args:
        total += num
    return total

print(sum_numbers(1, 2, 3))  # 输出:6
print(sum_numbers(4, 5, 6, 7))  # 输出:22
  • 解析*args 将传递给 sum_numbers 的所有参数收集成一个元组 (1, 2, 3)(4, 5, 6, 7)

2.2 **kwargs 的用法

  • **kwargs 用于接收不定数量的关键字参数,生成一个字典。可以在函数定义中使用 **kwargs 来接收任意数量的关键字参数。
示例2:使用 **kwargs 的函数
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, job="Engineer")
  • 解析**kwargs 将传递给 print_info 的关键字参数收集成一个字典 {'name': 'Alice', 'age': 30, 'job': 'Engineer'}

2.3 同时使用 *args**kwargs

  • 可以同时使用 *args**kwargs 来接收位置参数和关键字参数。
示例3:混合使用
def display(*args, **kwargs):
    print("位置参数:", args)
    print("关键字参数:", kwargs)

display(1, 2, 3, name="Alice", age=30)
  • 解析*args 接收位置参数 (1, 2, 3)**kwargs 接收关键字参数 {'name': 'Alice', 'age': 30}

3. 包目录的名字和结构

3.1 包目录的命名规则

  • 包目录 是包含多个模块的文件夹。包目录通常由用户*命名,但必须包含一个 __init__.py 文件来告诉Python解释器该目录是一个包。
  • 目录名称:目录名称决定了导入包的路径。例如,目录名为 my_package/,则导入时应使用 import my_package

3.2 包的结构和影响

  • 包结构 决定了模块的组织方式。良好的包结构能方便代码的维护和模块的调用。如果包结构混乱,导入模块时可能会产生错误。
示例1:包的标准结构
my_package/           # 包目录
    __init__.py       # 包初始化文件
    module1.py        # 模块1
    module2.py        # 模块2
  • 可以通过以下方式导入模块:
from my_package import module1
import my_package.module2
示例2:子包的嵌套结构
my_package/           # 包目录
    __init__.py       # 包初始化文件
    sub_package/      # 子包
        __init__.py   # 子包初始化文件
        module3.py    # 模块3
  • 可以通过以下方式导入子包中的模块:
from my_package.sub_package import module3

3.3 my_package/ 是否必要

  • 包目录(如 my_package/)并非必须命名为 “my_package”,但该目录的名字决定了导入时使用的路径。例如,目录名为 utilities/,则导入时应使用 import utilities
  • 如果缺少 __init__.py 文件,Python会无法识别该目录为包,导致导入失败。

4. __init__.py 文件的作用

4.1 __init__.py 的作用

  • __init__.py 是一个特殊的文件,用来标识某个目录是Python包。没有这个文件,Python解释器就不会将该目录识别为包。它还可以用于定义包的公共接口,允许导入特定模块和属性。
  • 在Python 3.3之后,__init__.py 文件已经不是严格必须的,但为了明确结构、维护兼容性和定义包的行为,通常仍会包含此文件。

4.2 __init__.py 文件的内容

  • __init__.py 文件可以是空文件,也可以包含初始化代码,或者定义需要导入的模块。它的内容可以根据需要*定制,通常用于:
    • 导入包中的模块。
    • 初始化包的环境(例如设置变量、执行启动逻辑)。
    • 定义包的公共接口。
示例1:空的 __init__.py
# my_package/__init__.py
  • 这是一个空的 __init__.py 文件。它的存在只是为了告诉Python解释器,这个目录是一个包。这样,my_package 目录中的模块可以被导入。
示例2:带有初始化代码的 __init__.py
# my_package/__init__.py
print("my_package 被导入")
  • 这种写法可以让你在导入包时自动执行某些初始化操作。当使用 import my_package 时,会输出 my_package 被导入
示例3:导入包中的模块
  • __init__.py 文件还可以用于控制包中的模块被导入时的行为。例如,在 __init__.py 中指定哪些模块可以被导入:
# my_package/__init__.py
from .module1 import function1
from .module2 import function2
  • 这样,在外部使用 from my_package import * 时,就可以直接使用 function1function2,而不需要单独导入 module1module2
示例4:定义包的公共接口
  • 通过在 __init__.py 中定义 __all__ 列表,可以控制包被导入时的公共接口:
# my_package/__init__.py
__all__ = ['module1', 'module2']
  • 这样,from my_package import * 只会导入 module1module2,其他模块不会被导入。

4.3 总结

  • __init__.py 文件主要用于:
    1. 标识目录是Python包。
    2. 控制包的初始化行为。
    3. 定义包的公共接口。
  • 虽然在Python 3.3之后,__init__.py 文件不是必须的,但它仍然是组织和管理包的最佳实践。