python知识点整理

一、大文件读取(生成器实现)

       利用python的f.read(1024),一次读取1024字节到内存中,同时用yield返回读取的结果,这样当内存小于文件大小时,可以分批读取文件,并进行操作。

def bigFileRead(f, new_line):
    buf = ""
    while True:
        while new_line in buf:          # 如果一次性读取出的字符串包含多个分隔符,那么循环操作,查分分隔符,相当于做了split
            pos = buf.index(new_line)
            yield buf[:pos]
            buf = buf[pos+len(new_line):]
        chunk = f.read(5)
        if not chunk:                   # 读取结束
            yield buf
            break
        buf += chunk
with open("input.txt", encoding="utf-8") as f:
    for line in bigFileRead(f, "{|}"):
        print(line)

二、for else 用法

        for后面跟else,当正常遍历完for循环后,会接着执行else里面的代码。如果再执行for循环的时候,break了,那么就不会执行else了。

for i in range(5):
    if i == 3:
        break
else:               # 当for循环是break出来的时候,不会执行else,否则都会执行else里面的语句
    print("out")

三、解开n层的list或tuple

        递归方法,解开n层的list或tuple。

def splitList(lst):
    ret = []
    for x in lst:
        if (type(x)==list) or (type(x)==tuple):
            ret.extend(splitList(x))  # 处理外层
        else:
            ret.append(x)               # 处理内层
    return ret
k = [2, ['23', ['234', (5, 6, 7)], ['9823', 9239]]]
print(splitList(k))

四、GIL(全局解释器锁)

        首先明确一点,每个CPU在同一时刻只能执行一个python的线程。在python多线程下,每个线程的执行方式:

  1. 获取GIL
  2. 执行代码直到sleep或等待网络I/O
  3. 释放GIL

        可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。

        在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过 sys.setcheckinterval 来调整),进行释放。 而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。 并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。

        而在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。

        python对CPU密集型和IO密集型的多线程的友好性区别:

  1. CPU密集型代码(各种循环处理、计数等等),在这种情况下,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
  2. IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

        多核多线程比单核多线程更差,原因是单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行。但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低

        python下想要充分利用多核CPU,就用多进程。这是因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。

五、函数传参*、**的区别 | fun(*args,**kwargs)中的*args,**kwargs什么意思?

        *args是直接将value作为传入参数,**kwargs是将key和value一起作为传入参数。

def args_test(x, y, *args):
    print(x, y, args)
args_test(1,2,3,4,5)        # *args 用来将参数打包成tuple给函数体调用

def kwargs_test(**kwargs):
    print(kwargs)
kwargs_test(a=1,b=2,c=3)    # **kwargs 打包关键字参数成dict给函数体调用

def param_test(arg, *args, **kwargs):
    print(arg, args, kwargs)
param_test(1,3,5,a=6,b=9)   # 参数arg、*args、**kwargs三个参数的位置必须是一定的。必须是(arg,*args,**kwargs)这个顺序,否则程序会报错。

def test(*args,**kwargs):
    print(args, kwargs)
l = [1,2,3,4]
d = {"a":1,"b":2}
test(l, d)                  # l,d这2个变量都会传递给args这个形参,作为args变量的两个元素,kwargs是一个空的字典,没有任何参数传递一个它
test(*l,**d)                # 这个变量就会被赋值给args,d这个变量就会被赋值给kwargs

def foo(action=None, **kwargs):
    print("action",action,sep="=================>")
    print("kwargs", kwargs, sep="=================>")
d = {"a":1,"b":2}
foo(d)                      # d会被赋值给action参数
foo(**d)                    # d会被赋值给kwargs参数
d = {"action":"action","a":1}
foo(**d)                    # action会被d里面的action覆盖

六、装饰器作用、语法糖

        装饰器是给现有的模块增添新的小功能。

import logging

def use_logging(func):
    def wrapper():
        logging.info("%s is running" % func.__name__)
        return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
    return wrapper
def foo():
    print('i am foo1')
foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapper
foo()                   # 执行foo()就相当于执行 wrapper()

# 等价于使用@语法糖
def use_logging(func):
    def wrapper():
        logging.info("%s is running" % func.__name__)
        return func()
    return wrapper
@use_logging
def foo():
    print("i am foo2")
foo()

# 传参装饰器 - *args
def use_logging(func):
    def wrapper(*args):
        logging.info("%s is running" % func.__name__)
        return func(*args)
    return wrapper
@use_logging
def foo(*args):
    print("~~~~", args)
