python系列之详解面向对象的属性

不为失败找理由,只为成功找方法。所有的不甘,因为还心存梦想,所以在你放弃之前,好好拼一把,只怕心老,不怕路长。

python系列前期章节

  1. python系列之注释与变量
  2. python系列之输入输出语句与数据类型
  3. python系列之运算符
  4. python系列之控制流程语句
  5. python系列之字符串
  6. python系列之列表
  7. python系列之元组
  8. python系列之字典
  9. python系列之集合
  10. python系列之函数基础
  11. python系列之函数进阶
  12. python系统之综合案例1:用python打造智能诗词生成助手
  13. python系列之综合案例2:用python开发《魔法学院入学考试》文字冒险游戏
  14. python系列之类与对象:面向对象编程(用造人计划秒懂面向对象)

1、前言:为什么属性很重要?

想象一下,你正在建造一个机器人。这个机器人有各种部件:手臂、腿、传感器等。在Python面向对象编程中,属性就像是这些部件 - 它们定义了对象的特征和状态。掌握属性,你就掌握了让对象"活起来"的关键!

本文将带你从零开始,全面了解Python中的属性,让你在面向对象编程的路上少走弯路,多些乐趣!

2、本章指南

  1. 类与实例属性:有什么区别?
  2. 属性访问控制:公有、保护和私有
  3. 属性装饰器:@property的魔力
  4. 动态属性:让对象更灵活
  5. 描述符:高级属性控制
  6. 属性最佳实践与常见陷阱

3、类属性与实例属性:有什么区别?

3.1 类属性 - 大家共享的"家族特征"

类属性是所有实例共享的属性,就像家族姓氏一样,所有家庭成员都共享。

class Robot:
    # 类属性
    family_name = "AI-Robot"
    robot_count = 0  # 跟踪创建了多少个机器人
    
    def __init__(self, name):
        self.name = name  # 实例属性
        Robot.robot_count += 1  # 每次创建实例时增加计数

# 创建两个机器人
r2d2 = Robot("R2-D2")
c3po = Robot("C-3PO")

print(r2d2.family_name)  # 输出: AI-Robot
print(c3po.family_name)  # 输出: AI-Robot
print(f"已创建机器人数量: {Robot.robot_count}")  # 输出: 已创建机器人数量: 2

3.2 实例属性 - 每个对象独有的"个人特征"

实例属性是每个对象特有的,就像每个人的名字不同。

class Robot:
    def __init__(self, name, serial_number):
        self.name = name  # 实例属性
        self.serial_number = serial_number  # 实例属性

r2d2 = Robot("R2-D2", "12345")
c3po = Robot("C-3PO", "67890")

print(r2d2.name)  # 输出: R2-D2
print(c3po.name)  # 输出: C-3PO

3.3 类属性 vs 实例属性:一个常见的坑

class Robot:
    parts = []  # 类属性 - 所有实例共享!
    
    def __init__(self, name):
        self.name = name
        # 注意:下面这样做会导致问题!
        self.parts.append("处理器")

r1 = Robot("Robo-1")
r2 = Robot("Robo-2")

print(r1.parts)  # 输出: ['处理器', '处理器'] 
# 啊哦!两个机器人的parts列表是同一个!

# 正确的方式:
class CorrectRobot:
    def __init__(self, name):
        self.name = name
        self.parts = []  # 实例属性 - 每个对象独立
        self.parts.append("处理器")

r1 = CorrectRobot("Robo-1")
r2 = CorrectRobot("Robo-2")

print(r1.parts)  # 输出: ['处理器']
print(r2.parts)  # 输出: ['处理器']
# 完美!每个机器人有自己的部件列表

4、属性访问控制:公有、保护和私有

Python中没有真正的私有属性,但它提供了一些约定来指示属性的访问级别。

4.1 公有属性 - 大家随便看

class Robot:
    def __init__(self, name):
        self.name = name  # 公有属性

robot = Robot("Optimus")
print(robot.name)  # 可以直接访问
robot.name = "Bumblebee"  # 可以直接修改

4.2 保护属性 - 礼貌地说"请勿随意触摸"

class Robot:
    def __init__(self, name, battery_level):
        self.name = name
        self._battery_level = battery_level  # 保护属性(单下划线)

robot = Robot("Wall-E", 80)
print(robot._battery_level)  # 技术上可以访问,但不应该这样做

4.3 私有属性 - 强一点的"禁止入内"

class Robot:
    def __init__(self, name, secret_code):
        self.name = name
        self.__secret_code = secret_code  # 私有属性(双下划线)

robot = Robot("R2-D2", "12345")
# print(robot.__secret_code)  # 错误!AttributeError
print(robot._Robot__secret_code)  # 输出: 12345 
# 但还是可以通过名称修饰访问,所以Python的"私有"更多是约定

5、属性装饰器:@property的魔力

@property装饰器让你可以把方法当作属性访问,这非常有用!

5.1 基本用法

class Robot:
    def __init__(self, name, battery_level):
        self.name = name
        self._battery_level = battery_level
    
    @property
    def battery_status(self):
        """将电池电量转换为描述性状态"""
        if self._battery_level > 70:
            return "电量充足"
        elif self._battery_level > 30:
            return "电量中等"
        else:
            return "电量不足,请充电"

