上药三品,神与气精

曾因酒醉鞭名马 生怕情多累美人


  • 首页

  • 关于

  • 分类

  • 标签

  • 归档

  • 搜索

top-questions-python

发表于 2018-03-17 | 阅读次数:
字数统计: 1.2k | 阅读时长 ≈ 4

1.python2 编码问题

  • python代码内部请全部使用unicode编码,在获取外部内容时,先decode为unicode,向外输出时再encode为Str
  • 在定义变量或者正则时,也定义unicode字符,如a=u”中文”;res=r””+u”正则”。
a="\\u8fdd\\u6cd5\\u8fdd\u89c4" #变量a的内容为unicode编码,变量a为string编码(""前不要加u)
b=a.decode('unicode-escape')
print b

a="\\xe5\\x85\\xb3\\xe4\\xba\\x8e\\xe4" #变量a的内容为string编码,变量a为string编码(""前不要加u)
b=a.decode('string-escape')
print b

2.多线程问题

python带有GIL解释器锁的概念,同一时刻只能有一个线程在运行,遇到IO操作才会释放切换。

协程不同于线程的地方在于协程不是操作系统进行切换,而是由程序员编码进行切换的,也就是说切换是由程序员控制的,这样就没有了线程所谓的安全问题。

这也就是常选的gevent 方案

cpu 密集 多进程
io密集 多线程 或者协程

3.协程

4.闭包

全局变量降低了函数或模块之间的通用性,不同的函数或模块都要依赖于全局变量。同样,全局变量降低了代码的可读性,阅读者可能并不知道调用的某个变量是全局变量。

# 原始情况
time = 0

def insert_time(min):
    time = time + min
    return  time

print(insert_time(2))
print(insert_time(10))


# 闭包
time = 0


def study_time(time):
    def insert_time(min):
        nonlocal  time
        time = time + min
        return time

    return insert_time


f = study_time(time)
print(f(2))
print(time)
print(f(10))
print(time)

这里最直接的表现就是全局变量 time 至此至终都没有修改过,这里还是用了 nonlocal 关键字,表示在函数或其他作用域中使用外层(非全局)变量。

这种内部函数的局部作用域中可以访问外部函数局部作用域中变量的行为,我们称为: 闭包。更加直接的表达方式就是,当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。k

闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。而且使用闭包,可以使代码变得更加的优雅。装饰器,是基于闭包实现的。

所有函数都有一个 __closure__ 属性,如果函数是闭包的话,那么它返回的是一个由 cell 组成的元组对象。cell 对象的 cell_contents 属性就是存储在闭包中的变量。

5.装饰器问题

6.爬虫问题

7.魔法方法

一个类创建的过程是怎样的,先是调用了 __new方法来创建一个对象,把参数传给 \init__ 方法进行实例化。

其实在实际开发中,很少会用到 __new 方法,除非你希望能够控制类的创建。通常讲到 \new__ ,都是牵扯到 metaclass(元类)的。

当然当一个对象的生命周期结束的时候,析构函数 __del__ 方法会被调用。但是这个方法是 Python 自己对对象进行垃圾回收的。

Python 没有真正意义上的私有属性。然后这就导致了对 Python 类的封装性比较差。我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 get 方法和 set 方法。Python 其实可以通过魔术方法来实现封装。

方法说明

__getattr__(self, name)该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。

__setattr(self, name, value)定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意,实现 \setattr__ 时要避免”无限递归”的错误,

__delattr(self, name) \delattr 与 \setattr 很像,只是它定义的是你删除属性时的行为, 实现 \delattr__ 是同时要避免”无限递归”的错误

__getattribute(self, name) \getattribute 定义了你的属性被访问时的行为,相比较,\getattr__ 只有该属性不存在时才会起作用。

因此,在支持 __getattribute的 Python 版本,调用\getattr 前必定会调用 \getattribute__

__getattribute__ 同样要避免”无限递归”的错误。

8.差异

Python3.x 中 input() 函数接受一个标准输入数据,返回为 string 类型。

