个性化阅读
专注于IT技术分析

Python设计模式:适用于时尚代码

本文概述

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

在Python中, 没有什么义务让你编写类并从中实例化对象。如果你的项目中不需要复杂的结构, 则只需编写函数即可。更好的是, 你可以编写平面脚本来执行一些简单而又快速的任务, 而无需构建代码。

同时, Python是100%的面向对象语言。怎么样?简而言之, Python中的所有内容都是一个对象。函数是对象, 是一流的对象(无论什么意思)。函数是对象这一事实很重要, 因此请记住这一点。

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

但是, 由于Python非常强大和灵活, 因此在其中进行编程时我们需要一些规则(或模式)。因此, 让我们看看什么是模式, 以及它们与Python的关系。我们还将继续实现一些基本的Python设计模式。

为什么Python有益于模式?

任何编程语言都适用于模式。实际上, 应该在任何给定的编程语言的上下文中考虑模式。模式, 语言语法和性质都对我们的编程施加了限制。来自语言语法和语言性质(动态, 功能, 面向对象等)的限制可能会有所不同, 其存在的原因也会有所不同。模式带来的限制是有原因的, 它们是有目的的。这是模式的基本目标;告诉我们如何做某事以及如何不做某事。稍后, 我们将讨论模式, 尤其是Python设计模式。

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

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

鸣叫

Python的理念建立在经过深思熟虑的最佳实践的思想之上。 Python是一种动态语言(我已经说过吗?), 因此, 它已经实现了许多流行的设计模式或使它们易于实现, 并且只需几行代码。 Python中内置了一些设计模式, 因此即使不知道, 我们也会使用它们。由于语言的性质, 不需要其他模式。

例如, Factory是一种结构化Python设计模式, 旨在创建新对象, 向用户隐藏实例化逻辑。但是, Python设计中对象的创建是动态设计的, 因此不需要像Factory这样的添加。当然, 你可以随意实现它。在某些情况下, 它确实很有用, 但只是例外, 并非常规。

Python的哲学有什么好处?让我们开始(在Python终端中进行探索):

> >> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

这些可能不是传统意义上的模式, 但是这些规则以最优雅, 最有用的方式定义了” Pythonic”编程方法。

我们也有帮助构建代码的PEP-8代码准则。当然, 除了某些适当的例外, 这对我来说是必须的。顺便说一下, PEP-8本身鼓励使用以下例外情况:

但最重要的是:知道何时需要不一致-有时样式指南就不适用。如有疑问, 请运用最佳判断。查看其他示例并确定最合适的方法。不要犹豫, 问!

将PEP-8与The Zen of Python(也是PEP-PEP-20)结合使用, 你将拥有创建可读性和可维护性代码的完美基础。添加设计模式, 你就可以创建具有一致性和可扩展性的各种软件系统。

Python设计模式

什么是设计模式?

一切始于四人帮(GOF)。如果你不熟悉GOF, 请进行快速在线搜索。

设计模式是解决众所周知的问题的常用方法。 GOF定义的设计模式有两个主要原则:

  • 编程到接口而不是实现。
  • 优先考虑对象组成而不是继承。

让我们从Python程序员的角度来仔细研究这两个原则。

编程到接口而不是实现

想想鸭子打字。在Python中, 我们不喜欢根据这些接口定义接口和程序类, 是吗?但是, 听我说!这并不意味着我们不考虑界面, 事实上, 在Duck Typing中我们一直在这样做。

让我们来谈谈臭名昭著的Duck Typing方法, 看看它如何适合这种范例:编程接口。

如果它看起来像鸭子而嘎嘎像鸭子,那就是鸭子!

如果它看起来像鸭子而嘎嘎像鸭子, 那就是鸭子!

鸣叫

我们不必理会对象的性质, 也不必关心对象是什么。我们只想知道它是否能够满足我们的需求(我们仅对对象的界面感兴趣)。

物体会发出嘎嘎声吗?所以, 让它嘎嘎!

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

我们为鸭子定义了接口吗?没有!我们是否对接口进行编程而不是对实现进行编程?是!而且, 我觉得这很好。

正如Alex Martelli在他关于Python的Design Patterns的著名演讲中所指出的那样, “教鸭子进行打字要花一些时间, 但是之后可以省去很多工作!”

优先考虑对象组成而不是继承

现在, 这就是我所说的Python原理!与将一个类(或更常见的是多个类)包装在另一类中相比, 我创建的类/子类更少。

而不是这样做:

class User(DbObject):
    pass

我们可以做这样的事情:

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)

优点是显而易见的。我们可以限制公开的包装类的方法。我们可以在运行时注入持久性实例!例如, 今天它是一个关系数据库, 但是明天它可以是任何东西, 具有我们需要的界面(同样是讨厌的鸭子)。

组合对于Python而言是优雅而自然的。

行为模式

