您的位置:新葡亰496net > 奥门新萄京娱乐场 > 新葡亰496net:主干语法,多进度与二十多线程

新葡亰496net:主干语法,多进度与二十多线程

发布时间:2019-07-05 12:57编辑:奥门新萄京娱乐场浏览(60)

    Python语言 近年来 人气爆棚 。它广泛应用于网络开发运营,数据科学,网络开发,以及网络安全问题中。

    一、            Python主要应用领域

    编译型与解释型。

    GIL 是啥子东西

    先声明下 GIL 其实并不是 Python 语言的特性,它其实是在实现 Python 解释器(CPython)时所引入的一个概念。

    那么,CPython 又是什么呢? 类比 C ,这是一套语言的标准,但是可以再不同的编译器上来编译成可执行代码。比较有名的编译器例如 GCC, Visual C 等。Python 也一样,相同的代码可以通过 CPython、PyPy、Psyco、JPython等不同的 Python 环境来执行。比如 JPython 就没有 GIL。

    因为 CPython 是大部分环境下默认的 Python 执行环境,所以很多人在概念上就默认 CPython 就是 Python,也就想当然的把 GIL 作为了 Python 语言的缺陷。所以我这里再次明确一点: GIL 并不是 Python 的特性, Python 完全可以不依赖于 GIL。

    那么 CPython 中的 GIL 又是什么呢?

    全称是 Global Interpreter Lock。

    官网是这么解释的:

    一个防止多线程并发执行机器码的一个 Mutex。

    尼玛啊,这不就是一个 Bug 般存在的全局锁嘛!表急。。。

    新葡亰496net 1

    然而, Python 在速度上完全没有优势可言。

    (1)       云计算

      编译器是把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快; 

    真是 Bug ?

    玩过 Python 的,我相信不少都在担心 GIL 在影响多线程程序的执行性能。但,我这次想说你担心的未免太多了。

    这并不是 Python 中的 Bug ,它的存在只不过解决了部分问题,并不能解决全部的问题。毕竟人都无完人。

    图片来自 unsplash

    在速度上,Java如何同C,C ,C#或者Python相比较?答案几乎完全取决于要运行的应用。在这个问题上,没有完美的评判标准,然而The Computer Language Benchmarks Game 是一个不错的方法。

    (2)       WEB开发

      而解释器则是只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的.

    那么,我们应该怎么合理的使用 GIL 呢?

    新葡亰496net:主干语法,多进度与二十多线程。在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。

    而对于依赖CPU的程序,你需要弄清楚执行的计算的特点。 例如,优化底层算法要比使用多线程运行快得多。 类似的,由于Python是解释执行的,如果你将那些性能瓶颈代码移到一个C语言扩展模块中, 速度也会提升的很快。如果你要操作数组,那么使用NumPy这样的扩展会非常的高效。 最后,你还可以考虑下其他可选实现方案,比如PyPy,它通过一个JIT编译器来优化执行效率 (不过在写这本书的时候它还不能支持Python 3)。

    还有一点要注意的是,线程不是专门用来优化性能的。 一个CPU依赖型程序可能会使用线程来管理一个图形用户界面、一个网络连接或其他服务。 这时候,GIL会产生一些问题,因为如果一个线程长期持有GIL的话会导致其他非CPU型线程一直等待。 事实上,一个写的不好的C语言扩展会导致这个问题更加严重, 尽管代码的计算部分会比之前运行的更快些。

    说了这么多,现在想说的是我们有两种策略来解决GIL的缺点。 首先,如果你完全工作于Python环境中,你可以使用 multiprocessing 模块来创建一个进程池, 并像协同处理器一样的使用它。例如,假如你有如下的线程代码:

    # Performs a large calculation (CPU bound)
    def some_work(args):
        ...
        return result
    
    # A thread that calls the above function
    def some_thread():
        while True:
            ...
            r = some_work(args)
        ...
    

    修改代码,使用进程池:

    # Processing pool (see below for initiazation)
    pool = None
    
    # Performs a large calculation (CPU bound)
    def some_work(args):
        ...
        return result
    
    # A thread that calls the above function
    def some_thread():
        while True:
            ...
            r = pool.apply(some_work, (args))
            ...
    
    # Initiaze the pool
    if __name__ == '__main__':
        import multiprocessing
        pool = multiprocessing.Pool()
    

    这个通过使用一个技巧利用进程池解决了GIL的问题。 当一个线程想要执行CPU密集型工作时,会将任务发给进程池。 然后进程池会在另外一个进程中启动一个单独的Python解释器来工作。 当线程等待结果的时候会释放GIL。 并且,由于计算任务在单独解释器中执行,那么就不会受限于GIL了。 在一个多核系统上面,你会发现这个技术可以让你很好的利用多CPU的优势。

    另外一个解决GIL的策略是使用C扩展编程技术。 主要思想是将计算密集型任务转移给C,跟Python独立,在工作的时候在C代码中释放GIL。 这可以通过在C代码中插入下面这样的特殊宏来完成:

    #include "Python.h"
    ...
    
    PyObject *pyfunc(PyObject *self, PyObject *args) {
       ...
       Py_BEGIN_ALLOW_THREADS
       // Threaded C code
       ...
       Py_END_ALLOW_THREADS
       ...
    }
    

    如果你使用其他工具访问C语言,比如对于Cython的ctypes库,你不需要做任何事。 例如,ctypes在调用C时会自动释放GIL。

    前言:为什么有人说 Python 的多线程是鸡肋,不是真正意义上的多线程?

    链接:

    (3)       科学计算、人工智能

      动态语言和静态语言
      通常我们所说的动态语言、静态语言是指动态类型语言和静态类型语言。

    讨论

    许多程序员在面对线程性能问题的时候,马上就会怪罪GIL,什么都是它的问题。 其实这样子太不厚道也太天真了点。 作为一个真实的例子,在多线程的网络编程中神秘的 stalls 可能是因为其他原因比如一个DNS查找延时,而跟GIL毫无关系。 最后你真的需要先去搞懂你的代码是否真的被GIL影响到。 同时还要明白GIL大部分都应该只关注CPU的处理而不是I/O.

    如果你准备使用一个处理器池,注意的是这样做涉及到数据序列化和在不同Python解释器通信。 被执行的操作需要放在一个通过def语句定义的Python函数中,不能是lambda、闭包可调用实例等, 并且函数参数和返回值必须要兼容pickle。 同样,要执行的任务量必须足够大以弥补额外的通信开销。

    另外一个难点是当混合使用线程和进程池的时候会让你很头疼。 如果你要同时使用两者,最好在程序启动时,创建任何线程之前先创建一个单例的进程池。 然后线程使用同样的进程池来进行它们的计算密集型工作。

    C扩展最重要的特征是它们和Python解释器是保持独立的。 也就是说,如果你准备将Python中的任务分配到C中去执行, 你需要确保C代码的操作跟Python保持独立, 这就意味着不要使用Python数据结构以及不要调用Python的C API。 另外一个就是你要确保C扩展所做的工作是足够的,值得你这样做。 也就是说C扩展担负起了大量的计算任务,而不是少数几个计算。

    这些解决GIL的方案并不能适用于所有问题。 例如,某些类型的应用程序如果被分解为多个进程处理的话并不能很好的工作, 也不能将它的部分代码改成C语言执行。 对于这些应用程序,你就要自己需求解决方案了 (比如多进程访问共享内存区,多解析器运行于同一个进程等)。 或者,你还可以考虑下其他的解释器实现,比如PyPy。

    看到这里,也许你会疑惑。这很正常,所以让我们带着问题来阅读本文章吧。
    问题:
    1、Python 多线程为什么耗时更长?
    2、为什么在 Python 里面推荐使用多进程而不是多线程?

    (4)       系统运维

       (1)动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言,其他的各种脚本语言如VBScript也多少属于动态类型语言。

    1 基础知识

    现在的 PC 都是多核的,使用多线程能充分利用 CPU 来提供程序的执行效率。

    基于我对The Computer Language Benchmarks Game超过十年的观察,相比于Java,C#,Go,JavaScript, C 等,Python是最慢的语言之一。其中包括了 JIT (C#, Java) 和 AOT (C, C )编译器,以及解释型语言,例如JavaScript。

    (5)       金融

        (2)静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C 是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。

    1.1 线程

    线程是一个基本的 CPU 执行单元。它必须依托于进程存活。一个线程是一个execution context(执行上下文),即一个 CPU 执行时所需要的一串指令。

    动态编译:

    (6)       图形GUI

      强类型定义语言和弱类型定义语言

    1.2 进程

    进程是指一个程序在给定数据集合上的一次执行过程,是系统进行资源分配和运行调用的独立单位。可以简单地理解为操作系统中正在执行的程序。也就说,每个应用程序都有一个自己的进程。

    每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。

    二、            Python在一些公司的应用

        (1)强类型定义语言:强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。举个例子:如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。强类型定义语言是类型安全的语言。

    1.3 两者的区别

    • 线程必须在某个进行中执行。
    • 一个进程可包含多个线程,其中有且只有一个主线程。
    • 多线程共享同个地址空间、打开的文件以及其他资源。
    • 多进程共享物理内存、磁盘、打印机以及其他资源。

    静态编译:

    (1)       谷歌

        (2)弱类型定义语言:数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。

    1.4 线程的类型

    线程的因作用可以划分为不同的类型。大致可分为:

    • 主线程
    • 子线程
    • 守护线程(后台线程)
    • 前台线程

    (2)       CIA

    强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。另外,“这门语言是不是动态语言”与“这门语言是否类型安全”之间是完全没有联系的!
      例如:Python是动态语言,是强类型定义语言(类型安全的语言); VBScript是动态语言,是弱类型定义语言(类型不安全的语言); JAVA是静态语言,是强类型定义语言(类型安全的语言)。

    2 Python 多线程

    注意:当我提到“Python”时,我指的是CPython这个官方的解释器。我也将在本文中提及其他的解释器。

    (3)       NASA

      通过上面这些介绍,我们可以得出,python是一门动态解释性的强类型定义语言。

    2.1 GIL

    其他语言,CPU 是多核时是支持多个线程同时执行。但在 Python 中,无论是单核还是多核,同时只能由一个线程在执行。其根源是 GIL 的存在。

    GIL 的全称是 Global Interpreter Lock(全局解释器锁),来源是 Python 设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是“通行证”,并且在一个 Python 进程中,GIL 只有一个。拿不到通行证的线程,就不允许进入 CPU 执行。

    而目前 Python 的解释器有多种,例如:

    • CPython:CPython 是用C语言实现的 Python 解释器。 作为官方实现,它是最广泛使用的 Python 解释器。

    • PyPy:PyPy 是用RPython实现的解释器。RPython 是 Python 的子集, 具有静态类型。这个解释器的特点是即时编译,支持多重后端(C, CLI, JVM)。PyPy 旨在提高性能,同时保持最大兼容性(参考 CPython 的实现)。

    • Jython:Jython 是一个将 Python 代码编译成 Java 字节码的实现,运行在JVM (Java Virtual Machine) 上。另外,它可以像是用 Python 模块一样,导入 并使用任何Java类。

    • IronPython:IronPython 是一个针对 .NET 框架的 Python 实现。它 可以用 Python 和 .NET framewor k的库,也能将 Python 代码暴露给 .NET 框架中的其他语言。

    GIL 只在 CPython 中才有,而在 PyPy 和 Jython 中是没有 GIL 的。

    每次释放 GIL锁,线程进行锁竞争、切换线程,会消耗资源。这就导致打印线程执行时长,会发现耗时更长的原因。

    并且由于 GIL 锁存在,Python 里一个进程永远只能同时执行一个线程(拿到 GIL 的线程才能执行),这就是为什么在多核CPU上,Python 的多线程效率并不高的根本原因。

    我想要回答这样一个问题:当运行同一个程序时,为什么Python会 比其他语言慢2到10倍?为什么我们无法将它变得更快?

    (4)       YouTube

      python的优缺点。

      先看优点

    1. Python的定位是“优雅”、“明确”、“简单”,所以Python程序看上去总是简单易懂,初学者学Python,不但入门容易,而且将来深入下去,可以编写那些非常非常复杂的程序。
    2. 开发效率非常高,Python有非常强大的第三方库,基本上你想通过计算机实现任何功能,Python官方库里都有相应的模块进行支持,直接下载调用后,在基础库的基础上再进行开发,大大降低开发周期,避免重复造轮子。
    3. 高级语言————当你用Python语言编写程序的时候,你无需考虑诸如如何管理你的程序使用的内存一类的底层细节
    4. 可移植性————由于它的开源本质,Python已经被移植在许多平台上(经过改动使它能够工 作在不同平台上)。如果你小心地避免使用依赖于系统的特性,那么你的所有Python程序无需修改就几乎可以在市场上所有的系统平台上运行
    5. 可扩展性————如果你需要你的一段关键代码运行得更快或者希望某些算法不公开,你可以把你的部分程序用C或C 编写,然后在你的Python程序中使用它们。
    6. 可嵌入性————你可以把Python嵌入你的C/C 程序,从而向你的程序用户提供脚本功能。

      再看缺点:

    1. 速度慢,Python 的运行速度相比C语言确实慢很多,跟JAVA相比也要慢一些,因此这也是很多所谓的大牛不屑于使用Python的主要原因,但其实这里所指的运行速度慢在大多数情况下用户是无法直接感知到的,必须借助测试工具才能体现出来,比如你用C运一个程序花了0.01s,用Python是0.1s,这样C语言直接比Python快了10倍,算是非常夸张了,但是你是无法直接通过肉眼感知的,因为一个正常人所能感知的时间最小单位是0.15-0.4s左右,哈哈。其实在大多数情况下Python已经完全可以满足你对程序速度的要求,除非你要写对速度要求极高的搜索引擎等,这种情况下,当然还是建议你用C去实现的。
    2. 代码不能加密,因为PYTHON是解释性语言,它的源码都是以名文形式存放的,不过我不认为这算是一个缺点,如果你的项目要求源代码必须是加密的,那你一开始就不应该用Python来去实现。
    3. 线程不能利用多CPU问题,这是Python被人诟病最多的一个缺点,GIL即全局解释器锁(Global Interpreter Lock),是计算机程序设计语言解释器用于同步线程的工具,使得任何时刻仅有一个线程在执行,Python的线程是操作系统的原生线程。在Linux上为pthread,在Windows上为Win thread,完全由操作系统调度线程的执行。一个python解释器进程内有一条主线程,以及多条用户程序的执行线程。即使在多核CPU平台上,由于GIL的存在,所以禁止多线程的并行执行。关于这个问题的折衷解决方法,我们在以后线程和进程章节里再进行详细探讨。

      当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。

      由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。

    2.2 创建多线程

    Python提供两个模块进行多线程的操作,分别是threadthreading
    前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。

    • 方法1:直接使用threading.Thread()
    import threading
    
    # 这个函数名可随便定义
    def run(n):
        print("current task:", n)
    
    if __name__ == "__main__":
        t1 = threading.Thread(target=run, args=("thread 1",))
        t2 = threading.Thread(target=run, args=("thread 2",))
        t1.start()
        t2.start()
    
    • 方法2:继承threading.Thread来自定义线程类,重写run方法
    import threading
    
    class MyThread(threading.Thread):
        def __init__(self, n):
            super(MyThread, self).__init__()  # 重构run函数必须要写
            self.n = n
    
        def run(self):
            print("current task:", n)
    
    if __name__ == "__main__":
        t1 = MyThread("thread 1")
        t2 = MyThread("thread 2")
    
        t1.start()
        t2.start()
    

    以下是最主要的原因:

    (5)       Dropbox

      python的种类。

      CPython

        当我们从Python官方网站下载并安装好Python 3.6后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

        CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。

      IPython

        IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

        CPython用>>>作为提示符,而IPython用In [``序号``]:作为提示符。

      PyPy

        PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。

        绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。

      Jython

        Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

      IronPython

    新葡亰496net:主干语法,多进度与二十多线程。    IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

    Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。

    2.3 线程合并

    Join函数执行顺序是逐个执行每个线程,执行完毕后继续往下执行。主线程结束后,子线程还在运行,join函数使得主线程等到子线程结束时才退出。

    import threading
    
    def count(n):
        while n > 0:
            n -= 1
    
    if __name__ == "__main__":
        t1 = threading.Thread(target=count, args=("100000",))
        t2 = threading.Thread(target=count, args=("100000",))
        t1.start()
        t2.start()
        # 将 t1 和 t2 加入到主线程中
        t1.join()
        t2.join()
    
    • “它是GIL(Global Interpreter Lock全局解释器锁)”
    • “它是解释型语言而非编译语言”
    • “它是动态类型语言”

    (6)       Instagram

       内容编码。

        python2解释器在加载 .py 文件中的代码时,会对内容进行编码(默认ascill),而python3对内容进行编码的默认为utf-8。

        ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言,其最多只能用 8 位来表示(一个字节),即:2**8 = 256,所以,ASCII码最多只能表示 256 个符号。

    Bin(二进制)
    Oct(八进制)
    Dec(十进制)
    Hex(十六进制)
    缩写/字符
    解释
    0000 0000
    0
    0
    00
    NUL(null)
    空字符
    0000 0001
    1
    1
    01
    SOH(start of headline)
    标题开始
    0000 0010
    2
    2
    02
    STX (start of text)
    正文开始
    0000 0011
    3
    3
    03
    ETX (end of text)
    正文结束
    0000 0100
    4
    4
    04
    EOT (end of transmission)
    传输结束
    0000 0101
    5
    5
    05
    ENQ (enquiry)
    请求
    0000 0110
    6
    6
    06
    ACK (acknowledge)
    收到通知
    0000 0111
    7
    7
    07
    BEL (bell)
    响铃
    0000 1000
    10
    8
    08
    BS (backspace)
    退格
    0000 1001
    11
    9
    09
    HT (horizontal tab)
    水平制表符
    0000 1010
    12
    10
    0A
    LF (NL line feed, new line)
    换行键
    0000 1011
    13
    11
    0B
    VT (vertical tab)
    垂直制表符
    0000 1100
    14
    12
    0C
    FF (NP form feed, new page)
    换页键
    0000 1101
    15
    13
    0D
    CR (carriage return)
    回车键
    0000 1110
    16
    14
    0E
    SO (shift out)
    不用切换
    0000 1111
    17
    15
    0F
    SI (shift in)
    启用切换
    0001 0000
    20
    16
    10
    DLE (data link escape)
    数据链路转义
    0001 0001
    21
    17
    11
    DC1 (device control 1)
    设备控制1
    0001 0010
    22
    18
    12
    DC2 (device control 2)
    设备控制2
    0001 0011
    23
    19
    13
    DC3 (device control 3)
    设备控制3
    0001 0100
    24
    20
    14
    DC4 (device control 4)
    设备控制4
    0001 0101
    25
    21
    15
    NAK (negative acknowledge)
    拒绝接收
    0001 0110
    26
    22
    16
    SYN (synchronous idle)
    同步空闲
    0001 0111
    27
    23
    17
    ETB (end of trans. block)
    结束传输块
    0001 1000
    30
    24
    18
    CAN (cancel)
    取消
    0001 1001
    31
    25
    19
    EM (end of medium)
    媒介结束
    0001 1010
    32
    26
    1A
    SUB (substitute)
    代替
    0001 1011
    33
    27
    1B
    ESC (escape)
    换码(溢出)
    0001 1100
    34
    28
    1C
    FS (file separator)
    文件分隔符
    0001 1101
    35
    29
    1D
    GS (group separator)
    分组符
    0001 1110
    36
    30
    1E
    RS (record separator)
    记录分隔符
    0001 1111
    37
    31
    1F
    US (unit separator)
    单元分隔符
    0010 0000
    40
    32
    20
    (space)
    空格
    0010 0001
    41
    33
    21
    !
    叹号
    0010 0010
    42
    34
    22
    "
    双引号
    0010 0011
    43
    35
    23
    #
    井号
    0010 0100
    44
    36
    24
    $
    美元符
    0010 0101
    45
    37
    25
    %
    百分号
    0010 0110
    46
    38
    26
    &
    和号
    0010 0111
    47
    39
    27
    '
    闭单引号
    0010 1000
    50
    40
    28
    (
    开括号
    0010 1001
    51
    41
    29
    )
    闭括号
    0010 1010
    52
    42
    2A
    *
    星号
    0010 1011
    53
    43
    2B
    加号
    0010 1100
    54
    44
    2C
    ,
    逗号
    0010 1101
    55
    45
    2D
    -
    减号/破折号
    0010 1110
    56
    46
    2E
    .
    句号
    00101111
    57
    47
    2F
    /
    斜杠
    00110000
    60
    48
    30
    0
    数字0
    00110001
    61
    49
    31
    1
    数字1
    00110010
    62
    50
    32
    2
    数字2
    00110011
    63
    51
    33
    3
    数字3
    00110100
    64
    52
    34
    4
    数字4
    00110101
    65
    53
    35
    5
    数字5
    00110110
    66
    54
    36
    6
    数字6
    00110111
    67
    55
    37
    7
    数字7
    00111000
    70
    56
    38
    8
    数字8
    00111001
    71
    57
    39
    9
    数字9
    00111010
    72
    58
    3A
    :
    冒号
    00111011
    73
    59
    3B
    ;
    分号
    00111100
    74
    60
    3C
    <
    小于
    00111101
    75
    61
    3D
    =
    等号
    00111110
    76
    62
    3E
    >
    大于
    00111111
    77
    63
    3F
    ?
    问号
    01000000
    100
    64
    40
    @
    电子邮件符号
    01000001
    101
    65
    41
    A
    大写字母A 
    01000010
    102
    66
    42
    B
    大写字母B
    01000011
    103
    67
    43
    C
    大写字母C
    01000100
    104
    68
    44
    D
    大写字母D
    01000101
    105
    69
    45
    E
    大写字母E
    01000110
    106
    70
    46
    F
    大写字母F
    01000111
    107
    71
    47
    G
    大写字母G
    01001000
    110
    72
    48
    H
    大写字母H
    01001001
    111
    73
    49
    I
    大写字母I
    01001010
    112
    74
    4A
    J
    大写字母J
    01001011
    113
    75
    4B
    K
    大写字母K
    01001100
    114
    76
    4C
    L
    大写字母L
    01001101
    115
    77
    4D
    M
    大写字母M
    01001110
    116
    78
    4E
    N
    大写字母N
    01001111
    117
    79
    4F
    O
    大写字母O
    01010000
    120
    80
    50
    P
    大写字母P
    01010001
    121
    81
    51
    Q
    大写字母Q
    01010010
    122
    82
    52
    R
    大写字母R
    01010011
    123
    83
    53
    S
    大写字母S
    01010100
    124
    84
    54
    T
    大写字母T
    01010101
    125
    85
    55
    U
    大写字母U
    01010110
    126
    86
    56
    V
    大写字母V
    01010111
    127
    87
    57
    W
    大写字母W
    01011000
    130
    88
    58
    X
    大写字母X
    01011001
    131
    89
    59
    Y
    大写字母Y
    01011010
    132
    90
    5A
    Z
    大写字母Z
    01011011
    133
    91
    5B
    [
    开方括号
    01011100
    134
    92
    5C
    反斜杠
    01011101
    135
    93
    5D
    ]
    闭方括号
    01011110
    136
    94
    5E
    ^
    脱字符
    01011111
    137
    95
    5F
    _
    下划线
    01100000
    140
    96
    60
    `
    开单引号
    01100001
    141
    97
    61
    a
    小写字母a 
    01100010
    142
    98
    62
    b
    小写字母b
    01100011
    143
    99
    63
    c
    小写字母c
    01100100
    144
    100
    64
    d
    小写字母d
    01100101
    145
    101
    65
    e
    小写字母e
    01100110
    146
    102
    66
    f
    小写字母f
    01100111
    147
    103
    67
    g
    小写字母g
    01101000
    150
    104
    68
    h
    小写字母h
    01101001
    151
    105
    69
    i
    小写字母i
    01101010
    152
    106
    6A
    j
    小写字母j
    01101011
    153
    107
    6B
    k
    小写字母k
    01101100
    154
    108
    6C
    l
    小写字母l
    01101101
    155
    109
    6D
    m
    小写字母m
    01101110
    156
    110
    6E
    n
    小写字母n
    01101111
    157
    111
    6F
    o
    小写字母o
    01110000
    160
    112
    70
    p
    小写字母p
    01110001
    161
    113
    71
    q
    小写字母q
    01110010
    162
    114
    72
    r
    小写字母r
    01110011
    163
    115
    73
    s
    小写字母s
    01110100
    164
    116
    74
    t
    小写字母t
    01110101
    165
    117
    75
    u
    小写字母u
    01110110
    166
    118
    76
    v
    小写字母v
    01110111
    167
    119
    77
    w
    小写字母w
    01111000
    170
    120
    78
    x
    小写字母x
    01111001
    171
    121
    79
    y
    小写字母y
    01111010
    172
    122
    7A
    z
    小写字母z
    01111011
    173
    123
    7B
    {
    开花括号
    01111100
    174
    124
    7C
    |
    垂线
    01111101
    175
    125
    7D
    }
    闭花括号
    01111110
    176
    126
    7E
    ~
    波浪号
    01111111
    177
    127
    7F
    DEL (delete)
    删除

     

        显然ASCII码无法将世界上的各种文字和符号全部表示,所以,就需要新出一种可以代表所有字符和符号的编码,即:Unicode

        Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,规定虽有的字符和符号最少由 16 位来表示(2个字节),即:2 **16 = 65536,
        注:此处说的的是最少2个字节,可能更多

        UTF-8,是对Unicode编码的压缩和优化,他不再使用最少使用2个字节,而是将所有的字符和符号进行分类:ascii码中的内容用1个字节保存、欧洲的字符用2个字节保存,东亚的字符用3个字节保存...

    2.4 线程同步与互斥锁

    线程之间数据共享的。当多个线程对某一个共享数据进行操作时,就需要考虑到线程安全问题。threading模块中定义了Lock 类,提供了互斥锁的功能来保证多线程情况下数据的正确性。

    用法的基本步骤:

    #创建锁
    mutex = threading.Lock()
    #锁定
    mutex.acquire([timeout])
    #释放
    mutex.release()
    

    其中,锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。

    具体用法见示例代码:

    import threading
    import time
    
    num = 0
    mutex = threading.Lock()
    
    class MyThread(threading.Thread):
        def run(self):
            global num 
            time.sleep(1)
    
            if mutex.acquire(1):  
                num = num   1
                msg = self.name   ': num value is '   str(num)
                print(msg)
                mutex.release()
    
    if __name__ == '__main__':
        for i in range(5):
            t = MyThread()
            t.start()
    

    那么以上哪种原因对性能影响最大呢?

    (7)       Facebook

       注释。

        当行注释:# 被注释内容

        多行注释:'''被注释内容''',或者"""被注释内容"""

    2.5 可重入锁(递归锁)

    为了满足在同一线程中多次请求同一资源的需求,Python 提供了可重入锁(RLock)。
    RLock内部维护着一个Lock和一个counter变量,counter 记录了 acquire 的次数,从而使得资源可以被多次 require。直到一个线程所有的 acquire 都被 release,其他的线程才能获得资源。

    具体用法如下:

    #创建 RLock
    mutex = threading.RLock()
    
    class MyThread(threading.Thread):
        def run(self):
            if mutex.acquire(1):
                print("thread "   self.name   " get mutex")
                time.sleep(1)
                mutex.acquire()
                mutex.release()
                mutex.release()
    

    “它是全局解释器锁”

    (8)       Redhat

      变量

        变量是什么?  变量:把程序运行的中间结果临时的存在内存里,以便后续的代码调用。

        变量定义的规则:

    • 变量名只能是 字母、数字或下划线的任意组合
    • 变量名的第一个字符不能是数字
    • 以下关键字不能声明为变量名
      ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield'] 

    2.6 守护线程

    如果希望主线程执行完毕之后,不管子线程是否执行完毕都随着主线程一起结束。我们可以使用setDaemon(bool)函数,它跟join函数是相反的。它的作用是设置子线程是否随主线程一起结束,必须在start() 之前调用,默认为False

    现代计算机的CPU通常是多核的,并且有些拥有多个处理器。为了充分利用多余的处理能力,操作系统定义了一种低级的结构叫做线程:一个进程(例如Chrome浏览器)可以产生多个线程并且指导内部系统。

    (9)       豆瓣

    新葡亰496net,  常量

        常量即指不变的量,如pai 3.141592653..

    2.7 定时器

    如果需要规定函数在多少秒后执行某个操作,需要用到Timer类。具体用法如下:

    from threading import Timer
    
    def show():
        print("Pyhton")
    
    # 指定一秒钟之后执行 show 函数
    t = Timer(1, hello)
    t.start()  
    

    如果一个进程是CPU密集型,那么其负载可以被多核同时处理,从而有效提高大多数应用的速度。

    (10)   知乎

      流程控制之--if。

      if...else 语句

        单分支

       if 条件:
          满足条件后要执行的代码
    

        双分支

       """
       if 条件:
          满足条件执行代码
       else:
          if条件不满足就走这段
       """
       AgeOfOldboy = 48
    
       if AgeOfOldboy > 50 :
          print("Too old, time to retire..")
       else:
          print("还能折腾几年!")
    

    Python的缩进有以下几个原则:

    • 顶级代码必须顶行写,即如果一行代码本身不依赖于任何条件,那它必须不能进行任何缩进
    • 同一级别的代码,缩进必须一致

    3 Python 多进程

    当我写这篇文章时,我的Chrome浏览器同时拥有44个线程。注意,基于POSIX(比如MacOS和Linux)和Windows操作系统相比,线程的结构和API是不同的。操作系统也会处理线程的调度问题。

    (11)   春雨医生

      流程控制之--while循环。

        基本循环

     
    while 条件:
         
        # 循环体
     
        # 如果条件为真,那么循环体则执行
        # 如果条件为假,那么循环体不执行

        循环中止语句 

    如果在循环的过程中,因为某些原因,你不想继续循环了,怎么把它中止掉呢?这就用到break 或 continue 语句

    • break用于完全结束一个循环,跳出循环体执行循环后面的语句
    • continue和break有点类似,区别在于continue只是终止本次循环,接着还执行后面的循环,break则完全终止循环
      count = 0
      while count <= 100 : #只要count<=100就不断执行下面的代码
          print("loop ", count)
          if count == 5:
              break
          count  =1 #每执行一次,就把count 1,要不然就变成死循环啦,因为count一直是0
    
      print("-----out of while loop ------")
    

      while ... else ..

        与其它语言else 一般只与if 搭配不同,在Python 中还有个while ...else 语句

        while 后面的else 作用是指,当while 循环正常执行完,中间没有被break 中止的话,就会执行else后面的语句

        count = 0
        while count <= 5 :
            count  = 1
          print("Loop",count)
    
        else:
            print("循环正常执行完啦")
        print("-----out of while loop ------")
    

        如果执行过程中被break啦,就不会执行else的语句啦

        count = 0
        while count <= 5 :
            count  = 1
          if count == 3:break
            print("Loop",count)
    
        else:
            print("循环正常执行完啦")
        print("-----out of while loop ------")
    

     

     

     

    3.1 创建多进程

    Python 要进行多进程操作,需要用到muiltprocessing库,其中的Process类跟threading模块的Thread类很相似。所以直接看代码熟悉多进程。

    • 方法1:直接使用Process, 代码如下:
    from multiprocessing import Process  
    
    def show(name):
        print("Process name is "   name)
    
    if __name__ == "__main__": 
        proc = Process(target=show, args=('subprocess',))  
        proc.start()  
        proc.join()
    
    • 方法1:继承Process来自定义进程类,重写run方法, 代码如下:
    from multiprocessing import Process
    import time
    
    class MyProcess(Process):
        def __init__(self, name):
            super(MyProcess, self).__init__()
            self.name = name
    
        def run(self):
            print('process name :'   str(self.name))
            time.sleep(1)
    
    if __name__ == '__main__':
        for i in range(3):
            p = MyProcess(i)
            p.start()
        for i in range(3):
            p.join()
    

    如果你之前没有做过多线程编程,你需要快速熟悉锁的概念。区别于单线程进程,你需要确保当内存中的变量被修改时,多线程不会同时试图访问或者改变同一个存储地址。

    (12)   等等

    3.2 多进程通信

    进程之间不共享数据的。如果进程之间需要进行通信,则要用到Queue模块或者Pipi模块来实现。

    • Queue

    Queue 是多进程安全的队列,可以实现多进程之间的数据传递。它主要有两个函数,putget

    put() 用以插入数据到队列中,put 还有两个可选参数:blocked 和 timeout。如果 blocked 为 True(默认值),并且 timeout 为正值,该方法会阻塞 timeout 指定的时间,直到该队列有剩余的空间。如果超时,会抛出 Queue.Full 异常。如果 blocked 为 False,但该 Queue 已满,会立即抛出 Queue.Full 异常。

    get()可以从队列读取并且删除一个元素。同样,get 有两个可选参数:blocked 和 timeout。如果 blocked 为 True(默认值),并且 timeout 为正值,那么在等待时间内没有取到任何元素,会抛出 Queue.Empty 异常。如果blocked 为 False,有两种情况存在,如果 Queue 有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出 Queue.Empty 异常。

    具体用法如下:

    from multiprocessing import Process, Queue
    
    def put(queue):
        queue.put('Queue 用法')
    
    if __name__ == '__main__':
        queue = Queue()
        pro = Process(target=put, args=(queue,))
        pro.start()
        print(queue.get())   
        pro.join()
    
    • Pipe

    Pipe的本质是进程之间的用管道数据传递,而不是数据共享,这和socket有点像。pipe() 返回两个连接对象分别表示管道的两端,每端都有send() 和recv()函数。

    如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据。

    具体用法如下:

    from multiprocessing import Process, Pipe
    
    def show(conn):
        conn.send('Pipe 用法')
        conn.close()
    
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe() 
        pro = Process(target=show, args=(child_conn,))
        pro.start()
        print(parent_conn.recv())   
        pro.join()
    

    当CPython创建变量时,它会预先分配存储空间,然后计算当前变量的引用数目。这个概念被称为引用计数。如果引用计数为零,那么它将从系统中释放对应存储区域。

    三、            Python

    3.3 进程池

    创建多个进程,我们不用傻傻地一个个去创建。我们可以使用Pool模块来搞定。

    Pool 常用的方法如下:

    方法 含义
    apply() 同步执行(串行)
    apply_async() 异步执行(并行)
    terminate() 立刻关闭进程池
    join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后使用
    close() 等待所有进程结束后,才关闭进程池

    具体用法见示例代码:

    from multiprocessing import Pool
    def show(num):
        print('num : '   str(num))
    
    if __name__=="__main__":
        pool = Pool(processes = 3)
        for i in xrange(6):
            # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
            pool.apply_async(show, args=(i, ))       
        print('======  apply_async  ======')
        pool.close()
        #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
        pool.join()
    

    这就是为什么在CPython中创造“临时”变量不会使应用占用大量的存储空间——尤其是当应用中使用了for循环这一类可能大量创建“临时”变量的结构时。

    python动态语言,强类型语言

    4 选择多线程还是多进程?

    在这个问题上,首先要看下你的程序是属于哪种类型的。一般分为两种 CPU 密集型 和 I/O 密集型。

    • CPU 密集型:程序比较偏重于计算,需要经常使用 CPU 来运算。例如科学计算的程序,机器学习的程序等。

    • I/O 密集型:顾名思义就是程序需要频繁进行输入输出操作。爬虫程序就是典型的 I/O 密集型程序。

    如果程序是属于 CPU 密集型,建议使用多进程。而多线程就更适合应用于 I/O 密集型程序。


    上篇文章:爬虫实战一:爬取当当网所有 Python 书籍
    推荐阅读:内容提取神器 beautiful Soup 的用法


    当存在多个线程调用变量时,CPython如何锁住引用计数成为了一个挑战。而“全局解释锁”应运而生,它能够谨慎控制线程的执行。无论有多少的线程,解释器每次只能执行一个操作。

    (一)    优点

    这对Python的性能意味着什么呢?

    (1)           Python的定位是“优雅”、“明确”、“简单”,所以Python程序看上去总是简单易懂,初学者学Python,不但入门容易,而且将来深入下去,可以编写那些非常非常复杂的程序。

    如果你的应用基于单线程、单解释器,那么讨论速度这一点就毫无意义,因为去掉GIL并不会影响代码性能。

    (2)           开发效率非常高,Python有非常强大的第三方库,基本上你想通过计算机实现任何功能,Python官方库里都有相应的模块进行支持,直接下载调用后,在基础库的基础上再进行开发,大大降低开发周期,避免重复造轮子。

    如果你想使用线程在单解释器(Python 进程)中实现并发,并且你的线程为IO密集型(例如网络IO或磁盘IO),你就会看到GIL争用的结果。

    (3)           高级语言——当你用Python语言编写程序的时候,你无需考虑诸如如何管理你的程序使用的内存一类的底层细节

    如果你有一个网络应用(例如Django)并且使用WSGI,那么每一个对于你的网络应用的请求将是一个独立的Python解释器,因此每个请求只有一个锁。因为Python解释器启动很慢,一些WSGI便集成了能够使保持Python进程的“守护进程”  。

    (4)           可移植性——由于它的开源本质,Python已经被移植在许多平台上(经过改动使它能够工 作在不同平台上)。如果你小心地避免使用依赖于系统的特性,那么你的所有Python程序无需修改就几乎可以在市场上所有的系统平台上运行

    那么其他Python解释器的速度又如何呢?

    (5)           可扩展性——如果你需要你的一段关键代码运行得更快或者希望某些算法不公开,你可以把你的部分程序用C或C 编写,然后在你的Python程序中使用它们。

    PyPy拥有GIL,通常比CPython快至少三倍。

    (6)           可嵌入性——你可以把Python嵌入你的C/C 程序,从而向你的程序用户提供脚本功能。

    Jython没有GIL,因为在Jython中Python线程是用Java线程表示的,这得益于JVM内存管理系统。

    (二)    缺点:

    JavaScript是如何做到这一点的呢?

    (1)           速度慢,Python 的运行速度相比C语言确实慢很多,跟JAVA相比也要慢一些,因此这也是很多所谓的大牛不屑于使用Python的主要原因,但其实这里所指的运行速度慢在大多数情况下用户是无法直接感知到的,必须借助测试工具才能体现出来,比如你用C运一个程序花了0.01s,用Python是0.1s,这样C语言直接比Python快了10倍,算是非常夸张了,但是你是无法直接通过肉眼感知的,因为一个正常人所能感知的时间最小单位是0.15-0.4s左右,哈哈。其实在大多数情况下Python已经完全可以满足你对程序速度的要求,除非你要写对速度要求极高的搜索引擎等,这种情况下,当然还是建议你用C去实现的。

    首先,所有的Javascript引擎使用标记加清除的垃圾收集系统,而之前提到GIL的基本诉求是CPython的存储管理算法。

    (2)           代码不能加密,因为PYTHON是解释性语言,它的源码都是以名文形式存放的,不过我不认为这算是一个缺点,如果你的项目要求源代码必须是加密的,那你一开始就不应该用Python来去实现。

    JavaScript没有GIL,但因为它是单线程的,所以也并不需要GIL。

    (3)           线程不能利用多CPU问题,这是Python被人诟病最多的一个缺点,GIL即全局解释器锁(Global Interpreter Lock),是计算机程序设计语言解释器用于同步线程的工具,使得任何时刻仅有一个线程在执行,Python的线程是操作系统的原生线程。在Linux上为pthread,在Windows上为Win thread,完全由操作系统调度线程的执行。一个python解释器进程内有一条主线程,以及多条用户程序的执行线程。即使在多核CPU平台上,由于GIL的存在,所以禁止多线程的并行执行。关于这个问题的折衷解决方法,我们在以后线程和进程章节里再进行详细探讨。

    JavaScript通过事件循环和承诺/回调模式来实现异步编程的并发。Python有与异步事件循环相似的过程。

    (三)       Python解释器

     “因为它是解释型语言”

    (1)           CPython

     

    当我们从Python官方网站下载并安装好Python 2.7后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

    我经常听到这句话。我觉得这只是对于CPython实际运行方式的一种简单解释。如果你在终端中输入python myscript.py,那么CPython将对这段代码开始一系列的读取,词法分析,解析,编译,解释和运行。

    CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。

    这个过程中的重要步骤是在编译阶段创建一个.pyc 文件,这个字节码序列将被写入Python3下__pycache__/ 路径中的一个文件(对于Python2,文件路径相同)。这个步骤不仅仅应用于脚本文件,也应用于所有导入的代码,包括第三方模块。

    (2)           IPython

    所以大多时候(除非你写的代码只运行一次),Python是在解释字节码并且本地执行。下面我们将Java和C#.NET相比较:

    IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

    Java编译成一门“中间语言”,然后Java虚拟机读取字节代码并即时编译为机器代码。.NET的通用中间语言(CIL)是一样的,它的通用语言运行时间(CLR)也采用即时编译的方法转化为机器代码。

    CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

    那么,如果Python用的是和Java和C#一样的虚拟机和某种字节代码,为什么在基准测试中它却慢得多?首先,.NET和Java是采用JIT编译的。

    (3)           PyPy

    JIT,又称即时编译,需要一种中间语言来把代码进行分块(或者叫数据帧)。预编译(AOT, Ahead of Time)器的设计保证了CPU能够在交互之前理解代码中的每一行。

    PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。

    JIT本身不会使执行速度更快,因为它仍然执行相同的字节码序列。但是,JIT允许在运行时进行优化。好的JIT优化器可以检测哪些部分执行次数比较多,这些部分被称为“热点”。然后,它将用更高效的代码替换它们,完成优化。

    绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。

    这就意味着当计算机应用程序需要重复做一件事情的时候,它就会更加地快。另外,我们要知道Java和C#是强类型语言(变量需要预定义),因此优化器可以对代码做更多的假设。

    (4)           Jython

    PyPy使用即时编译器,并且前文也有提到它比CPython更快。这篇关于基准测试的文章介绍得更为详细——什么版本的Python最快?

    Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

    链接:

    (5)           IronPython

    IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

    那么,为什么CPython不使用即时编译器呢?

    Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。

    JIT存在一些缺点:其中一个是启动时间。CPython启动时间已经相对较慢,PyPy比CPython还要慢2-3倍。众所周知,Java虚拟机的启动速度很慢。为了解决这个问题,.NET CLR在系统启动的时候就开始运行,但CLR的开发人员还开发了专门运行CLR的操作系统来加快它。

    ps:部分内容摘自【金角大王等唐僧的日子】的随笔

    如果你有一个运行时间很长的Python进程,并且其代码可以被优化(因为它包含前文所述的“热点”),那么JIT就能够起到很大作用。

    但是,CPython适用于各类应用。因此,如果你使用Python开发命令行应用程序,每次调用CLI时都必须等待JIT启动,这将非常缓慢。

    CPython必须尽量多地尝试不同的案例以保证通用性,而把JIT插入到CPython中可能会让这个项目停滞不前。

    如果你想要借助JIT的力量,而且你的工作量还比较大,那么使用PyPy吧。

    “因为它是一个动态类型语言”

     

    在静态类型语言中,定义变量时必须声明类型。C, C , Java, C#, Go都是这种语言。

    在动态类型语言中,类型的概念依旧存在,但是这个变量的类型是动态变化的。

    a = 1

    a = “foo”

    在上面这个例子中,Python创建第二个变量的时候用了同样的名字,但是变量类型是str(字符型),这样就对先前在内存中给a分配的空间进行了释放和再分配。

    静态类型语言的这种设计并不是为了麻烦大家——它们是按照CPU的运行方式设计的。如果最终需要将所有内容都转化为简单的二进制操作,那就必须将对象和类型转换为低级数据结构。

    Python自动完成了这个过程,我们看不见,也没必要看见。

    不必声明类型不是使Python变慢的原因。Python语言的设计使我们几乎可以创建任何动态变量。我们可以在运行时替换对象中的方法,也可以胡乱地把低级系统调用赋给一个值。几乎怎么修改都可以。

    正是这种设计使得优化Python变得异常困难。

    为了阐明我的观点,我将使用一个MacOS中的应用。它是一个名为Dtrace的系统调用跟踪工具。CPython发行版没有内置DTrace,因此你必须重新编译CPython。以下演示中使用3.6.6版本。

    wget

    unzip v3.6.6.zip

    cd v3.6.6

    ./configure –with-dtrace

    make

    现在python.exe将在整条代码中使用Dtrace跟踪器。Paul Ross就Dtrace做了一篇很棒的短演讲。 你可以下载Python的DTrace启动文件来测试函数调用、执行时间、CPU时间、系统调用等各种有意思的事情。例如:

    sudo dtrace -s toolkit/<tracer>.d -c ‘../cpython/python.exe script.py’

    DTrace启动文件:

    演讲链接:

    py_callflow跟踪器显示应用程序中的所有函数调用

    因此,是Python的动态类型让它变慢的吗?

    • 比较和转换类型是耗时的,因为每次读取、写入变量或引用变量类型时都会进行检查
    • 很难优化一种如此动态的语言。其他语言之所以那么快是因为他们牺牲了一定的灵活性,从而提高了性能。
    • 了解一下Cython,它结合了C-Static类型和Python来优化已知类型的代码,可以提供84倍速度的性能提升。

    结论

     

    Python的缓慢主要是由于它动态和多用途的特点。它可以用于解决几乎所有问题,但是更加优化而快捷的替代方案可能存在。

    但是,有一些方法可以通过利用异步计算,理解分析工具,以及考虑使用多个解释器来优化Python应用程序。

    对于有些启动时间相对不重要,并且即时编译器(JIT)可以提高效率的应用,可以考虑使用PyPy。

    对于性能优先并且有更多静态变量的代码部分,请考虑使用Cython。

    多线程爬取表情包

    有一个网站,叫做“斗图啦”,网址是:https://www.doutula.com/。这里面包含了许许多多的有意思的斗图图片,还蛮好玩的。有时候为了斗图要跑到这个上面来找表情,实在有点费劲。于是就产生了一个邪恶的想法,可以写个爬虫,把所有的表情都给爬下来。这个网站对于爬虫来讲算是比较友好了,他不会限制你的headers,不会限制你的访问频率(当然,作为一个有素质的爬虫工程师,爬完赶紧撤,不要把人家服务器搞垮了),不会限制你的IP地址,因此技术难度不算太高。但是有一个问题,因为这里要爬的是图片,而不是文本信息,所以采用传统的爬虫是可以完成我们的需求,但是因为是下载图片所以速度比较慢,可能要爬一两个小时都说不准。因此这里我们准备采用多线程爬虫,一下可以把爬虫的效率提高好几倍。

    一、分析网站和爬虫准备工作:

    构建所有页面URL列表:

    这里我们要爬的页面不是“斗图啦”首页,而是最新表情页面https://www.doutula.com/photo/list/,这个页面包含了所有的表情图片,只是是按照时间来排序的而已。我们把页面滚动到最下面,可以看到这个最新表情使用的是分页,当我们点击第二页的时候,页面的URL变成了https://www.doutula.com/photo/list/?page=2,而我们再回到第一页的时候,page又变成了1,所以这个翻页的URL其实很简单,前面这一串https://www.doutula.com/photo/list/?page=都是固定的,只是后面跟的数字不一样而已。并且我们可以看到,这个最新表情总共是有869页,因此这里我们可以写个非常简单的代码,来构建一个从1到869的页面的URL列表:

    1.  

      # 全局变量,用来保存页面的URL的

    2.  

      PAGE_URL_LIST = []

    3.  

      BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='

    4.  

      for x in range(1, 870):

    5.  

      url = BASE_PAGE_URL str(x)

    6.  

      PAGE_URL_LIST.append(url)

    获取一个页面中所有的表情图片链接:

    我们已经拿到了所有页面的链接,但是还没有拿到每个页面中表情的链接。经过分析,我们可以知道,其实每个页面中表情的HTML元素构成都是一样的,因此我们只需要针对一个页面进行分析,其他页面按照同样的规则,就可以拿到所有页面的表情链接了。这里我们以第一页为例,跟大家讲解。首先在页面中右键->检查->Elements,然后点击Elements最左边的那个小光标,再把鼠标放在随意一个表情上,这样下面的代码就定位到这个表情所在的代码位置了:

    新葡亰496net 2

    01.png

    可以看到,这个img标签的class是等于img-responsive lazy image_dtz,然后我们再定位其他表情的img标签,发现所有的表情的img标签,他的class都是img-responsive lazy image_dtz

    新葡亰496net 3

    02.png

    新葡亰496net 4

    03.png

    因此我们只要把数据从网上拉下来,然后再根据这个规则进行提取就可以了。这里我们使用了两个第三方库,一个是requests,这个库是专门用来做网络请求的。第二个库是bs4,这个库是专门用来把请求下来的数据进行分析和过滤用的,如果没有安装好这两个库的,可以使用以下代码进行安装(我使用的是python2.7的版本):

    1.  

      # 安装requests

    2.  

      pip install requests

    3.  

      # 安装bs4

    4.  

      pip install bs4

    5.  

      # 安装lxml解析引擎

    6.  

      pip install lxml

    然后我们以第一个页面为例,跟大家讲解如何从页面中获取所有表情的链接:

    1.  

      # 导入requests库

    2.  

      import requests

    3.  

      # 从bs4中导入BeautifulSoup

    4.  

      from bs4 import BeautifulSoup

    5.  

       

    6.  

      # 第一页的链接

    7.  

      url = 'https://www.doutula.com/photo/list/?page=1'

    8.  

      # 请求这个链接

    9.  

      response = requests.get(url)

    10.  

      # 使用返回的数据,构建一个BeautifulSoup对象

    11.  

      soup = BeautifulSoup(response.content,'lxml')

    12.  

      # 获取所有class='img-responsive lazy image_dtz'的img标签

    13.  

      img_list = soup.find_all('img', attrs={'class': 'img-responsive lazy image_dta'})

    14.  

      for img in img_list:

    15.  

      # 因为src属性刚开始获取的是loading的图片,因此使用data-original比较靠谱

    16.  

      print img['data-original']

    这样我们就可以在控制台看到本页中所有的表情图片的链接就全部都打印出来了。

    下载图片:

    有图片链接后,还要对图片进行下载处理,这里我们以一张图片为例:http://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fhpi3ysfocj205i04aa9z.jpg,来看看Python中如何轻轻松松下载一张图片:

    1.  

      import urllib

    2.  

      url = 'http://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fhpi3ysfocj205i04aa9z.jpg'

    3.  

      urllib.urlretrieve(url,filename='test.jpg')

    这样就可以下载一张图片了。

    结合以上三部分内容:

    以上三部分,分别对,如何构建所有页面的URL,一个页面中如何获取所有表情的链接以及下载图片的方法。接下来把这三部分结合在一起,就可以构建一个完整但效率不高的爬虫了:

    1.  

      # 导入requests库

    2.  

      import requests

    3.  

      # 从bs4中导入BeautifulSoup

    4.  

      from bs4 import BeautifulSoup

    5.  

      import urllib

    6.  

      import os

    7.  

       

    8.  

      # 全局变量,用来保存页面的URL的

    9.  

      PAGE_URL_LIST = []

    10.  

      BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='

    11.  

      for x in range(1, 870):

    12.  

      url = BASE_PAGE_URL str(x)

    13.  

      PAGE_URL_LIST.append(url)

    14.  

       

    15.  

       

    16.  

      for page_url in PAGE_URL_LIST:

    17.  

      # 请求这个链接

    18.  

      response = requests.get(page_url)

    19.  

      # 使用返回的数据,构建一个BeautifulSoup对象

    20.  

      soup = BeautifulSoup(response.content,'lxml')

    21.  

      # 获取所有class='img-responsive lazy image_dtz'的img标签

    22.  

      img_list = soup.find_all('img', attrs={'class': 'img-responsive lazy image_dta'})

    23.  

      for img in img_list:

    24.  

      # 因为src属性刚开始获取的是loading的图片,因此使用data-original比较靠谱

    25.  

      src = img['data-original']

    26.  

      # 有些图片是没有http的,那么要加一个http

    27.  

      if not src.startswith('http'):

    28.  

      src = 'http:' src

    29.  

      # 获取图片的名称

    30.  

      filename = src.split('/').pop()

    31.  

      # 拼接完整的路径

    32.  

      path = os.path.join('images',filename)

    33.  

      urllib.urlretrieve(src,path)

    以上这份代码。可以完整的运行了。但是效率不高,毕竟是在下载图片,要一个个排队下载。如果能够采用多线程,在一张图片下载的时候,就完全可以去请求其他图片,而不用继续等待了。因此效率比较高,以下将该例子改为多线程来实现。

    二、多线程下载图片:

    这里多线程我们使用的是Python自带的threading模块。并且我们使用了一种叫做生产者和消费者的模式,生产者专门用来从每个页面中获取表情的下载链接存储到一个全局列表中。而消费者专门从这个全局列表中提取表情链接进行下载。并且需要注意的是,在多线程中使用全局变量要用锁来保证数据的一致性。以下是多线程的爬虫代码(如果有看不懂的,可以看视频,讲解很仔细):

    1.  

      #encoding: utf-8

    2.  

       

    3.  

      import urllib

    4.  

      import threading

    5.  

      from bs4 import BeautifulSoup

    6.  

      import requests

    7.  

      import os

    8.  

      import time

    9.  

       

    10.  

      # 表情链接列表

    11.  

      FACE_URL_LIST = []

    12.  

      # 页面链接列表

    13.  

      PAGE_URL_LIST = []

    14.  

      # 构建869个页面的链接

    15.  

      BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='

    16.  

      for x in range(1, 870):

    17.  

      url = BASE_PAGE_URL str(x)

    18.  

      PAGE_URL_LIST.append(url)

    19.  

       

    20.  

      # 初始化锁

    21.  

      gLock = threading.Lock()

    22.  

       

    23.  

      # 生产者,负责从每个页面中提取表情的url

    24.  

      classProducer(threading.Thread):

    25.  

      defrun(self):

    26.  

      while len(PAGE_URL_LIST) > 0:

    27.  

      # 在访问PAGE_URL_LIST的时候,要使用锁机制

    28.  

      gLock.acquire()

    29.  

      page_url = PAGE_URL_LIST.pop()

    30.  

      # 使用完后要及时把锁给释放,方便其他线程使用

    31.  

      gLock.release()

    32.  

      response = requests.get(page_url)

    33.  

      soup = BeautifulSoup(response.content, 'lxml')

    34.  

      img_list = soup.find_all('img', attrs={'class': 'img-responsive lazy image_dta'})

    35.  

      gLock.acquire()

    36.  

      for img in img_list:

    37.  

      src = img['data-original']

    38.  

      if not src.startswith('http'):

    39.  

      src = 'http:' src

    40.  

      # 把提取到的表情url,添加到FACE_URL_LIST中

    41.  

      FACE_URL_LIST.append(src)

    42.  

      gLock.release()

    43.  

      time.sleep(0.5)

    44.  

       

    45.  

      # 消费者,负责从FACE_URL_LIST提取表情链接,然后下载

    46.  

      classConsumer(threading.Thread):

    47.  

      defrun(self):

    48.  

      print '%s is running' % threading.current_thread

    49.  

      while True:

    50.  

      # 上锁

    51.  

      gLock.acquire()

    52.  

      if len(FACE_URL_LIST) == 0:

    53.  

      # 不管什么情况,都要释放锁

    54.  

      gLock.release()

    55.  

      continue

    56.  

      else:

    57.  

      # 从FACE_URL_LIST中提取数据

    58.  

      face_url = FACE_URL_LIST.pop()

    59.  

      gLock.release()

    60.  

      filename = face_url.split('/')[-1]

    61.  

      path = os.path.join('images', filename)

    62.  

      urllib.urlretrieve(face_url, filename=path)

    63.  

       

    64.  

      if __name__ == '__main__':

    65.  

      # 2个生产者线程,去从页面中爬取表情链接

    66.  

      for x in range(2):

    67.  

      Producer().start()

    68.  

       

    69.  

      # 5个消费者线程,去从FACE_URL_LIST中提取下载链接,然后下载

    70.  

      for x in range(5):

    71.  

      Consumer().start()

    本文由新葡亰496net发布于奥门新萄京娱乐场,转载请注明出处:新葡亰496net:主干语法,多进度与二十多线程

    关键词: