作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
安德烈Boyanov
验证专家 in Engineering
28 Years of Experience

Andrei是一名IT专业人士,拥有从低级编程到复杂系统设计和实现的经验.

Expertise

Share

让我们再说一遍:Python是一种具有动态类型和动态绑定的高级编程语言. 我认为它是一种功能强大的高级动态语言. 许多开发人员都喜欢Python,因为它的语法清晰, 结构良好的模块和包, 以及它巨大的灵活性和各种现代功能.

在Python中,没有什么强制要求您编写类并从中实例化对象. 如果你的项目不需要复杂的结构,你可以只写函数. Even better, 您可以编写一个简单的脚本来执行一些简单而快速的任务,而无需结构化代码. 因此,Python程序设计可以是不受约束和直接的.

同时,Python是一种100%面向对象的语言. How’s that? 简单地说, Python中的一切都是对象. 函数是对象,第一类对象(不管这意味着什么). 函数是对象这个事实很重要,所以请记住它.

So, 你可以用Python编写简单的脚本, 或者只是打开Python终端并在那里执行语句(这非常有用!). 但同时,你也可以创建复杂的框架、应用程序、库等等. 你可以用Python做很多事情. 当然有一些限制,但这不是本文的主题.

However, 因为Python是如此强大和灵活, 在编程时,我们需要一些规则(或模式). 那么,让我们看看什么是模式,以及它们与Python的关系. 我们还将继续实现一些基本的Python设计模式.

为什么Python适合模式?

任何编程语言都适合于模式. 实际上,应该在任何给定编程语言的上下文中考虑模式. 模式、语言语法和性质都对我们的编程施加了限制. 这些限制来自于语言的语法和语言的性质(动态的), functional, 面向对象的, 和类似的)可以有所不同, 它们存在的原因也是如此. 来自模式的限制是有原因的,它们是有目的的. That’s the basic goal of patterns; to tell us how to do something and how not to do it. 稍后我们将讨论模式,特别是Python设计模式.

Python是一种动态且灵活的语言. Python设计模式是利用其巨大潜力的好方法.

Python是一种动态且灵活的语言. Python设计模式是利用其巨大潜力的好方法.

Python的哲学是建立在深思熟虑的最佳实践之上的. Python是一种动态语言(我已经说过了吗?) and as such, 已经实现了, 或者使它易于实现, 一些流行的设计模式与几行代码. 有些设计模式是内置在Python中的,所以我们在不知情的情况下使用它们. 即使是经验一般的开发人员也可以在现有的Python代码中搜索设计模式,并一眼识别它们. 由于语言的性质,不需要其他模式.

For example, Factory 结构化的Python设计模式旨在创建新对象吗, 对用户隐藏实例化逻辑. 但是在Python中对象的创建在设计上是动态的,所以像Factory这样的添加是不必要的. 当然,如果您愿意,您可以自由地实现它. 也许在某些情况下,它真的很有用,但它们是例外,而不是常态.

Python的哲学有什么好呢? 让我们从 this (在Python终端中探索):

> >> import this
《欧博体育app下载》,作者:蒂姆·彼得斯

美总比丑好.
显式优于隐式.
简单总比复杂好.
复杂总比复杂好.
平的比嵌套的好.
稀疏比密集好.
可读性计数.
特殊情况没有特殊到违反规则的程度.
虽然实用胜过纯粹.
错误不应该无声地传递.
除非被明确噤声.
面对模棱两可,拒绝猜测的诱惑.
应该有一种——最好只有一种——明显的方法来做到这一点.
虽然这种方法一开始可能并不明显,除非你是荷兰人.
现在总比不做好.
虽然永远不要比现在更好.
如果实现很难解释,那就是一个坏主意.
如果实现很容易解释,这可能是个好主意.
名称空间是一个非常棒的想法——让我们做更多这样的事情!

这些可能不是传统意义上的Python模式, 但是这些规则定义了以最优雅和最有用的方式进行编程的“python”方法.

我们也有PEP-8代码指南来帮助构建我们的代码. 这对我来说是必须的,当然也有一些适当的例外. 顺便说一下,这些例外是PEP-8本身鼓励的:

