百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Python教程(二十四):封装和抽象

off999 2025-08-01 20:05 63 浏览 0 评论

昨天,我们学习了继承和多态,掌握了面向对象编程的核心概念。今天,我们将学习封装和抽象 — 面向对象编程的另外两个重要支柱。

封装保护数据并提供受控的访问,而抽象则隐藏复杂性并提供简洁的接口。


今天您将学习什么

  • 什么是封装以及如何实现封装
  • 访问修饰符和属性装饰器
  • 抽象类和抽象方法
  • 接口和抽象基类
  • 真实世界示例:银行账户、数据库连接、日志系统

什么是封装?

封装是面向对象编程的核心概念之一,它将数据(属性)和操作数据的方法(行为)捆绑在一起,并隐藏内部实现细节。

封装的优势:

  • 数据保护:防止外部直接访问和修改数据
  • 接口简化:提供清晰的公共接口
  • 维护性:内部实现可以改变而不影响外部代码
  • 数据验证:在数据访问时进行验证

1. 基本封装

使用私有属性

class BankAccount:
    """银行账户类 - 封装示例"""
    
    def __init__(self, account_number, owner_name, initial_balance=0):
        # 私有属性(以双下划线开头)
        self.__account_number = account_number
        self.__owner_name = owner_name
        self.__balance = initial_balance
        self.__transactions = []
        self.__is_active = True
    
    # 公共方法 - 提供受控的访问
    def get_balance(self):
        """获取余额"""
        return self.__balance
    
    def get_account_info(self):
        """获取账户信息"""
        return {
            'account_number': self.__account_number,
            'owner_name': self.__owner_name,
            'balance': self.__balance,
            'is_active': self.__is_active
        }
    
    def deposit(self, amount):
        """存款"""
        if not self.__is_active:
            print("账户已被冻结,无法存款")
            return False
        
        if amount <= 0:
            print("存款金额必须大于0")
            return False
        
        self.__balance += amount
        self.__add_transaction('deposit', amount)
        print(f"存款成功:{amount}元,当前余额:{self.__balance}元")
        return True
    
    def withdraw(self, amount):
        """取款"""
        if not self.__is_active:
            print("账户已被冻结,无法取款")
            return False
        
        if amount <= 0:
            print("取款金额必须大于0")
            return False
        
        if amount > self.__balance:
            print("余额不足")
            return False
        
        self.__balance -= amount
        self.__add_transaction('withdraw', amount)
        print(f"取款成功:{amount}元,当前余额:{self.__balance}元")
        return True
    
    # 私有方法 - 内部使用
    def __add_transaction(self, transaction_type, amount):
        """添加交易记录"""
        transaction = {
            'type': transaction_type,
            'amount': amount,
            'balance': self.__balance,
            'timestamp': '2025-07-15 10:30:00'  # 简化时间戳
        }
        self.__transactions.append(transaction)
    
    def get_transaction_history(self):
        """获取交易历史(只读)"""
        return self.__transactions.copy()  # 返回副本,防止外部修改
    
    def freeze_account(self):
        """冻结账户"""
        self.__is_active = False
        print(f"账户{self.__account_number}已被冻结")
    
    def unfreeze_account(self):
        """解冻账户"""
        self.__is_active = True
        print(f"账户{self.__account_number}已解冻")

# 使用封装的银行账户
account = BankAccount("001", "张三", 1000)

# 正确的访问方式
print(f"余额:{account.get_balance()}元")
print(f"账户信息:{account.get_account_info()}")

# 进行交易
account.deposit(500)
account.withdraw(200)

# 查看交易历史
transactions = account.get_transaction_history()
print(f"交易历史:{transactions}")

# 尝试直接访问私有属性(会失败)
try:
    print(account.__balance)  # AttributeError
except AttributeError:
    print("无法直接访问私有属性")

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("温度不能低于绝对零度(-273.15°C)")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """华氏度属性(计算属性)"""
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        """设置华氏度"""
        self.celsius = (value - 32) * 5/9
    
    @property
    def kelvin(self):
        """开尔文温度属性(计算属性)"""
        return self._celsius + 273.15
    
    @kelvin.setter
    def kelvin(self, value):
        """设置开尔文温度"""
        if value < 0:
            raise ValueError("开尔文温度不能为负")
        self.celsius = value - 273.15

