目录
[1. 对象与类:蓝图与实例](#1. 对象与类:蓝图与实例)
[1.1 什么是类?什么是对象?](#1.1 什么是类?什么是对象?)
[1.2 Python 中的类定义与对象创建](#1.2 Python 中的类定义与对象创建)
[2. 实例化:从类到对象的过程](#2. 实例化:从类到对象的过程)
[3. 面向对象编程(OOP)四大特性概述](#3. 面向对象编程(OOP)四大特性概述)
[4. 封装 ------ 数据隐藏与接口](#4. 封装 —— 数据隐藏与接口)
[4.1 Python 中的访问控制:约定与名称改编](#4.1 Python 中的访问控制:约定与名称改编)
[4.2 使用 @property 实现优雅的访问控制](#4.2 使用 @property 实现优雅的访问控制)
[5. 变量作用域:实例变量、类变量、局部变量与全局变量](#5. 变量作用域:实例变量、类变量、局部变量与全局变量)
[5.1 实例变量 vs 类变量](#5.1 实例变量 vs 类变量)
[5.2 方法中的局部变量](#5.2 方法中的局部变量)
[5.3 self 的作用与 C++ 的 this 指针](#5.3 self 的作用与 C++ 的 this 指针)
[5.4 全局变量与函数作用域](#5.4 全局变量与函数作用域)
[6. 综合实战:封装一个简单的银行账户系统](#6. 综合实战:封装一个简单的银行账户系统)
[7. 常见陷阱与最佳实践](#7. 常见陷阱与最佳实践)
理解对象与类的本质,掌握封装与作用域,写出更健壮的面向对象代码
面向对象编程(OOP)是一种组织代码的方式,它将数据(属性)和行为(方法)封装在一起,形成"对象"。Python 从设计之初就是一门面向对象的语言,而 C++ 则是混合范式语言。
1. 对象与类:蓝图与实例
1.1 什么是类?什么是对象?
-
类(Class):是创建对象的蓝图或模板。它定义了对象应该具有的属性(数据)和方法(行为)。
-
对象(Object):是根据类创建的具体实例。每个对象都有自己的状态(属性值),并可以执行类中定义的行为。
生活类比:类就像一张"汽车设计图纸",定义了汽车的品牌、颜色、最高时速、启动方法等。对象就是根据这张图纸生产出来的具体汽车,每辆车有自己的颜色、车牌号,但都遵循图纸的规范。
1.2 Python 中的类定义与对象创建
class Car:
"""汽车类"""
# 类属性(所有实例共享)
wheels = 4
# 实例初始化方法(构造器)
def __init__(self, brand, color):
self.brand = brand # 实例属性
self.color = color
# 实例方法
def drive(self):
return f"{self.color} {self.brand} is driving."
# 实例化:创建对象
my_car = Car("Tesla", "red")
your_car = Car("BMW", "blue")
print(my_car.wheels) # 4(类属性)
print(my_car.drive()) # red Tesla is driving.
print(your_car.drive()) # blue BMW is driving.
Car.wheels = 3 # 修改类属性
print(my_car.wheels) # 3(所有实例受影响)
print(Car.wheels) # 3
逐行解析:
-
class Car::定义名为Car的类。 -
wheels = 4:类属性,属于类本身,所有实例共享。 -
def __init__(self, brand, color)::初始化方法,当创建Car实例时自动调用。self代表当前实例。 -
self.brand = brand:创建实例属性brand并赋值。 -
self.color = color:创建实例属性color。 -
def drive(self)::实例方法,第一个参数必须是self,用于访问实例属性。 -
my_car = Car("Tesla", "red"):实例化,调用__init__,返回实例对象赋值给my_car。 -
my_car.wheels:访问类属性,如果实例没有同名属性,则会向上查找类属性。 -
Car.wheels = 3:通过类名修改类属性,所有实例的wheels访问都会改变(除非实例自己覆盖了该属性)。
C++ 联动:
-
C++ 中类与对象的概念类似,但语法不同:
class Car {
public:
static int wheels; // 类属性(静态成员)
std::string brand;
std::string color;Car(std::string b, std::string c) : brand(b), color(c) {} std::string drive() { return color + " " + brand + " is driving."; }};
int Car::wheels = 4; // 静态成员定义
Car my_car("Tesla", "red"); -
区别:C++ 中类属性(静态成员)需要在类外单独定义;Python 的类属性直接定义在类体内。C++ 有明确的
public/private访问控制,而 Python 主要依赖命名约定。
2. 实例化:从类到对象的过程
实例化就是调用类创建对象的过程。Python 中,类的实例化分为两步:
-
调用
__new__方法创建空对象(很少重写)。 -
调用
__init__方法初始化对象。class Dog:
def init(self, name):
self.name = name实例化
d = Dog("Buddy")
print(d.name) # Buddy
解析:
-
Dog("Buddy")触发了实例化。 -
实际上
__new__返回一个Dog实例(空壳),然后__init__填充属性name。 -
最终对象赋值给
d。
C++ 联动:
-
C++ 中实例化对象有两种方式:栈上
Dog d("Buddy");或堆上Dog* d = new Dog("Buddy");。构造函数直接完成内存分配和初始化(堆上需要new)。 -
Python 的所有对象都在堆上分配,由垃圾回收管理,不需要手动释放。
3. 面向对象编程(OOP)四大特性概述
OOP 的四大特性是:封装、继承、多态、抽象。本文重点讲解封装 和变量作用域(继承与多态将在后续文章中深入)。
-
封装:将数据(属性)和操作数据的方法捆绑在一起,并隐藏内部实现细节,对外只暴露必要的接口。
-
继承:子类复用父类的代码。
-
多态:同一接口,不同实现。
-
抽象:提取共性,定义接口规范。
4. 封装 ------ 数据隐藏与接口
封装有两个层面的含义:
-
数据与操作的捆绑:将相关属性和方法放在同一个类中。
-
访问控制:限制外部直接访问内部数据,通过公开的方法(getter/setter)来读写。
4.1 Python 中的访问控制:约定与名称改编
Python 没有像 C++ 那样的 private、protected 关键字,而是采用命名约定:
| 命名模式 | 含义 | 外部访问 |
|---|---|---|
name |
公有(public) | 可以直接访问 |
_name |
受保护(protected) | 约定外部不应直接访问 |
__name |
私有(private) | 名称改编,但仍可访问 |
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # 公有
self._password = "1234" # 受保护(约定)
self.__balance = balance # 私有(名称改编)
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
acc = BankAccount("Alice", 1000)
print(acc.owner) # Alice(直接访问公有)
print(acc._password) # 1234(可以访问,但不推荐)
# print(acc.__balance) # AttributeError
print(acc.get_balance()) # 1000(通过公有方法访问)
print(acc._BankAccount__balance) # 1000(名称改编后仍然可以访问,但不应这样做)
逐行解析:
-
self.owner = owner:公有属性,任何地方都可直接读写。 -
self._password = "1234":单下划线开头,约定为"受保护",外部可以访问但应视为内部实现细节,不建议使用。IDE 和工具会提示警告。 -
self.__balance = balance:双下划线开头,Python 会进行名称改编(name mangling) ,实际属性名变为_BankAccount__balance。这样做的目的是防止子类意外覆盖,但并不能真正阻止外部访问。 -
get_balance():公有方法,提供对私有属性的受控访问。 -
外部代码可以通过
_BankAccount__balance访问,但强烈不建议依赖这种实现细节。
4.2 使用 @property 实现优雅的访问控制
class Temperature:
def __init__(self, celsius):
self._celsius = celsius # 内部存储
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
t = Temperature(25)
print(t.celsius) # 25(通过 getter)
t.celsius = 30 # 通过 setter 验证
print(t.fahrenheit) # 86.0(只读计算属性)
# t.fahrenheit = 100 # AttributeError: can't set attribute
解析:
-
@property将方法转为只读属性,访问t.celsius实际调用celsius方法。 -
@celsius.setter允许赋值操作t.celsius = 30时执行自定义逻辑(验证)。 -
fahrenheit只有 getter,没有 setter,因此是只读的计算属性。
C++ 联动:
-
C++ 中实现封装通常需要手写 getter/setter 函数,例如:
class Temperature {
private:
double celsius;
public:
double getCelsius() const { return celsius; }
void setCelsius(double value) {
if (value < -273.15) throw std::invalid_argument("...");
celsius = value;
}
double getFahrenheit() const { return celsius * 9/5 + 32; }
}; -
Python 的
@property使得 getter/setter 的调用看起来像普通属性,更加简洁自然。C++ 中无法直接实现这种语法糖(除非重载operator.,但 C++ 不允许)。
5. 变量作用域:实例变量、类变量、局部变量与全局变量
5.1 实例变量 vs 类变量
-
实例变量 :属于每个实例,在
__init__或实例方法中用self.变量定义,不同实例的值相互独立。 -
类变量:属于类本身,在类体内直接定义,所有实例共享。
class Employee:
company = "TechCorp" # 类变量
def init(self, name):
self.name = name # 实例变量e1 = Employee("Alice")
e2 = Employee("Bob")
print(e1.company, e2.company) # TechCorp TechCorp
e1.company = "NewCorp" # 实际上是创建了实例变量 company,隐藏了类变量
print(e1.company, e2.company) # NewCorp TechCorp
Employee.company = "GlobalCorp" # 修改类变量
print(e1.company, e2.company) # NewCorp GlobalCorp(e1 有自己的实例变量,不受影响)
解析:
-
访问
e1.company时,先查找实例是否有company属性,没有则查找类属性。 -
赋值
e1.company = "NewCorp"会在实例上创建新属性,不再共享类变量。 -
修改类变量通过
Employee.company影响所有未覆盖的实例。
5.2 方法中的局部变量
在实例方法内部定义的变量是局部变量,只在方法执行期间存在。
class Counter:
def increment(self, x):
temp = x + 1 # 局部变量,方法结束后销毁
return temp
5.3 self 的作用与 C++ 的 this 指针
-
self是实例方法的第一个参数,指向当前调用该方法的对象。它是显式传递的。 -
this在 C++ 中是隐式的指针,在成员函数内部可以直接使用。class Point:
def init(self, x, y):
self.x = x
self.y = ydef distance(self, other): return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
C++ 对应:
class Point {
public:
double x, y;
Point(double x, double y) : x(x), y(y) {}
double distance(const Point& other) const {
return sqrt((this->x - other.x)*(this->x - other.x) + ...);
}
};
区别:
-
Python 的
self是显式的,必须作为第一个参数;C++ 的this是隐式的关键字。 -
Python 中的实例属性需要显式通过
self访问;C++ 中可以直接访问成员变量(x等价于this->x)。
5.4 全局变量与函数作用域
在类或方法中,可以通过 global 关键字访问和修改全局变量。
global_count = 0
class Tracker:
def increment(self):
global global_count
global_count += 1
t = Tracker()
t.increment()
print(global_count) # 1
建议:尽量避免在类中使用全局变量,会破坏封装性。
C++ 联动:
- C++ 中全局变量可以直接访问,没有类似 Python 的
global声明,因为 C++ 的作用域规则是编译期静态的。
6. 综合实战:封装一个简单的银行账户系统
class Account:
"""银行账户类,演示封装和作用域"""
_bank_name = "Python Bank" # 类变量(约定受保护)
def __init__(self, owner, initial_balance=0):
self.owner = owner # 公有
self.__balance = initial_balance # 私有
self._transaction_log = [] # 受保护
@property
def balance(self):
return self.__balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Amount must be positive")
self.__balance += amount
self._log(f"Deposited {amount}")
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Amount must be positive")
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
self._log(f"Withdrew {amount}")
def _log(self, msg):
self._transaction_log.append(msg)
def get_log(self):
return self._transaction_log.copy()
@classmethod
def get_bank_name(cls):
return cls._bank_name
@staticmethod
def is_valid_owner(name):
return len(name) > 1 and name.isalpha()
# 使用
acc = Account("Alice", 100)
acc.deposit(50)
acc.withdraw(30)
print(acc.balance) # 120
print(acc.get_log()) # ['Deposited 50', 'Withdrew 30']
print(Account.get_bank_name())# Python Bank
print(Account.is_valid_owner("Bob")) # True
解析:
-
_bank_name是类变量(约定受保护),通过类方法get_bank_name访问。 -
__balance是私有实例属性,通过@property提供只读访问。 -
_log是受保护方法,仅在内部使用。 -
类方法
get_bank_name通过cls访问类属性。 -
静态方法
is_valid_owner与类或实例无关,仅作为工具函数。
7. 常见陷阱与最佳实践
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 在类体内直接调用实例方法 | 类体是定义阶段,还没有实例,不能调用实例方法 | 在 __init__ 或其他实例方法中调用 |
忘记 self 参数导致 TypeError |
定义实例方法时必须包含 self,调用时自动传递 |
确保每个实例方法的第一个参数是 self |
| 错误地使用类变量修改实例属性 | obj.class_var = value 会创建实例属性,而不是修改类变量 |
使用 ClassName.class_var = value 修改类变量 |
过度使用 global 或类变量代替实例变量 |
破坏封装,导致状态混乱 | 优先使用实例变量,必要时提供访问方法 |
| 依赖名称改编实现私有,却仍从外部访问 | __name 只是改名,并非真正的私有,不能实现严格封装 |
遵守命名约定,不要访问 _ClassName__name |
在 __init__ 中返回非 None 的值 |
__init__ 必须返回 None,否则触发 TypeError |
只在 __init__ 中初始化,不要 return 非空 |
C++ 中的类似陷阱:
-
C++ 中忘记
public访问限定符导致所有成员默认私有。 -
在构造函数初始化列表中遗漏成员,导致未初始化。
-
静态成员需要在类外定义。
感谢你的观看,期待我们下次再见!