但最重要的是:知道什么时候不一致——有时风格指南并不适用. 当你有疑问的时候,用你最好的判断. 看看其他的例子,决定哪个看起来最好. 不要犹豫,尽管问!

结合PEP-8和Python的禅宗(也是PEP- PEP-20), 您将有一个完美的基础来创建可读和可维护的代码. 添加设计模式,您就可以创建具有一致性和可发展性的各种软件系统.

Python设计模式

什么是设计模式?

一切都从“四人帮”开始。. 如果你不熟悉,可以在网上快速搜索一下 the GOF.

设计模式是解决已知问题的常用方法. 两个主要原则是GOF定义的设计模式的基础:

  • 编程到接口而不是实现.
  • 优先选择对象组合而不是继承.

让我们从Python程序员的角度仔细看看这两个原则.

编程到接口而不是实现

Think about Duck Typing. 在Python中,我们不喜欢根据这些接口定义接口和程序类,不是吗? 但是,听我说! 这并不意味着我们不考虑界面,事实上,在使用Duck Typing时,我们一直在考虑这个问题.

让我们来谈谈臭名昭著的Duck Typing方法,看看它是如何适应这种范式的:程序到接口.

如果它看起来像鸭子,叫起来也像鸭子,那它就是鸭子!

如果它看起来像鸭子,叫起来也像鸭子,那它就是鸭子!

我们不关心物体的性质, we don’t have to care what the object is; we just want to know if it’s able to do what we need (we are only interested in the interface of the object).

物体会嘎嘎叫吗? 所以,让它嘎嘎叫吧!

try:
    bird.quack()
除了AttributeError:
    self.lol()

我们为鸭子定义接口了吗? No! 我们是对接口而不是实现进行编程吗? Yes! 我觉得这样很好.

正如Alex Martelli在他著名的关于Python的演讲中指出的那样 软件设计 patterns, “教鸭子打字需要花点时间,但可以帮你省下很多事后的工作!”

优先选择对象组合而不是继承

这就是我所说的a Pythonic principle! 与包装一个类(或更频繁)相比,我创建了更少的类/子类, (几个班)在另一个班.

而不是这样做:

类用户(DbObject):
    pass

我们可以这样做:

class User:
    _persist_methods = ['get', 'save', 'delete']

    Def __init__(self, persister):
        self._persister = persister

    Def __getattr__(self, attribute):
        self中的If属性._persist_methods:
            返回getattr(自我._persister属性)

优点是显而易见的. 我们可以限制暴露包装类的哪些方法. 我们可以在运行时注入持久性实例! For example, 今天它是一个关系数据库, 但明天可能是什么都行, 有了我们需要的界面(还是那些讨厌的鸭子).

对Python来说,组合是优雅而自然的.

行为模式

行为模式涉及对象之间的交流, 对象如何相互作用并完成给定的任务. 根据GOF原则,Python共有11种行为模式: 责任链, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template, Visitor.

我发现这些模式非常有用,但这并不意味着其他模式组就没有用处.

Iterator

迭代器是内置在Python中的. 这是该语言最强大的特征之一. 几年前,我在某个地方读到迭代器让Python变得很棒,我认为现在仍然是这样. 对Python迭代器和生成器有足够的了解,你就会了解这个特定Python模式所需的一切.

责任链

此模式为我们提供了一种使用不同方法处理请求的方法, 每个都处理请求的特定部分. 好的代码最好的原则之一就是 单一职责 principle.

每段代码必须做一件事,而且只能做一件事.

这个原则在这个设计模式中得到了深度集成.

For example, 如果我们想过滤某些内容,我们可以实现不同的过滤器, 每个人都在做一种精确而明确的过滤. 这些过滤器可以用来过滤令人不快的词语、广告、不合适的视频内容等等.

类ContentFilter(对象):
    def __init__(self, filters=None):
        self._filters = list()
        if filters不为None:
            self._filters += filters

    Def filter(self, content):
        对于filter in self._filters:
            Content =过滤器(内容)
        返回内容

filter = ContentFilter([
                offensive_filter,
                ads_filter,
                porno_video_filter])
