1. python自学笔记第六节---面向对象 + 三大特性 + 魔法方法 + 单例模式


一、类与对象

1.1 类的三要素

  • 类名:见名知意,大驼峰命名法(People
  • 属性:对象的特征描述
  • 方法:对象具有的功能(行为)

1.2 基本定义与使用

class People:
    height = 180                 # 类属性
    width = 140
    def run(self):               # self 代表类本身,调用实例方法时 Python 自动将对象引用传给第一个参数 self
        print(self)              # <__main__.People object at 0x...>
        print("我在跑步")

# 实例化对象 类名加小括号
peo = People()
# peo 和 self 内存地址相同,相当于 Java 的 this
print(peo)                       # <__main__.People object at 0x...>

# 调用类中的属性和方法
print(peo.height)                # 180
print(peo.width)                 # 140
print(People.height)             # 180 类名也能访问类属性
peo.run()                        # 我在跑步
  • self 等价于 Java 的 this,但 Python 必须显式写在第一个参数位。
  • Java 调用 obj.method() 隐式传 this,Python 是显式接收。

1.3 实例属性 vs 类属性

class Person:
    height = 180                 # 类属性(公共)
    width = 140
    def run(self):
        print("我在慢跑")
        print(f"使用实例属性:打印实例属性:{self.age}")

pe = Person()
pe.age = 24                      # 实例属性(私有)
pe.run()                         # 使用实例属性:打印实例属性:24

pe2 = Person()
# pe2.run()                      # 报错 因为 pe2 没有年龄属性
pe2.age = 30
pe2.run()                        # 使用实例属性:打印实例属性:30

区别:

  • 实例属性只能由对象名访问,不能用类名访问
  • 类属性 对象名和类名都可以访问
  • 问题:每创建一个对象都要手动赋值实例属性,效率低 -> 用构造方法解决

二、构造方法与析构函数

2.1 __init__() 构造方法

class Test:
    def __init__(self):
        print("这是__init__方法里的打印")
te = Test()                      # 会直接打印,无需调用

# 带参构造
class Peoson:
    def __init__(self, name, age, height):
        self.name = name         # 实例属性
        self.age = age
        self.height = height
    def play(self):
        print(f"我在和我朋友打游戏,她的基本信息是,名字:{self.name}-身高:{self.height}-年龄:{self.age}")

pe = Peoson("susu", 18, 165)
pe.play()                        # 我在和我朋友打游戏,她的基本信息是,名字:susu-身高:165-年龄:18
  • __init__() 在实例化对象时自动执行,对应 Java 的构造器 Person(...)
  • Java 一个类可重载多个构造器,Python 用默认参数 + *args 模拟。

2.2 __del__() 析构函数

# 删除对象的时候,解释器会默认调用 __del__() 方法
# class Person:
#     def __init__(self, name, age, height):
#         print("构造方法 首先执行init方法;", end=" ")
#     def __del__(self):
#         print("这是析构方法,删除对象 或代码结束后执行;", end=" ")
# p = Person("susu", 18, 165)
# print("这是倒数第二行代码;", end=" ")
# print("这是倒数第一行代码;", end=" ")
# # 输出:构造方法... 倒数第二... 倒数第一... 析构方法
#
# pe = Person("susu", 18, 165)
# del pe
# print("这是倒数第二行代码;", end=" ")
# print("这是倒数第一行代码;", end=" ")
# # 输出:构造方法... 析构方法... 倒数第二... 倒数第一... 析构方法
  • __del__ 对应 Java 的 finalize()(已废弃),但 Python 中也少用,资源释放优先用 with 上下文管理器。

三、封装:私有与隐藏

3.1 隐藏属性(双下划线 __

class Person:
    age = 18                     # 类属性
    __name = "susu"              # 隐藏属性
    def __run(self):             # 隐藏方法
        print(f"我叫{self.__name} 我{self.age}岁了 我喜欢跑步")

p = Person()
print(p.age)                     # 18
# print(p.name)                  # 报错
# 原理:隐藏属性实际被改名为 _类名__属性名
print(p._Person__name)           # susu
p._Person__run()                 # 我叫susu 我18岁了 我喜欢跑步

3.2 私有属性 vs 隐藏属性

命名 含义 外部访问 子类继承 from xxx import *
xxx 普通属性/方法
_xxx 私有(约定) ✅ 可访问 ❌ 无法导入
__xxx 隐藏(名称改写) ❌(需 _类名__xxx
class Man:
    _age = 28                    # 私有属性
    __name = "susu"              # 隐藏属性
    def _run(self):              # 私有方法
        return f"我叫{self.__name} 今年{self._age}岁 我喜欢跑步。"

m = Man()
print(m._age)                    # 28 外部能调用
print("私有方法:", m._run())     # 私有方法:我叫susu 今年28岁 我喜欢跑步。
  • Python 的"私有"是约定而非强制,单下划线 _xxx 仅提示"别在外部用"。
  • Java 的 private 是强制的,反射才能突破;Python 的 __xxx 是名称改写,绕过也不难。

四、继承

4.1 单继承与方法重写

class father:
    def money(self):
        print("我有100万")
    def house(self):
        print("我有一套房子")

# 子类继承父类 pass 占位符
class Person(father):
    pass

# 方法重写
class son(father):
    def house(self):
        print("我有两套房子")

# 子类还可以有子类继承,使用 super().方法 调用父类
class grandson(son):
    def money(self):
        print("我赚了200万")
        super().house()          # 我有两套房子

gd = grandson()
gd.money()                       # 我赚了200万 / 我有两套房子
sn = son()
sn.money()                       # 我有100万
  • super() 对应 Java 的 super,用于调用父类方法。
  • pass / None 是占位符,类不想写内容时用。

4.2 新式类与 object

# 经典类:不由任意内置类型派生
class A:
    pass

# 新式类:继承 object 或其子类 -- 推荐使用
class A(object):
    pass
  • object 是 Python 为所有对象提供的基类(顶级父类)。
  • Python 3 中所有类默认继承 object,无需显式写。
  • dir() 查看类的属性和方法:
print(dir(object))
# ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', ...]
print(dir(Dog))                  # 比父类多出自己的方法

4.3 多继承(Python 独有)

class Father:
    def shouyi(self):
        print("我会捏面人的手艺")
class Mother:
    def shouyi(self):
        print("我会做衣服")

# 父类顺序决定相同方法调用哪个
class Son(Father, Mother): pass
sn = Son()
sn.shouyi()                      # 我会捏面人的手艺(Father 在前)

class Son(Mother, Father): pass
sn1 = Son()
sn1.shouyi()                     # 我会做衣服(Mother 在前)

__mro__ 查看方法搜索顺序:

print(Son.__mro__)
# (<class '__main__.Son'>, <class '__main__.Mother'>, <class '__main__.Father'>, <class 'object'>)
  • 多继承是 Python 与 Java 的重大差异:Java 不支持类多继承(只能 implements 多接口),就是为了避免"父类有相同方法该调谁"的菱形问题。
  • Python 用 C3 线性化算法__mro__)解决,按继承列表顺序查找。

弊端:

  • 容易引发冲突(继承的多对象有相同方法)
  • 提高代码设计的复杂度

五、多态

class Father:
    def shouyi(self):
        print("我会捏面人的手艺")
class Mother:
    def shouyi(self):
        print("我会做衣服")

# 继承和重写父类方法
class Son(Father, Mother):
    def shouyi(self):
        print("我会做应用开发")
class Son1(Father, Mother):
    def shouyi(self):
        print("我会做平面设计")

# 定义统一接口(注:不是 Java 里的 interface)
def shouyi(obj):
    obj.shouyi()

# 通过传递不同对象,执行不同方法
shouyi(Son())                    # 我会做应用开发
  • Python 多态是"鸭子类型":不看类型看行为,有 shouyi 方法就能传。
  • Java 多态靠继承 + 重写 + 父类引用指向子类对象,Python 不需要继承关系也能多态。

六、静态方法与类方法

6.1 静态方法 @staticmethod

class Father:
    @staticmethod
    def shouyi():                # 没有 self / cls
        print("我会捏面人的手艺.")

# 类名访问
Father.shouyi()                  # 我会捏面人的手艺.
# 对象名访问
fa = Father()
fa.shouyi()                      # 我会捏面人的手艺.

# 带参
class Father:
    @staticmethod
    def shouyi(name):
        print(f"{name}我会捏面人的手艺.")
Father.shouyi("www")             # www我会捏面人的手艺.
  • 静态方法和类无关,可被转化为函数使用。
  • 对应 Java 的 static 方法,可用类名直接调用。
  • 优势:取消不必要的参数传递,减少内存占用和性能消耗。

6.2 类方法 @classmethod

class Father:
    name = "www"                 # 类属性
    @classmethod
    def shouyi(cls):             # cls 代表类对象本身
        print("这个在类方法里面")
        print(cls)               # <class '__main__.Father'>
        print(cls.name)          # www 相当于 Father.name

print(Father)                    # <class '__main__.Father'> 和 cls 相同
Father.shouyi()
  • 类方法第一个参数必须是类对象,一般以 cls 命名。
  • 类本质上也是一个对象,cls 就是这个类对象本身。
  • 一般配合类属性使用。

6.3 三种方法总结

方法类型 装饰器 第一参数 访问实例属性 访问类属性
实例方法 self self.xxx 类名.xxx
静态方法 @staticmethod 可访问但无意义
类方法 @classmethod cls cls.xxx
class Person(object):
    name = "wxy"                 # 类属性
    def __init__(self):
        self.age = 18            # 实例属性
    def run(self):               # 实例方法
        print(self.age, end=" ")
        print(Person.name)
    @staticmethod
    def eat():
        pass
    @classmethod
    def song(cls):
        print(cls.name, end=" ")

pe = Person()
pe.run()                         # 18 wxy
Person().run()                   # 18 wxy

总结:

  • 类属性是公共的,所有方法都能访问
  • 静态方法不需要访问类属性,因为它和类、对象没有关系
  • 实例属性是私有的,只有实例方法内部能使用

七、__new__ 与单例模式

7.1 __new__ vs __init__

方法 作用 返回 级别
__new__ 创建对象 对象引用 类级别
__init__ 初始化对象 实例级别
class Test01(object):
    def __init__(self):
        print("这是init初始化方法")
    def __new__(self, *args, **kwargs):
        print("这是new方法", end=" ")
te = Test01()                    # 这是new方法
print(te)                        # None,因为 __new__ 没返回实例,阻断了 __init__

执行流程:

1. obj = Person()
        ↓
2. Python 内部调用 Person.__new__(cls)  ← 造对象
        ↓
3. 判断返回值:
   ├─ 返回了 Person 的实例?-> 继续执行 __init__(self)
   └─ 返回 None 或其他东西?-> __init__ 不会执行
class Test02(object):
    def __init__(self):
        print("这是test02中的init方法")
    def __new__(self, *args, **kwargs):
        return super().__new__(self)  # 返回实例,继续执行 __init__

te2 = Test02()                   # 先 new 后 init
te21 = Test02()
print(te2)                       # 内存地址不同
print(te21)
  • Java 没有这种"造对象"和"初始化"分离的机制,Python 通过 __new__ 控制对象创建过程,是实现单例、元类的基础。

7.2 单例模式(通过 __new__ 实现)

class Test03(object):
    def __init__(self):
        print("这是Test03类中的init方法")
    obj = None
    def __new__(self, *args, **kwargs):
        print("这是Test03类中的new方法")
        if self.obj is None:
            self.obj = super().__new__(self)
        return self.obj

te3 = Test03()
te31 = Test03()
te32 = Test03()
print(te3)                       # 内存地址相同
print(te31)                      # 内存地址相同
print(te32)                      # 内存地址相同

单例模式实现方式:

  1. 通过 @classmethod
  2. 通过装饰器实现
  3. 通过重写 __new__() 实现(重点)
  4. 通过导入模块实现(模块自动编译为 .pyc,多次导入引用同一对象)

应用场景: 回收站对象、音乐播放器、游戏场景管理器、数据库连接池。

弊端: 多线程访问容易引发线程安全问题。

  • 对应 Java 的饿汉式 / 懒汉式 / 静态内部类 / 枚举单例,Python 用 __new__ 改写更直接。

八、魔法方法与魔法属性

使用 __ 开头/结尾的方法都是魔法方法,有特殊含义或功能,自己不要轻易定义。

方法/属性 作用
__new__ 在内存中为对象分配空间并返回对象引用
__init__ 初始化对象或给属性赋值(构造函数)
__del__ 删除对象(析构函数)
__doc__ 类或方法的描述信息
__module__ 当前操作对象所在模块
__class__ 当前操作对象所在的类
__str__ 对象的描述信息(print(obj) 时调用)
__call__ 使实例成为可调用对象 ⭐
__dict__ 返回对象的属性(字典形式)

8.1 __doc__ 描述信息

class Test04(object):
    """规范 类的描述信息只能使用多行注释"""  # 要用多行注释才能被 __doc__ 打印
    def run(self):
        """跑步方法"""
        print(f"{self.name}在跑步")

print(Test04.__doc__)            # 规范 类的描述信息只能使用多行注释
print(Test04.run.__doc__)        # 跑步方法

8.2 __module____class__

print(Test04.__module__)         # __main__ 本模块
from py02 import testImportUse as tu
a = tu.A()
print(a.__module__)              # py02.testImportUse
print(a.__class__)               # <class 'py02.testImportUse.A'>

8.3 __str__ 对象描述

# 如果定义了 __str__,print(obj) 时默认输出其返回值
# 注意:__str__() 必须返回一个字符串
class Test05(object):
    def __str__(self):
        return "这是Test05里的str方法"
te5 = Test05()
print(te5)                       # 这是Test05里的str方法
  • 对应 Java 的 toString(),打印对象时自动调用。

8.4 __call__ 可调用对象 ⭐

# 可调用对象:函数、内置函数、类都是可调用对象
# callable() 判断一个对象是否是可调用对象
class Test06(object):
    def te6Ff(self):
        pass
te6 = Test06()
print(callable(te6))             # False
print(callable(te6.te6Ff))       # True

# 让对象本身可调用
class Test07(object):
    def __call__(self):
        return "这是Test07类里的call方法"
te7 = Test07()
print(callable(te7))             # True
print(te7())                     # 这是Test07类里的call方法
  • __call__ 让实例像函数一样被调用,Java 没有等价机制。
  • 适用场景:函数对象、策略模式、nn.Module(PyTorch 前向传播就是 __call__ 触发的)。

8.5 __dict__ 属性字典

# 只包含实例自己的属性,以字典返回
class Person:
    species = "人类"             # 类属性
    def __init__(self, name, age):
        self.name = name         # 实例属性
        self.age = age

pe = Person("wxy", 18)
print(pe.__dict__)               # {'name': 'wxy', 'age': 18}

# 通过 __dict__ 动态增删属性
pe.__dict__["city"] = "北京"     # 等价于 pe.city = "北京"
print(pe.city)                   # 北京
del pe.__dict__["age"]           # 等价于 del pe.age
print(pe.__dict__)               # {'name': 'wxy', 'city': '北京'}
  • Python 对象本质是"属性字典",可以动态增删属性,Java 对象结构是编译期固定的(反射除外)。

📌 与 Java 的对比

概念 Java Python
self / this this(隐式) self(显式第一参数)
构造方法 与类同名 __init__
私有属性 private(强制) _xxx 约定 / __xxx 名称改写
继承 单继承 + 多实现接口 多继承(C3 线性化)
多态 继承 + 重写 + 父类引用 鸭子类型(不需继承关系)
静态方法 static @staticmethod
类方法 static(无 cls 概念) @classmethod(带 cls)
单例 饿汉/懒汉/静态内部类/枚举 重写 __new__ / 模块导入
toString toString() __str__
对象可调用 不支持 __call__
动态增删属性 反射 __dict__ 直接操作

📌 Java 转 python 三个思维转换

1. 多继承不等于"危险"

Java 禁止多继承是为了避免菱形问题,但 Python 用 C3 线性化(__mro__)解决了顺序问题。多继承在 Mixin 模式下非常强大(如 class MyView(LoginRequiredMixin, View)),不要因为 Java 的限制就不敢用。

2. "私有"是约定不是强制

Python 哲学:"We are all consenting adults here"(大家都是成年人)。_xxx 是提示,__xxx 是名称改写,都能绕过。不要期望 Python 的封装像 Java 的 private 那样安全,而应靠约定 + 测试保障设计意图。

3. 魔法方法是 Python OOP 的灵魂

Java 的 toString / equals / hashCode 是 Object 方法的重写;Python 的魔法方法更多更细:__str__ / __repr__ / __call__ / __getitem__ / __iter__ / __enter__……掌握魔法方法,才能写出"像内置类型一样自然"的类。


posted @ 2026-07-25 22:33  爱笑的橘子  阅读(0)  评论(0)    收藏  举报