# 使用属性装饰器
temp = Temperature(25)
print(f"摄氏度:{temp.celsius}°C")
print(f"华氏度:{temp.fahrenheit}°F")
print(f"开尔文:{temp.kelvin}K")

# 设置温度
temp.celsius = 30
print(f"新的摄氏度:{temp.celsius}°C")

temp.fahrenheit = 86
print(f"新的华氏度:{temp.fahrenheit}°F")

# 验证温度
try:
    temp.celsius = -300  # 会抛出异常
except ValueError as e:
    print(f"错误:{e}")

真实世界示例1:数据库连接管理器

import sqlite3
import threading
from contextlib import contextmanager

class DatabaseConnection:
    """数据库连接类 - 封装示例"""
    
    def __init__(self, database_path):
        self._database_path = database_path
        self._connection = None
        self._lock = threading.Lock()
        self._is_connected = False
    
    def connect(self):
        """连接数据库"""
        if self._is_connected:
            print("数据库已连接")
            return
        
        try:
            self._connection = sqlite3.connect(self._database_path)
            self._is_connected = True
            print(f"成功连接到数据库:{self._database_path}")
        except Exception as e:
            print(f"连接数据库失败:{e}")
            raise
    
    def disconnect(self):
        """断开数据库连接"""
        if not self._is_connected:
            print("数据库未连接")
            return
        
        try:
            self._connection.close()
            self._is_connected = False
            self._connection = None
            print("数据库连接已断开")
        except Exception as e:
            print(f"断开数据库连接失败:{e}")
    
    def execute_query(self, query, parameters=None):
        """执行查询"""
        if not self._is_connected:
            raise RuntimeError("数据库未连接")
        
        with self._lock:  # 线程安全
            try:
                cursor = self._connection.cursor()
                if parameters:
                    cursor.execute(query, parameters)
                else:
                    cursor.execute(query)
                
                if query.strip().upper().startswith('SELECT'):
                    return cursor.fetchall()
                else:
                    self._connection.commit()
                    return cursor.rowcount
            except Exception as e:
                self._connection.rollback()
                raise RuntimeError(f"查询执行失败:{e}")
    
    @contextmanager
    def transaction(self):
        """事务管理器"""
        if not self._is_connected:
            raise RuntimeError("数据库未连接")
        
        try:
            yield self
            self._connection.commit()
        except Exception:
            self._connection.rollback()
            raise
    
    def get_connection_status(self):
        """获取连接状态"""
        return {
            'is_connected': self._is_connected,
            'database_path': self._database_path
        }
    
    def __enter__(self):
        """上下文管理器入口"""
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器出口"""
        self.disconnect()

# 使用数据库连接管理器
def create_sample_database():
    """创建示例数据库"""
    db = DatabaseConnection(":memory:")  # 内存数据库
    
    with db:
        # 创建表
        db.execute_query("""
            CREATE TABLE users (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                email TEXT UNIQUE,
                age INTEGER
            )
        """)
        
        # 插入数据
        db.execute_query(
            "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
            ("张三", "zhangsan@email.com", 25)
        )
        db.execute_query(
            "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
            ("李四", "lisi@email.com", 30)
        )
        
        # 查询数据
        results = db.execute_query("SELECT * FROM users")
        print("用户数据:")
        for row in results:
            print(f"  ID: {row[0]}, 姓名: {row[1]}, 邮箱: {row[2]}, 年龄: {row[3]}")
    
    return db

# 使用事务
def demonstrate_transaction():
    """演示事务使用"""
    db = DatabaseConnection(":memory:")
    
    with db:
        # 创建表
        db.execute_query("""
            CREATE TABLE accounts (
                id INTEGER PRIMARY KEY,
                name TEXT,
                balance REAL
            )
        """)
        
        # 使用事务
        with db.transaction():
            db.execute_query(
                "INSERT INTO accounts (name, balance) VALUES (?, ?)",
                ("账户A", 1000)
            )
            db.execute_query(
                "INSERT INTO accounts (name, balance) VALUES (?, ?)",
                ("账户B", 500)
            )
            
            # 转账操作
            db.execute_query(
                "UPDATE accounts SET balance = balance - 200 WHERE name = ?",
                ("账户A",)
            )
            db.execute_query(
                "UPDATE accounts SET balance = balance + 200 WHERE name = ?",
                ("账户B",)
            )
        
        # 查看结果
        results = db.execute_query("SELECT * FROM accounts")
        print("转账后的账户余额:")
        for row in results:
            print(f"  {row[1]}: {row[2]}元")

# 运行示例
print("=== 数据库连接示例 ===")
create_sample_database()

print("\n=== 事务示例 ===")
demonstrate_transaction()

真实世界示例2:日志系统

import logging
import os
from datetime import datetime
from enum import Enum

class LogLevel(Enum):
    """日志级别枚举"""
    DEBUG = logging.DEBUG
    INFO = logging.INFO
    WARNING = logging.WARNING
    ERROR = logging.ERROR
    CRITICAL = logging.CRITICAL

class Logger:
    """日志系统类 - 封装示例"""
    
    def __init__(self, name, log_file=None, level=LogLevel.INFO):
        self._name = name
        self._log_file = log_file
        self._level = level
        self._logger = None
        self._formatter = None
        self._handlers = []
        
        self._setup_logger()
    
    def _setup_logger(self):
        """设置日志器"""
        self._logger = logging.getLogger(self._name)
        self._logger.setLevel(self._level.value)
        
        # 创建格式化器
        self._formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )
        
        # 添加控制台处理器
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(self._formatter)
        self._logger.addHandler(console_handler)
        self._handlers.append(console_handler)
        
        # 添加文件处理器(如果指定了日志文件)
        if self._log_file:
            file_handler = logging.FileHandler(self._log_file, encoding='utf-8')
            file_handler.setFormatter(self._formatter)
            self._logger.addHandler(file_handler)
            self._handlers.append(file_handler)
    
    def debug(self, message):
        """记录调试信息"""
        self._logger.debug(message)
    
    def info(self, message):
        """记录一般信息"""
        self._logger.info(message)
    
    def warning(self, message):
        """记录警告信息"""
        self._logger.warning(message)
    
    def error(self, message):
        """记录错误信息"""
        self._logger.error(message)
    
    def critical(self, message):
        """记录严重错误信息"""
        self._logger.critical(message)
    
    def log_with_context(self, level, message, context=None):
        """记录带上下文的日志"""
        if context:
            message = f"{message} | 上下文: {context}"
        
        if level == LogLevel.DEBUG:
            self.debug(message)
        elif level == LogLevel.INFO:
            self.info(message)
        elif level == LogLevel.WARNING:
            self.warning(message)
        elif level == LogLevel.ERROR:
            self.error(message)
        elif level == LogLevel.CRITICAL:
            self.critical(message)
    
    def get_log_file_path(self):
        """获取日志文件路径"""
        return self._log_file
    
    def get_logger_info(self):
        """获取日志器信息"""
        return {
            'name': self._name,
            'level': self._level.name,
            'log_file': self._log_file,
            'handlers_count': len(self._handlers)
        }

class ApplicationLogger:
    """应用程序日志管理器 - 抽象示例"""
    
    def __init__(self, app_name):
        self._app_name = app_name
        self._loggers = {}
        self._setup_loggers()
    
    def _setup_loggers(self):
        """设置各种日志器"""
        # 创建日志目录
        log_dir = f"logs/{self._app_name}"
        os.makedirs(log_dir, exist_ok=True)
        
        # 系统日志
        self._loggers['system'] = Logger(
            f"{self._app_name}.system",
            f"{log_dir}/system.log",
            LogLevel.INFO
        )
        
        # 错误日志
        self._loggers['error'] = Logger(
            f"{self._app_name}.error",
            f"{log_dir}/error.log",
            LogLevel.ERROR
        )
        
        # 访问日志
        self._loggers['access'] = Logger(
            f"{self._app_name}.access",
            f"{log_dir}/access.log",
            LogLevel.INFO
        )
    
    def log_system_event(self, message, level=LogLevel.INFO):
        """记录系统事件"""
        self._loggers['system'].log_with_context(level, message)
    
    def log_error(self, error_message, error_details=None):
        """记录错误"""
        context = f"错误详情: {error_details}" if error_details else None
        self._loggers['error'].error(f"错误: {error_message}")
        if context:
            self._loggers['error'].error(context)
    
    def log_access(self, user_id, action, resource):
        """记录访问日志"""
        message = f"用户 {user_id} 执行了 {action} 操作,资源: {resource}"
        self._loggers['access'].info(message)
    
    def get_logger(self, logger_type):
        """获取特定类型的日志器"""
        return self._loggers.get(logger_type)
    
    def get_all_loggers_info(self):
        """获取所有日志器信息"""
        return {
            logger_type: logger.get_logger_info()
            for logger_type, logger in self._loggers.items()
        }

# 使用日志系统
def demonstrate_logging():
    """演示日志系统使用"""
    # 创建应用程序日志管理器
    app_logger = ApplicationLogger("MyApp")
    
    # 记录系统事件
    app_logger.log_system_event("应用程序启动", LogLevel.INFO)
    app_logger.log_system_event("数据库连接成功", LogLevel.INFO)
    app_logger.log_system_event("内存使用率较高", LogLevel.WARNING)
    
    # 记录错误
    try:
        # 模拟错误
        result = 10 / 0
    except Exception as e:
        app_logger.log_error("除零错误", str(e))
    
    # 记录访问日志
    app_logger.log_access("user123", "登录", "系统")
    app_logger.log_access("user123", "查看", "用户资料")
    app_logger.log_access("user456", "删除", "文件")
    
    # 查看日志器信息
    print("日志器信息:")
    for logger_type, info in app_logger.get_all_loggers_info().items():
        print(f"  {logger_type}: {info}")

# 运行日志示例
print("=== 日志系统示例 ===")
demonstrate_logging()

封装和抽象的最佳实践

推荐做法:

  • 使用私有属性保护数据
  • 提供清晰的公共接口
  • 使用属性装饰器进行数据验证
  • 实现适当的抽象层次

避免的做法:

  • 暴露过多内部实现细节
  • 创建过于复杂的接口
  • 忽略数据验证
  • 违反封装原则

高级封装特性

描述符

class ValidatedAttribute:
    """验证属性描述符"""
    
    def __init__(self, min_value=None, max_value=None, allowed_values=None):
        self.min_value = min_value
        self.max_value = max_value
        self.allowed_values = allowed_values
        self.name = None
    
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)
    
    def __set__(self, instance, value):
        # 验证值
        if self.allowed_values and value not in self.allowed_values:
            raise ValueError(f"值必须是以下之一: {self.allowed_values}")
        
        if self.min_value is not None and value < self.min_value:
            raise ValueError(f"值不能小于 {self.min_value}")
        
        if self.max_value is not None and value > self.max_value:
            raise ValueError(f"值不能大于 {self.max_value}")
        
        instance.__dict__[self.name] = value

class Person:
    """使用描述符的Person类"""
    
    age = ValidatedAttribute(min_value=0, max_value=150)
    grade = ValidatedAttribute(allowed_values=['A', 'B', 'C', 'D', 'F'])
    
    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

# 使用描述符
person = Person("张三", 25, "A")
print(f"姓名: {person.name}, 年龄: {person.age}, 成绩: {person.grade}")

try:
    person.age = -5  # 会抛出异常
except ValueError as e:
    print(f"错误: {e}")

try:
    person.grade = "X"  # 会抛出异常
except ValueError as e:
    print(f"错误: {e}")

回顾

今天您学习了:

  • 封装的基本概念和实现
  • 属性装饰器的使用
  • 抽象类和接口的概念
  • 真实世界示例:数据库连接、日志系统

封装和抽象是面向对象编程的重要支柱,掌握这些概念将让您能够创建更加安全和易维护的代码!

相关推荐

microsoft国际版(微软国际版bing)

win10系统自带的edge浏览器不分国内版和国际版。搜索引擎Bing分为国内版和国际版,区别如下一、迎合不同人群的需求。国际版显得更加个性,他迎合了不同人群的需求,在收缩上更加方便化和智能化,后期可...

为什么格式化sd卡后不能用了

SD卡受损后不格式化直接修复的方法:1,当系统发现SD卡受损之后,会提示进行格式化。2,把SD卡插入读卡器,接到电脑USB后,电脑提示格式化,点取消。然后查看一下属性。直接使用属性中的工具-开始检查...

c盘删除的东西能恢复吗(c盘删掉的东西还在吗)

    1.重新安装。  如果是安装在c盘下的普通应用程序,重新下载安装即可;  2.备份恢复或重装系统。  如果是删除了系统文件,在系统备过份且知道删除...

电脑如何重装系统winxp(电脑如何重装xp系统教程)

重装XP系统的步骤如下:1.首先备份您的重要文件和数据。2.插入XP系统安装光盘,重新启动计算机。3.在启动时按下计算机制造商指定的键(通常是F2、F12、Esc等)进入BIOS设置或启动菜单。...

win8密钥永久激活工具(win8密钥永久激活工具在哪)

win8.1正式版升级win10,自动激活,win8.1的密钥不能激活win10,升级win10正式版以后,无论怎么安装win10,系统都是自动激活状态。终极PID检测产品密钥:GCRJD...

制作系统安装u盘教程(制作系统安装盘及系统安装的步骤)

答u盘做系统详细步骤如下:  1.首先我们要准备一个10G内存的空白u盘,然后在装机吧官网下载一个装机吧一键重装系统软件,安装完成后打开软件,制作模式选择”USB-HDD“分区格式为”NTFS“点击...

联想win7怎么进入bios设置(联想win7进入bios设置win10)
联想win7怎么进入bios设置(联想win7进入bios设置win10)

联想电脑win7进入bios设置的具体步骤如下: 1、首先我们打开电脑的同时,按下键盘上的“F2”。2、然后我们在弹出来的窗口中就可以进入到BIOS界面中。3、然后我们按下键盘上的“F10”,之后回车确定即可退出。联想电脑win7...

2025-11-09 14:03 off999

优盘里面的文件被误删了能否找回

如果您的文件在很久以前被误删并且没有进行任何操作,那么有可能通过一些专业的数据恢复工具来恢复被删除的文件。以下是一些可能的操作步骤:1.停止使用U盘:为了最大限度地提高恢复成功的几率,请停止使用U盘...

电脑系统程序下载(电脑应用程序下载)

1、首先下载并安装DriverDroid,运行后根据设置向导进行设置。2、然后注意安卓手机已获取ROOT,设置时需要连接电脑。3、将手机自动下载的bios文件移动到镜像根目录下(手机内存/Downlo...

万能网卡驱动离线安装包下载

电脑没网是吧,那你可以先用手机下载。之后放电脑上安装的万能网卡驱动下载地址http://drivers.160.com/universal/camera.html该驱动能够使大部分的网卡能够被系统...

正版office和盗版区别(office正版和盗版可以共存吗)

区别主要有三方面:1.office正版是付费的,而且价格相对而言较高,盗版呢价格相对低或者干脆免费。2.office正版因为是官方发行,文件肯定完整,功能齐全,稳定。盗版呢一般都是破译的或者是拷贝的,...

ヽ这个符号怎么打出来(这个符号怎么打出来是在中间的)

下载酷狗拼音,软键盘就有了。ˋ☆╲ヽ

120g固态硬盘够用吗(10几年的老电脑换个固态硬盘)

一般办公家用还是够用了,分两个区,系统盘分50G,剩余的分一个区做资料盘。特殊要求,资料文件比较多的话,128g是不够用,只能分一个区。这个主要取决于您电脑主要的用途,如果您的电脑只是用来日常办公和娱...

谷歌浏览器google(谷歌浏览器googleplay)

GoogleChrome,又称Google浏览器,是一个美国Google(谷歌)公司开发的网页浏览器。该浏览器是基于其他开源软件所撰写,包括WebKit,目标是提升稳定性、速度和安全性,并创造出简单且...

android13正式版下载(安卓版本13)

出现该问题的原因是,用户在设置里开启了新下载的APP,仅添加到APP资源库选项。大家只要进入“设置-主屏幕”,把新下载的APP,改为“添加到主屏幕”即可解决问题。修改完成后,你再进入AppStore下...

取消回复欢迎 发表评论: