Python 面向对象编程核心:对象、实例化、封装与变量作用域

目录

[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 中,类的实例化分为两步:

  1. 调用 __new__ 方法创建空对象(很少重写)。

  2. 调用 __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. 封装 ------ 数据隐藏与接口

封装有两个层面的含义:

  1. 数据与操作的捆绑:将相关属性和方法放在同一个类中。

  2. 访问控制:限制外部直接访问内部数据,通过公开的方法(getter/setter)来读写。

4.1 Python 中的访问控制:约定与名称改编

Python 没有像 C++ 那样的 privateprotected 关键字,而是采用命名约定:

命名模式 含义 外部访问
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 = y

    复制代码
      def 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 访问限定符导致所有成员默认私有。

  • 在构造函数初始化列表中遗漏成员,导致未初始化。

  • 静态成员需要在类外定义。

感谢你的观看,期待我们下次再见!

相关推荐
薛定谔的悦1 小时前
光伏-储能-负荷联合预测:给 EMS 装上“预知能力“
java·数据库·人工智能·python·储能
大菜菜小个子1 小时前
template<typename T>使用
java·开发语言·算法
L_09071 小时前
【C++】C++11 新特性
开发语言·c++
方也_arkling1 小时前
【Java-Day15】API篇-ArrayList集合
java·开发语言
我是一颗柠檬1 小时前
【Java后端技术亮点】动态路由权限(按钮级权限),细粒度控制到按钮级别
java·开发语言·后端·状态模式
Fanfanaas1 小时前
C++ 继承
java·开发语言·jvm·c++·学习·算法
在繁华处1 小时前
Java从零到熟练(十一):Spring框架入门
java·开发语言·spring
十五年专注C++开发1 小时前
cereal 库:C++ 序列化的轻量之选
开发语言·c++·序列化·反序列化·cereal
星卯教育tony2 小时前
2026年全国青少年信息素养大赛主题应用 数字守艺人 丝路新城 星火征程 智传民韵 c++ python scratch 所有真题免费分享
开发语言·c++