面向对象编程(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)
my_dog.bark()
1.2 类属性和实例属性
-
类属性 是属于类的属性,由所有对象共享。
-
实例属性 是属于对象的属性,由每个对象独立拥有。
示例:
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__
方法,以确保继承父类的属性。
示例:
class Animal:
def __init__(self, name):
self.name = name
class 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 子类和父类的类属性相互作用
-
类属性 是属于类本身的属性,由所有对象共享。子类可以继承父类的类属性,同时也可以修改或定义自己的类属性。
- 如果子类修改父类的类属性,它会在子类中创建一个新的属性,而不会影响父类的属性。类属性是相对独立的,只有当通过父类访问时,才会保持一致。
示例:
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 私有属性
- 在类中,属性名前加
__
(双下划线)可以将属性设为私有,仅能在类内部访问。私有属性不会被子类继承。
示例:
class Animal:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
animal = Animal("Lucky")
print(animal.get_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))
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
或函数结束。
示例解释:
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
counter = 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
访问模块中的属性和方法,就像访问对象的属性和方法一样。
示例:
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()
-
解析:
- 定义了一个装饰器函数
simple_decorator(func)
,它接受一个函数作为参数,并返回一个新函数 wrapper()
。
-
wrapper()
函数在执行目标函数前后分别添加了打印操作。
- 使用
@simple_decorator
应用装饰器,相当于将 say_hello
函数传递给 simple_decorator
,然后用 wrapper
替换原始的 say_hello
函数。
- 当
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")
-
解析:
-
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
的函数
def sum_numbers(*args):
total = 0
for num in args:
total += num
return total
print(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
的函数
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
- 这是一个空的
__init__.py
文件。它的存在只是为了告诉Python解释器,这个目录是一个包。这样,my_package
目录中的模块可以被导入。
示例2:带有初始化代码的 __init__.py
print("my_package 被导入")
- 这种写法可以让你在导入包时自动执行某些初始化操作。当使用
import my_package
时,会输出 my_package 被导入
。
示例3:导入包中的模块
-
__init__.py
文件还可以用于控制包中的模块被导入时的行为。例如,在 __init__.py
中指定哪些模块可以被导入:
from .module1 import function1
from .module2 import function2
- 这样,在外部使用
from my_package import *
时,就可以直接使用 function1
和 function2
,而不需要单独导入 module1
和 module2
。
示例4:定义包的公共接口
- 通过在
__init__.py
中定义 __all__
列表,可以控制包被导入时的公共接口:
__all__ = ['module1', 'module2']
- 这样,
from my_package import *
只会导入 module1
和 module2
,其他模块不会被导入。
4.3 总结
-
__init__.py
文件主要用于:
- 标识目录是Python包。
- 控制包的初始化行为。
- 定义包的公共接口。
- 虽然在Python 3.3之后,
__init__.py
文件不是必须的,但它仍然是组织和管理包的最佳实践。