l = ["张三", 12]
foo(l)

# 传参装饰器 - **kwargs
def use_logging(func):
    def wrapper(**kwargs):
        logging.info("%s is running" % func.__name__)
        return func(**kwargs)
    return wrapper
@use_logging
def foo(**kwargs):
    print("~~~~", kwargs)
foo(name="张三", age=12)

"""
@a
@b
@c
def f ():   # 个函数还可以同时定义多个装饰器,它等效于 f=a(b(c(f)))
    pass
"""

七、is 和 == 的区别

        python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。此外还有其他要素,比如计数字段,该字段便于后面垃圾回收机制做参考。

        is比较的是两个对象的id值是否相等,也就是比较两个对象是否为同一个实例对象,是否指向同一个内存地址。

        ==比较的是两个对象的内容是否相等,默认会调用对象的__eq__()方法。

a = 256
b = 256
c = 1000
d = 10*3
e = 2000
f = 2000
print(a is b)   # True、出于对性能的考虑,Python内部会缓存一些整型对象,但凡是需要用些小整数时,就从这里面取,不再去临时创建新的对象。故而id相同
print(c is d)   # False、1000的id 和 10*3的value一样,但是对应的id不同
print(e is f)   # True、第一条

八、python内存管理

        python中一切皆对象,对象又可以分为可变对象和不可变对象。如果修改后地址不变,则是可变对象,否则为不可变对象。

        (1)内存池

        python有内存池机制,pymalloc机制。 当创建大量消耗小内存的对象时,c语言中频繁调用new/malloc会导致大量的内存碎片,致使效率降低。 在python中使用内存池,内存池的概念就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。 查看源码,可以看到pymalloc对于小的对象,pymalloc会在内存池中申请空间,一般是少于256kb,如果是大的对象,则直接调用new/malloc来申请新的内存空间。

        (2-1)垃圾回收机制

        python采用GC作为自动内存管理机制,GC要做的有2件事,一是找到内存中无用的垃圾对象资源,二是清除找到的这些垃圾对象,释放内存给其他对象使用。

        优点: 简单 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。

        缺点: 需要额外的空间维护引用计数。 不能解决对象的循环引用。(主要缺点) 。对象循环参考下面的代码

        (2-2)标记清除

        标记清除主要是解决循环引用问题。 对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。 从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。 非活动对象会被GC回收。 详情可参考:https://juejin.im/post/5ca2471df265da307b2d45a3

        (2-3)分代回收

        python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代。 python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉。 而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

a = {}      # 对象a的引用计数为 1
b = {}      # 对象b的引用计数为 1
a['b']=b    # b的引用计数增1
b['a']=a    # a的引用计数增1
del a       # a的引用减 1,最后a对象的引用为 1
del b       # b的引用减 1, 最后b对象的引用为 1
# 循环引用
# 执行del后,a、b对象已经没有任何引用指向这两个对象,需要被回收,但是a、b的计数器并不为0,故而无法通过计数法对两个对象回收,它们会一直驻留在内存中,就会造成了内存泄漏。

九、python魔法函数

  1. 魔法函数以__开始、__结束,是python内置的函数
  2. 自定义的类中使用魔法函数,该类就具有了该魔法函数的功能,比如使用iter魔法函数,该类就具有迭代功能。
  3. 魔法函数不要自己构建,而是使用python提供的魔法函数
class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list
    def __getitem__(self, item):
        return self.employee[item]
    def __len__(self):
        return 2
company = Company(["tom", "bob", "jane"])
# 实例化对象
employe = company.employee
for em in employe:
    print(em)
print()
# 魔法函数,自动调用__getitem__
for em in company:
    print(em)
print()
# 魔法函数,调用__len__,全局关键字len的优先级小于class里面的魔法函数
print(len(company))

十、理解__new__和__init__的区别

        __init__方法为初始化方法、__new__方法才是真正的构造函数。

  1. 默认Foo类继承object类,而object类里面包含__new__方法,故而调用Foo类时,会自动调用object里面的__new__构造方法
  2. __new__ 方法创建实例对象供__init__ 方法使用,__init__方法定制实例对象。__new__方法必须返回值
  3. __init__方法为初始化方法,为类的实例提供一些属性或完成一些动作。__init__方法不需要返回值
  4. 如果在自定义的类中重构了__new__方法,那么调用该类时,就会使用重构的__new__方法
