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) # 内存地址相同
单例模式实现方式:
- 通过
@classmethod - 通过装饰器实现
- 通过重写
__new__()实现(重点) - 通过导入模块实现(模块自动编译为
.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__……掌握魔法方法,才能写出"像内置类型一样自然"的类。

浙公网安备 33010602011771号