前言

  在 Java Spring Boot 等等的后端领域,会大量使用依赖注入的方式来简化复杂的设计模式。
  实现参数的自动化注入。
  这些设计方式在 Python 的世界里使用不多,因为 Python 语言足够灵活。
  倘若需要开发复杂的框架,使用 依赖注入 框架可以简化很多代码。

Github 地址
官方说明文档

依赖注入解决的问题

参考文章

  在日常开发中,我们的方法调用可能会越来越深。

1
2
3
4
5
6
7
8
9
10

def create_robot(robot_name):
create_robot_hand()

def create_robot_hand():
create_robot_finger()

def create_robot_finger():
print("create_robot_finger")

  上面是一个简单的机器人创建调用函数。
  调用方式会伴随则系统的复杂程度逐层深入。
  到了 create_robot_finger 深度的时候,可能会需要在上层传入参数控制 finger 的数量

1
2
3
4
5
6
7
8
9

def create_robot(robot_name,finger_num=10):
create_robot_hand(finger_num=finger_num)

def create_robot_hand(finger_num=10):
create_robot_finger(finger_num=finger_num)

def create_robot_finger(finger_num=10):
print("create_robot_finger finder_number:{0}".format(finger_num))

  这需要将参数补充到 调用链条 的每一个函数当中。
  如果只是上面的 三层 调用深度,那可能手动修改维护还不是什么问题。
  但倘若调用深度很深,那这个代码修改量就会非常庞大。
  不利于代码的扩展和维护。


  在 Python 的世界里,解决这个问题的方法有很多。

  1. 导入 配置 模块,外部获取参数配置
  2. 面向对象 注入依赖,从实例化中获取参数配置

方案一 导入模块

1
2
3
4
5
6
7
8
"""settings.py"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

ROBOT_FINGER_NUM = 10

1
2
3
4
5
6
7
8
9
10
11
import settings

def create_robot(robot_name):
create_robot_hand()

def create_robot_hand():
create_robot_finger()

def create_robot_finger():
print("create_robot_finger finder_number:{0}".format(settings.ROBOT_FINGER_NUM))

  通过模块的方式将参数转移到外部,进行配置。
  这个做法可以解决参数传递的问题。

  缺点就是参数管理会比较麻烦,通常是将全局配置的参数都放到一个文件方便集中管理。
  但是这样会导致不同的逻辑调用的参数都会塞到一个文件里面,并不是十分整洁。

方案二 注入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from dependencies import Injector
import attr


@attr.s
class Robot(object):
finger_num = attr.ib(default=10)

def create_robot(self,robot_name):
self.create_robot_hand()

def create_robot_hand(self):
self.create_robot_finger()

def create_robot_finger(self):
print("create_robot_finger finder_number:{0}".format(self.finger_num))


class Container(Injector):
finger_num = 10
robot = Robot
Container.robot.create_robot("robot name")
# 打印 create_robot_finger finder_number:10

# `dependencies` 的实现等价于下面的代码
robot = Robot(finger_num=10)
robot.create_robot("robot name")

  使用 dependencies 库实现依赖注入,自动将容器内的数据填充到 类的实例化过程中。
  通过类的属性实现参数传递。

dependencies 介绍

  通过上面的案例可以看到。
  dependencies 可以自动实例化类,填充类初始化需要的参数。
  但它的功能还远不止这么简单。
  它还可以实现多个类实例化的自动填充,只要参数变量名命名配置好即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from dependencies import Injector
import attr

@attr.s
class Robot(object):
servo = attr.ib()
controller = attr.ib()
settings = attr.ib()
di_environment = attr.ib()

def run(self):
print("controller di_environment",self.controller.di_environment)
print("self di_environment",self.di_environment)
print("settings threshold",self.settings.threshold)
print("servo threshold",self.servo.threshold)

@attr.s
class Servo(object):
threshold = attr.ib()

@attr.s
class Controller(object):
di_environment = attr.ib()

@attr.s
class Settings(object):
threshold = attr.ib()

class Container(Injector):
threshold = 1
di_environment = "production"

robot = Robot
servo = Servo
settings = Settings
controller = Controller

Container.robot.run()
# 打印:
# controller di_environment production
# self di_environment production
# settings threshold 1
# servo threshold 1

  通过 dependencies 可以根据属性命名自动填充多个类的参数数据。
  container 的逻辑等价于下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
threshold = 1
di_environment = "production"
servo = Servo(threshold)
settings = Settings(threshold)
controller = Controller(di_environment)
robot = Robot(servo,controller,settings,di_environment)
robot.run()
# 打印:
# controller di_environment production
# self di_environment production
# settings threshold 1
# servo threshold 1

  但是 dependencies 库根据参数的命名自动实例化对象,参数的调整变得简单可控。

dependencies 实现 caller 方法

参考文章

  利用 依赖注入 可以分离 依赖 和 业务 逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import attr
from dependencies import Injector


class Editor(object):
def install_language(self,lang):
print("install language:{0}".format(lang))

editor = Editor()

@attr.s(frozen=True, slots=True)
class ChangeLanguage(object):
editor = attr.ib()

def __call__(self,lang):
self.editor.install_language(lang)

class Container(Injector):
editor = editor
change_language = ChangeLanguage

Container.change_language("en_US")
# 打印: install language:en_US

  利用 dependencies 可以构建出 caller 对象。
  caller 虽然用类构建,但是调用方式和方法一致,可以方法需要用到的依赖用类实例化的方式进行注入。
  实现依赖和传参的分离。

总结

  依赖注入可以很好解决函数调用过深的问题,让代码结构更加清晰。