class Foo(object):
    def __new__(cls, *agrs, **kwds):
        inst = object.__new__(cls, *agrs, **kwds)
        print("重构的__new__:", inst)
        return inst
    def __init__(self, price=50):
        self.price = price
    def how_much_of_book(self, n):
        print("初始化__init__:", self)
        return self.price * n
foo = Foo()    # 当class中重构了__new__函数后,会优先调用重构的__new__函数
print(foo.how_much_of_book(8))

十一、三元表达式

        为真时的结果 if 判定条件 else 为假时的结果

print(100 if 77>66 else 99)

十二、lambda表达式

        lamba的代码简洁、返回结果也很简洁,但是lamba并不会带来程序运行效率的提高。

g = lambda x:x+2                    # lamba传一个参数
info = [g(x) for x in range(10)]
print(info)

m = lambda x,y,z:(x-y)*z            # lamba传多个参数
print(m(3,1,2))

dict1 = {"a":4, "b":3, "c":9}
dict2 = sorted(dict1.items(), key=lambda item:item[1], reverse=True)
print(dict2)

十三、assert断言

        检查条件,不符合就报错终止程序。

a = -1
# assert a>0  # 报错
assert a<0
print(a)

十四、连接字符串用join还是+

        join的性能明显好于+。这是因为,当用操作符+连接字符串的时候,每执行一次+都会申请一块新的内存,然后复制上一个+操作的结果和本次操作的右操作符到这块内存空间,因此用+连接字符串的时候会涉及好几次内存申请和复制。而join在连接字符串的时候,会先计算需要多大的内存存放结果,然后一次性申请所需内存并将字符串复制过去,这是为什么join的性能优于+的原因。

str1 = " ".join(["hello", "world"])
str2 = "hello " + "world"
print(str1)
print(str2)

十五、深拷贝和浅拷贝

        python对象分为容器对象和非容器对象

        容器对象:

                列表、元组、字典和集合

        非容器对象(对于非容器类型的对象没有拷贝一说):

                数字、字符串和其他'原子'类型的对象

    浅拷贝:

  1. 是将一个对象的地址赋值给一个变量,让新变量指向该地址
  2. 新的对象改变,旧的对象也会跟着改变

    深拷贝:

  1. 在另一块内存地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的
  2. 新的对象改变,旧的对象不会跟着改变
from copy import deepcopy

# 浅拷贝,
a = ['hello',[1,2,3]]
b1 = a[:]
b2 = a
print([id(x) for x in a])   # 只是将新变量指向旧的变量地址
print([id(x) for x in b1])
print([id(x) for x in b2])
b2[0] = "p"
print(a)                    # 新的变量做了更改,旧的变量也会跟着改变

# 深拷贝
a = ['hello',[1,2,3]]
b = deepcopy(a)
print([id(x) for x in a])   # 地址不同
print([id(x) for x in b])
b[0] = "p"
print(a)                    # 新的对象改变,旧的对象不会跟着改变