Python2.x 中 input() 相等于 eval(raw_input(prompt)) ,用来获取控制台的输入。

python-big-web-project

发表于 2018-03-17 | 阅读次数:
字数统计: 2.3k | 阅读时长 ≈ 7

严格来说,MVC是一个古老的概念,从早期的Smalltalk开始已经不再适合于web应用开发。Django的开发人员已经正确意识到在Python中我们实际使用的是MTV(model,template,view):

  • 模板包含了HTML的内容以及页面显示逻辑。它是使用模板语言如Kajiki来编写,从视图(view)中获取数据,然后展示在页面中。

  • 视图(有时也称为“控制器”),仅仅是使用Python语言编写的中间代码。它借助于web框架将所有的内容放在一起。它可以看到其他的所有层,并且定义了URLs,将它们映射到web框架中用于接收数据的函数,然后利用其他层以最终发送响应给web框架。它应该尽可能得小,因为它的代码是不能重复利用的,即使你尽量地缩减它,web表单也会促使其逐渐地变得复杂。

  • 模型层本质上是一个持久层:它最重要的依赖就是SQLAlchemy。模型知道如何去保存数据,构成整个项目中最可重用的代码。当然它并不清楚HTTP相关的内容和你所使用的框架。它代表了排除用户界面细节的系统本质内容。

但是稍等下,哪里?在视图还是模型中?你应该在哪里放置程序的灵魂:业务规则?模板层已经自动被排除掉,因为它并不是Python编写的。所以剩下3个可能的答案:

  • 视图层,这是最糟糕的选择。视图层应该仅仅包含中间代码,将代码数量保持尽可能得小,并且同系统中的其他部分隔离开,所以系统应该能在web框架中、使用中以及单元测试中独立访问。另外,业务逻辑应该存在于更加可重用的地方。视图层被视为展示逻辑的一部分,所以业务逻辑被排除在外。实际上,除了Web UI之外,在创建desktop UI时,开发者应该忽略视图和HTTP相关的内容,需要业务逻辑尽可能地被重用,因此,我们应该排除掉视图层。

  • 模型层,这个是可能的选择,因为模型层至少是可重用的。但是模型层主要关注于持久化,它应该更少地依赖于SQLAlchemy(它已经是一个非常复杂的东西)。

新的层,这才是正确的答案。下面将举例来更好地理解这部分内容。

如果你要创建一个博客,那么MTV正好满足你的需求。但是对于更加复杂的项目来说,其实还是至少缺了一层。你应该将业务逻辑旋转到一个新的、可重用的层次中,大多数人称之为“Service”层,但是我更喜欢称之为”Action“层。

为什么你需要这层?

在大型应用中,单个用户的操作引起多项活动是非常常见的。比如,用户成功地注册了你的服务,那么你的业务逻辑中可能会触发非常多的后台处理:

在关系数据库多张表中新增数据,使用了模型层。

将发送邮件给用户的任务放置到队列中。

将发送短信给用户的任务放置到队列中。

将创建实际使用服务时必需的空间和其他准备性资源的任务放置到队列中。

将更新用户数据的任务放置到队列中。

….

这是一个理解“业务逻辑“的好例子:给定一个用户操作(比如注册),系统就要做一些必需的操作。这种业务逻辑被单个函数捕获会更好;这个函数应该在哪一层呢?

如果所有的这些都实现在模型层,你能想象到它将变得多复杂吗?模型层在只面对持久化时已经很艰难了。现在想象一下模型层处理所有这些事务,它要使用多少外部服务?文件头部应该包括多少imports?反过来看,有多少模块愿意引入这个模型,可能在系统启动前就因为创建的循环依赖而导致系统崩溃。

循环依赖其实就是你没有正确认清系统架构的明显的标识。

对于依赖于Celery的模型来说,了解如何去发送邮件、短信以及使用外部服务等是不应该的做法。持久化对于模型层来说已经是非常复杂的主题了。你应该在模型层之外去处理这些业务逻辑——在模型层和视图层之间的一层。所以称之为”Action“层。

另外,模型层在关系型数据库中经常被映射到一张单独的表。如果你在用户表和订阅表中插入一条记录,哪一个模型应该包含上述逻辑呢?这几乎是不能确定的。因为实际执行的操作远超过了用户表和订阅表的范围。因此,业务逻辑应该被定义在任何模型之外。

当开发人员执行维护时,有时她想按步骤地执行每一步,而有时她想一次性执行完所有操作。分别地实现这些操作并在单个Action层函数中调用是有帮助的。

你可能会怀疑我提出的方法难道不是反面模式域模型的一种吗?没有动作的模型恰恰与面向对象设计相反!我并没有说“将所有的方法从模型中移除”。我的意思是指“将需要外部调用的方法移除”。模型中的方法仅仅用来使用它所需要的数据,并且属于模型中的那些数据。一个面向世界的,调用外部服务的并且很少使用自身数据的方法不应该被放置在模型中。

另一个使得这种架构成功的原因就是测试。TDD教会了程序员去让程序变得解耦,这样做通常会使得软件更加健壮。如果你想要创建一个Celery的应用,并且在你测试之前已经知道了其他的外部服务,那么你将经常陷入头痛中。

还有将业务逻辑放置在视图层之外的最后一个原因。在未来,当你最终决定从Flask过渡到Pyramid时,你很乐意将视图层保持简洁。如果所有的视图都是在与web框架间交互,并且动作层会执行所有的函数,那么你的代码就做到了非常好的隔离。Web框架通常都很贪婪,不要让你的系统跟随他们的脚步。

所以下面就是我所提议的在Python中构建大型应用的层次结构:

模型层是最底层的,最可重用并且可见的层。它仅专注于持久化,模型层是可以包含动作的,只不过这个动作仅仅属于这个模型。模型可以被其他层所返回,以各种方式在请求的结尾返回给模板。

外部服务。对每个服务都创建一个比如说发送邮件。

动作层。这是系统中的核心层,它包含了业务逻辑以及工作流。它使用外部服务去实现特定的目标并且借助模型层来持久化数据。通过以上这些层,它支撑起了整个系统,包括配置,除了用户界面。

模板层仅包括了页面展示逻辑比如说从列表中循环输出构成一个HTML表格。

视图层,这是最高层的,最不可重用的层。它依赖于(与系统中其他层隔离)web框架。并且依赖于表单验证库。它可以看到模板层以及动作层,但是不能直接调用模型层——它必须通过动作层。但是当一个动作层返回了模型数据,那么它可以被传递给模板中(一个Celery任务可以类比于一个web视图)。

这种体系结构有助于避免了在会话层中进行调试因为它清楚地定义了各自的职责。同时它也是明显经得起检测的,因为它做到了很好的解耦,因此可以减少测试的并且减少了模拟的次数。

好的架构总是解耦性非常好的。如果你曾经陷入到一个循环依赖中,你可以想一下是否真的定义好了每一层的职责。当你放弃了并且从一个函数中引入内容,那么你的架构已经失败了。

这并不是说你的web应用必须与Celery应用隔离开。在他们之间可以重用代码——特别是模型——但是对于Celery应用来说,不应该引入web框架!获取配置也不例外,因为在Python中读取配置是非常简单的。

python-top-30

发表于 2018-03-17 | 阅读次数:
字数统计: 368 | 阅读时长 ≈ 1

1.家庭助理

2.PyTorch

3.Grumpy

4.sanic

5.Python-fire

6.spaCy

7.pipenv

8.micropython

9.prophet

10.SerpentAI

11.dash

12.instaPy

13.apistar

14.faiss

15.MechanicalSoup:一个与网站自动化交互的Python库

16.Better-exceptions:用Python编写的自动地漂亮的和有用的异常处理

17.Flashtext:从句子中提取关键词或替换句子中的关键词

18.Maya:用Python实现人类的日期时间

19.Mimesis (v1.0):Python库,有助于为不同的目的以不同的语言生成模拟数据。这些数据在软件开发和测试的不同阶段特别有用