robot = Robot("Terminator", 50)
print(robot.battery_status)  # 输出: 电量中等
# 注意:我们像访问属性一样使用了方法,没有括号!

5.2 设置器和删除器

class Robot:
    def __init__(self, name):
        self.name = name
        self._age = 0  # 私有属性
    
    @property
    def age(self):
        """机器人的年龄(只读)"""
        return self._age
    
    @age.setter
    def age(self, value):
        """设置年龄,但有验证"""
        if value < 0:
            raise ValueError("年龄不能为负数!")
        self._age = value
    
    @age.deleter
    def age(self):
        """删除年龄前的清理操作"""
        print("备份年龄数据...")
        self._age = 0

robot = Robot("Data")
print(robot.age)  # 输出: 0

robot.age = 5  # 使用setter
print(robot.age)  # 输出: 5

# robot.age = -1  # 抛出ValueError

del robot.age  # 使用deleter,输出: 备份年龄数据...
print(robot.age)  # 输出: 0

6、动态属性:让对象更灵活

有时我们不知道对象会有哪些属性,或者想动态添加属性。

6.1 动态添加属性

class Robot:
    pass

robot = Robot()
robot.name = "Marvin"  # 动态添加属性
robot.mood = "抑郁"  # 再添加一个

print(f"{robot.name} 感觉很 {robot.mood}")

6.2 dict:查看对象的所有属性

class Robot:
    def __init__(self, name):
        self.name = name
        self.age = 1

robot = Robot("Bender")
robot.mood = "开心"  # 动态添加

print(robot.__dict__)  # 输出: {'name': 'Bender', 'age': 1, 'mood': '开心'}

6.3 slots:优化内存使用

对于属性固定的类,可以使用__slots__来节省内存。

class EfficientRobot:
    __slots__ = ['name', 'age', 'serial_number']  # 只允许这些属性
    
    def __init__(self, name, age, serial_number):
        self.name = name
        self.age = age
        self.serial_number = serial_number

robot = EfficientRobot("Optimus", 5, "12345")
# robot.mood = "勇敢"  # 错误!AttributeError

7、描述符:高级属性控制

描述符是更高级的属性控制机制,允许更精细地控制属性的获取、设置和删除。

7.1 简单的描述符示例

class PositiveNumber:
    """描述符:确保值是正数"""
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, instance, owner):
        return instance.__dict__.get(self.name, 0)
    
    def __set__(self, instance, value):
        if value <= 0:
            raise ValueError("必须是正数!")
        instance.__dict__[self.name] = value

class Robot:
    age = PositiveNumber()  # 使用描述符
    battery_level = PositiveNumber()  # 另一个描述符
    
    def __init__(self, name, age, battery_level):
        self.name = name
        self.age = age
        self.battery_level = battery_level

robot = Robot("Number5", 1, 100)
print(robot.age)  # 输出: 1

# robot.age = -1  # 抛出ValueError

8、属性最佳实践与常见陷阱

最佳实践

  1. 使用属性而不是直接暴露内部数据

    # 不好
    class Robot:
        def __init__(self, battery):
            self.battery = battery
    
    # 好
    class Robot:
        def __init__(self, battery):
            self._battery = battery
        
        @property
        def battery(self):
            return self._battery
        
        @battery.setter
        def battery(self, value):
            if 0 <= value <= 100:
                self._battery = value
            else:
                raise ValueError("电量必须在0-100之间")
    
  2. 使用保护属性表示内部实现

    class Robot:
        def __init__(self):
            self._internal_data = []  # 保护属性,表示是内部实现
    
  3. 谨慎使用动态属性

    # 通常更好的是明确定义所有属性
    class Robot:
        def __init__(self, name, age):
            self.name = name
            self.age = age
            # 而不是后面动态添加属性
    

常见陷阱

  1. 可变类属性的陷阱

    class Robot:
        friends = []  # 所有实例共享同一个列表!
        
        def add_friend(self, friend):
            self.friends.append(friend)
    
    r1 = Robot()
    r2 = Robot()
    
    r1.add_friend("Human")
    print(r2.friends)  # 输出: ['Human'] - 啊哦!
    
  2. 过度使用@property

    # 不需要用@property包装每个简单属性
    class OverEngineeredRobot:
        def __init__(self, name):
            self._name = name
        
        @property
        def name(self):
            return self._name
        
        @name.setter
        def name(self, value):
            self._name = value
    
    # 简单点更好
    class SimpleRobot:
        def __init__(self, name):
            self.name = name
    

9、总结:属性使用速查表

场景推荐方法
简单的数据属性直接使用实例属性 self.attr = value
需要验证或计算的属性使用 @property 和 setter
内部实现细节使用保护属性 self._internal
真正不应该被外部访问的属性使用私有属性 self.__private(但记住Python中没有真正的私有,比如_类名__属性名)
所有实例共享的数据使用类属性
需要高度控制属性访问使用描述符
属性固定的类,需要节省内存使用 __slots__
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值