python 性能优化实例练习二 —— 细节优化(ctypes等,未完)

本章继续沿用Fernando Doglio写的《Python性能分析与优化》的知识体系,主要是根据细节优化方面再结合自身的实际应用进行介绍,其重点为ctypes的使用。

文章目录

  • 函数返回值缓存/函数查询表
  • 默认参数的用法
  • 列表综合表达式生成器
  • ctypes
    • ctypes的使用
  • 字符串连接
    • 列表内字符串合并
    • 变量内插法
  • 其他Python优化技巧
  • 去掉不必要的程序

函数返回值缓存/函数查询表

默认参数的用法

默认参数(default argument)可以在函数创建时就确定输入值,而不用在运行阶段才确定输入。

在这里复习一下python参数:

  1. 位置参数(positional argument)

  2. 关键词参数(keyword argument)

    def trapezoid_area(base_up, base_down, height)
    	return 1/2 * (base_up + base_down) * height
    
    trapezoid_area(1, 2, 3) # 位置参数
    trapezoid_area(base_up=1, base_down=2, height=3) # 关键词参数
    trapezoid_area(height=3, base_down=2, base_up=1) # right
    trapezoid_area(height=1, base_down=2, 1) # wrong
    trapezoid_area(base_up=1, base_down=2, 3) # right
    trapezoid_area(1, 2, height=3)
    
  3. 默认参数

    默认参数 = 默认值,调用函数时,默认参数的值如果没有传入,则被认为是默认值。

    默认参数一定要放在位置参数 后面,不然程序会报错。

    def trapezoid_area(base_up, base_down, height=3)
    	return 1/2 * (base_up + base_down) * height
    trapezoid_area(1, 2) 
    trapezoid_area(1, 2, height=3) # 位置参数
    
  4. 可变参数 (variable argument)

    可变参数就是传入的参数个数是可变的,可以是 0, 1, 2 到任意个,是不定长的参数。

    def printinfo(arg1, *args): # *args - 可变参数,可以是从零个到任意个,自动组装成元组。
        print('arg1:' + str(arg1))
        print('args:', end=' ')
        for var in args:
            print(var, end=' ')
        print(' ')
    
    
    printinfo(10)  # 10
    printinfo(70, 60, 50)
    

    程序的运行结果为:

    arg1:10
    args:  
    arg1:70
    args: 60 50  
    
    Process finished with exit code 0
    
  5. 关键字参数 (keyword argument)

    def printinfo(arg1, *args, **kwargs): # **kwargs - 关键字参数,可以是从零个到任意个,自动组装成字典。
        print(arg1)
        print(args)
        print(kwargs)
    
    
    printinfo(70, 60, 50)
    # 70
    # (60, 50)
    # {}
    printinfo(70, 60, 50, a=1, b=2)
    # 70
    # (60, 50)
    # {'a': 1, 'b': 2}
    

    程序中注释部分即为运行结果。

  6. 命名关键字参数 (name keyword argument)

    • *, nkw - 命名关键字参数,用户想要输入的关键字参数,定义方式是在nkw 前面加个分隔符 *
    • 如果要限制关键字参数的名字,就可以用「命名关键字参数」
    • 使用命名关键字参数时,要特别注意不能缺少参数名
    def printinfo(arg1, *, nkw, **kwargs):
        print(arg1)
        print(nkw)
        print(kwargs)
    
    
    printinfo(70, nkw=10, a=1, b=2)
    # 70
    # 10
    # {'a': 1, 'b': 2}
    
    printinfo(70, 10, a=1, b=2)
    # TypeError: printinfo() takes 1 positional argument but 2 were given
    
  7. 参数组合

    在 Python 中定义函数,可以用位置参数、默认参数、可变参数、命名关键字参数和关键字参数,这 5 种参数中的 4 个都可以一起使用,但是注意,参数定义的顺序必须是:

    • 位置参数、默认参数、可变参数和关键字参数。
    • 位置参数、默认参数、命名关键字参数和关键字参数。

    要注意定义可变参数和关键字参数的语法:

    • *args 是可变参数,args 接收的是一个 tuple
    • **kwargs 是关键字参数,kw 接收的是一个 dict

    命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。定义命名关键字参数不要忘了写分隔符 *,否则定义的是位置参数。

    警告:虽然可以组合多达 5 种参数,但不要同时使用太多的组合,否则函数很难懂。

