引言:为什么要学这些 “冷门” 特性?
很多 Python 开发者在入门后,只会用print()、for循环、类的基本定义,但对类型判断、对象属性遍历、属性封装等核心底层特性一知半解。而这些特性恰恰是 Python “灵活与强大” 的根源:
type/isinstance是类型系统与多态的基础;dir()/__dict__是 ** 对象自省(Introspection)** 的核心工具,让你能 “看透” 对象的内部结构;property装饰器是面向对象封装的 “优雅解决方案”,彻底告别get_xxx()/set_xxx()的冗余写法。
本文将通过代码实战 + 原理分析,系统梳理这三类特性的用法、区别与适用场景,让你从 “会用 Python” 升级到 “懂 Python”。
一、type 与 isinstance:不是 “重复发明” 的类型判断工具
1.1 核心区别:继承关系的 “有无”
type()和isinstance()都用于判断对象类型,但本质逻辑完全不同:
type():返回对象的直接类型,不考虑继承关系;isinstance():判断对象是否为某个类型或其子类的实例,考虑继承关系。
1.2 基本用法与对比
示例 1:直接类型判断
# 基础类型测试
a = 10
b = "abc"
c = [1,2,3]
print(type(a)) # <class 'int'>
print(type(b)) # <class 'str'>
print(type(c)) # <class 'list'>
# 严格判断类型
print(type(a) is int) # True
print(type(b) is str) # True
print(type(c) is list) # True
示例 2:继承关系测试
class Animal: # 父类
def __init__(self, name):
self.name = name
class Dog(Animal): # 子类,继承自Animal
def bark(self):
print("Woof!")
# 创建Dog实例
dog = Dog("旺财")
# type()不考虑继承,只看直接类型
print(type(dog) is Dog) # True
print(type(dog) is Animal) # False(重点:即使Dog继承自Animal,type仍返回Dog)
# isinstance()考虑继承
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True(重点:Dog是Animal的子类,所以isinstance返回True)
print(isinstance(dog, object)) # True(所有Python类都继承自object)
1.3 type 的 “隐藏技能”:动态创建类(元编程入门)
type()不仅能判断类型,还能直接创建类(它是所有 Python 类的 “元类”)。语法:
type(类名, (父类1, 父类2), {类属性与方法字典})
示例 3:用 type 动态创建类
# 动态创建一个名为Person的类,继承自object,包含属性species和方法say_hello
Person = type(
"Person", # 类名
(object,), # 父类元组(单继承时注意加逗号)
{
"species": "human", # 类属性
"say_hello": lambda self: print(f"Hello from {self.name}") # 类方法
}
)
# 创建Person实例并测试
p = Person()
p.name = "张三" # 给实例添加name属性
print(p.species) # human
p.say_hello() # Hello from 张三
print(type(Person)) # <class 'type'>(所有类都是type的实例)
1.4 适用场景总结
| 工具 | 适用场景 | 注意事项 |
|---|---|---|
type() | 1. 严格判断对象的直接类型;2. 动态创建类(元编程) | 不考虑继承关系,不适合多态场景 |
isinstance() | 1. 判断对象是否属于某个类型或其子类;2. 支持多态;3. 第二个参数可传元组(如isinstance(obj, (int, str))) | 不能判断对象的直接类型,只能判断继承关系 |
二、dir () 与__dict__:“看透” 对象内部结构的两把钥匙
2.1 核心区别:“所有属性” 与 “自身属性”
dir():内置函数,返回对象所有可访问的属性 / 方法列表(包括继承自父类的,自动排序);__dict__:对象的内置属性,返回对象自身定义的属性 / 方法字典(不包括继承的,键为属性名,值为属性值)。
2.2 基本用法与对比
示例 4:类与实例的属性查看
class Person:
# 类属性(所有实例共享)
species = "human"
def __init__(self, name, age):
# 实例属性(每个实例独立)
self.name = name
self.age = age
# 类方法(所有实例共享)
def say_hello(self):
print(f"Hello, I'm {self.name}")
# 创建实例
p = Person("李四", 20)
# -------------------------- dir()的结果 --------------------------
print("dir(p)的结果(部分):")
for item in dir(p):
if not item.startswith("__"): # 过滤掉特殊方法(双下划线开头)
print(f" - {item}")
# 输出:age、name、say_hello、species(包含继承的类属性和方法)
# -------------------------- __dict__的结果 --------------------------
print("\np.__dict__的结果:")
print(p.__dict__) # {'name': '李四', 'age': 20}(仅实例自身定义的属性)
print("\nPerson.__dict__的结果(部分):")
for k, v in Person.__dict__.items():
if not k.startswith("__"):
print(f" - {k}: {v}")
# 输出:species、__init__、say_hello(类自身定义的属性和方法)
2.3 特殊情况:__slots__对__dict__的影响
当类定义了__slots__属性时,实例将不再生成__dict__,只能拥有__slots__中指定的属性(用于内存优化,适合实例数量极多的场景)。
示例 5:__slots__的影响
class Student:
__slots__ = ("name", "age") # 限制实例只能有name和age属性
def __init__(self, name, age):
self.name = name
self.age = age
s = Student("王五", 18)
print(s.name) # 18
print(hasattr(s, "__dict__")) # False(实例没有__dict__属性)
print(dir(s)) # 仍能看到name、age,以及继承的特殊方法
2.4 适用场景总结
| 工具 | 适用场景 | 注意事项 |
|---|---|---|
dir() | 1. 快速查看对象的所有可访问属性 / 方法;2. 调试时了解对象的能力 | 包含继承的属性,可能有冗余 |
__dict__ | 1. 查看对象自身定义的属性 / 方法;2. 动态修改对象的属性(如obj.__dict__['new_attr'] = 10) | 不包含继承的属性,当类定义__slots__时实例没有__dict__ |
三、property 装饰器:让属性 “带脑子” 的封装方案
3.1 为什么需要 property?
面向对象的 “封装” 原则要求:类的属性不应直接暴露给外部,应通过 getter/setter 方法访问。但传统的get_xxx()/set_xxx()写法太冗余:
# 传统封装写法(冗余)
class Person:
def __init__(self, name, age):
self._age = age # _开头表示“私有”(约定俗成,非强制)
def get_age(self):
return self._age
def set_age(self, value):
if 0 < value < 120:
self._age = value
else:
raise ValueError("Age must be between 1 and 119")
p = Person("赵六", 25)
print(p.get_age()) # 25(调用方法,麻烦)
p.set_age(30) # 设置年龄,麻烦
property装饰器的出现就是为了解决这个问题:把方法 “伪装” 成属性,既保留了封装的安全性,又有属性的简洁性。
3.2 property 的三种用法:getter/setter/deleter
property装饰器提供了三个子装饰器,分别对应属性的读取、修改、删除:
示例 6:完整的 property 用法
class Student:
def __init__(self, name, age):
self._name = name # 私有属性(约定俗成用_开头)
self._age = age
# -------------------------- 1. getter:读取属性 --------------------------
@property
def age(self):
print("【getter】正在读取age属性")
return self._age
# -------------------------- 2. setter:修改属性 --------------------------
@age.setter # 注意:装饰器名必须和getter的方法名一致
def age(self, value):
print("【setter】正在修改age属性")
# 数据验证:年龄必须是1-119的整数
if not isinstance(value, int):
raise TypeError("Age must be an integer!")
if 0 < value < 120:
self._age = value
else:
raise ValueError("Age must be between 1 and 119!")
# -------------------------- 3. deleter:删除属性 --------------------------
@age.deleter # 装饰器名同样要和getter一致
def age(self):
print("【deleter】正在删除age属性")
del self._age
# 测试
s = Student("孙七", 18)
print(s.age) # 调用getter:输出【getter】... 18
s.age = 20 # 调用setter:输出【setter】...
# s.age = 150 # 抛出ValueError:年龄超出范围
# s.age = "20" # 抛出TypeError:不是整数
del s.age # 调用deleter:输出【deleter】...
# print(s.age) # 抛出AttributeError:age属性已被删除
3.3 property 的 “原始写法”:作为类属性使用
除了装饰器写法,property还可以作为类属性直接使用,语法为:
属性名 = property(fget=getter函数, fset=setter函数, fdel=deleter函数, doc=文档字符串)
示例 7:property 的类属性写法
class Person:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value.title() # 自动首字母大写
# 用property函数创建类属性name
name = property(
fget=get_name,
fset=set_name,
doc="Person的姓名属性,自动首字母大写"
)
p = Person("wang wu")
print(p.name) # Wang Wu(自动首字母大写)
p.name = "li si"
print(p.name) # Li Si
3.4 避坑:property 名与实例变量名不能重名
如果property的方法名与实例变量名完全一致,会导致无限递归:
# 错误示例:无限递归
class Person:
def __init__(self, age):
self.age = age # 这里的self.age会调用setter,而不是创建实例变量
@property
def age(self):
return self.age # 调用self.age时会再次调用getter,导致无限递归
@age.setter
def age(self, value):
self.age = value # 同样会无限递归
解决方案:实例变量名用下划线开头(如self._age),property的方法名用正常名称(如self.age),避免冲突。
3.5 适用场景总结
- 数据验证:对属性的赋值进行类型、范围等校验(如年龄必须是 1-120 的整数);
- 数据转换:自动对属性值进行转换(如姓名自动首字母大写、日期自动格式化);
- 懒加载:延迟计算属性值(如只有当访问
user.orders时,才从数据库查询用户的订单); - 兼容性:将旧的 getter/setter 方法平滑过渡为属性,不影响原有代码的调用。
四、特性关联与综合实战
4.1 综合案例:带类型校验和属性封装的学生管理系统
我们将结合isinstance、property、dir()等特性,写一个安全、优雅的学生管理系统核心模块:
class Student:
def __init__(self, name, age, score):
self._name = name
self._age = age
self._score = score
# 姓名:只读属性(只能在初始化时设置)
@property
def name(self):
return self._name
# 年龄:带范围校验
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if isinstance(value, int) and 6 <= value <= 22:
self._age = value
else:
raise ValueError("Age must be an integer between 6 and 22!")
# 分数:带范围校验
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if isinstance(value, (int, float)) and 0 <= value <= 100:
self._score = value
else:
raise ValueError("Score must be a number between 0 and 100!")
# 打印学生信息
def __str__(self):
return f"Student(name={self.name}, age={self.age}, score={self.score})"
# 测试
students = []
# 添加学生
students.append(Student("张三", 18, 95))
students.append(Student("李四", 19, 88))
# 修改学生年龄和分数
students[0].age = 20 # 成功
students[1].score = 92 # 成功
# students[0].age = 30 # 抛出ValueError
# 查看学生对象的属性
print("\n学生对象的可访问属性(dir()):")
for item in dir(students[0]):
if not item.startswith("__"):
print(f" - {item}") # 输出:age、name、score、__str__等
print("\n学生对象的自身属性(__dict__):")
print(students[0].__dict__) # {'_name': '张三', '_age': 20, '_score': 95}
# 遍历打印所有学生
print("\n所有学生信息:")
for student in students:
print(f" - {student}")
4.2 输出结果
学生对象的可访问属性(dir()):
- age
- name
- score
学生对象的自身属性(__dict__):
{'_name': '张三', '_age': 20, '_score': 95}
所有学生信息:
- Student(name=张三, age=20, score=95)
- Student(name=李四, age=19, score=92)
总结:从 “会用” 到 “懂用” 的关键
- type:严格判断直接类型,或动态创建类(元编程);
- isinstance:考虑继承关系的类型判断,支持多态;
- dir():快速查看对象的所有可访问属性,适合调试;
- dict:查看对象自身定义的属性,支持动态修改;
- property:优雅的属性封装方案,实现数据验证、转换、懒加载等高级功能。
这些特性是 Python “面向对象” 和 “元编程” 的基础,掌握它们能让你写出更安全、更优雅、更可维护的 Python 代码。


被折叠的 条评论
为什么被折叠?