行为模式涉及对象之间的通信, 对象如何交互以及完成给定任务。根据GOF原则, Python中共有11种行为模式:责任链, 命令, 解释器, 迭代器, 介体, 备忘录, 观察员, 状态, 策略, 模板, 访客。

我发现这些模式非常有用, 但这并不意味着其他模式组不是。

迭代器

迭代器内置在Python中。这是该语言最强大的特性之一。几年前, 我在某处读到迭代器使Python变得很棒, 我认为情况仍然如此。充分了解Python迭代器和生成器, 你将了解有关此特定Python模式的所有信息。

责任链

这种模式为我们提供了一种使用不同方法处理请求的方法, 每个方法都针对请求的特定部分。要知道, 好的代码的最佳原则之一就是”单一责任”原则。

每段代码都只能做一件事情。

该原理已深深地融入了这种设计模式。

例如, 如果我们要过滤某些内容, 我们可以实现不同的过滤器, 每个过滤器都执行一种精确且明确定义的过滤类型。这些过滤器可用于过滤令人反感的文字, 广告, 不合适的视频内容等。

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)

命令

这是我作为程序员实现的首批Python设计模式之一。那使我想起:模式不是发明的, 而是被发现的。它们存在, 我们只需要查找并使用它们即可。我是在多年前实施的一项令人惊叹的项目中发现的:专用WYSIWYM XML编辑器。在代码中大量使用此模式之后, 我在一些站点上了解了更多有关此模式的信息。

在某些情况下, 由于某种原因, 我们需要先准备将要执行的内容, 然后在需要时执行它, 该命令模式非常方便。优势在于以这种方式封装动作使Python开发人员可以添加与执行的动作相关的其他功能, 例如撤消/重做, 或保留动作的历史记录等。

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

class RenameFileCommand(object):
    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.execute(RenameFileCommand('docs/cv.doc', 'docs/cv-en.doc'))
history.execute(RenameFileCommand('docs/cv1.doc', 'docs/cv-bg.doc'))
history.undo()
history.undo()

创作模式

让我们首先指出创建模式在Python中并不常用。为什么?由于语言的动态性质。

比我曾经说过的聪明一点的人说, Factory是Python内置的。这意味着语言本身为我们提供了以足够优雅的方式创建对象所需的所有灵活性。我们很少需要在顶部实现任何东西, 例如Singleton或Factory。

在一个Python Design Patterns教程中, 我找到了关于创建设计模式的描述, 这些描述指出这些设计”模式提供了一种在隐藏创建逻辑的同时创建对象的方法, 而不是直接使用new运算符实例化对象的方法。”

几乎可以总结出问题所在:Python中没有新的运算符!

不过, 让我们看看如果我们觉得通过使用这种模式可能会获得优势, 我们将如何实现一些实现。

辛格尔顿

当我们要保证运行时仅存在一个给定类的实例时, 可以使用Singleton模式。我们真的需要Python中的这种模式吗?根据我的经验, 可以轻松地简单地有意创建一个实例, 然后使用它而不是实施Singleton模式。

但是, 如果你想实现它, 这是一个好消息:在Python中, 我们可以更改实例化过程(以及几乎所有其他操作)。还记得我之前提到的__new __()方法吗?开始了:

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

在此示例中, Logger是Singleton。

这些是在Python中使用Singleton的替代方法:

  • 使用模块。
  • 在应用程序顶层的某个位置(也许在配置文件中)创建一个实例。
  • 将实例传递给需要它的每个对象。这是一个依赖项注入, 它是一种功能强大且易于掌握的机制。

依赖注入

我不打算讨论依赖注入是否是一种设计模式, 但我会说这是实现松散耦合的一种很好的机制, 它有助于使我们的应用程序可维护和可扩展。将其与Duck Typing结合使用, Force将与你同在。总是。

我在这篇文章的创建模式部分中列出了它, 因为它处理了何时(甚至更好:在哪里)创建对象的问题。它是在外部创建的。最好说, 这些对象根本不在我们使用它们的地方创建, 因此依赖项不在使用它的地方创建。使用者代码接收外部创建的对象并使用它。有关更多参考, 请阅读此Stackoverflow问题的最高支持答案。

这是对依赖项注入的很好的解释, 使我们对这种特殊技术的潜力有了很好的了解。基本上, 答案通过以下示例说明了问题:不要自己从冰箱里拿东西喝, 而要指出需要。告诉父母你需要在午餐时喝点东西。

Python为我们提供了轻松实现所需的一切。考虑一下用其他语言(例如Java和C#)实现的可能性, 你将很快意识到Python的魅力。

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

class Command:

    def __init__(self, authenticate=None, authorize=None):
        self.authenticate = authenticate or 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)

我们将身份验证器和授权者方法注入Command类。 Command类的所有需求就是成功执行它们, 而不必担心实现细节。这样, 我们可以将Command类与我们决定在运行时使用的任何身份验证和授权机制一起使用。

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

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)

