一、先看问题:同一个东西,创建了好几份

想象一个场景:你有一个数据库连接,创建一次就够了。但你的三个服务类各自内部都自己创建了一份:

class Database:
    def __init__(self, host):
        print(f"连接数据库: {host}")  # 每次创建都要连接,很慢

class UserService:
    def __init__(self, host):
        self.db = Database(host)   # 创建第1份

class OrderService:
    def __init__(self, host):
        self.db = Database(host)   # 创建第2份

class PayService:
    def __init__(self, host):
        self.db = Database(host)   # 创建第3份
        
user_svc = UserService("localhost")   # 打印:连接数据库: localhost
order_svc = OrderService("localhost") # 打印:连接数据库: localhost
pay_svc = PayService("localhost")     # 打印:连接数据库: localhost

运行一下:

user_svc = UserService("localhost")   # 打印:连接数据库: localhost
order_svc = OrderService("localhost") # 打印:连接数据库: localhost
pay_svc = PayService("localhost")     # 打印:连接数据库: localhost
# 连了3次!其实连1次就够了

而且它们是三个不同的对象,互不相干:

user_svc.db is order_svc.db   # False,不是同一个

这就好比:三个人去同一个办公室,每个人都自己配了一把钥匙,其实共享一把就够了。

二、解决思路:不要自己创建,让别人传给你

2.1 最简单的做法:强制传参

class UserService:
    def __init__(self, db):       # 不自己创建了,要求外部传入
        self.db = db

使用:

db = Database("localhost")              # 只创建1份
user_svc = UserService(db)              # 传进去
order_svc = OrderService(db)            # 传进去
user_svc.db is order_svc.db   # True,同一个!

问题:单独用 UserService 的时候,必须手动先创建 Database,很麻烦:

svc = UserService(???)   # 不传 db 就报错,必须先创建 Database

2.2 更好的做法:你传了就用你的,没传我自己创建

class UserService:
    def __init__(self, host, db=None):
        #                     ^^^^^^ 不传的话默认是 None,不会报错
        self.db = db or Database(host)
        #         ^^  ^^  ^^^^^^^^^^^^^
        #        有传入就用传入的,没传入就自己创建

这个 or 是什么意思?

self.db = db or Database(host)

就像口语说的:“db 你有吗?有就用你的,没有我就自己搞一个。”

Python 的 or 规则很简单:从左到右看,遇到"有东西"就返回:

你传了什么 结果
一个 Database 对象 用你传的
None(没传) 自己创建一个

现在两种用法都行:

# 用法1:传进去,共享同一个
db = Database("localhost")
svc1 = UserService("localhost", db=db)   # 用你传的

# 用法2:不传,自己创建
svc2 = UserService("localhost")           # 没传,自己创建一个
这就好比:你带钥匙了就用你的,没带我再配一把。

三、谁来创建那把"共享的钥匙"?

上面的做法还需要手动创建 Database 再传进去。谁来负责创建?谁来保证只创建一次?

需要一个"管家"来统一管理:

class App:
    def __init__(self, host):
        self._db = None          # 先不创建,标记为"空"
        self._user_svc = None
        self._order_svc = None

    @property
    def db(self):
        if self._db is None:                    # 第一次来?空的 → 创建
            self._db = Database(host)
        return self._db                          # 以后再来?已有 → 直接给

    @property
    def user_svc(self):
        if self._user_svc is None:
            self._user_svc = UserService(host, db=self.db)
            #                                       ^^^^^^^^ 传入同一个 db
        return self._user_svc

    @property
    def order_svc(self):
        if self._order_svc is None:
            self._order_svc = OrderService(host, db=self.db)
            #                                        ^^^^^^^^ 传入同一个 db
        return self._order_svc

使用:

app = App("localhost")

app.user_svc    # 第一次访问,触发创建 Database,传给 UserService
app.order_svc   # 再访问,Database 已经有了,传同一个给 OrderService

app.user_svc.db is app.order_svc.db   # True!共享同一个

打印结果只有一行:

连接数据库: localhost   # 只连接了一次!

四、懒加载:用到才创建,不用就不创建

上面的管家还有个好处:你不访问,它就不创建。

app = App("localhost")   # 什么都没创建,瞬间完成
app.user_svc             # 只创建 Database 和 UserService,不碰 OrderService

# app.order_svc  # 没访问过,OrderService 不会创建 

这就好比:钥匙放在管家那,谁要用谁去拿,不用就不拿。

关键就是这段代码:

@property
def db(self):
    if self._db is None:              # 空的?→ 创建
        self._db = Database(host)
    return self._db                    # 已有?→ 直接返回

第一次访问 app.db:self._db 是 None → 创建 → 存起来 第二次访问 app.db:self._db 不是 None → 跳过创建 → 直接返回

防止重复创建的是 if 判断,不是 @property。

如果去掉 if:

@property
def db(self):
    self._db = Database(host)   # 每次都创建新的
    return self._db

app.db  # 连接数据库: localhost
app.db  # 连接数据库: localhost  ← 又连了一次!
app.db  # 连接数据库: localhost  ← 又又连了一次!

五、@property 是干嘛的?

@property 就是把方法变成属性,调用时不用加 ():

# 有 @property
app.db        # 像访问属性一样,不用加括号

# 没有 @property
app.db()      # 像调用方法一样,必须加括号

为什么用 @property 更好?

  1. 读起来更自然
app.db      # "拿一下 db"——像拿一个东西
app.db()    # "执行一下 db"——像做一个动作

获取实例是"拿一个东西",不是"做一个动作",不加括号更自然。

  1. 防止误改
app.db = xxx  # 报错!没有 setter,不能随便覆盖
  1. 以后改内部逻辑,外面不用改代码
#以前:普通属性

class App:
    def __init__(self):
        self.db = Database(host)

#改成懒加载

class App:
    @property
    def db(self):
        if self._db is None:
            self._db = Database(host)
        return self._db

# 外面用的代码不用改
app.db  # 两种写法都一样

六、完整流程:一图看懂

app = App("localhost")
  │
  │  _db=None, _user_svc=None, _order_svc=None
  │  (什么都没创建,瞬间完成)
  │
  ├── app.user_svc
  │     │
  │     ├── 先要 self.db → _db 是 None → 创建 Database #1 → 存到 _db
  │     │
  │     └── 再创建 UserService(db=#1) → 存到 _user_svc
  │
  ├── app.order_svc
  │     │
  │     ├── 先要 self.db → _db 不是 None → 直接返回 #1(不重复创建!)
  │     │
  │     └── 再创建 OrderService(db=#1) → 存到 _order_svc
  │
  └── app.db → 直接返回 #1

最终结果:3个模块,只有1个 Database 实例

七、一句话总结每个技术点

技术 一句话
db=None 不传也能用,不会报错
db or Database(host) 有就用你的,没有我自己搞
if self._db is None 空的才创建,有了就直接给
@property 不用加括号,像拿东西一样自然
门面类统一管理 一个管家管所有钥匙,保证只有一把

更多推荐