面向对象进阶:成员、修饰符、嵌套与特殊方法
1. 成员
面向对象编程的核心在于组织代码,其成员可以分为以下几类:
- 变量
- 实例变量: 每个对象独立拥有,用于存储各自的数据。
- 类变量: 类共享,被所有实例访问,适用于存储公共数据。
- 方法
- 绑定方法: 默认包含
self参数,通过对象调用,self指向调用者对象。 - 类方法: 使用
@classmethod装饰,默认包含cls参数,cls指向类本身。可通过类或对象调用。 - 静态方法: 使用
@staticmethod装饰,无默认参数,独立于类和实例,可通过类或对象调用。
- 绑定方法: 默认包含
- 属性: 通过
@property装饰器将方法转换为可通过属性访问(如obj.attr)的成员,允许在访问时执行方法逻辑,并可结合@attr.setter和@attr.deleter实现读写删除操作。
1.1 变量:实例变量与类变量
实例变量属于特定的对象实例,而类变量则被所有实例共享。优先使用类变量存储所有实例共有的数据,以减少冗余。
class Person:
country = "中国" # 类变量
def __init__(self, name, age):
self.name = name # 实例变量
self.age = age # 实例变量
def show(self):
# 可以通过 self.country 访问类变量,也可通过 Person.country 访问
message = f"{self.country}-{self.name}-{self.age}"
print(message)
print(Person.country) # 输出: 中国
p1 = Person("武沛齐", 20)
print(p1.name) # 输出: 武沛齐
print(p1.age) # 输出: 20
print(p1.country) # 输出: 中国 (访问类变量)
p1.show() # 输出: 中国-武沛齐-20
# 修改实例变量
p1.name = "root"
p1.num = 19 # 新增实例变量
# 修改实例变量 country 会创建一个新的实例变量,不会影响类变量
p1.country = "china"
print(p1.country) # 输出: china
print(Person.country) # 输出: 中国 (类变量未变)
# 修改类变量会影响所有未覆盖实例变量的实例
Person.country = "美国"
print(Person.country) # 输出: 美国
p2 = Person("Alex", 22)
print(p2.country) # 输出: 美国
# 继承中的变量访问
class Base:
country = "中国"
class Person(Base):
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
# 访问类变量,可通过类名或实例访问
message = f"{Person.country}-{self.name}-{self.age}"
print(message)
# 读操作
print(Base.country) # 输出: 中国
print(Person.country) # 输出: 中国 (继承自Base)
obj = Person("武沛齐", 19)
print(obj.country) # 输出: 中国 (通过实例访问继承的类变量)
# 写操作
Base.country = "china" # 修改Base的类变量
Person.country = "泰国" # 修改Person的类变量(会覆盖Base的)
obj.country = "日本" # 在实例obj上创建新的实例变量country,优先级最高
print(Base.country) # 输出: china
print(Person.country) # 输出: 泰国
print(obj.country) # 输出: 日本
# 面试题示例:类变量与继承
class Parent:
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x) # 输出: 1 1 1
Child1.x = 2 # 在 Child1 类中新增或修改实例变量x,不影响Parent和Child2
print(Parent.x, Child1.x, Child2.x) # 输出: 1 2 1
Parent.x = 3 # 修改Parent的类变量
print(Parent.x, Child1.x, Child2.x) # 输出: 3 2 3
1.2 方法:绑定、类方法与静态方法
Python 中方法的调用非常灵活,但其本质区别在于默认参数和调用时 self 或 cls 的指向。
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def f1(self): # 绑定方法
print(f"绑定方法: {self.name}")
@classmethod
def f2(cls): # 类方法
print(f"类方法: {cls}")
@staticmethod
def f3(): # 静态方法
print("静态方法")
# 通过对象调用
obj = Foo("武沛齐", 20)
obj.f1() # 内部相当于 Foo.f1(obj)
obj.f2() # cls 指向 Foo
obj.f3() # 无默认参数
# 通过类调用
Foo.f2() # cls 指向 Foo
Foo.f3() # 无默认参数
# Foo.f1() # 错误,绑定方法需要实例
# 静态方法常用于工具类,不依赖于类或实例状态
class DownloadHelper:
@staticmethod
def download_file(url, save_path):
print(f"Downloading from {url} to {save_path}")
# 实际下载逻辑...
DownloadHelper.download_file("http://example.com/file.zip", "/tmp/file.zip")
# 类方法常用于工厂模式,例如创建特定配置的对象
class MyClass:
def __init__(self, value):
self.value = value
@classmethod
def from_string(cls, s):
# 假设字符串格式为 "value=XXX"
if s.startswith("value="):
value = int(s.split("=")[1])
return cls(value) # 使用cls创建实例
return None
instance = MyClass.from_string("value=123")
if instance:
print(instance.value) # 输出: 123
# @classmethod vs @staticmethod
# - @classmethod 接收类本身 (cls) 作为第一个参数,适合需要访问或修改类状态的操作,
# 并且在继承时能正确处理子类。
# - @staticmethod 不接收隐式的第一个参数 (self 或 cls),行为更像独立的函数,
# 适合不依赖于类或实例状态的工具函数。
1.3 属性
使用 @property 装饰器可以将方法伪装成属性,使得访问方式更简洁,例如 obj.attribute 而非 obj.get_attribute()。
class Rectangle:
def __init__(self, width, height):
self._width = width # 使用下划线约定为内部使用
self._height = height
@property
def width(self):
"""获取宽度"""
print("Getting width...")
return self._width
@width.setter
def width(self, value):
"""设置宽度"""
print("Setting width...")
if value <= 0:
raise ValueError("Width must be positive")
self._width = value
@property
def area(self):
"""计算面积(只读属性)"""
print("Calculating area...")
return self._width * self._height
rect = Rectangle(10, 5)
# 访问属性,无需括号
print(rect.width) # 输出: Getting width... \n 10
print(rect.area) # 输出: Calculating area... \n 50
# 修改属性 (通过 setter)
rect.width = 20 # 输出: Setting width...
print(rect.width) # 输出: Getting width... \n 20
print(rect.area) # 输出: Calculating area... \n 100
# rect.area = 100 # 错误:AttributeError: can't set attribute 'area' (因为没有 @area.setter)
# 另一种定义属性的方式 (较少使用)
class OldStyleProperty:
def get_x(self):
print("Getting x")
return self._x
def set_x(self, value):
print("Setting x")
self._x = value
def del_x(self):
print("Deleting x")
del self._x
x = property(get_x, set_x, del_x, "Docstring for x")
old_prop = OldStyleProperty()
old_prop.x = 10 # 调用 set_x
print(old_prop.x) # 调用 get_x, 输出: Getting x \n 10
del old_prop.x # 调用 del_x
# 属性与实例变量重名时的潜在问题
class Foo:
def __init__(self, name):
# self.name = name # 如果这里直接赋值,会与下面的 @property.setter 冲突
self._name = name # 推荐使用下划线区分
@property
def name(self):
print("Getting name...")
return self._name
@name.setter
def name(self, value):
print("Setting name...")
self._name = value
foo_obj = Foo("Alex")
print(foo_obj.name) # 输出: Getting name... \n Alex
foo_obj.name = "Bob" # 输出: Setting name...
print(foo_obj.name) # 输出: Getting name... \n Bob
2. 成员修饰符 (公有与私有)
Python 使用命名约定来表示成员的可访问性:
- 公有成员: 默认情况下,成员(变量、方法)都是公有的,可以在类内部或外部任何地方访问。
- 私有成员: 在成员名称前加上两个下划线 (
__) 会使其成为"私有"。这实际上是一种名称改编 (name mangling),使得在类外部直接访问变得困难,但并非完全禁止。
私有成员主要用于封装,防止外部意外修改,增强代码的健壮性。
class Example:
def __init__(self, public_var, private_var):
self.public_var = public_var # 公有变量
self.__private_var = private_var # 私有变量
def public_method(self):
print(f"Public method accessing: {self.public_var}")
self.__private_method() # 在内部可以访问私有方法
def __private_method(self):
print(f"Private method accessing: {self.__private_var}")
# 访问公有成员
obj = Example(10, 20)
print(obj.public_var) # 输出: 10
obj.public_method() # 输出: Public method accessing: 10 \n Private method accessing: 20
# 尝试访问私有成员 (通常会失败)
# print(obj.__private_var) # AttributeError: 'Example' object has no attribute '__private_var'
# obj.__private_method() # AttributeError: 'Example' object has no attribute '__private_method'
# 名称改编 (Name Mangling) - 了解即可,不推荐在外部直接使用
# Python 将 __private_var 改编为 _ClassName__private_var
print(obj._Example__private_var) # 输出: 20
obj._Example__private_method() # 输出: Private method accessing: 20
# 私有成员不能被子类继承 (严格来说,是名称改编后的成员子类无法直接访问)
class Base:
def __private_base_method(self):
print("Base private method")
class Derived(Base):
def access_base_private(self):
# self.__private_base_method() # 错误,这是 Derived 的私有方法,不是 Base 的
# print(self.__private_base_method) # 错误
pass # 无法直接调用 Base 的私有方法
# 注意:如果子类也定义了同名的私有方法,它们是不同的私有方法
class Base2:
def __private(self):
print("Base2 private")
class Derived2(Base2):
def __private(self): # 这是 Derived2 的另一个私有方法
print("Derived2 private")
# super().__private() # 无法直接调用 Base2 的私有方法
def call_both(self):
self.__private() # 调用 Derived2.__private
# super().__private() # 仍无法调用 Base2.__private
d2 = Derived2()
d2.call_both() # 输出: Derived2 private
# d2._Base2__private() # 还是可以访问,但不推荐
3. 对象嵌套 (组合与关联)
对象之间可以存在各种关系,其中常见的包括组合(一个对象包含其他对象作为其一部分)和关联(一个对象引用另一个对象)。这使得我们可以构建更复杂的系统。
class Employee:
def __init__(self, name, emp_id):
self.name = name
self.emp_id = emp_id
print(f"Employee {self.name} created.")
def get_details(self):
return f"ID: {self.emp_id}, Name: {self.name}"
class Department:
def __init__(self, name):
self.name = name
self.employees = [] # Department 组合了 Employee 对象列表
print(f"Department '{self.name}' created.")
def add_employee(self, emp):
if isinstance(emp, Employee):
self.employees.append(emp)
print(f"{emp.name} added to {self.name}.")
else:
print("Invalid employee object.")
def list_employees(self):
print(f"\nEmployees in {self.name}:")
if not self.employees:
print(" No employees yet.")
else:
for emp in self.employees:
print(f" - {emp.get_details()}")
# 创建对象
hr = Department("Human Resources")
it = Department("Information Technology")
emp1 = Employee("Alice", "E101")
emp2 = Employee("Bob", "E102")
emp3 = Employee("Charlie", "E103")
# 建立关系 (组合)
hr.add_employee(emp1)
hr.add_employee(emp2)
it.add_employee(emp3)
# 查看关系
hr.list_employees()
it.list_employees()
# 关联示例:一个学生属于一个班级
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
def get_info(self):
return f"Student: {self.name} (ID: {self.student_id})"
class Class:
def __init__(self, name, class_code):
self.name = name
self.class_code = class_code
self.students = [] # Class 引用了 Student 对象
def enroll_student(self, student):
if isinstance(student, Student):
self.students.append(student)
print(f"{student.name} enrolled in {self.name} ({self.class_code}).")
else:
print("Invalid student object.")
def show_enrollment(self):
print(f"\nEnrollment for {self.name} ({self.class_code}):")
for s in self.students:
print(f" - {s.get_info()}")
# 创建对象
math_class = Class("Mathematics", "MATH101")
physics_class = Class("Physics", "PHYS101")
student_a = Student("David", "S001")
student_b = Student("Eve", "S002")
student_c = Student("Frank", "S003")
# 建立关联
math_class.enroll_student(student_a)
math_class.enroll_student(student_b)
physics_class.enroll_student(student_c)
# 查看关联
math_class.show_enrollment()
physics_class.show_enrollment()
4. 特殊成员
Python 类中包含一系列以双下划线开头和结尾的特殊方法(也称为"魔术方法"或"dunder methods"),它们赋予对象特定的行为,如初始化、字符串表示、可调用性、容器行为等。
__init__(self, ...):对象初始化方法,在对象创建后被自动调用。__new__(cls, ...)`:对象构造方法,在__init__之前调用,负责创建并返回对象实例。__call__(self, ...)`:使对象本身可以像函数一样被调用。__str__(self)`:返回对象的字符串表示,用于str()和print()。__repr__(self)`:返回对象的"官方"字符串表示,通常用于调试。__dict__:一个字典,包含对象的所有实例属性。__getitem__(self, key)`:实现索引访问(如obj[key])。__setitem__(self, key, value)`:实现索引赋值(如obj[key] = value)。__delitem__(self, key)`:实现索引删除(如del obj[key])。__enter__(self)` 和__exit__(self, exc_type, exc_val, exc_tb):实现上下文管理协议,配合with语句使用,用于资源(如文件、数据库连接)的自动获取和释放。__add__(self, other)、__sub__(self, other)` 等:实现算术运算符重载。__iter__(self)` 和__next__(self):定义迭代器协议,使对象可迭代。
class MyObject:
def __init__(self, name):
self.name = name
print(f"__init__ called for {self.name}")
def __new__(cls, name):
print(f"__new__ called for {name}")
instance = super().__new__(cls) # 创建空对象
return instance
def __call__(self, greeting="Hello"):
print(f"{greeting}, {self.name}!")
def __str__(self):
return f"MyObject(name='{self.name}')"
def __repr__(self):
return f"MyObject('{self.name}')" # 类似 __init__ 的参数
def __add__(self, other):
if isinstance(other, MyObject):
return f"{self.name}+{other.name}"
return NotImplemented # 表示不支持该操作
def __getitem__(self, key):
print(f"Accessing item with key: {key}")
if key == 0: return "first"
if key == 1: return "second"
raise IndexError("Index out of range")
def __enter__(self):
print("Entering context...")
return self # 返回的对象可以被 'as' 接收
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting context...")
if exc_type:
print(f" An exception occurred: {exc_type.__name__}: {exc_val}")
# 返回 True 可以抑制异常,否则异常会继续传播
# __new__ 和 __init__
obj1 = MyObject("Instance1")
# 输出: __new__ called for Instance1
# __init__ called for Instance1
# __call__
obj1("Greetings") # 输出: Greetings, Instance1!
# __str__ 和 __repr__
print(str(obj1)) # 输出: MyObject(name='Instance1')
print(repr(obj1)) # 输出: MyObject('Instance1')
print(obj1) # 内部调用 __str__, 输出: MyObject(name='Instance1')
# __add__
obj2 = MyObject("Instance2")
result_add = obj1 + obj2
print(result_add) # 输出: Instance1+Instance2
# __getitem__
print(obj1[0]) # 输出: Accessing item with key: 0 \n first
print(obj1[1]) # 输出: Accessing item with key: 1 \n second
# print(obj1[2]) # 输出: Accessing item with key: 2 \n IndexError: Index out of range
# __enter__ 和 __exit__
with obj1 as ctx:
print(f"Inside context with: {ctx.name}")
# 输出: Entering context...
# Inside context with: Instance1
# Exiting context...
class MyException(Exception):
pass
try:
with obj1 as ctx:
print("Doing something that might raise an exception...")
raise MyException("Something went wrong")
except MyException as e:
print(f"Caught expected exception: {e}")
# 输出: Entering context...
# Doing something that might raise an exception...
# Exiting context...
# An exception occurred: MyException: Something went wrong
# Caught expected exception: Something went wrong
# __dict__
print(obj1.__dict__) # 输出: {'name': 'Instance1'}
# 迭代器协议 (__iter__ 和 __next__)
class MyIterator:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __iter__(self):
return self # 返回自身作为迭代器
def __next__(self):
if self.current >= self.limit:
raise StopIteration
value = self.current
self.current += 1
return value
my_iter = MyIterator(3)
print(next(my_iter)) # 输出: 0
print(next(my_iter)) # 输出: 1
print(next(my_iter)) # 输出: 2
# print(next(my_iter)) # 抛出 StopIteration
# for 循环自动处理迭代器
for i in MyIterator(4):
print(f"Loop item: {i}")
# 输出: Loop item: 0
# Loop item: 1
# Loop item: 2
# Loop item: 3