列表综合表达式生成器

将初始化等操作放在程序计算前面,用列表综合表达式替换for循环。

final_state_list = [FinalStatus() for x in range(opt.multiple_camera_number)]

ctypes

ctypes库可以让开发者直接进入Python的底层,借助C语言的力量进行开发。这个库只有官方版本解释器(CPython)里面才有,因为这个版本是C语言写的。其他版本,如PyPy和Jython,都不能接入这个库。

这时我们可以把关键代码写成C语言,编译成一个库,然后导入Python当作模块使用。

如何判断python解释器是哪一个我将原文中的代码进行了修改,只要cv一下在自己的python环境里跑一下就知道用的是哪一个解释器了。

import sys
import os
try:
    from platform import python_implementation
except ImportError: # pragma: no cover
    def python_implementation():
        """Return a string identifying the Python implementation."""
        if 'PyPy' in sys.version:
            print('PyPy')
        if os.name == 'java':
            print('Jython')
        if sys.version.startswith('IronPython'):
            print('IronPython')
finally:
    print('CPython')

ctypes的使用

关于ctypes的使用,看了几篇帖子感觉都讲得有点复杂,个人感觉ctypes的使用大致是将纯种的c代码编译成外部链接库,在python中导入这个外部链接库进行使用。

第一步:先创建ctypes_test.c

// 这是找素数的程序
#include 
#include 
int check_prime(int a)
{
    int c;
    for ( c = 2 ; c <= sqrt(a) ; c++ ) {
        if ( a%c == 0 )
        return 0;
    }
    return 1;
}

第二步:编译该文件

$ gcc -shared -o ct.so -fPIC .\ctypes_test.c

我的是在anaconda中的环境,并且已经安装了gcc的gcc version 8.1.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project),关于gcc的参数进行一些补充:

参数 功能
-shared 此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。
-static 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库。
-fPIC 表示是动态链接库?

第三步:在python程序中调用生成的库

import time
import ctypes
import math
# ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
check_primes_types = ctypes.CDLL('./ct.so').check_prime  # ctypes.CDLL('路径').函数名

def check_prime(x):
    values = range(2, int(math.sqrt(x)))
    for i in values:
        if x % i == 0:
            return False
    return True


init = time.time()
numbers_py = [x for x in range(1000000) if check_prime(x)]
print("Full python version: %s seconds" % (time.time() - init))
init = time.time()
numbers_c = [x for x in range(1000000) if check_primes_types(x)]
print("C version: %s seconds" % (time.time() - init))
print(len(numbers_py))

注意事项

返回值:关于ctypes调用dll时的参数类型和返回值类型问题

参数:python调用c/c++代码以及解决ctypes.ArgumentError: argument 1: class ‘TypeError’: Don’t know how to convert

参数为数组:ctypes的运用(把一个numpy数组传入c中)

字符串连接

由于字符串是不可变的,每当我们要做任何改变字符串内容的操作时,其实都是创建了一个带有新内容的新字符串,我们的变量会指向新创建的字符串。

列表内字符串合并

full_doc = ""
for word in word_list:
	full_doc += word

改写为

full_doc = "".join(world_list)

变量内插法

document = "%s%s%s%s" % (title, introduction, main_piece, conclusion)

其他Python优化技巧

去掉不必要的程序

# 173行
imc = im0.copy() if opt.save_crop else im0  # for save_crop

你可能感兴趣的:(Python里的碰撞,python,性能,ctypes)