20.开放式无纸化:扫描、索引和归档所有的纸质文档。一个文档管理系统

21.Fsociety:黑客工具包。渗透测试框架

22.LivePython:实时可视化跟踪Python代码

23.Hatch:用于Python的现代项目、包和虚拟环境管理器

24.Tangent:用纯Python实现源到源的可调试导数

25.Clairvoyant:识别和监控短期股票走势的历史线索的Python程序

26.MonkeyType:Python通过收集运行时类型生成静态类型注释的系统

27.Eel:一个小的Python库,用于制作简单的电子类HTML / js GUI应用程序

28.Surprise v1.0:建立和分析推荐系统的Python scikit**

29.Gain:获取每个人的Web爬行框架

30.PDFTabExtract: 一组从PDF文件中提取表的工具,有助于对扫描文档进行数据挖掘

简单看看汤不热的网站

发表于 2018-03-15 | 分类于 pachong | 阅读次数:
字数统计: 647 | 阅读时长 ≈ 2

这里就不截图进行介绍了

每个tumblr的个人空间都是一个二级域名,你甚至可以绑定你自己的域名。

在个人主页上, 是一个微博式的消息列表,有文字,图片,视频等形式。消息的展现,是页面上的JavaScript脚本 通过请求Tumblr的Api来获取返回信息,然后添加到页面上的。通过API,可以省掉很多麻烦,至少不必分析整个页面的html来提取需要的信息了。

基础的url 是这样的:

http://{0}.tumblr.com/api/read?type{1}&num={2}&start={3}

第一个参数是要访问的用户空间的用户名;

第二个参数是媒体类型, 图片为“photo”,视频为“video”;

第三个参数为请求的资源数;

第四个参数为从第几个资源开始

返回的数据是XML格式的数据,基本的层级为Tumblr>posts>post。图片的URL在post的photo-url字段中,视频与此类似,就不再演示了。 获取到媒体资源的url之后,就可以进行下载了。

video类型的资源的url,需要从player属性中进行进一步匹配才能得出最后的结果。

