当前位置:首页 > 技术 > 正文内容

面向对象进阶:成员、修饰符、嵌套与特殊方法

访客 技术 2026年10月26日 9

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
    

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。