十六、数组、链表、队列、堆栈等容器对象的区别

    数组:

  • python中没有数组的概念,这里把数组单独拿出来,是为了后面与列表做比较
  • 在c语言中,定义一个数组,必须指定数组的长度和数据类型,例如 int a[4]={10,12,16,33}
  • 相当于在内存中开辟长度为20的连续空间(注意是连续空间),a就指向该数组第一个元素的指针,每个内存空间真真实实的存储着10/12/16/33的二进制

    列表:

  • python中的列表可以混合存储任意数据类型,这是因为列表中存的是元素的内存地址,而不是元素的值。
  • 对元素取值时,先在列表中找到元素的内存地址,再通过内存地址找到元素。因此,比起数组,对列表元素的取值要慢一些。
  • 列表没有长度限制,当我们通过append方法向列表中添加元素时,如果列表满了,那么新申请2倍于原来列表的内存地址,并将原列表的值拷贝到新的内存地址中。
  • 列表的删除,插入的时间复杂度是O(N)

    栈:

  • 栈是一个数据集合,遵循先进后出的特性
  • 进栈:append
  • 出栈:pop
  • 查看栈顶元素:li[-1]
  • 在python中通常用list来实现栈

    堆栈溢出:

  • 计算机中的内存可以分为堆存储、栈存储。其中程序中的变量保存在堆中,栈用来保存函数调用上下文等。一般程序嵌套层不会太多,因此堆的比例远大于栈的比例
  • 我们通常说的内存溢出,就是堆溢出
  • 在python中,函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出

    队列:

  • 队列是一个数据集合,遵循先进先出的特性
  • 在python中通常用deque来实现队列

    链表

  • 链表中每一个元素都是一个对象,每个对象称为一个节点,包含有数据域key和指向下一个节点的指针next。通过各个节点之间的相互连接,最终串联成一个链表
  • 数组和列表都是连续存储的,删除和插入元素,其它元素需要补过去或者后移
  • 不过数组删除插入要耗时一些,时间复杂度是O(n),而链表则不会这样,它的时间复杂度是O(1)
  • 在python中通常定义class来实现链表

    集合与字典

  • 哈希表:是一种线性的存储结构(类似于数组),根据关键码的值(key, value)直接进行访问。将对象的Key通过一个哈希函数hash(key)换成一个整型数字,然后将该数字对数组长度进行取余,取余结果就当作数组的下标,将对象的值value存储在以该数字为下标的数组空间里。
  • 例如:数据集合{1,7},假设存在哈希函数hash(x)使得hash(1)=0,hash(7)=4,那么这个哈希表被存储为[1,None, None, None, 7]。当我们查找7所在的位置时,通过哈希函数获得该元素在哈希表中的下标(hash(7)=4),然后在下标为4的位置取出该元素。哈希表查询速度非常的快,几乎是O(1)的时间复杂度。
  • 哈希冲突:由于哈希表的下标范围是有限的,而元素关键字的值是接近无限的,因此可能会出现h(102)=56, h(2003)=56这种情况。此时,两个元素映射到同一个下标处,造成哈希冲突。
  • 字典:python中的字典就是使用哈希表存储的。通过哈希函数将字典中元素的key映射为数组/列表下标。假如hash(‘name’)=3, hash(‘age’)=1, h(‘hobby’)=4, 则哈希表存储为[None, 18, None, 'Lena', '瓜子']。在字典键值对数量不多的情况下,几乎不会发生哈希冲突,此时查找一个元素的时间复杂度为O(1)。
# 16-1、括号匹配问题
def check(exp):
    list1 = []
    for char in exp:
        if char in ["(", "[", "{"]:
            list1.append(char)
        elif char == ")":
            if list1[-1]=="(":
                list1.pop()
        elif char == "]":
            if list1[-1]=="[":
                list1.pop()
        elif char == "}":
            if list1[-1]=="{":
                list1.pop()
    if len(list1) == 0:
        return True
    else:
        return False
