第十章 面向对象之类和对象
1. 面向过程和面向对象
面向过程编程(Procedural Programming)和面向对象编程(OOP)是两种不同的编程范式。
Python是一种混合型的语言,既支持面向过程的编程,也支持面向对象的编程。
面向过程的编程是一种以过程为中心的编程方式,主要以函数形式体现,关注解决问题的步骤。
面向对象的编程是一种以对象为中心的编程方式,主要以类和对象的形式体现,关注在解决问题的过程中涉及哪些对象以及这些对象如何交互。
2. 类和对象

2.1 类(Class)
特点:
-
类是对大量对象共性的抽象
-
类的属性和函数统称为类的成员。
-
类是创建对象的模板
-
类是客观事物在人脑中的主观反映
2.2 对象(Object)
-
在自然界中,只要是客观存在的事物都是对象
-
类是抽象的,对象是类的实例(Instance),是具体的。
-
一个对象有自己的状态(属性)、行为(函数)和唯一的标识(本质上指内存中所创建的对象的地址)
2.3 定义类
语法
python
class 类名:
"""类说明文档"""
类体
说明:
-
类名一般使用大驼峰命名法
-
类体中可以包含以下内容:
文档字符串(
docstring)类变量(所有实例共享)
类函数(操作类数据)
实例函数(操作实例数据)
静态函数(独立函数)
特殊函数(如
__init__,__str__等)属性 (
@property控制访问)嵌套类(类内部定义类)
类的完整示例结构:
python
class MyClass:
"""这是一个示例类,演示类体的组成部分。"""
class_var = "类变量" # 类变量
def __init__(self, value): # 构造函数
self.instance_var = value # 实例变量
def __str__(self): # 重载函数
return "MyClass 实例"
def instance_method(self): # 实例函数
print(f"实例函数,访问实例变量: {self.instance_var}")
@classmethod
def class_method(cls): # 类函数
print(f"类函数,访问类变量: {cls.class_var}")
@staticmethod
def static_method(): # 静态函数
print("静态函数,不依赖实例或类")
@property
def prop(self): # 属性
return self.instance_var
说明:
- 类函数和静态函数的区别
- 使用
@classmethod装饰,第一个参数是cls(指向类本身) - 使用
@staticmethod装饰,不自动传递self或cls
- 类变量和类属性区别
| 特性 | 类变量(class_var) |
类属性(@property) |
|---|---|---|
| 定义方式 | 直接定义在类内部 | 用 @property 装饰函数 本质还是函数,函数伪装成属性 |
| 存储位置 | 类级别(所有实例共享) | 实例级别(每个实例独立) |
| 访问方式 | 类名.类变量 或 实例.类变量 |
只能通过 实例.属性 访问 |
| 是否可修改 | 可以直接修改(但实例修改可能创建新变量) | 默认只读(需 @prop.setter 才能修改) |
案例:
python
class Employee:
# 类变量(Class Variable) - 所有实例共享
company = "TechCorp" # 直接定义在类内部
def __init__(self, name, salary):
self.name = name # 实例变量
self.salaries = salary # 受保护的实例变量(约定)
# 类属性(@property) - 动态计算/控制访问
@property
def salary(self): # Getter(像属性一样访问)
return self.salaries
@salary.setter
def salary(self, value): # Setter(验证逻辑)
if value < 0:
raise ValueError("薪水不能为负数!")
self.salaries = value
# 另一个类属性(只读)
@property
def email(self): # 动态生成
return f"{self.name.lower()}@{Employee.company.lower()}.com"
# ============= 测试 =============
# 1. 类变量(Class Variable)
print(Employee.company) # 直接通过类访问 -> "TechCorp"
emp1 = Employee("Alice", 5000)
emp2 = Employee("Bob", 6000)
print(emp1.company) # "TechCorp"(共享类变量)
print(emp2.company) # "TechCorp"(共享类变量)
# 修改类变量(影响所有实例)
Employee.company = "DataInc"
print(emp1.company) # "DataInc"
print(emp2.company) # "DataInc"
# 通过实例"修改"(实际创建新实例变量)
emp1.company = "Google"
print(emp1.company) # "Google"(实例变量)
print(emp2.company) # "DataInc"(仍为类变量)
# 2. 类属性(@property)
print(emp1.salary) # 5000(像属性一样访问Getter)
emp1.salary = 7000 # 调用Setter(允许修改)
print(emp1.salary) # 7000
try:
emp1.salary = -1000 # 触发Setter验证
except ValueError as e:
print(e) # "薪水不能为负数!"
# 只读属性测试
print(emp1.email) # "alice@datainc.com"(动态计算)
try:
emp1.email = "new@email.com" # ❌ 无Setter,只读
except AttributeError as e:
print(e) # "can't set attribute"
2.4 类的操作
类支持两种操作,成员引用和实例化。
1. 成员引用
1、语法
python
类名.成员名
2、案例
python
class Person:
"""人的类"""
home = "earth" # 类变量
def __init__(self):
self.age = 0
def eat(self): # 实例函数
print("实例函数:eating被调用了...")
@classmethod
def drink(cls): # 类函数
print("类函数:drinking被调用了...")
@staticmethod
def static_method():
print("静态函数:static method被调用了...")
@property
def name(self):
return "Person"
# 1.访问类变量
home = Person.home
# 2.访问类函数
drink_function = Person.drink
# 3.访问实例函数
eat_function = Person.eat
# 4.访问类的静态函数
static_method = Person.static_method
# 5.访问类的特殊函数
init_method = Person.__init__
# 6.访问类的属性
name_property = Person.name
# 7.访问类的说明文档
doc = Person.__doc__
print(f"1.访问person类变量: {home}") # earth
print(f"2.访问person类函数: {drink_function}") # <bound method Person.drink of <class '__main__.Person'>>
print(f"3.访问person类实例函数函数: {eat_function}") # <function Person.eat at 0x0000027317C0BE20>
print(f"4.访问person类的静态函数: {static_method}") # <function Person.static_method at 0x000001A1E81EBF60>
print(f"5.访问person类的特殊函数: {init_method}") # <function Person.__init__ at 0x000001A1E81EBF70>
print(f"6.访问person类的属性: {name_property}") # <property object at 0x000001A1E81EBF80>
print(f"7.访问person类的说明文档: {doc}") # 人的类
2. 实例化
1、语法:
python
变量名 = 类名()
2、案例
python
class Person:
"""人的类"""
home = "earth" # 类变量
def __init__(self):
self.age = 0
def eat(self): # 实例函数
print("实例函数:eating被调用了...")
@classmethod
def drink(cls): # 类函数
print("类函数:drinking被调用了...")
@staticmethod
def static_method():
print("静态函数:static method被调用了...")
@property
def name(self):
return "Person"
# 2.实例化
p = Person()
print(f"1.用实例对象获取类变量结果: {p.home}")
print(f"2.用实例对象获取实例变量结果: {p.age}")
print(f"3.用实例对象获取实例函数结果: {p.eat()}")
print(f"4.用实例对象获取类函数结果: {p.drink()}")
print(f"5.用实例对象获取静态函数结果: {p.static_method()}")
print(f"6.用实例对象获取实例属性结果: {p.name}")
2.5 __ __init()____函数
特点:
- init () 函数的调用时机在实例通过 **new()**被创建之后
- 一般用于初始化一些数据
- 当类定义了 init () 函数后,在类实例化的时候会自动调用 init() 函数
- 也可以向 init() 函数中传参。
- init()函数必须至少指定一个参数self
- 无返回值(隐式返回
None) - 可以调用类的其他函数(实例函数、静态函数、类函数、特殊函数)
- 可以访问类变量
- 可以动态添加属性
场景:
- 需要初始化实例属性时(如
self.name = name)。 - 需要在实例化时执行一些逻辑(如连接数据库)。
3. self
特点:
self- 是一个约定俗成的名称,可以指定任意名字,但是强烈不建议。
- self 代表类的实例自身
- 当调用
实例对象.实例函数()时,Python 会自动将实例对象作为第一个参数self传给实例函数
python
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
def drink(self):
print("drinking...")
p = Person("张三") # 创建一个对象
# 1.当调用 p.eat() 时,Python 会自动将 p 作为第一个参数 self 传给 eat()
p.eat() # eating...()
# 2.直接调用 Person.eat(p) 是手动传递实例
Person.eat(p) # eating...
三大核心作用:
-
访问实例属性 。在函数内部通过
self.属性名访问或修改实例的属性 -
调用其他实例函数 通过
self.函数名()调用同一个类的其他实例函数 -
区分实例变量和局部变量避免函数内的局部变量与实例属性命名冲突
4. 属性
注意:这里面说的属性和类属性(@property)不是同一回事。严格来说属性指的就是变量。
1 类属性
1)特点:
- 也叫类变量,在类中 函数外 定义的属性
- 所有该类的实例共享同一个类属性
- 类属性可以是任意Python对象
- 基本数据类型(
int,str,bool,float) - 容器类型(
list,dict,set,tuple) - 函数、类、模块
- 基本数据类型(
2)访问
通过 类名.属性名 或 实例名.属性名 访问
python
class Person:
"""人的类"""
home = "earth" # 定义类属性
print(Person.home) # 通过类名访问类属性
p1 = Person() # 创建一个实例对象
print(p1.home) # 通过实例名访问类属性 实例自身没有同名属性时,会向上查找类属性
3)操作
通过 类名.属性名 添加 与修改类属性
python
class Person:
"""人的类"""
Person.home = "earth" # 添加类属性
print(Person.home) # earth
Person.home = "mars" # 修改类属性
print(Person.home) # mars
注意:
①:通过 实例名.属性名 则不能 添加 与修改 不可变类型的类属性
python
class Dog:
species = "犬属后裔"
d1 = Dog()
d2 = Dog()
d1.species = "金毛猎犬" # 创建实例属性,不影响类属性
print(d1.species) # 输出: "金毛猎犬"(实例属性)
print(d2.species) # 输出: "犬属后裔"(类属性未变)
print(Dog.species) # 输出: "犬属后裔"(类属性未被修改)
②:通过 实例名.属性名 则可以 添加 与修改 可变类型的类属性
python
class Cat:
toys = ["ball"] # 类属性(可变对象)
c1 = Cat()
c2 = Cat()
c1.toys.append("foods") # 修改类属性指向的列表
print(c2.toys) # 输出: ["ball", "foods"](所有实例受影响)
print(Cat.toys) # 输出: ["ball", "foods"]
③: 类属性可以是任意Python对象:
python
import math
from typing import List, Dict
# 外部函数
def sqrt(x: float) -> float:
return math.sqrt(x)
# 外部类
class Multiplier:
def run(self, a: float, b: float) -> float:
return a * b
class DataProcessor:
"""类属性包含所有类型的示例"""
# 1. 基本数据类型
version = "1.0" # 字符串
enabled = True # 布尔值
pi= 3.1415926 # 浮点数
max_retries = 3 # 整数
# 2. 容器类型
default_config: Dict[str, int] = {"timeout": 10, "retries": 3} # 字典
supported_types: List[str] = ["int", "float", "str"] # 列表
prime_numbers: set = {2, 3, 5, 7} # 集合
coordinate: tuple = (10, 20) # 元组
# 3. 函数/类/模块
math_func = sqrt # 外部函数
multiplier_cls = Multiplier # 外部类
math_module = math # 外部模块
@classmethod
def process_data(cls, x: float, a: float, b: float) -> tuple:
"""综合调用各类属性"""
# 调用基本数据类型
print(f"MaxRetries: {cls.max_retries}, Enabled: {cls.enabled}")
# 调用容器类型
print(f"Config: {cls.default_config}, Primes: {cls.prime_numbers}")
# 调用外部函数、类、模块
result1 = cls.math_func(x) # 函数
result2 = cls.multiplier_cls().run(a, b) # 类
result3 = cls.math_module.pow(a, b) # 模块
return (result1, result2, result3)
# 使用示例
if __name__ == "__main__":
print("===== 类属性访问 =====")
print("基本数据类型:", DataProcessor.version, DataProcessor.pi)
print("容器类型:", DataProcessor.supported_types, DataProcessor.coordinate)
print("\n===== 函数调用 =====")
results = DataProcessor.process_data(16, 2, 3)
print("计算结果:", results) # 输出: (4.0, 6, 8.0)
2 实例属性
1)特点:
-
也叫属性变量,在类中 函数中 定义的属性
-
在类的函数中通过
self.属性名定义的属性 -
每个实例拥有自己独立的实例属性
-
实例属性优先于类属性(当属性名相同时)
-
实例属性可以是任意Python对象
-
基本数据类型(
int,str,bool,float) -
容器类型(
list,dict,set,tuple) -
函数、类、模块
-
自定义类的实例
-
2)访问:
通过 实例名.属性名 访问,不能通过类名.属性名访问
python
# 通过 实例名.属性名 访问
class People:
"""人的类"""
def __init__(self, name, age):
self.name = name # 定义实例属性
self.age = age # 定义实例属性
p1 = People("张三", 18) # 创建一个实例对象
print(p1.name, p1.age) # 张三 18
p2 = People("李四", 81) # 创建一个实例对象
print(p2.name, p2.age) # 李四 81
print(People.name) # 报错 AttributeError: type object 'People' has no attribute 'name'
3)操作
通过 实例名.属性名 添加与修改实例属性
python
class Human :
"""人的类"""
pass
h1 = Human () # 创建一个实例对象
h1.name = "张三" # 添加实例属性
h1.age = 18 # 添加实例属性
print(h1.name, h1.age) # 张三 18
h1.age = 25 # 修改实例属性 直接通过赋值修改
print(h1.name, h1.age) # 张三 25
注意:
实例属性优先级高于类属性(同名时)
python
class Dog:
category = "动物" # 类属性
def __init__(self):
self.category = "犬科" # 实例属性
d = Dog()
print(d.category) # 输出:"犬科"(实例属性优先)
print(Dog.category) # 输出:"动物"
5. 函数
Python的类中有四种函数:实例函数、静态函数、类函数、特殊函数
1. 实例函数
特点:
-
实例函数在类中定义,第一个参数为self,代表实例本身。
-
实例函数一般都是实例对象调用
-
实例函数也可以通过类名调用,通过类名调用时,需手动传入实例作为第一个参数
-
可以访问属性 、(类属性、实例属性)、函数(类函数、静态函数、实例函数、特殊函数)
访问示例:
①:实例属性:
python
class Person:
def __init__(self, name):
self.name = name # 实例属性
def greet(self):
print(f"Hello, {self.name}!") # 访问实例属性
p = Person("Alice")
p.greet() # 输出: Hello, Alice!
②:访问类属性:
python
class Dog:
species = "犬属后裔" # 类属性
def show_species(self):
print(f"我喜欢的物种是 {self.species}") # 方式1:直接访问
print(f"我喜欢的物种是: {self.__class__.species}") # 方式2:通过 __class__
d = Dog()
d.show_species() # 输出: 我喜欢的物种是犬属后裔
③:访问类函数
python
class Cat:
@classmethod
def get_species(cls):
return "家猫"
def call_class_method(self):
print(self.__class__.get_species()) # 方式1:通过 __class__
print(Cat.get_species()) # 方式2:直接通过类名
c = Cat()
c.call_class_method() # 输出: 家猫
④:全局变量
python
GLOBAL_VAR = "I'm global"
class MyClass:
def show_global(self):
print(GLOBAL_VAR) # 直接访问全局变量
obj = MyClass()
obj.show_global() # 输出: I'm global
⑤:外部函数
python
def external_func():
return "External function called"
class MyClass:
def use_external_func(self):
print(external_func()) # 调用外部函数
obj = MyClass()
obj.use_external_func() # 输出: External function called
⑥: 内置函数和内置函数
python
class ListManager:
def __init__(self):
self.data = []
def add_and_print(self, item):
self.data.append(item) # 调用 list.append() 内置函数
print(f"Added: {item}") # 调用 print() 内置函数
manager = ListManager()
manager.add_and_print("Python") # 输出: Added: Python
⑦:其他实例的函数
如果实例持有其他对象的引用,可以调用其他实例的函数:
python
class Logger:
def log(self, message):
print(f"LOG: {message}")
class DataProcessor:
def __init__(self):
self.logger = Logger() # 持有 Logger 实例
def process(self):
self.logger.log("Processing data") # 调用其他实例的函数
processor = DataProcessor()
processor.process() # 输出: LOG: Processing data
⑧:类的特殊函数
python
class Book:
def __init__(self, title):
self.title = title
def display(self):
print(str(self)) # 触发 __str__
def __str__(self):
return f"Book: {self.title}"
book = Book("Python Guide")
book.display() # 输出: Book: Python Guide
⑨:导入的模块
python
import math
class Calculator:
def circle_area(self, radius):
return math.pi * radius # 访问 math 模块
calc = Calculator()
print(calc.circle_area(2)) # 输出: 6.28...
2. 类函数
特点
- 类函数使用
@classmethod装饰器定义,第一个参数为cls,代表类本身(不是实例)。 - 类函数可以通过 类名直接调用 ,也可以通过 实例调用(不推荐,可能引起混淆)。
- 主要用于操作 类属性 或实现 工厂模式,不能直接访问实例属性(除非通过实例参数传入)。
- 可以访问 类属性、其他类函数、静态函数
访问示例:
❌
python
# 1.类函数访问实例属性
class User:
def __init__(self, name):
self.name = name # 实例属性
@classmethod
def print_name(cls):
print(cls.name) # ❌ 错误!类函数无法直接访问实例属性
# 尝试调用
User("Alice").print_name()
# 2.类函数访问实例属性 通过实例参数传入
class User:
def __init__(self, name):
self.name = name # 实例属性
@classmethod
def print_name(cls, user_instance): # 显式传入实例
print(user_instance.name) # ✅ 通过实例访问属性
# 使用示例
user = User("Alice")
User.print_name(user) # 输出: Alice
✅
① 访问类属性
python
class Dog:
species = "犬属后裔" # 类属性
@classmethod
def show_species(cls):
print(f"物种是: {cls.species}") # 直接通过 cls 访问类属性
Dog.show_species() # 输出: 物种是: 犬属后裔
② 修改类属性(全局配置)
python
class Bank:
interest_rate = 0.03 # 类属性
@classmethod
def update_interest(cls, new_rate):
cls.interest_rate = new_rate # 修改类属性
Bank.update_interest(0.05)
print(Bank.interest_rate) # 输出: 0.05
③ 调用其他类函数
python
class Cat:
@classmethod
def get_species(cls):
return "家猫"
@classmethod
def print_species(cls):
print(cls.get_species()) # 调用其他类函数
Cat.print_species() # 输出: 家猫
④ 访问静态函数
python
class MathUtils:
@staticmethod
def square(x):
return x * x
@classmethod
def sum_of_squares(cls, a, b):
return cls.square(a) + cls.square(b) # 调用静态函数
print(MathUtils.sum_of_squares(2, 3)) # 输出: 13 (4 + 9)
3. 静态函数
特点
- 使用
@staticmethod装饰器定义,不需要self或cls参数。 - 与类和实例无关,只是放在类中的普通函数。
- 可以通过 类名直接调用 或 实例调用(但无自动参数传递)。
- 不能直接访问类属性或实例属性(除非通过类名或实例参数传入)。
- 适合实现与类相关但不依赖类或实例状态的工具函数
访问示例
① 基本静态函数(无参数依赖)
python
class StringUtils:
@staticmethod
def is_palindrome(s):
return s == s[::-1] # 判断字符串是否回文
# 通过类调用
print(StringUtils.is_palindrome("madam")) # 输出: True
# 通过实例调用(不推荐,无意义)
utils = StringUtils()
print(utils.is_palindrome("hello")) # 输出: False
② 静态函数访问类属性(需显式通过类名)
python
class Config:
LOG_LEVEL = "INFO" # 类属性
@staticmethod
def show_log_level():
print(Config.LOG_LEVEL) # 必须通过类名访问
Config.show_log_level() # 输出: INFO
③ 静态函数调用其他静态函数
python
class MathOps:
@staticmethod
def square(x):
return x * x
@staticmethod
def cube(x):
return MathOps.square(x) * x # 调用其他静态函数
print(MathOps.cube(3)) # 输出: 27
④ 实用工具场景(数据格式转换)
python
class DateParser:
@staticmethod
def parse_iso_date(date_str):
year, month, day = date_str.split("-")
return int(year), int(month), int(day)
date = DateParser.parse_iso_date("2023-12-25")
print(date) # 输出: (2023, 12, 25)
实例函数、类函数、静态函数的对比
| 特性 | 实例函数 | 类函数(@classmethod) | 静态函数(@staticmethod) |
|---|---|---|---|
| 装饰器 | 无 | @classmethod |
@staticmethod |
| 第一个参数 | self(实例对象) |
cls(类对象) |
无(无需 self 或 cls) |
| 访问类属性 | ✅ self.__class__.attr |
✅ 直接通过 cls |
❌ 需显式通过类名(如 Config.attr) |
| 访问实例属性 | ✅ 直接通过 self |
❌ 除非显式传入实例 | ❌ 完全无关 |
| 调用方式 | 实例.函数() |
类名.函数() 或 实例.函数() |
类名.函数() 或 实例.函数() |
| 修改类属性 | ❌ 需通过类名 | ✅ 直接通过 cls |
❌ 需通过类名 |
| 修改实例属性 | ✅ 直接修改 | ❌ 除非显式传入实例 | ❌ 完全无关 |
| 典型用途 | 操作实例数据和状态 | 操作类属性、工厂模式 | 工具函数(与类相关但无状态依赖) |
4. 特殊函数
函数名中有两个前缀下划线和两个后缀下划线的函数为特殊函数,也叫魔法函数。上文提到的 init() 就是一个特殊函数。这些函数会在进行特定的操作时自动被调用。
几个常见的特殊函数:
1)new()
对象实例化时第一个调用的函数。
2)init()
类的初始化函数。
3)del()
对象的销毁器,定义了当对象被垃圾回收时的行为。使用 del xxx 时不会主动调用 del() ,除非此时引用计数==0。
4)str()
定义了对类的实例调用 str() 时的行为。
5)repr()
定义对类的实例调用 repr() 时的行为。 str() 和 repr() 最主要的差别在于目标用户。 repr() 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 str() 则产生人类可读的输出。
6)getattribute()
属性访问拦截器,定义了属性被访问前的操作。
6. 动态添加属性与函数
6.1 动态给对象添加属性
python
class Person:
def __init__(self, name=None):
self.name = name
p = Person("张三")
print(p.name) # 张三
p.age = 18
print(p.age) # 18
6.2 动态给类添加属性
python
class Person:
def __init__(self, name=None):
self.name = name
p = Person("张三")
print(p.name) # 张三
Person.age = 0
print(Person.age) # 0
print(p.age) # 0
6.3 动态给对象添加函数
1) 添加普通函数
python
class Person:
def __init__(self, name=None):
self.name = name
def eat():
print("吃饭")
p = Person("张三")
p.eat = eat
p.eat() # 吃饭
2)添加实例函数
给对象添加的实例函数只绑定在当前对象上,不对其他对象生效,而且需要传入 self 参数。需要使用 types.MethodType(函数名,实例对象) 来添加实例函数。
python
import types
class Person:
def __init__(self, name=None):
self.name = name
def eat(self):
print(f"{self.name}在吃饭")
p = Person("张三")
p.eat = types.MethodType(eat, p)
p.eat() # 张三在吃饭
6.4 动态给类添加函数
给类添加的函数对它的所有对象都生效,添加类函数需要传入 cls 参数,添加静态函数则不需要。
python
class Person:
home = "earth"
def __init__(self, name=None):
self.name = name
# 定义类函数
@classmethod
def come_from(cls):
print(f"来自{cls.home}")
# 定义静态函数
@staticmethod
def static_function():
print("static function")
Person.come_from = come_from
Person.come_from() # 来自earth
Person.static_function = static_function
Person.static_function() # static function
第十一章 面向对象之三大特性
1. 封装
Python 的封装是面向对象编程(OOP)的核心特性之一,它通过隐藏对象内部实现细节,仅暴露必要的接口来操作数据。Python 的封装具有以下特点:
访问控制
Python 使用命名约定(而非严格的访问修饰符)实现访问控制:
| 类型 | 命名规则 | 访问范围 | 示例 |
|---|---|---|---|
| 公有(Public) | 普通命名(无前缀) | 任意位置可访问 | self.name |
| 保护(Protected) | 单下划线 _ 开头 |
约定为"仅内部或子类 使用"(实际仍可访问,通过import可以访问) | self._age |
| 私有(Private) | 双下划线 __ 开头 |
仅类内部可访问(通过名称修饰) | self.__secret |
案例:
python
class BankAccount:
def __init__(self):
self.balance = 0 # 公有属性
self._password = "123" # 保护属性(约定)
self.__key = "abc" # 私有属性(实际被重命名为 _BankAccount__key)
account = BankAccount()
print(account.balance) # ✅ 正常访问
print(account._password) # ⚠️ 可访问但不建议(违反约定)
print(account.__key) # ❌ 报错:AttributeError(需用 account._BankAccount__key)
属性控制(@property)
通过 @property 装饰器将函数伪装成属性,实现对属性的安全访问和验证:
- 只读属性 :不定义
@属性名.setter - 数据验证:在 setter 中检查合法性
案例:
python
class Person:
def __init__(self, age):
self._age = age # 保护属性
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("年龄不能为负数!")
self._age = value
p = Person(20)
p.age = 25 # ✅ 调用 setter
print(p.age) # 输出: 25
p.age = -5 # ❌ 触发 ValueError
函数隐藏
- 公有函数:普通函数,外部可直接调用。
- 私有函数 :双下划线
__开头,仅类内部使用。
案例:
python
class Database:
def __connect(self): # 私有函数
print("连接数据库")
def query(self):
self.__connect() # ✅ 类内部可调用
return "数据"
db = Database()
db.query() # ✅ 输出: "连接数据库"
db.__connect() # ❌ 报错:AttributeError
动态属性管理
通过 __slots__ 限制实例可添加的属性,节省内存并防止动态添加属性:
python
class Student:
__slots__ = ["name", "age"] # 只允许这两个属性
s = Student()
s.name = "Alice" # ✅
s.grade = 90 # ❌ AttributeError: 'Student' object has no attribute 'grade'
名称修饰
对私有属性(__xxx),Python 会自动重命名为 _类名__xxx,防止子类意外覆盖
案例:
python
class Parent:
def __init__(self):
self.__secret = "父类隐私" # 实际存储为 _Parent__secret
class Child(Parent):
def __init__(self):
super().__init__()
self.__secret = "子类隐私" # 实际存储为 _Child__secret
child = Child()
print(child._Parent__secret) # 输出: "父类隐私"
print(child._Child__secret) # 输出: "子类隐私"
2. 继承
特点:
- 子类(派生类)继承父类(基类)中的属性和函数,实现代码重用。
- 子类可以新增自己特有的函数,也可以重写父类的函数。
- 子类不能继承父类的私有属性和私有函数,因为存在名称改写。
2.1 单继承
1)语法
python
class 类名(父类):
类体
在类名后括号内指定要继承的父类。
2)案例
python
class Person:
"""人的类"""
home = "earth" # 定义类属性
def __init__(self, name):
self.name = name # 定义实例属性
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人"""
color = "yellow" # 定义类属性
class WhiteRace(Person):
"""白种人"""
color = "white" # 定义类属性
class BlackRace(Person):
"""黑种人"""
color = "black" # 定义类属性
y1 = YellowRace("张三")
print(y1.home) # earth
print(y1.color) # yellow
print(y1.name) # 张三
y1.eat() # eating...
w1 = WhiteRace("李四")
print(w1.home) # earth
print(w1.color) # white
print(w1.name) # 李四
w1.eat() # eating...
b1 = BlackRace("王五")
print(b1.home) # earth
print(b1.color) # black
print(b1.name) # 王五
b1.eat() # eating..
3)场景
电商平台的用户系统
我们需要实现一个用户管理系统,包含:
- 基础用户 (
BaseUser):提供登录/注销功能 - 客户 (
Customer):继承基础用户,增加购物功能
python
class BaseUser:
def __init__(self, username, password):
self.username = username
self.password = password
self.is_logged_in = False
def login(self):
self.is_logged_in = True
print(f"{self.username} 登录成功")
def logout(self):
self.is_logged_in = False
print(f"{self.username} 已注销")
class Customer(BaseUser):
def __init__(self, username, password):
super().__init__(username, password) # 调用父类初始化
self.cart = []
def add_to_cart(self, item):
if self.is_logged_in:
self.cart.append(item)
print(f"已添加 {item} 到购物车")
else:
print("请先登录!")
def view_cart(self):
print(f"购物车内容: {self.cart}")
# 创建客户实例
customer = Customer("user123", "mypassword")
customer.login() # 调用继承自 BaseUser 的函数
customer.add_to_cart("iPhone") # 调用子类特有函数
2.2 多继承
调用属性、函数时先在子类中查找,若不存在则从左到右依次查找父类中是否包含属性、函数。
1)语法
python
class 类名(父类1, 父类2, ...):
类体
2)案例
python
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人"""
color = "yellow"
def run(self):
print("runing...")
class Student(Person):
"""学生"""
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self):
print("studying...")
class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace
"""中国学生"""
country = "中国"
y1 = ChineseStudent("张三", "三年级")
print(y1.home, y1.color, y1.country, y1.name, y1.grade)
y1.eat()
y1.run()
y1.study()
3)场景:
日志系统 + 数据验证
python
class Logger:
def log(self, message):
print(f"log: {message}")
class Validator:
def validate(self, data):
if not data:
raise ValueError("数据不能为空")
class DataProcessor(Logger, Validator):
def process(self, data):
self.validate(data) # 继承 Validator
self.log("数据处理中") # 继承 Logger
return data.upper()
processor = DataProcessor()
result = processor.process("hello")
print(f"数据处理完:{result}") # 输出: HELLO
2.3 复用父类函数
子类可以在类中使用 super().函数名() 或 父类名.函数名() 来调用父类的函数。
- super().函数名()
python
# 1.定义基类
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}:先吃饭")
# 2.定义子类黄颜色
class YellowRace(Person):
"""黄种人"""
color = "yellow"
def run(self):
print("runing...")
# 3.定义子类学生
class Student(Person):
"""学生"""
def __init__(self, name, language):
super().__init__(name)
self.language = language
def study(self):
print("先吃再学")
super().eat() # 子类中调用父类的函数
print(f"然后学习:{self.language}")
# 4.定义子类中国学生,继承了Student和YellowRace
class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace
"""中国学生"""
country = "中国"
y1 = ChineseStudent("张三", "python")
y1.study()
- 父类名.函数名()
python
# 1.定义基类
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}:先吃饭")
# 2.定义子类黄颜色
class YellowRace(Person):
"""黄种人"""
color = "yellow"
def run(self):
print("runing...")
# 3.定义子类学生
class Student(Person):
"""学生"""
def __init__(self, name, language):
super().__init__(name)
self.language = language
def study(self):
print("先吃再学")
Person.eat(self) # 子类中调用父类的函数
print(f"然后学习:{self.language}")
# 4.定义子类中国学生,继承了Student和YellowRace
class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace
"""中国学生"""
country = "中国"
y1 = ChineseStudent("张三", "python")
y1.study()
2.3 函数重写
在子类中定义与父类函数重名的函数,调用时会调用子类中重写的函数。
python
class Person:
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class Chinese(Person):
color = "yellow"
# 重写父类函数
def eat(self):
print("用筷子吃")
y1 = Chinese("张三")
y1.eat()
注意:子类重写 init () 并调用时,不会执行父类的 init () 函数。如有必要,需在子类 init () 中使用 super().init () 来调用父类的 init() 函数。
python
class Person:
def __init__(self, name):
self.name = name
class Chinese(Person):
def __init__(self, name, area):
super().__init__(name) # 调用父类的__init__()
self.area = area
y1 = Chinese("张三", "北京")
print(y1.name, y1.area)
3. 多态
多态 指的是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。简单说就是"一个接口,多种实现"。
多态特点:
- 鸭子类型(Duck Typing):Python的多态基于"如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子"的理念,不依赖于继承关系。
- 动态绑定:在运行时确定对象的类型并调用相应的函数。
多态的实现方式:
1)基于继承实现多态
python
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵!"
def animal_speak(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_speak(dog) # 输出: 汪汪!
animal_speak(cat) # 输出: 喵喵!
2) 鸭子类型实现多态
换句话说,不关心对象的类型,只关心对象是否具有某个函数或属性。如果对象有这个函数,它就可以被调用,而不管它具体是什么类。
python
class Dog:
def speak(self):
return "汪汪!"
class Cat:
def speak(self):
return "喵喵!"
class Person:
def speak(self):
return "你好!"
def make_speak(obj):
print(obj.speak())
dog = Dog()
cat = Cat()
person = Person()
make_speak(dog) # 输出: 汪汪!
make_speak(cat) # 输出: 喵喵!
make_speak(person) # 输出: 你好!
说明:
Dog、Cat、Person没有继承同一个父类 ,但它们都有speak()函数。make_speak()函数不检查obj的类型,只关心它是否有speak()函数。- 只要传入的对象有
speak(),就能正常运行,不管它是什么类。
这就是鸭子类型------关注行为(函数),而不是类型
思考:Python 的有没有内置函数采用鸭子类型?
len(obj):只要 obj 实现了 __len__(),就能计算长度
for x in obj:只要 obj 实现了 __iter__()就能迭代
print(obj):只要 obj 实现了 __str__(),就能打印
python
print(len("hello")) # 字符串
print(len([1, 2, 3])) # 列表
print(len({"a": 1, "b": 2})) # 字典
第十二章 错误和异常
1.异常介绍
Python是一门解释型语言,只有在程序运行后才会执行语法检查。所以,只有在运行或测试程序时,才会真正知道该程序能不能正常运行。
Python有两种错误很容易辨认:语法错误和异常。
1.1 语法错误
程序解析时遇到的错误。
例如以下程序,因缺少 : 而出现语法错误
python
while True
print("请输入一个整数(输入0结束):")
# SyntaxError: expected ':'
1.2 异常
Python 程序的语法是正确的,在运行它的时候,也有可能发生错误。运行期检测到的错误被称为异常。
例如以下程序,因变量名未找到而引发NameError。
python
print(var1)
# NameError: name 'var1' is not defined. Did you mean: 'vars'?
1.3 异常处理
当程序运行的时候,出现异常的时候提供解决方案,不终止程序,可以让程序继续执行。
1. try except
1)语法
python
try:
可能发生异常的代码
except:
异常处理的代码
# 或者
try:
可能发生异常的代码
except 异常类型1 as 变量名1:
异常处理的代码
except(异常类型2, 异常类型3) as 变量名2:
异常处理的代码
except:
异常处理的代码
说明:
-
如果没有发生异常,程序会忽略except中的代码,继续向下执行。
-
如果发生了异常,会忽略try中剩余代码,根据异常类型匹配到相应的 except 并执行其中的代码。
-
如果发生了异常,且异常类型无法和指定except匹配,异常将向外传递。
-
一个except可以同时处理多个异常,将这些异常放在一个元组中。
-
最后一个 except 可以忽略异常类型,它将被作为通配符使用。
3)案例
3.1 捕获所有类型的异常
python
try:
result = 3 / 1
print("没有发生异常")
except:
print("发生异常了")
print("End")
3.2 捕获指定类型的异常以及获取异常信息
python
try:
result = 3 / 0
print("发生异常了")
except ZeroDivisionError as e:
print(e)
except (RuntimeError, TypeError, NameError) as e:
print(e)
except:
print("Unexpected error")
print("End")
2. else
如果你希望在try块中有些操作执行成功后,再执行其它代码,那就可以把代码放到else语句块中。
1)语法
python
try:
可能发生异常的代码
except 异常类型1 as 变量名1:
异常处理的代码
except 异常类型2 as 变量名2:
异常处理的代码
else:
没有异常时执行的代码
2)说明
-
从执行效果上说,将代码放到else块和直接放到try块中是一样的。
-
将try正常执行完毕而没有引发任何异常后被执行的代码放到else中。提供了一种清晰的逻辑区分,将正常情况的代码与异常处理代码分开,使代码更易于理解和维护,有助于代码的可读性和可维护性。
3)案例
python
try:
result = 3 / 1
except ZeroDivisionError:
print("除数不能为零!")
else:
print(f"结果是: {result}")
3. finally
- 可选,但是如果选,只能放在最后
- 无论是否发生异常都会执行的代码
- 通常用于执行一些必须要进行的清理操作,例如关闭文件、释放资源(如网络连接、数据库连接、锁等)
1)语法
python
try:
可能发生异常的代码
except 异常类型1 as 变量名1:
异常处理的代码
except 异常类型2 as 变量名2:
异常处理的代码
else:
没有异常时执行的代码
finally:
无论是否发生异常都会执行的代码
- 案例
python
try:
result = 3 / 0
except NameError as e:
print(e)
else:
print(result)
finally:
print("finally")
print("End")
#输出结果:
# finally
# Traceback (most recent call last):
# File "e:\Hello\hello.py", line 15, in <module>
# result = 3 / 0
# ~~^~~
# ZeroDivisionError: division by zero
try:
result = 3 / 0
except ZeroDivisionError as e:
print(e)
else:
print(result)
finally:
print("finally")
print("End")
#输出结果:
# division by zero
# finally
# End
1.4 抛出异常
1.4.1 raise
当你想要在代码中明确表示发生了错误或异常情况时,不想捕获处理,可以使用 raise 来抛出异常。
1)语法
python
raise 异常类型("异常描述")
- 案例
python
def int_add(x, y):
if isinstance(x, int) and isinstance(y, int):
return x + y
else:
raise TypeError("参数类型错误")
print(int_add(1, 2)) # 3
print(int_add("1", "2")) # TypeError: 参数类型错误
1.4.2 assert断言
assert用于判断一个表达式,在表达式条件为False的时候触发异常,常用于调试程序。
1)语法
python
assert 表达式 [,异常描述]
# [, error_message] 表示 error_message 是可选的,实际使用时不需要写方括号
- 工作原理
- 如果
expression的值为True,程序继续正常执行 - 如果
expression的值为False,则抛出AssertionError异常- 如果没有提供
error_message,抛出默认的AssertionError - 如果提供了
error_message,它会作为异常信息显示
- 如果没有提供
3)案例
python
# 1.基本用法
x = 5
assert x == 5 # 不会触发异常
assert x == 3 # 触发AssertionError
# 2.带错误信息的断言
x = 10
assert x % 2 == 0, "x必须是偶数"
assert x > 20, f"x的值{x}必须大于20" # 触发AssertionError: x的值10必须大于20
# 3.在函数中使用
def divide(a, b):
assert b != 0, "除数不能为0"
return a / b
print(divide(10, 2)) # 正常执行
print(divide(10, 0)) # 触发AssertionError: 除数不能为0
- 注意:
- 不要用于数据验证:assert主要用于调试和开发阶段,可能会被Python的优化模式忽略
- 错误处理 :生产环境中应该使用
try/except来处理预期可能发生的错误 - 性能影响:大量使用assert可能会影响程序性能
1.5 自定义异常
通过直接或者间接继承Exception类来创建自己的异常,这就是自定义异常。
python
# 自定义异常案例
class MyError(Exception):
def __init__(self, value):
print("自定义异常构造:", value)
self.value = value
def __str__(self):
print("自定义异常str:", self.value)
return repr(self.value)
try:
# 业务逻辑;
i = 1 / 0
except Exception as e:
print("触发自定义异常:", e.args)
raise MyError(1)
1.6 异常的传递
当存在 try 嵌套或函数嵌套时,若内层出现了异常且在内层无法处理,会将异常一层一层向外传递,直到异常被处理或程序报错。
python
try:
try:
try:
print(1 / 0)
except NameError as e:
print("第三层", e)
except TypeError as e:
print("第二层", e)
except Exception as e:
print("第一层", type(e), e)
# 第一层 <class 'ZeroDivisionError'> division by zero
1.7 with关键字
Python中的with语句用于异常处理,封装了try except finally编码范式,提供了一种简洁的方式来确保资源的正确获取和释放,同时处理可能发生的异常,提高了易用性。使代码更清晰、更具可读性。
- 语法
python
with expression as variable:
# 代码块
- 说明
- expression: 必须返回一个上下文管理器对象
- variable:用于接收
expression返回的上下文管理器对象或它的__enter__()函数的返回值
- 工作原理:
with语句背后的工作机制涉及两个特殊函数:
__enter__()- 进入上下文时调用,返回值会赋给as后的变量__exit__()- 退出上下文时调用,负责清理工作
执行流程
- 计算
expression,获取上下文管理器对象 - 调用上下文管理器的
__enter__()函数 - 如果有
as子句,将__enter__()的返回值赋给变量 - 执行
with代码块中的语句 - 无论代码块是否发生异常,都会调用
__exit__()函数- 如果代码块正常执行完毕,
__exit__()的三个参数都为None - 如果发生异常,
__exit__()会接收到异常类型、值和追踪信息
- 如果代码块正常执行完毕,
- 案例
打开一个文件并向其中写入内容,验证出现异常后文件是否正常关闭。
python
# 1.常规方式
try:
file = open("test.txt", "w")
file.write(a) # a未定义
file.close()
finally:
print("文件是否关闭:", file.closed) # 文件是否关闭: False
# 2 使用 try finally
try:
file = open("test.txt", "w")
try:
file.write(a) # a未定义
finally:
file.close()
finally:
print("文件是否关闭:", file.closed) # 文件是否关闭: True
# 3.使用 with
try:
with open("test.txt", "w") as f:
f.write(a) # a未定义
finally:
print("文件是否关闭:", f.closed) # 文件是否关闭: True
1.8 Python常见异常
1. 异常基类
| 异常 | 说明 |
|---|---|
| BaseException | 所有内置异常的基类。它不应该被用户自定义类直接继承(这种情况请使用[Exception])。 |
| Exception | 所有内置的非系统退出类异常都派生自此类。所有用户自定义异常也应当派生自此类。 |
| ArithmeticError | 此基类用于派生针对各种算术类错误而引发的内置异常:[OverflowError], [ZeroDivisionError], [FloatingPointError])。 |
| BufferError | 当与[缓冲区]相关的操作无法执行时将被引发。 |
| LookupError | 此基类用于派生当映射或序列所使用的键或索引无效时引发的异常:[IndexError][KeyError]。这可以通过 [codecs.lookup()]来直接引发。 |
2. 具体异常
| 异常 | 说明 |
|---|---|
| AssertionError | 当 [assert] 语句失败时将被引发。 |
| AttributeError | 当属性引用或赋值失败时将被引发。 |
| IndexError | 当序列抽取超出范围时将被引发。 |
| KeyError | 当在现有键集合中找不到指定的映射(字典)键时将被引发。 |
| KeyboardInterrupt | 当用户按下中断键 (通常为 Control-C 或 Delete) 时将被引发。 |
| MemoryError | 当一个操作耗尽内存但情况仍可(通过删除一些对象)进行挽救时将被引发。 |
| NameError | 当某个局部或全局名称未找到时将被引发。 |
| OSError | 此异常在一个系统函数返回系统相关的错误时将被引发,此类错误包括 I/O 操作失败例如 文件未找到 或 磁盘已满 等。 |
| SyntaxError | 当解析器遇到语法错误时引发。 |
| TypeError | 当一个操作或函数被应用于类型不适当的对象时将被引发。 |
第十三章 模块与包
1.模块
概念:Python中一个以.py结尾的源文件即为一个模块(Module),中可以包含变量、函数和类等。通常我们把能够实现某一特定功能的代码放置在一个文件中作为一个模块。
好处:
- 提高了代码的可维护性,也提高了代码的复用性
- 使用模块也可以避免名称冲突,相同名字的函数或变量可以分别存在与不同的模块中。
1. 创建模块
模块名说明:
- 模块名区分大小写。
- 始终使用小写字母命名模块。
- 避免使用Python标准库模块名作为自定义模块名。
python
# 1.创建一个标准模块my_add.py
num =100
def add(a, b):
"""求两个数的和"""
return a + b
# 2.创建一个不标准的模块
# 2.1 假设我们创建一个名为sys.py的文件:
# sys.py 内容
def hello():
print("This is my custom sys module")
# main.py 然后尝试使用:
import sys
sys.hello() # 这会报错,因为实际导入的是Python的标准sys模块
# AttributeError: module 'sys' has no attribute 'hello'
2. 导入模块
2.1 全部导入import
导入模块的所有成员,通过模块名.成员名的方式访问。即使多次使用 import导入同一模块,模块也只会被导入一次。
1)语法
python
import 模块名 [as 别名]
2)案例
在同一目录下创建一个main.py文件,在其中导入 my_add.py 模块并使用。
python
# 导入模块
import my_add
# 使用模块
print(my_add.add(1, 2))
print(my_add.num)
也可以在导入模块时给模块起别名。
python
# 导入模块
import my_add as a1
# 使用模块
print(a1.add(1, 2))
print(a1.num)
2.2 局部导入 from import
概念: 指定导入模块的部分成员,直接通过成员名的方式访问。
特点:
- 只能使用其导入的成员,未导入的成员不能使用。
- 如果多个模块中存在重名成员,后一次导入会覆盖前一次导入。
1)语法
python
from 模块名 import 成员名1[as 别名], 成员名2[as 别名],...
2)案例
python
# 1.使用导入的成员
from my_add import add
print(add(1, 2)) # 只能使用导入的成员add
print(num) # NameError: name 'num' is not defined
# 2.重名变量,后一次导入会覆盖前一次导入
# 2.1 创建新的模块my_multi.py
num =200
_str1="abc"
def multi(a, b):
"""求两个数的积"""
return a * b
# 2.2 导入模块
from my_add import add, num
from my_multi import num
print(add(1, 2))
print(num) # my_multi的num
# 3.通过别名区分不同模块的变量
# 导入模块
# 导入模块
from my_add import num as a1
from my_multi import num as m1
# 使用模块
print(a1) # my_add的num 100
print(m1) # my_multi的num 200
2.3 局部导入 from import *
导入模块中所有不以单下划线开头的成员,直接通过成员名的方式访问。
1)语法
python
from 模块名 import *
2)案例
导入模块
python
from my_add import *
# 使用模块
print(add(1, 2)) # 3
print(num) # 100
print(_str1) # NameError: name '_str1' is not defined
2.4 导入模块搜索顺序
当导入一个模块时,会按照以下顺序进行查找:
(1)当前目录。
(2)PYTHONPATH环境变量中的目录。
(3)包含标准 Python 模块以及这些模块所依赖的任何 extension module 的目录。
可以使用以下方式查看模块搜索顺序:
python
import sys
print(sys.path)
2.5 all
- 控制
from module import *导入的内容 - 当使用
from module import *时,只有__all__列表中指定的名称会被导入
python
# test_my_module.py
__all__ = ['public_func', 'PUBLIC_VAR'] # 明确指定公开接口
def public_func():
return "This is public"
def _private_func():
return "This is private"
PUBLIC_VAR = 42
_PRIVATE_VAR = 99
import test_my_module
print(test_my_module.public_func()) # 可以访问
print(test_my_module.PUBLIC_VAR) # 可以访问
print(test_my_module._private_func()) # NameError: name '_private_func' is not defined
print(test_my_module._PRIVATE_VAR) # NameError: name '_PRIVATE_VAR' is not defined
注意 :Python中以下划线(_)开头的成员会被视为私有,使用from module import *时默认不会导入这些成员。但__all__的存在有更重要的用途。
以下是命名约定 vs __all__的实质区别
| 特性 | 下划线命名约定 | __all__ |
|---|---|---|
| 约束范围 | 仅影响import * |
明确声明公开API,精确控制API表面,防止意外暴露 |
| 可访问性 | 仍可显式导入 | 可完全隐藏非列表成员 |
| 灵活性 | 固定规则 | 可自定义 |
下面通过具体示例展示两者在控制导入行为上的关键区别:
案例1:仅使用下划线命名约定
python
# module_with_underscore.py
def public_func():
return "我是公开函数"
def _private_func():
return "我是私有函数(单下划线)"
def __really_private_func():
return "我是强私有函数(双下划线)"
python
# importer_underscore.py
from module_with_underscore import * # 只能导入public_func
print(public_func()) # 正常工作
# print(_private_func()) # 报错: NameError
# print(__really_private_func()) # 报错: NameError
# 但是可以显式导入私有成员
from module_with_underscore import _private_func, __really_private_func
print(_private_func()) # 正常工作
print(__really_private_func()) # 正常工作
案例2:使用__all__控制
python
# module_with_all.py
__all__ = ['public_func'] # 明确指定只公开这一个函数
def public_func():
return "我是公开函数"
def utility_func(): # 没有下划线但不是公开API
return "我是实用函数(没有下划线但不在__all__中)"
def _private_func():
return "我是私有函数"
python
# importer_all.py
from module_with_all import * # 只能导入public_func
print(public_func()) # 正常工作
# print(utility_func()) # 报错: NameError
# print(_private_func()) # 报错: NameError
# 尝试显式导入非__all__成员
try:
from module_with_all import utility_func
print("能导入utility_func:", utility_func())
except ImportError as e:
print("导入utility_func失败:", e)
try:
from module_with_all import _private_func
print("能导入_private_func:", _private_func())
except ImportError as e:
print("导入_private_func失败:", e)
关键区别说明
- 下划线命名的局限性 :
- 只能防止
import *导入私有成员 - 用户仍可通过显式导入访问
_private_func和__really_private_func - 如果忘记加下划线(如
utility_func),会被意外导出
- 只能防止
__all__的严格控制 :- 即使成员没有下划线前缀(如
utility_func),只要不在__all__中,import *就不会导出。单独导入模块中不在__all__以及私有的都可以。 - 提供了真正的API边界控制
- 即使成员没有下划线前缀(如
2.6 init
__init__.py是Python包的标识文件,它决定了包的初始化行为和公开接口。以下是常见的编写内容:
1. 基础功能
1.1 包初始化代码
python
# 包级别的初始化代码 注意被首次导入时自动执行
print(f"初始化 {__name__} 包")
1.2 定义__all__
python
# 明确公开接口
__all__ = ['module1', 'module2', 'main_func'] # import *导入模块
2. 模块导入
2.1 导入模块与子模块
python
from .module1 import Class1, function1 # 从当前包的 module1 导入
from .subpackage import Class2
3. 包版本与元信息
3.1 版本定义
python
__version__ = '1.0.0'
__author__ = 'Your Name'
__license__ = 'MIT'
3.2 包文档字符串
python
"""
我的Python包
这个包提供了处理XX问题的各种工具:
- 功能1
- 功能2
- 功能3
"""
4. 高级用法
4.1 包级别函数/变量
python
# 包级别工具函数
def package_util():
return "包级别的工具函数"
# 包级别配置
DEFAULT_CONFIG = {'option': 'value'}
4.2 导入时验证
python
# 检查依赖
try:
import numpy
except ImportError:
raise ImportError("此包需要numpy,请先安装: pip install numpy")
2.7 name
在 Python 中,name 是一个特殊的内置变量
- 当一个Python文件被直接运行时,该文件的__name__属性值为"main"。
- 当一个Python文件作为模块被导入时,__name__属性会被设置为该模块的名称(即文件名,不包含 .py 后缀)。
使用 name == "main" 避免测试代码被执行
python
num = 100
num1 = 200
def add(a, b):
"""求两个数的和"""
return a + b
def sub(a, b):
"""求两个数的差"""
return a - b
if __name__ == "__main__":
print(add(10,20))
2.8 dir
dir() 是一个内置函数,主要用于列出对象的属性和方法,或者列出当前作用域中定义的名称,并以一个字符串列表的形式返回。
1、当你不传递任何参数调用 dir() 时,它会列出当前作用域中定义的名称,包括变量、函数、类等
python
def my_function():
pass
variable = 10
print(dir())
#['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'my_function', 'variable']
2、 当你将一个对象作为 dir() 的参数时,它会返回该对象的属性和方法列表。
python
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def method1(self):
pass
obj = MyClass()
print(dir(obj))
#['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'method1', 'x', 'y']
3、当你将一个模块作为 dir() 的参数时,它会返回该模块中定义的名称列表,包括函数、类、变量等
python
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'cbrt', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'exp2', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'sumprod', 'tan', 'tanh', 'tau', 'trunc', 'ulp']
2.包
概念:
在Python中,包Package是一种组织Python模块的方式,它比模块更高一级,用于将相关的模块组织在一起,形成层次化的模块命名空间。
特点:
- 包是一个包含
__init__.py文件的目录 - 它可以包含多个模块(.py文件)和子包
- 通过点式记法访问,如
package.subpackage.module
2.1 包创建
直接在pycharm中创建
python
test_my_package/
my_package/ # 包目录
├──__init__.py # 包初始化文件
├──module1.py # 模块1
└──subpackage/ # 子包
├──__init__.py
└──module2.py
test.py # 用于测试 (不用__init__.py文件)
2.2 导入包
1. 全局导入 import
1.1 全局导入整个包
1)语法
python
import 包名 [as 别名]
- 调用
python
包名.成员名
- 使用
python
import my_package
print(my_package.PACKAGE_NAME)
# 运行结果
# 初始化 mypackage 包
# my_package
2. 局部导入 from import
- 局部从包中导入特定模块
- 局部从子包中导入特定模块
- 局部从包的特定模块导入特定成员
- 局部从子包特定模块导入特定成员
2.1 局部导入包下的模块
1 从包导入特定模块
1)语法
python
from 包名 import 模块名 [as 别名]
2)调用方式
python
模块名.成员名
- 使用
python
from my_package import module1
print(module1.public_function())
# 运行结果
# 初始化 my_package 包
# 这是module1的公共函数
2 从子包导入特定模块
1)语法
python
from 包名.子包名 import 模块名 [as 别名]
2)调用方式
python
模块名.成员名
3)使用
python
from my_package.subpackage import module2
print(module2.module2_function())
2.2 局部导入包下模块的成员
1 从包的特定模块导入特定成员
1)语法
python
from 包名.模块名 import 成员名 [as 别名]
2)调用方式
python
成员名
3)使用
python
from my_package.module1 import public_function
print(public_function())
# 运行结果
# 初始化 my_package 包
# 这是module1的公共函数
2 从子包的特定模块导入特定成员
1)语法
python
from 包名.子包名.模块名 import 成员名 [as 别名]
2)调用方式
python
成员名
3)使用
python
from my_package.subpackage.module2 import module2_function
print(module2_function())
3. 局部导入 from import *
概述:导入指定模块除了我们在导入的时候通过from 包名 import 模块名之外,还可以在包的__all__中指定要导入的模块。
1)语法
python
from 包名 import *
2)调用方式
python
模块.成员
3)使用
python
# 1.在my_package包下的__init__方法中指定可被访问的模块
__all__ = ["module1"]
# 2.在在my_package包新建一个module1_all.py文件用于测试
# 3.使用
from my_package import *
print(module1.all_module1_function())
print(module1_all.all_module1_function())#编译报错
4)注意
当我们使用 from 包 import * 时,Python并不会查找并导入包的所有子模块,因为这将花费很长的时间,并且可能会产生我们不想要的副作用。
3. 常用标准库(包)
标准库指的是在安装Python时就一同被安装的库。这些库经过精心挑选和开发,旨在为Python开发者提供通用且强大的工具集,涵盖各种不同的应用领域。
| 名称 | 说明 |
|---|---|
| os | 多种操作系统接口。 |
| sys | 系统相关的形参和函数。 |
| time | 时间的访问和转换。 |
| datetime | 提供了用于操作日期和时间的类。 |
| math | 数学函数。 |
| random | 生成伪随机数。 |
| re | 正则表达式匹配操作。 |
| json | JSON 编码器和解码器。 |
| collections | 实现了一些专门化的容器,提供了对 Python 的通用内建容器 dict、list、set 和 tuple 的补充。 |
| functools | 高阶函数,以及可调用对象上的操作 |
| hashlib | 安全哈希与消息摘要。 |
| urllib | URL 处理模块。 |
| smtplib | SMTP 协议客户端,邮件处理。 |
| zlib | 与 gzip 兼容的压缩。 |
| gzip | 对 gzip 文件的支持。 |
| bz2 | 对 bzip2 压缩算法的支持。 |
| multiprocessing | 基于进程的并行。 |
| threading | 基于线程的并行。 |
| copy | 浅层及深层拷贝操作。 |
| socket | 低层级的网络接口。 |
| shutil | 提供了一系列对文件和文件集合的高阶操作,特别是提供了一些支持文件拷贝和删除的函数。 |
| glob | Unix 风格的路径名模式扩展。 |
更多标准库可参考https://docs.python.org/zh-cn/3/library/index.html。
4. 引入第三方库
当需要使用Python中没有内置的库时,可以通过以下方式安装第三方库
1.使用pip安装
pip是Python包管理工具,该工具提供了对 Python 包的查找、下载、安装、卸载的功能。pip 默认的源是 Python Package Index(PyPI),其地址为 https://pypi.org/simple/,如果下载比较慢,还可以指定其它的源。
- 阿里云:http://mirrors.aliyun.com/pypi/simple/
- 豆瓣:http://pypi.douban.com/simple/
- 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
1)pip常用命令
查看我们已经安装的软件包
python
pip list
安装软件包-具体包名就什么可以到PyPI上查找
python
pip install 包名
卸载软件包
python
pip uninstall 包名
临时使用其他源
python
pip install -i http://mirrors.aliyun.com/pypi/simple/ 包名
永久修改源
python
pip config set global.index-url http://mirrors.aliyun.com/pypi/simple/
package包下的__init__方法中指定可被访问的模块__all__ = ["module1"]
在my_package包新建一个module1_all.py文件用于测试
使用
python
from my_package import *
print(module1.all_module1_function())
print(module1_all.all_module1_function())#编译报错
注意
当我们使用 from 包 import * 时,Python并不会查找并导入包的所有子模块,因为这将花费很长的时间,并且可能会产生我们不想要的副作用。
3. 常用标准库(包)
标准库指的是在安装Python时就一同被安装的库。这些库经过精心挑选和开发,旨在为Python开发者提供通用且强大的工具集,涵盖各种不同的应用领域。
| 名称 | 说明 |
|---|---|
| os | 多种操作系统接口。 |
| sys | 系统相关的形参和函数。 |
| time | 时间的访问和转换。 |
| datetime | 提供了用于操作日期和时间的类。 |
| math | 数学函数。 |
| random | 生成伪随机数。 |
| re | 正则表达式匹配操作。 |
| json | JSON 编码器和解码器。 |
| collections | 实现了一些专门化的容器,提供了对 Python 的通用内建容器 dict、list、set 和 tuple 的补充。 |
| functools | 高阶函数,以及可调用对象上的操作 |
| hashlib | 安全哈希与消息摘要。 |
| urllib | URL 处理模块。 |
| smtplib | SMTP 协议客户端,邮件处理。 |
| zlib | 与 gzip 兼容的压缩。 |
| gzip | 对 gzip 文件的支持。 |
| bz2 | 对 bzip2 压缩算法的支持。 |
| multiprocessing | 基于进程的并行。 |
| threading | 基于线程的并行。 |
| copy | 浅层及深层拷贝操作。 |
| socket | 低层级的网络接口。 |
| shutil | 提供了一系列对文件和文件集合的高阶操作,特别是提供了一些支持文件拷贝和删除的函数。 |
| glob | Unix 风格的路径名模式扩展。 |
更多标准库可参考https://docs.python.org/zh-cn/3/library/index.html。
4. 引入第三方库
当需要使用Python中没有内置的库时,可以通过以下方式安装第三方库
1.使用pip安装
pip是Python包管理工具,该工具提供了对 Python 包的查找、下载、安装、卸载的功能。pip 默认的源是 Python Package Index(PyPI),其地址为 https://pypi.org/simple/,如果下载比较慢,还可以指定其它的源。
- 阿里云:http://mirrors.aliyun.com/pypi/simple/
- 豆瓣:http://pypi.douban.com/simple/
- 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
1)pip常用命令
查看我们已经安装的软件包
python
pip list
安装软件包-具体包名就什么可以到PyPI上查找
python
pip install 包名
卸载软件包
python
pip uninstall 包名
临时使用其他源
python
pip install -i http://mirrors.aliyun.com/pypi/simple/ 包名
永久修改源
python
pip config set global.index-url http://mirrors.aliyun.com/pypi/simple/