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

Andrei Boyanov

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

Expertise

Years of Experience

28

Share

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

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

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

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

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

Why Is Python Good For Patterns?

任何编程语言都适合于模式. 实际上,应该在任何给定编程语言的上下文中考虑模式. 模式、语言语法和性质都对我们的编程施加了限制. 这些限制来自于语言的语法和语言的性质(动态的), functional, object oriented, and the like) can differ, 它们存在的原因也是如此. 来自模式的限制是有原因的,它们是有目的的. 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, already implements, or makes it easy to implement, 一些流行的设计模式与几行代码. 有些设计模式是内置在Python中的,所以我们在不知情的情况下使用它们. 即使是经验一般的开发人员也可以在现有的Python代码中搜索设计模式,并一眼识别它们. 由于语言的性质,不需要其他模式.

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

Python的哲学有什么好呢? Let’s start with this (在Python终端中探索):

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

Beautiful is better than ugly.
显式优于隐式.
Simple is better than complex.
复杂总比复杂好.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
特殊情况没有特殊到违反规则的程度.
虽然实用胜过纯粹.
错误不应该无声地传递.
Unless explicitly silenced.
面对模棱两可,拒绝猜测的诱惑.
应该有一种——最好只有一种——明显的方法来做到这一点.
虽然这种方法一开始可能并不明显,除非你是荷兰人.
Now is better than never.
虽然永远不要比现在更好.
如果实现很难解释,那就是一个坏主意.
如果实现很容易解释,这可能是个好主意.
名称空间是一个非常棒的想法——让我们做更多这样的事情!

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

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

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

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

Python Design Patterns

What Is A Design Pattern?

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

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

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

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

编程到接口而不是实现

Think about Duck Typing. 在Python中,我们不喜欢根据这些接口定义接口和程序类,不是吗? But, listen to me! 这并不意味着我们不考虑界面,事实上,在使用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).

Can the object quack? So, let it quack!

try:
    bird.quack()
except AttributeError:
    self.lol()

我们为鸭子定义接口了吗? No! 我们是对接口而不是实现进行编程吗? Yes! And, I find this so nice.

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

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

Now, that’s what I call a Pythonic principle! 与包装一个类(或更频繁)相比,我创建了更少的类/子类, (几个班)在另一个班.

Instead of doing this:

class User(DbObject):
    pass

We can do something like this:

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

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

    Def __getattr__(self, attribute):
        if attribute in self._persist_methods:
            return getattr(self._persister, attribute)

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

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

Behavioral Patterns

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

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

Iterator

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

Chain of responsibility

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

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

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

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

class ContentFilter(object):
    def __init__(self, filters=None):
        self._filters = list()
        if filters is not None:
            self._filters += filters

    def filter(self, content):
        for filter in self._filters:
            content = filter(content)
        return content

filter = ContentFilter([
                offensive_filter,
                ads_filter,
                porno_video_filter])
filtered_content = filter.filter(content)

Command

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

命令模式在以下情况下很方便, for some reason, 我们需要从准备将要执行的内容开始,然后在需要的时候执行. 这样做的好处是,以这种方式封装操作可以实现 Python developers 添加与执行的操作相关的附加功能, such as undo/redo, 或者保存行动历史之类的.

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

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

    def execute(self):
        os.rename(self._from, self._to)

    def undo(self):
        os.rename(self._to, self._from)

class History(object):
    def __init__(self):
        self._commands = list()

    def execute(self, command):
        self._commands.append(command)
        command.execute()

    def undo(self):
        self._commands.pop().undo()

history = History()
history.执行(RenameFileCommand(“文档/ cv.doc', 'docs/cv-en.doc'))
history.(RenameFileCommand(文档/ cv1执行.doc', 'docs/cv-bg.doc'))
history.undo()
history.undo()

Creational Patterns

让我们首先指出,创建模式在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, like Singleton or Factory.

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

这很好地概括了问题:我们 don’t have a new operator in Python!

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

Singleton

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

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

class Logger(object):
    Def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_logger'):
            cls._logger = super(Logger, cls
                    ).__new__(cls, *args, **kwargs)
        return cls._logger

In this example, Logger is a Singleton.

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

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

Dependency Injection

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

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

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

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

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

class Command:

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

    Def execute(self, user, action):
        self.authenticate(user)
        self.authorize(user, action)
        return action()

if in_sudo_mode:
    command = command (always_authenticated, always_authorized)
else:
    command = Command(config.authenticate, config.authorize)
command.execute (current_user delete_user_action)

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

我们已经展示了如何通过构造函数注入依赖项, 但是我们可以通过直接设置对象属性轻松地注入它们, unlocking even more potential:

command = Command()

if in_sudo_mode:
    command.Authenticate = always_authenticated
    command.authorize = always_authorized
else:
    command.authenticate = config.authenticate
    command.authorize = 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模拟入门.

You may also want to research Prototype, Builder and Factory design patterns.

Structural Patterns

Facade

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

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

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

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

Python Facade设计模式示例:

class Car(object):

    def __init__(self):
        self._tyres = [Tyre('front_left'),
                             Tyre('front_right'),
                             Tyre('rear_left'),
                             Tyre('rear_right'), ]
        self._tank = Tank(70)

    def tyres_pressure(self):
        return [tyre.pressure for tyre in self._tyres]

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

没有惊喜,没有诡计,没有 Car class is a Facade, and that’s all.

Adapter

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

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

def log(message, destination):
    destination.write('[{}] - {}'.format(datetime.now(), message))

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

import socket

class SocketWriter(object):

    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.send(message, (self._ip, self._port))

def log(message, destination):
    destination.write('[{}] - {}'.format(datetime.now(), message))

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

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

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

Decorator

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

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

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

def execute(user, action):
    self.authenticate(user)
    self.authorize(user, action)
    return action()

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

这样写就好了:

def execute(action):
    return action()

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

Def execute(action, *args, **kwargs):
    return action()

def autheticated_only(method):
    Def decoration (*args, **kwargs):
        如果check_authenticated (kwargs(“用户”)):
            return method(*args, **kwargs)
        else:
            raise UnauthenticatedError
    return decorated

def authorized_only(method):
    Def decoration (*args, **kwargs):
        如果check_authorized(kwargs['user'], kwargs['action']):
            return method(*args, **kwargs)
        else:
            raise UnauthorizeddError
    return decorated

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

Now the execute() method is:

  • Simple to read
  • 只做一件事(至少在查看代码时)
  • 装饰有认证
  • Is decorated with authorization

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

def autheticated_only(method):
    Def decoration (*args, **kwargs):
        如果check_authenticated (kwargs(“用户”)):
            返回方法(*args, **kwargs)
        else:
            raise UnauthenticatedError
    return decorated


def authorized_only(method):
    Def decoration (*args, **kwargs):
        如果check_authorized(kwargs['user'], kwargs['action']):
            return method(*args, **kwargs)
        else:
            raise UnauthorizedError
    return decorated


@authorized_only
@authenticated_only
Def execute(action, *args, **kwargs):
    return action()

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

您可能还想仔细看看Python的functools模块. There is much to discover there!

Conclusion

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

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

然而,它给你的远不止这些. 它给你写作的“自由” really bad code. Don’t do it! 不要重复你自己(DRY),不要写超过80个字符的代码行. And don’t forget to use design patterns 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
Andrei Boyanov's profile image
Andrei Boyanov

Located in Brussels, Belgium

Member since December 3, 2015

About the author

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

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

Expertise

Years of Experience

28

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

订阅意味着同意我们的 privacy policy

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

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.