print(check("({[()]})"))
# 16-2、迷宫问题,深度遍历,栈实现
maze = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
        [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
        [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
# 约定按四个方向探寻:
dirs = [lambda x, y: (x-1, y),  # 左
        lambda x, y: (x+1, y),  # 右
        lambda x, y: (x, y-1),  # 上
        lambda x, y: (x, y+1)]  # 下
def mazePath(x1, y1, x2, y2):
    stack = []
    stack.append((x1, y1))                          # 起点位置入栈
    while stack:                                    # 栈不为空时循环;栈空代表死迷宫(找遍所有的路,直到回退到起点位置,继续回退(出栈),栈空)
        curNode = stack[-1]                         # 栈顶,表示当前位置
        if (curNode[0]==x2) and (curNode[1]==y2):   # 到达终点,栈中的元素就是最终路径
            for node in stack:                      # 打印路径
                print(node)
            return True
        for dir in dirs:                            # 依次进行4个方向的探寻
            nextNode = dir(curNode[0], curNode[1])  # 下一个位置坐标
            if maze[nextNode[0]][nextNode[1]]==0:   # 0代表通道,说明找到下一个方块
                stack.append(nextNode)              # 添加到栈顶,更新当前位置
                maze[nextNode[0]][nextNode[1]]=-1   # 将0赋值为-1,标记为已经走过,防止死循环
                break                               # 退出
        else:                                       # 四个方向都找完,没有通道,那么开始回退
            maze[curNode[0]][curNode[1]] = -1       # 标记死路
            stack.pop()                             # 弹出栈顶,回退
    print("死迷宫")
    return False  # 死迷宫
mazePath(1, 1, 8, 8)
# 16-3、迷宫问题,广度遍历,队列实现
from collections import deque

maze = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
        [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
        [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
dirs = [lambda x, y: (x-1, y),  # 左
        lambda x, y: (x+1, y),  # 右
        lambda x, y: (x, y-1),  # 上
        lambda x, y: (x, y+1)]  # 下
def mazePath2(x1, y1, x2, y2):
    queue = deque()
    path = []                                       # 存放走过的节点
    queue.append((x1, y1, -1))                      # queue中元素:(1, 1, -1),前两位是坐标,第三位是其上一步在path中的索引。这里用-1标记起点。queue出了包含正确的路径,也会包含一些噪音节点。
    maze[x1][y1] = -1
    while queue:
        curNode = queue.popleft()
        path.append(curNode)
        if curNode[0]==x2 and curNode[1]==y2:
            printPath(path)
            return True
        for dir in dirs:
            nextNode = dir(curNode[0], curNode[1])
            if maze[nextNode[0]][nextNode[1]]==0:   # 0代表通道,说明找到下一个方块,因为是广度遍历,故而上下左右都会尝试走一下,不会break
                queue.append((nextNode[0], nextNode[1], len(path)-1))  # len(path)-1)记录来源索引,也就是记录当前节点的前一个节点路径
                maze[nextNode[0]][nextNode[1]]=-1   # 标记该节点已经走过,防止死循环
    return False
def printPath(path):
    curNode = path[-1]
    realpath = []                                   # 记录新的路径
    while curNode[2] != -1:                         # 反溯到起始节点时,break
        realpath.append(curNode[0:2])
        curNode = path[curNode[2]]                  # 通过索引找到上一步节点
    realpath.append(curNode[:2])
    realpath.reverse()                              # list取反
    print(realpath)
print(mazePath2(1, 1, 8, 8))

十七、提高python运行效率

        JIT:是just in time的缩写, 也就是即时编译编译器。通常python的执行过程是,将.py的文件编译成.pyc的字节码,之后PVM将字节码编译成对应的机器指令执行。在循环语句中,编译器也会一条一条的将代码编译成字节码之后再翻译为机器指令,这非常耗时。而JIT就是解决这个问题的,针对那些频繁运行的程序,JIT会将这部分程序直接编译为机器指令并缓存起来,之后在运行时直接调用缓存的机器指令即可,大大缩减了程序运行的时间

        CPython:一般说Python都是指CPython解释器

        Cython: Cython将Python代码编译成C源码,再把C源码转换成Python扩展模块

        Pypy:   Pypy最重要的一点就是Pypy集成了JIT。Pypy的优点是对纯Python项目兼容性极好,缺点是对很多C语言库支持性不好,不能导入一些库

        Numba:  Numba是一个库,是一个用于编译python数组和数值计算函数的编译器,可以在运行时将Python代码编译为本地机器指令,而不会强制大幅度的改变普通的Python代码

        Cython、Pypy、Numba的区别:

  1. 通用性:在三个方案中,Cython和Numba的兼容性都非常好,而Pypy对于部分库的支持较差(如Numpy,Scipy)
  2. 速度:  这三种方案的速度相差不大,通常来说Cython要快于Pypy,尤其是对于部分C扩展。Pypy要快于Numba,但针对于纯数值计算的工作,Numba甚至还要快于Cython。(通常用numba对numpy进行加速)
  3. 易用性:易用性最好的无疑是Pypy,Pypy是Python的解释器,我们针对纯Python使用Pypy,除了Pypy不支持的部分库外,不需要进行任何改动。然后是Numba,Numba的基本使用方法就是给函数加一个装饰器,易用性也很高,最后是Cython,因为Cython中需要使用Python+C混合编码,如果需要移植,代价会很大
import time
from numba import jit

# pypy 可以参考,https://zhuanlan.zhihu.com/p/28018577
time_start = time.time()
for i in range(100):
    for j in range(i):
        pass
time_end = time.time()
print("pypy执行耗时:", (time_end-time_start))   # python运行耗时3.5s、pypy运行耗时0.059s

# cPython代码可读性较差,一般很少用,故而不做详细介绍

# numba使用LLVM编译器架构将纯Python代码生成优化过的机器码,可以将面向数组和使用大量数学的python代码优化到与c,c++和Fortran类似的性能,而无需改变Python的解释器。
# python加速numpy等计算时,优先考虑numba
@jit
def f(x, y):
    # A somewhat trivial example
    return x + y
print("A = ", f(1,2))

# numba编译的函数可以调用其他编译函数
@jit
def square(x):
    return x ** 2
@jit
def hypot(x, y):
    return math.sqrt(square(x) + square(y))
print("B = ", hypot(1,2))

# numba有两种编译模式:nopython模式和object模式。前者能够生成更快的代码,但是有一些限制可能迫使numba退为后者。想要避免退为后者,而且抛出异常,可以传递nopython=True.
@jit(nopython=True)
def f(x, y):
    return x + y
print("C = ", f(1,2))

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值