面向对象编程(OOP)
1.1 类和对象
- 类 是一个模板,用来描述一类对象的属性和行为。通过定义类,你可以创建对象(也称为类的实例)。
- 对象 是类的实例,通过类创建的具体实例对象。
示例:
python">
class Dog:def __init__(self, name, age):self.name = name self.age = agedef bark(self):print(f"{self.name} 叫了,汪汪!")
my_dog = Dog("Lucky", 3)
print(my_dog.name)
my_dog.bark()
1.2 类属性和实例属性
- 类属性 是属于类的属性,由所有对象共享。
- 实例属性 是属于对象的属性,由每个对象独立拥有。
示例:
python">class Dog:species = "Canine" def __init__(self, name):self.name = name
print(Dog.species)
dog1 = Dog("Buddy")
dog2 = Dog("Max")
print(dog1.name)
print(dog2.name)
1.3 子类重写 __init__
方法
- 重写
__init__
方法:子类可以重写父类的 __init__
方法,从而增加新的实例属性或修改父类已有的属性。在重写时,可以使用 super()
函数调用父类的 __init__
方法,以确保继承父类的属性。
示例:
python">class Animal:def __init__(self, name):self.name = nameclass Dog(Animal):def __init__(self, name, breed):super().__init__(name) self.breed = breed my_dog = Dog("Lucky", "Golden Retriever")
print(my_dog.name)
print(my_dog.breed)
- 在这个例子中,
Dog
类重写了 __init__
方法,并使用 super().__init__(name)
调用了父类的 __init__
方法,继承了 name
属性,同时新增了 breed
属性。
1.4 子类和父类的类属性相互作用
- 类属性 是属于类本身的属性,由所有对象共享。子类可以继承父类的类属性,同时也可以修改或定义自己的类属性。
- 如果子类修改父类的类属性,它会在子类中创建一个新的属性,而不会影响父类的属性。类属性是相对独立的,只有当通过父类访问时,才会保持一致。
示例:
python">class Animal:species = "Animal"class Dog(Animal):species = "Canine"print(Animal.species)
print(Dog.species) my_dog = Dog()
print(my_dog.species)
- 在这个例子中,
Dog
类的 species
属性独立于 Animal
类的 species
,子类可以拥有自己的类属性,不会影响父类。
1.5 私有属性
- 在类中,属性名前加
__
(双下划线)可以将属性设为私有,仅能在类内部访问。私有属性不会被子类继承。
示例:
python">class Animal:def __init__(self, name):self.__name = name def get_name(self):return self.__nameanimal = Animal("Lucky")
print(animal.get_name())
1.6 方法重载(Python中的变通方式)
- 方法重载 是指同一个类中可以有多个同名方法,根据参数的不同执行不同的逻辑。然而,Python 不直接支持传统的函数重载,因为在Python中,函数名相同的后定义的会覆盖先定义的。可以通过可变参数(
*args
和 **kwargs
)来实现类似重载的行为。
示例:
python">class MathOperations:def add(self, *args):if len(args) == 1:return args[0] + 10elif len(args) == 2:return args[0] + args[1]else:return "参数数量不正确"math = MathOperations()
print(math.add(5))
print(math.add(5, 10))
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
或函数结束。
示例解释:
python">def count_up_to(max):count = 1while count <= max:yield count count += 1counter = count_up_to(3)
print(next(counter))
print(next(counter))
print(next(counter))
- 在第一次调用
next(counter)
时,生成器运行到 yield count
处,返回 count
的值并暂停执行。 - 第二次调用
next(counter)
时,生成器从暂停的地方继续执行,将 count
增加 1,然后再次遇到 yield
,返回新的 count
值。 - 这个过程一直持续到生成器函数执行完毕或不再遇到
yield
为止。
3. 模块与包细化
3.1 模块的导入与实例化
- 当你使用
import mymodule
时,Python解释器会将 mymodule.py
文件中的代码执行一次,并将模块作为对象实例加载到当前作用域中。此后,你可以使用 mymodule
访问模块中的属性和方法,就像访问对象的属性和方法一样。
示例:
python">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()
方法:用于移除字符串开头和结尾的空白字符(包括空格、换行符、制表符等)。
示例:
python">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:最简单的装饰器
python">def simple_decorator(func):def wrapper():print("在执行函数之前")func()print("在执行函数之后")return wrapper@simple_decorator
def say_hello():print("Hello, World!")say_hello()
- 解析:
- 定义了一个装饰器函数
simple_decorator(func)
,它接受一个函数作为参数,并返回一个新函数 wrapper()
。 wrapper()
函数在执行目标函数前后分别添加了打印操作。- 使用
@simple_decorator
应用装饰器,相当于将 say_hello
函数传递给 simple_decorator
,然后用 wrapper
替换原始的 say_hello
函数。 - 当
say_hello()
被调用时,实际执行的是 wrapper()
函数,添加了额外的逻辑。
1.2.2 装饰器的等价写法
- 使用
@decorator_name
只是一个语法糖,等价于以下写法:
python">def say_hello():print("Hello, World!")say_hello = simple_decorator(say_hello)
say_hello()
1.3 带参数的装饰器
- 有时我们希望装饰器本身能够接受参数。这时可以通过在装饰器外再定义一层包装函数来实现。
示例2:带参数的装饰器
python">def repeat(num_times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(num_times):func(*args, **kwargs)return wrapperreturn decorator@repeat(3)
def greet(name):print(f"Hello, {name}!")greet("Alice")
- 解析:
repeat(num_times)
是一个工厂函数,返回真正的装饰器 decorator
。decorator(func)
是接收函数并返回包装函数 wrapper
的实际装饰器。wrapper(*args, **kwargs)
可以接收任意数量和类型的参数,重复执行目标函数 num_times
次。
1.4 装饰器与函数参数(*args
和 **kwargs
)
- 在示例2中,
*args
和 **kwargs
用于接收目标函数的所有参数。*args
接收任意数量的非关键字参数,**kwargs
接收任意数量的关键字参数。这使得装饰器更通用,可以适用于各种函数。
2. 可变参数(*args
和 **kwargs
)
2.1 *args
的用法
*args
用于接收不定数量的位置参数,生成一个元组。可以在函数定义中使用 *args
来允许传递任意数量的参数。
示例1:使用 *args
的函数
python">def sum_numbers(*args):total = 0for num in args:total += numreturn totalprint(sum_numbers(1, 2, 3))
print(sum_numbers(4, 5, 6, 7))
- 解析:
*args
将传递给 sum_numbers
的所有参数收集成一个元组 (1, 2, 3)
或 (4, 5, 6, 7)
。
2.2 **kwargs
的用法
**kwargs
用于接收不定数量的关键字参数,生成一个字典。可以在函数定义中使用 **kwargs
来接收任意数量的关键字参数。
示例2:使用 **kwargs
的函数
python">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:混合使用
python">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 # 模块1module2.py # 模块2
python">from my_package import module1
import my_package.module2
示例2:子包的嵌套结构
my_package/ # 包目录__init__.py # 包初始化文件sub_package/ # 子包__init__.py # 子包初始化文件module3.py # 模块3
python">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
python">
- 这是一个空的
__init__.py
文件。它的存在只是为了告诉Python解释器,这个目录是一个包。这样,my_package
目录中的模块可以被导入。
示例2:带有初始化代码的 __init__.py
python">
print("my_package 被导入")
- 这种写法可以让你在导入包时自动执行某些初始化操作。当使用
import my_package
时,会输出 my_package 被导入
。
示例3:导入包中的模块
__init__.py
文件还可以用于控制包中的模块被导入时的行为。例如,在 __init__.py
中指定哪些模块可以被导入:
python">
from .module1 import function1
from .module2 import function2
- 这样,在外部使用
from my_package import *
时,就可以直接使用 function1
和 function2
,而不需要单独导入 module1
和 module2
。
示例4:定义包的公共接口
- 通过在
__init__.py
中定义 __all__
列表,可以控制包被导入时的公共接口:
python">
__all__ = ['module1', 'module2']
- 这样,
from my_package import *
只会导入 module1
和 module2
,其他模块不会被导入。
4.3 总结
__init__.py
文件主要用于: - 标识目录是Python包。
- 控制包的初始化行为。
- 定义包的公共接口。
- 虽然在Python 3.3之后,
__init__.py
文件不是必须的,但它仍然是组织和管理包的最佳实践。