技术的分析:

  • 发送http请求 使用requests模块
  • 返回的数据是xml xmltodic模块,将xml文档处理成类似Json对象,方便我们对数据进行访问。
  • 队列实现

    python中自带Queue模块,可以满足我们目前的队列需求,由于python2.7和python3.0中

    对queue模块的命名进行的变更,编程的时候需要注意。如果考虑兼容两个版本的话,可以 考虑引入

    six模块(https://pypi.python.org/pypi/six)。

    six模块是一个专门用于解决 从python2.x到python3.x的兼容性问题的模块,

    它对python版本变更导致到部分模块不能应用的问题 进行了内部处理,

    需要处理类似兼容问题的时候,可以考虑或者参考该模块的实现方式。

  • 多线程进行下载

  • 处理json

考虑到Tumblr需要FQ访问,如果本机不使用VPN的话,可能需要配置代理,代理采用json配置方式。 处理.使用python内置的json模块(https://docs.python.org/2/library/json.html)就可以了。

  • 使用正则

为了精确匹配url信息,我们需要使用正则表达式对xml数据的中字段值进行进一步处理,使用 内置的re模块(https://docs.python.org/2/library/re.html)就可以了。

django项目配置小结

发表于 2018-03-14 | 分类于 web | 阅读次数:
字数统计: 3.1k | 阅读时长 ≈ 12

dockerfile 的配置

FROM python:3.5

COPY ./requirements.txt /src
WORKDIR /src
RUN pip install -r requirements.txt

COPY . /src
EXPOSE <PORT>
CMD uwsgi --http :<PORT> --wsgi-file <path/to/wsgi.py>

python 3.3开始 标准库已经支持创建虚拟环境

pyvenv ENV
# 使用k神的pipenv 管理项目依赖
# 使用ensurepip 模块来引导启动pip python 3.4开始
python -m ensurepip 

使用 gitignore.io这个网站提供的 .gitignore 文件管理代码库文件

日志管理的话(以下是直接写到标准输出)

# settings.py 

# ...

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '[application] %(levelname)s %(asctime)s %(module)s %(message)s'
        }
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'stream': sys.stdout,
            'formatter': 'verbose'
        },
    },
    'loggers': {
        'app': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

自动化测试

既然是纯后端项目,工程师完全可以通过自动化测试来检测自己的代码。Django 本身对测试提供了很好的支持,可以通过 sqlite 来搭建测试数据库,还有基于内存的缓存,做测试不会增加对其他系统的依赖。开发起来事半功倍。

除了要写自动化测试代码,还要能统计测试覆盖率。目前我们用的是 coverage.py 这个工具,说实话没有 node.js 的 istanbul 好用,输出的报告没有 Istanbul 详细和易读。不过用来检查 “死代码” 还是够用的。

针对 http 代码的测试

有些项目需要对接的第三方系统比较多,比如微信认证、支付、短信等常见的,可能还有其他一些垂直业务领域的系统。这部分接口对接的代码,也应该纳入到测试当中,毕竟 Python 作为脚本语言,代码很容易出现错误。

这块一般是用 responses 这个模块来 mock http 请求。

定时任务

封装到command

import schedule
from django.core.management.base import BaseCommand

class Command(BaseCommand):

    def handle(self, *args, **kwargs):

        schedule.every(45).minutes.do(do_this)
        schedule.every().day.at('04:00').do(do_that)

        while True:
            schedule.run_pending()
            time.sleep(1)    

Django如何处理并发

  • django本身提供了一个wsgi的接口,可以通过gevent,uwsgi,fastcgi等实现高并发,这里的高并发采用协程,线程,和进程都可能,或者同时采用几种。

  • 对于操作数据库来说,线程安全其实不用太考虑,因为数据库的链接已经由数据库的连接池处理了,反而是数据库的竞争性的访问需要考虑多进程安全的问题,比如同时写某一个统计信息,那么就需要对这样的请求加锁,或这cas这样的机制来保证这种写操作不会冲突。

配置举例:

# /usr/local/nginx/conf/nginx.conf
server {  
    listen       8088;  
    server_name  localhost;  

    #charset koi8-r;  

    #access_log  logs/host.access.log  main;  

    location / {  
        root   html;  
        index  index.html index.htm;  
    }  



## 修改配置文件
server {
        listen       8088;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
       #注释以下四行
        #location / {
            #root   html;
            #index  index.html index.htm;
        #}
       #添加以下内容
        location / {
            try_files @uri @pp;
        }
        location @pp {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_pass http://127.0.0.1:5000;
            #反向代理的这个IP和端口
        }


## gun 配置文件 config.py

import gevent.monkey
import multiprocessing

gevent.monkey.patch_all()

#监听本机的5000端口
bind='0.0.0.0:5000'

preload_app = True

#开启进程
#workers=4
workers = multiprocessing.cpu_count() * 2 + 1

#每个进程的开启线程
threads = multiprocessing.cpu_count() * 2

backlog=2048

#工作模式为gevent
worker_class="gevent"

# debug=True

#如果不使用supervisord之类的进程管理工具可以是进程成为守护进程,否则会出问题
daemon = True

#进程名称
proc_name='gunicorn.pid'

#进程pid记录文件
pidfile='app_pid.log'

loglevel='debug'
logfile = 'debug.log'
accesslog = 'access.log'
access_log_format = '%(h)s %(t)s %(U)s %(q)s'
errorlog = 'error.log’

启动:

gunicorn -c config.py(gunicorn配置文件)  flask_nginx(flask启动文件):app

启动nginx:

sudo /usr/local/nginx/sbin/nginx    

Siege命令常用参数

-c 500 指定并发数500
-r 5 指定测试的次数5
-f urls.txt 制定url的文件
-i internet系统,随机发送url
-b 请求无需等待 delay=0
-t 5 持续测试5分钟,默认是分,5s为5秒
# -t和-r不能同时使用

模拟1000个并发向URL发送5次
测试:

siege -c 1000 -r 5 http://127.0.0.1:8088(gunicorn+gevent+nginx+flask)

从 Tornado 说起

刚开始,对 Tornado 的感觉最为新鲜,在官网介绍里其是一个无阻塞的Web服务器以及相关工具的集合,但 个人更为倾向其为一个颇为完备的微型 web 框架。Tornado 性能好的关键是其无阻塞异步的特性,但这魔术 似的效果是如何达成的呢?迷思与困惑。我那小脑袋里的思维还停留于多进程(多线程)那样的并发模型中, 实在有点难以理解 Tornado 的异步机制。

通过查阅各式文章以及源代码,整体的框架脉络开始逐渐在脑海中显现出来。其实,Tornado 的异步模型 是由事件驱动以及特定的回调函数(callback)所组成的!一直没有弄明白,Tornado 具体是如何实现 无阻塞异步,当清楚了事件驱动和回调函数的概念后,事情似乎又变得简单起来了。

对于一般的程序,在执行阶段若遇到 I/O 事件,整个进程将被阻塞住,直到 I/O 事件结束,程序又继续执行。 接设我们对一些 I/O 事件进行了定制,使其可以立即返回(即无阻塞),那么程序将能立即继续执行。但 问题又来了,那当 I/O 事件完成后又该怎么办呢?此时,回调函数的威力就出来了,我只需要将进行特定 处理的回调函数与该 I/O 事件绑定起来,当该 I/O 事件完成后就调用绑定的回调函数,就可以处理具体的 I/O 事件啦。啊,似乎还有一个问题,回调函数要如何与 I/O 事件绑定起来?最简单的想法是,直接通过 一个 while True 循环不断的轮询,当检测到 I/O 事件完成了即触发回调函数。但是,这样的效率当然不会 高,利用系统中高效的 I/O 事件轮询机制(epoll on Linux, kqueue on most BSD)就是最明智的 解决方案。于是,无阻塞 I/O +事件驱动+高效轮询方式便组成了 Tornado 的异步模型。

Tornado 的核心是 ioloop 和 iostream 这两个模块,前者提供了一个高效的 I/O 事件循环,后者则封装了 一个无阻塞的 socket 。通过向 ioloop 中添加网络 I/O 事件,利用无阻塞的 socket ,再搭配相应的回调 函数,便可达到梦寐以求的高效异步执行啦。多说无益,来看一下具体的示例:

from tornado import ioloop
from tornado.httpclient import AsyncHTTPClient

urls = ['http://www.google.com', 'http://www.yandex.ru', 'http://www.python.org']

def print_head(response):
    print ('%s: %s bytes: %r' % (response.request.url,
                                 len(response.body),
                                 response.body[:50]))

http_client = AsyncHTTPClient()
for url in urls:
    print ('Starting %s' % url)
    http_client.fetch(url, print_head)
ioloop.IOLoop.instance().start()    

因为使用了 AsyncHTTPClient 来处理请求操作,整个示例是异步执行的,即三个url请求无等待的依次发出。 我们可以看到 fetch 方法使用了 print_head 函数来作为回调函数,这意味着,当 fetch 完成了请求操作, 相应的 print_head 函数便会被触发调用。恩,… 额,…,乍看起来,使用 Tornado 进行异步编程似乎 并不难,让人跃跃欲试。但实际上,在现实生活中,事件驱动的编程还是会很费脑力,需要一定的创造性思维。 不过,这也许是 Tornado 受欢迎的原因之一呢。


看下 Gevent

Gevent 是基于协程(coroutine)实现的 Python 网络库,使用了轻量级的 greenlet 作为执行单元,并 基于 libevent 事件循环构建了直观的调用接口。

当时看到这样的描述,脑袋的第一反应是,协程??稍稍了解后,发现协程其实也不是什么高深的概念,协程 也被称为微线程,一看这别名就知道跟线程应该很类似。作为类比倒也可以这么认为,两者关键的区别在于, 线程是由系统进行调度的,而协程是由用户自己进行调度的。当知道这一事实后,立刻想到,这自行调度灵活 肯定是会很灵活,但要调度的话可是很有难度的吧?调度的方法暂时不谈,除了更为灵活外,自行调度的直接 结果当然就是省去了系统调度(什么用户态转内核态,以及什么 context switch),因此协程间切换的资源 消耗很小,再配合协程生成成本很低的另一特点,这可真是相当的美妙。事实上,Python 语言本身就支持基础 的协程的概念,generator 是其中的产物(这里)。

对于 Gevent,其使用的协程实际上就是 greenlet 。当你使用 greenlet 生成了一些协程,就可以在这些 协程里不断跳转执行,两个 greenlet 之间的跳转被称为切换(switch)。通过切换,我们就可以实现对协程 的调度。还应该知道的是,每个 greenlet 都拥有一个父 greenlet ,这是在 greenlet 初始化时就确定的。 当一个 greenlet 执行完毕后,执行权会切换到其父 greenlet 中。实际上,所有的 greenlet 会被组织成 一颗树,树根便是最“老资格”的 greenlet ,这个老 greenlet 确定了各 greenlet 间的逻辑关系。

上面说到协程必须自行调度,不会是要自己构造一个调度器吧?这当然可以做到,但不是必须,因为 Gevent 已经基于 greenlet 和 libevent 封装了许多基础常用的库,例如 socket 、event 和 queue 等,只要使用 这些库进行开发,或者对使用的标准库或第三方库打一下补丁(monket patch),就能保证生成的各协程在 I/O 等待时正确地进行切换,从而实现无阻塞的异步执行。

刚接触 Gevent 时,感觉跟传统的并发编程很类似,但了解渐深后,才发现这货实际上跟 Tornado 更为类似。 因为, Gevent 本质上也是事件驱动。实现的策略可以是,在将要执行 I/O 阻塞事件时,先在事件循环中对该事件 进行注册,关联的回调函数便是对当前协程的切换操作(current_greenlet.switch()),注册成功后即 切换回当前协程的父协程中进行执行(current_greenlet.parent.switch())。当注册的 I/O 事件被 触发后,事件循环在恰当时机便会执行该回调函数,也就是切换到原先的协程继续执行程序。从而,就实现 无阻塞的 I/O 事件处理。怎样,是否感觉相当的有趣? :)

Gevent 了不得的地方还在于,我们能像编写一般程序那样来编写异步程序,这可是弥足珍贵。为了更直观的 显示,让我们来看一下具体的运行示例:

import gevent
from gevent import monkey
# patches stdlib (including socket and ssl modules) to cooperate with other greenlets
monkey.patch_all()

import urllib2

urls = ['http://www.google.com', 'http://www.yandex.ru', 'http://www.python.org']

def print_head(url):
    print ('Starting %s' % url)
    data = urllib2.urlopen(url).read()
    print ('%s: %s bytes: %r' % (url, len(data), data[:50]))

jobs = [gevent.spawn(print_head, url) for url in urls]

gevent.joinall(jobs)

上面示例做的事情实际上跟前面 Tornado 的示例是一样,同样是异步的对url进行请求。在我看来,使用 Gevent 进行编程,无论是可读性还是可操作性都能让人满意。但也要清楚,在实际操作中,为了达到较理想 效果,经常还是需要根据不同的情况对代码进行一些相应的“雕琢”。还有一点很常被人忽略, Gevent 是 基于协程实现的 Python 网络库,其适用面更多的是在于网络 I/O 频繁的需求里,很多情况下 Gevent 可能 并不是很好的选择。总的来说,Gevent 确实很讨人喜爱,性能好,开销小,代码易维护,是广大 pythoner 手中的一大利器。

1…909192…109
John Cheung

John Cheung

improve your python skills

543 日志
33 分类
45 标签
RSS
GitHub Email
© 2020 John Cheung
本站访客数:
|
主题 — NexT.Pisces v5.1.4
博客全站共226.3k字