Filtered_content =过滤器.过滤器(内容)

Command

这是我作为程序员实现的第一个Python设计模式之一. 这提醒了我: 模式不是发明出来的,而是被发现的. 它们是存在的,我们只需要找到它们并加以利用. 我在多年前实现的一个了不起的项目中发现了这一点:一个特殊用途的WYSIWYM XML编辑器. 在代码中大量使用这种模式之后,我在一些站点上阅读了更多关于它的内容.

命令模式在以下情况下很方便, 出于某种原因, 我们需要从准备将要执行的内容开始,然后在需要的时候执行. 这样做的好处是,以这种方式封装操作可以实现 Python开发人员 添加与执行的操作相关的附加功能, 比如撤销/重做, 或者保存行动历史之类的.

让我们看看一个简单且经常使用的例子:

类RenameFileCommand(对象):
    Def __init__(self, from_name, to_name):
        self._from = from_name
        self._to = to_name

    def执行(自我):
        os.rename(self._from, self._to)

    def撤销(自我):
        os.rename(self._to, self._from)

类历史(对象):
    def __init__(自我):
        self._commands = list()

    Def execute(self, command):
        self._commands.追加(命令)
        command.execute()

    def撤销(自我):
        self._commands.pop().undo()

history =历史()
history.执行(RenameFileCommand(“文档/ cv.医生”、“docs / cv-en.doc'))
history.(RenameFileCommand(文档/ cv1执行.医生”、“docs / cv-bg.doc'))
history.undo()
history.undo()

创建型模式

让我们首先指出,创建模式在Python中并不常用. Why? 因为语言的动态性.

比我更聪明的人曾经说过,Factory是内置在Python中的. It means that the language itself provides us with all the flexibility we need to create objects in a sufficiently elegant fashion; we rarely need to implement anything on top, 比如Singleton或Factory.

在一个Python设计模式教程中, 我找到了描述这些设计的创造性设计模式的描述 模式提供了一种在隐藏创建逻辑的同时创建对象的方法, 而不是直接使用实例化对象 new operator.”

这很好地概括了问题:我们 don’t have a new Python中的运算符!

Nevertheless, 让我们看看如何实现一些, 我们是否应该觉得我们可以通过使用这种模式获得优势.

Singleton

当我们想要保证在运行时只存在一个给定类的实例时,使用Singleton模式. 在Python中我们真的需要这种模式吗? 根据我的经验, 简单地创建一个实例,然后使用它比实现单例模式更容易.

但是如果你想实现它, 这里有一些好消息:在Python中, 我们可以改变实例化过程(以及几乎任何其他东西). Remember the __new__() 我之前提到的方法? Here we go:

类日志记录器(对象):
    Def __new__(cls, *args, **kwargs):
        如果没有hasattr(cls, '_logger'):
            cls._logger = super(Logger, cls
                    ).__new__(cls, *args, **kwargs)
        return cls._logger

在这个例子中, Logger 是单态的.

以下是在Python中使用Singleton的替代方法:

  • Use a module.
  • 在应用程序顶层的某个地方创建一个实例,可能在配置文件中.
  • 将实例传递给每个需要它的对象. 这就是依赖注入,它是一种功能强大且易于掌握的机制.

依赖注入

我不打算讨论依赖注入是否是一种设计模式, 但我要说的是,它是实现松耦合的一个很好的机制, 它有助于我们的应用程序的可维护性和可扩展性. 将它与鸭子打字结合起来,原力将与您同在. Always.

我把它列在这篇文章的创建模式部分,因为它处理了何时(或者更好的是:在哪里)创建对象的问题. 它是在外部产生的. 更确切地说,对象根本不是在我们使用它们的地方创建的, 因此,依赖项不是在使用它的地方创建的. 消费者代码接收外部创建的对象并使用它. 为了进一步参考,请阅读对这个Stackoverflow问题的最多投票的答案.

它很好地解释了依赖注入,并让我们很好地了解了这种特殊技术的潜力. 基本上,答案用下面的例子解释了这个问题: 不要自己从冰箱里拿东西喝,而是说有需要. 告诉你的父母你需要在午餐时喝点什么.

Python为我们提供了实现这一点所需的一切. 考虑它在其他语言(如Java和c#)中的可能实现, 你很快就会发现Python的美妙之处.

让我们考虑一个简单的依赖注入的例子:

类的命令:

    def __init__(self, authenticate=None, authorize=None):
        self.Authenticate = Authenticate或self._not_authenticated
        self.Authorize =授权或self._not_autorized

    Def execute(self, user, action):
        self.验证(用户)
        self.授权(用户、动作)
        返回操作()

如果in_sudo_mode:
    command = command (always_authenticated, always_authorized)
else:
    command =命令(config . config).验证,配置.authorize)
command.execute (current_user delete_user_action)

We inject the authenticator and authorizer 命令类中的方法. Command类所需要的只是成功地执行它们,而不需要考虑实现细节. This way, 我们可以将Command类与我们决定在运行时使用的任何身份验证和授权机制一起使用.

我们已经展示了如何通过构造函数注入依赖项, 但是我们可以通过直接设置对象属性轻松地注入它们, 释放更多的潜力:

命令=命令()

如果in_sudo_mode:
    command.Authenticate = always_authenticated
    command.Authorize = always_authorized
else:
    command.Authenticate = config.authenticate
    command.授权= config.authorize
command.execute (current_user delete_user_action)

There is much more to learn about dependency injection; curious people would search for IoC, for example.

但在此之前,请阅读Stackoverflow的另一个答案 大多数人都为这个问题点赞.

Again, 我们刚刚演示了如何使用Python的内置功能在Python中实现这个美妙的设计模式.

我们不要忘记这一切的含义:依赖注入技术允许非常灵活和简单的单元测试. 想象一个架构,您可以在其中动态更改数据存储. 模拟数据库变成了一项微不足道的任务,不是吗? 欲知更多信息,请查看 Toptal的Python模拟入门.

你可能也想研究一下 Prototype, Builder and Factory 设计模式.

结构模式

Facade

这可能是最著名的Python设计模式.

假设您有一个包含大量对象的系统. 每个对象都提供了一组丰富的API方法. 你可以用这个系统做很多事情,但是如何简化界面呢? 为什么不添加一个接口对象来公开所有API方法的一个经过深思熟虑的子集呢? A Facade!

Facade是一种优雅的Python设计模式. 这是简化界面的完美方式.

Facade是一种优雅的Python设计模式. 这是简化界面的完美方式.

Python Facade设计模式示例:

汽车类(对象):

    def __init__(自我):
        self._tires = [Tyre('front_left'),
                             轮胎(“front_right”),
                             轮胎(“rear_left”),
                             轮胎(rear_right))
        self._tank =坦克(70)

    def tyres_pressure(自我):
        return [tyre.自备胎压._tyres]

    def fuel_level(自我):
        return self._tank.level

没有惊喜,没有诡计,没有 Car class is a Facade,就这样.

Adapter

If Facades 用于界面简化, Adapters 都是关于改变界面吗. 就像在系统期待鸭子的时候用了一头牛.

假设您有一个将信息记录到给定目的地的工作方法. 您的方法期望目标具有 write() 方法(例如,每个文件对象都有).

Def log(消息,目的地):
    destination.Write ('[{}] - {}').格式(datetime.现在(),消息)

我认为这是一个使用依赖注入编写得很好的方法, 这允许很大的可扩展性. 假设你想登录到某个 UDP 套接字而不是一个文件,你知道如何打开这个UDP套接字,但唯一的问题是 socket object has no write() method. You need an Adapter!

import socket

类SocketWriter(对象):

    Def __init__(self, ip, port):
        self._socket = socket.socket(socket.AF_INET,
                                     socket.SOCK_DGRAM)
        self._ip = ip
        self._port = port

    Def write(self, message):
        self._socket.发送消息,(自我._ip, self._port))

Def log(消息,目的地):
    destination.Write ('[{}] - {}').格式(datetime.现在(),消息)

upd_logger = SocketWriter('1.2.3.4', '9999')
log('发生了什么',udp_destination)

但为什么我发现 adapter so important? 当它与依赖注入有效地结合在一起时,它为我们提供了巨大的灵活性. 当我们只需要实现一个适配器就可以将新接口转换为已知接口时,为什么还要修改经过良好测试的代码来支持新接口呢?

你也应该检查和掌握 bridge and proxy 设计模式,由于它们的相似性 adapter. 想想在Python中实现它们是多么容易, 想想在你的项目中使用它们的不同方式.

Decorator

哦,我们是多么幸运啊! Decorators 真的很好,我们已经把它们整合到语言中了吗. 我最喜欢Python的一点是,它教会我们使用最佳实践. 这并不是说我们不需要意识到最佳实践(和设计模式), 特别是), 但对于Python,我觉得我是在遵循最佳实践, regardless. Personally, 我发现Python的最佳实践是直观的和第二天性的, 这是新手和精英开发者都喜欢的东西.

The decorator 模式是关于引入额外的功能,特别是, 在不使用继承的情况下完成.

那么,让我们看看如何在不使用内置Python功能的情况下修饰一个方法. 这里有一个简单的例子.

Def execute(user, action):
    self.验证(用户)
    self.授权(用户、动作)
    返回操作()

这里有什么不太好的 execute 函数的作用远不止执行某项操作. 我们没有严格遵守单一责任原则.

这样写就好了:

def执行(行动):
    返回操作()

我们可以在另一个地方实现任何授权和身份验证功能 decorator, like so:

Def execute(action, *args, **kwargs):
    返回操作()

def autheticated_only(方法):
    Def decoration (*args, **kwargs):
        如果check_authenticated (kwargs(“用户”)):
            返回方法(*args, **kwargs)
        else:
            提高UnauthenticatedError
    返回装饰

def authorized_only(方法):
    Def decoration (*args, **kwargs):
        如果check_authorized(kwargs['user'], kwargs['action']):
            返回方法(*args, **kwargs)
        else:
            提高UnauthorizeddError
    返回装饰

Execute = authenticated_only(Execute)
Execute = authorized_only(Execute)

Now the execute() method is:

  • Simple to read
  • 只做一件事(至少在查看代码时)
  • 装饰有认证
  • 被授权装饰

我们使用Python的集成装饰器语法编写相同的代码:

def autheticated_only(方法):
    Def decoration (*args, **kwargs):
        如果check_authenticated (kwargs(“用户”)):
            返回方法(*args, **kwargs)
        else:
            提高UnauthenticatedError
    返回装饰


def authorized_only(方法):
    Def decoration (*args, **kwargs):
        如果check_authorized(kwargs['user'], kwargs['action']):
            返回方法(*args, **kwargs)
        else:
            提高UnauthorizedError
    返回装饰


@authorized_only
@authenticated_only
Def execute(action, *args, **kwargs):
    返回操作()

重要的是要注意你是 不局限于函数 as decorators. 装饰器可能涉及整个类. 唯一的要求是它们必须是 callables. But we have no problem with that; we just need to define the __call__(self) method.

您可能还想仔细看看Python的functools模块. 那里有很多东西有待发现!

Conclusion

我已经展示了使用Python的设计模式是多么自然和容易, 但我也展示了如何用Python编程应该很容易, too.

“简单总比复杂好。” remember that? 也许您已经注意到,没有一个设计模式是完整和正式的描述. 没有显示复杂的全尺寸实现. 你需要以最适合你的风格和需求的方式去“感受”和实现它们. Python是一门很棒的语言,它为您提供了生成灵活且可重用代码所需的所有功能.

然而,它给你的远不止这些. 它给你写作的“自由” really bad code. Don’t do it! 不要重复你自己(DRY),不要写超过80个字符的代码行. And don’t forget to use 设计模式 where applicable; it’s one of the best ways to learn from others and gain from their wealth of experience free of charge.

聘请Toptal这方面的专家.
Hire Now
安德烈Boyanov

安德烈Boyanov

验证专家 in Engineering
28 Years of Experience

比利时布鲁塞尔

2015年12月3日成为会员

作者简介

Andrei是一名IT专业人士,拥有从低级编程到复杂系统设计和实现的经验.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

Toptal开发者

加入总冠军® community.