关于依赖注入, 还有更多的知识要学习。例如, 好奇的人会搜索IoC。

但是在你执行此操作之前, 请阅读另一个Stackoverflow答案, 该答案是对此问题最推崇的答案。

再次, 我们只是演示了如何在Python中实现这种美妙的设计模式仅仅是使用语言的内置功能的问题。

我们不要忘记这一切的含义:依赖项注入技术可实现非常灵活和容易的单元测试。想象一下一种架构, 你可以在其中即时更改数据。模拟数据库成为一项琐碎的任务, 不是吗?有关更多信息, 请查看srcmini的Python入门。

你可能还需要研究原型, 构建器和工厂设计模式。

结构模式

正面

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

假设你有一个包含大量对象的系统。每个对象都提供一组丰富的API方法。你可以使用此系统做很多事情, 但是如何简化界面呢?为什么不添加一个暴露所有API方法深思熟虑的子集的接口对象?外立面!

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

毫不奇怪, 没有花样, 汽车课是一个门面, 仅此而已。

适配器

如果使用Facades来简化界面, 则适配器只不过是改变界面而已。就像在系统期待鸭子的时候使用牛一样。

假设你有一种将信息记录到给定目标的有效方法。你的方法希望目标具有write()方法(例如, 每个文件对象都具有)。

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

我想说这是一个写得很好的方法, 带有依赖项注入, 可以实现极大的可扩展性。假设你要登录某个UDP套接字而不是文件, 你知道如何打开此UDP套接字, 但是唯一的问题是套接字对象没有write()方法。你需要一个适配器!

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('Something happened', udp_destination)

但是为什么我发现适配器如此重要?好吧, 当它与依赖注入有效结合时, 它为我们提供了极大的灵活性。当我们仅能实现将新接口转换为众所周知的接口的适配器时, 为什么还要修改经过良好测试的代码以支持新接口?

由于它们与适配器的相似性, 你还应该签出并掌握桥接和代理设计模式。考虑一下它们在Python中实现的难易程度, 并考虑在项目中使用它们的不同方式。

装饰器

哦, 我们真幸运!装饰器真的很好, 我们已经将它们集成到语言中。我在Python中最喜欢的是使用它教会我们使用最佳实践。这并不是说我们不必了解最佳实践(尤其是设计模式), 但是无论如何, 我都觉得使用Python可以遵循最佳实践。就个人而言, 我发现Python最佳实践是直观且第二手的, 这对于新手和精英开发人员都非常赞赏。

装饰器模式是关于引入其他功能的, 尤其是在不使用继承的情况下进行。

因此, 让我们检查一下如何在不使用内置Python功能的情况下修饰方法。这是一个简单的例子。

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

这里不好的是, execute函数的作用远不止是执行某件事。我们没有遵循这封信的单一责任原则。

只需编写以下代码将是很好的:

def execute(action):
    return action()

我们可以在装饰器中的另一个位置实现任何授权和身份验证功能, 如下所示:

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

def autheticated_only(method):
    def decorated(*args, **kwargs):
        if check_authenticated(kwargs['user']):
            return method(*args, **kwargs)
        else:
            raise UnauthenticatedError
    return decorated

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

execute = authenticated_only(execute)
execute = authorized_only(execute)

现在execute()方法是:

  • 易于阅读
  • 只做一件事(至少在查看代码时)
  • 装饰有认证
  • 装饰有授权

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

def autheticated_only(method):
    def decorated(*args, **kwargs):
        if check_authenticated(kwargs['user']):
            return method(*args, **kwargs )
        else:
            raise UnauthenticatedError
    return decorated


def authorized_only(method):
    def decorated(*args, **kwargs):
        if 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()

重要的是要注意, 你不仅限于充当装饰器的功能。装饰器可能涉及整个类。唯一的要求是它们必须是可调用的。但是我们对此没有任何问题。我们只需要定义__call __(self)方法。

你可能还需要仔细研究Python的functools模块。有很多发现!

总结

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

“简单胜于复杂”, 还记得吗?也许你已经注意到, 没有一个设计模式被完整和正式地描述过。没有显示复杂的全面实施。你需要以最适合你的风格和需求的方式”感受”并实施它们。 Python是一种很棒的语言, 它为你提供了生成灵活且可重复使用的代码所需的全部功能。

但是, 它给你的不只是这些。它使你可以”自由”地编写非常糟糕的代码。别做!不要重复自己(DRY), 也不要写超过80个字符的代码行。并且不要忘记在适用的情况下使用设计模式;这是向他人免费学习并从他们的丰富经验中获益的最好方法之一。

赞(0)
未经允许不得转载:srcmini » Python设计模式:适用于时尚代码

评论 抢沙发

评论前必须登录!