我的编程空间,编程开发者的网络收藏夹
学习永远不晚

PYTHON虚拟机的字节码设计方法是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

PYTHON虚拟机的字节码设计方法是什么

这篇文章主要介绍“PYTHON虚拟机的字节码设计方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“PYTHON虚拟机的字节码设计方法是什么”文章能帮助大家解决问题。

PYTHON 字节码设计

在本篇文章当中主要给大家介绍 cpython 虚拟机对于字节码的设计以及在调试过程当中一个比较重要的字段 co_lnotab 的设计原理!

一条 python 字节码主要有两部分组成,一部分是操作码,一部分是这个操作码的参数,在 cpython 当中只有部分字节码有参数,如果对应的字节码没有参数,那么 oparg 的值就等于 0 ,在 cpython 当中 opcode < 90 的指令是没有参数的。

PYTHON虚拟机的字节码设计方法是什么

opcode 和 oparg 各占一个字节,cpython 虚拟机使用小端方式保存字节码。

我们使用下面的代码片段先了解一下字节码的设计:

import disdef add(a, b):    return a + bif __name__ == '__main__':    print(add.__code__.co_code)    print("bytecode: ", list(bytearray(add.__code__.co_code)))    dis.dis(add)

上面的代码在 python3.9 的输出如下所示:

b'|\x00|\x01\x17\x00S\x00'
bytecode:  [124, 0, 124, 1, 23, 0, 83, 0]
  5           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

首先 需要了解的是 add.code.co_code 是函数 add 的字节码,是一个字节序列,list(bytearray(add.__code__.co_code)) 是将和这个序列一个字节一个字节进行分开,并且将其变成 10 进制形式。根据前面我们谈到的每一条指令&mdash;&mdash;字节码占用 2 个字节,因此上面的字节码有四条指令:

PYTHON虚拟机的字节码设计方法是什么

操作码和对应的操作指令在文末有详细的对应表。在上面的代码当中主要使用到了三个字节码指令分别是 124,23 和 83 ,他们对应的操作指令分别为 LOAD_FAST,BINARY_ADD,RETURN_VALUE。他们的含义如下:

  • LOAD_FAST:将 varnames[var_num] 压入栈顶。

  • BINARY_ADD:从栈中弹出两个对象并且将它们相加的结果压入栈顶。

  • RETURN_VALUE:弹出栈顶的元素,将其作为函数的返回值。

首先我们需要知道的是 BINARY_ADD 和 RETURN_VALUE,这两个操作指令是没有参数的,因此在这两个操作码之后的参数都是 0 。

但是 LOAD_FAST 是有参数的,在上面我们已经知道 LOAD_FAST 是将 co-varnames[var_num] 压入栈,var_num 就是指令 LOAD_FAST 的参数。在上面的代码当中一共有两条 LOAD_FAST 指令,分别是将 a 和 b 压入到栈中,他们在 varnames 当中的下标分别是 0 和 1,因此他们的操作数就是 0 和 1 。

字节码扩展参数

在上面我们谈到的 python 字节码操作数和操作码各占一个字节,但是如果 varnames 或者常量表的数据的个数大于 1 个字节的表示范围的话那么改如何处理呢?

为了解决这个问题,cpython 为字节码设计的扩展参数,比如说我们要加载常量表当中的下标为 66113 的对象,那么对应的字节码如下:

[144, 1, 144, 2, 100, 65]

其中 144 表示 EXTENDED_ARG,他本质上不是一个 python 虚拟机需要执行的字节码,这个字段设计出来主要是为了用与计算扩展参数的。

100 对应的操作指令是 LOAD_CONST ,其操作码是 65,但是上面的指令并不会加载常量表当中下标为 65 对象,而是会加载下标为 66113 的对象,原因就是因为 EXTENDED_ARG 。

现在来模拟一下上面的分析过程:

  • 先读取一条字节码指令,操作码等于 144 ,说明是扩展参数,那么此时的参数 arg 就等于 (1 x (1 << 8)) = 256 。

  • 读取第二条字节码指令,操作码等于 144 ,说明是扩展参数,因为前面 arg 已经存在切不等于 0 了,那么此时 arg 的计算方式已经发生了改变,arg = arg << 8 + 2 << 8 ,也就是说原来的 arg 乘以 256 再加上新的操作数乘以 256 ,此时 arg = 66048 。

  • 读取第三条字节码指令,操作码等于 100,此时是 LOAD_CONST 这条指令,那么此时的操作码等于 arg += 65,因为操作码不是 EXTENDED_ARG 因此操作数不需要在乘以 256 了。

上面的计算过程用程序代码表示如下,下面的代码当中 code 就是真正的字节序列 HAVE_ARGUMENT = 90 。

def _unpack_opargs(code):    extended_arg = 0    for i in range(0, len(code), 2):        op = code[i]        if op >= HAVE_ARGUMENT:            arg = code[i+1] | extended_arg            extended_arg = (arg << 8) if op == EXTENDED_ARG else 0        else:            arg = None        yield (i, op, arg)

我们可以使用代码来验证我们前面的分析:

import disdef num_to_byte(n):    return n.to_bytes(1, "little")def nums_to_bytes(data):    ans = b"".join([num_to_byte(n) for n in data])    return ansif __name__ == '__main__':    # extended_arg extended_num opcode oparg for python_version > 3.5    bytecode = nums_to_bytes([144, 1, 144, 2, 100, 65])    print(bytecode)    dis.dis(bytecode)

上面的代码输出结果如下所示:

b'\x90\x01\x90\x02dA'
          0 EXTENDED_ARG             1
          2 EXTENDED_ARG           258
          4 LOAD_CONST           66113 (66113)

根据上面程序的输出结果可以看到我们的分析结果是正确的。

源代码字节码映射表

在本小节主要分析一个 code object 对象当中的 co_lnotab 字段,通过分析一个具体的字段来学习这个字段的设计。

import disdef add(a, b):    a += 1    b += 2    return a + bif __name__ == '__main__':    dis.dis(add.__code__)    print(f"{list(bytearray(add.__code__.co_lnotab)) = }")    print(f"{add.__code__.co_firstlineno = }")

首先 dis 的输出第一列是字节码对应的源代码的行号,第二列是字节码在字节序列当中的位移。

上面的代码输出结果如下所示:

  源代码的行号  字节码的位移
  6           0 LOAD_FAST                0 (a)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_FAST               0 (a)
  7           8 LOAD_FAST                1 (b)
             10 LOAD_CONST               2 (2)
             12 INPLACE_ADD
             14 STORE_FAST               1 (b)
  8          16 LOAD_FAST                0 (a)
             18 LOAD_FAST                1 (b)
             20 BINARY_ADD
             22 RETURN_VALUE
list(bytearray(add.__code__.co_lnotab)) = [0, 1, 8, 1, 8, 1]
add.__code__.co_firstlineno = 5

从上面代码的输出结果可以看出字节码一共分成三段,每段表示一行代码的字节码。现在我们来分析一下 co_lnotab 这个字段,这个字段其实也是两个字节为一段的。比如上面的 [0, 1, 8, 1, 8, 1] 就可以分成三段 [0, 1], [8, 1], [8, 1] 。这其中的含义分别为:

  • 第一个数字表示距离上一行代码的字节码数目。

  • 第二个数字表示距离上一行有效代码的行数。

现在我们来模拟上面代码的字节码的位移和源代码行数之间的关系:

  • [0, 1],说明这行代码离上一行代码的字节位移是 0 ,因此我们可以看到使用 dis 输出的字节码 LOAD_FAST ,前面的数字是 0,距离上一行代码的行数等于 1 ,代码的第一行的行号等于 5,因此 LOAD_FAST 对应的行号等于 5 + 1 = 6 。

  • [8, 1],说明这行代码距离上一行代码的字节位移为 8 个字节,因此第二块的 LOAD_FAST 前面是 8 ,距离上一行代码的行数等于 1,因此这个字节码对应的源代码的行号等于 6 + 1 = 7。

  • [8, 1],同理可以知道这块字节码对应源代码的行号是 8 。

现在有一个问题是当两行代码之间相距的行数超过 一个字节的表示范围怎么办?在 python3.5 以后如果行数差距大于 127,那么就使用 (0, 行数) 对下一个组合进行表示,(0, x_1), (0, x_2) ... ,直到 x_1 + ... + x_n = 行数。

在后面的程序当中我们会使用 compile 这个 python 内嵌函数。当你使用Python编写代码时,可以使用compile()函数将Python代码编译成字节代码对象。这个字节码对象可以被传递给Python的解释器或虚拟机,以执行代码。

compile()函数接受三个参数:

  • source: 要编译的Python代码,可以是字符串,字节码或AST对象。

  • filename: 代码来源的文件名(如果有),通常为字符串。

  • mode: 编译代码的模式。可以是 'exec'、'eval' 或 'single' 中的一个。'exec' 模式用于编译多行代码,'eval' 用于编译单个表达式,'single' 用于编译单行代码。

import discode = """x=1y=2""" \+ "\n" * 500 + \"""z=x+y"""code = compile(code, '<string>', 'exec')print(list(bytearray(code.co_lnotab)))print(code.co_firstlineno)dis.dis(code)

上面的代码输出结果如下所示:

[0, 1, 4, 1, 4, 127, 0, 127, 0, 127, 0, 121]
1
  2           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (x)

  3           4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (y)

505           8 LOAD_NAME                0 (x)
             10 LOAD_NAME                1 (y)
             12 BINARY_ADD
             14 STORE_NAME               2 (z)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

根据我们前面的分析因为第三行和第二行之间的差距大于 127 ,因此后面的多个组合都是用于表示行数的。

505 = 3(前面已经有三行了) + (127 + 127 + 127 + 121)(这个是第二行和第三行之间的差距,这个值为 502,中间有 500 个换行但是因为字符串相加的原因还增加了两个换行,因此一共是 502 个换行)。

具体的算法用代码表示如下所示,下面的参数就是我们传递给 dis 模块的 code,也就是一个 code object 对象。

def findlinestarts(code):    """Find the offsets in a byte code which are start of lines in the source.    Generate pairs (offset, lineno) as described in Python/compile.c.    """    byte_increments = code.co_lnotab[0::2]    line_increments = code.co_lnotab[1::2]    bytecode_len = len(code.co_code)    lastlineno = None    lineno = code.co_firstlineno    addr = 0    for byte_incr, line_incr in zip(byte_increments, line_increments):        if byte_incr:            if lineno != lastlineno:                yield (addr, lineno)                lastlineno = lineno            addr += byte_incr            if addr >= bytecode_len:                # The rest of the lnotab byte offsets are past the end of                # the bytecode, so the lines were optimized away.                return        if line_incr >= 0x80:            # line_increments is an array of 8-bit signed integers            line_incr -= 0x100        lineno += line_incr    if lineno != lastlineno:        yield (addr, lineno)

PYTHON 字节码表

操作操作码
POP_TOP1
ROT_TWO2
ROT_THREE3
DUP_TOP4
DUP_TOP_TWO5
ROT_FOUR6
NOP9
UNARY_POSITIVE10
UNARY_NEGATIVE11
UNARY_NOT12
UNARY_INVERT15
BINARY_MATRIX_MULTIPLY16
INPLACE_MATRIX_MULTIPLY17
BINARY_POWER19
BINARY_MULTIPLY20
BINARY_MODULO22
BINARY_ADD23
BINARY_SUBTRACT24
BINARY_SUBSCR25
BINARY_FLOOR_DIVIDE26
BINARY_TRUE_DIVIDE27
INPLACE_FLOOR_DIVIDE28
INPLACE_TRUE_DIVIDE29
RERAISE48
WITH_EXCEPT_START49
GET_AITER50
GET_ANEXT51
BEFORE_ASYNC_WITH52
END_ASYNC_FOR54
INPLACE_ADD55
INPLACE_SUBTRACT56
INPLACE_MULTIPLY57
INPLACE_MODULO59
STORE_SUBSCR60
DELETE_SUBSCR61
BINARY_LSHIFT62
BINARY_RSHIFT63
BINARY_AND64
BINARY_XOR65
BINARY_OR66
INPLACE_POWER67
GET_ITER68
GET_YIELD_FROM_ITER69
PRINT_EXPR70
LOAD_BUILD_CLASS71
YIELD_FROM72
GET_AWAITABLE73
LOAD_ASSERTION_ERROR74
INPLACE_LSHIFT75
INPLACE_RSHIFT76
INPLACE_AND77
INPLACE_XOR78
INPLACE_OR79
LIST_TO_TUPLE82
RETURN_VALUE83
IMPORT_STAR84
SETUP_ANNOTATIONS85
YIELD_VALUE86
POP_BLOCK87
POP_EXCEPT89
STORE_NAME90
DELETE_NAME91
UNPACK_SEQUENCE92
FOR_ITER93
UNPACK_EX94
STORE_ATTR95
DELETE_ATTR96
STORE_GLOBAL97
DELETE_GLOBAL98
LOAD_CONST100
LOAD_NAME101
BUILD_TUPLE102
BUILD_LIST103
BUILD_SET104
BUILD_MAP105
LOAD_ATTR106
COMPARE_OP107
IMPORT_NAME108
IMPORT_FROM109
JUMP_FORWARD110
JUMP_IF_FALSE_OR_POP111
JUMP_IF_TRUE_OR_POP112
JUMP_ABSOLUTE113
POP_JUMP_IF_FALSE114
POP_JUMP_IF_TRUE115
LOAD_GLOBAL116
IS_OP117
CONTAINS_OP118
JUMP_IF_NOT_EXC_MATCH121
SETUP_FINALLY122
LOAD_FAST124
STORE_FAST125
DELETE_FAST126
RAISE_VARARGS130
CALL_FUNCTION131
MAKE_FUNCTION132
BUILD_SLICE133
LOAD_CLOSURE135
LOAD_DEREF136
STORE_DEREF137
DELETE_DEREF138
CALL_FUNCTION_KW141
CALL_FUNCTION_EX142
SETUP_WITH143
LIST_APPEND145
SET_ADD146
MAP_ADD147
LOAD_CLASSDEREF148
EXTENDED_ARG144
SETUP_ASYNC_WITH154
FORMAT_VALUE155
BUILD_CONST_KEY_MAP156
BUILD_STRING157
LOAD_METHOD160
CALL_METHOD161
LIST_EXTEND162
SET_UPDATE163
DICT_MERGE164
DICT_UPDATE165

关于“PYTHON虚拟机的字节码设计方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

PYTHON虚拟机的字节码设计方法是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

PYTHON虚拟机的字节码设计方法是什么

这篇文章主要介绍“PYTHON虚拟机的字节码设计方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“PYTHON虚拟机的字节码设计方法是什么”文章能帮助大家解决问题。PYTHON 字节码设计在本
2023-07-05

Python虚拟机中字节的实现原理是什么

这篇文章主要介绍“Python虚拟机中字节的实现原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python虚拟机中字节的实现原理是什么”文章能帮助大家解决问题。数据结构typedef st
2023-07-05

深入解析PYTHON 虚拟机令人拍案叫绝的字节码设计

这篇文章主要为大家介绍了PYTHON虚拟机中令人拍案叫绝的字节码设计深入详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-14

tplink虚拟主机设置的方法是什么

要设置TP-Link虚拟主机,可以按照以下步骤进行操作:1. 首先,确保你的计算机已经连接到了TP-Link的路由器,并且能够通过浏览器访问路由器的管理界面。2. 打开浏览器,输入路由器的默认IP地址,通常是192.168.0.1或192.
2023-08-16

vm虚拟机设置网络的方法是什么

设置 VM 虚拟机网络的方法如下:1. 在 VM 虚拟机中打开 “设置” 菜单;2. 选择 “网络适配器” 选项卡;3. 选择 “桥接模式” 或 “NAT 模式”;4. 如果选择 “桥接模式”,则需要选择物理网卡,以便 VM 虚拟机可以访问
2023-05-31

永久虚拟主机设置的方法是什么

永久虚拟主机的设置方法如下:1.选择好可靠的虚拟主机提供商,并购买永久虚拟主机。2.登录虚拟主机控制面板,选择“域名管理”或“网站设置”等选项,进入设置页面。3.输入你的域名,并添加DNS解析,将域名解析到虚拟主机的IP地址上。4.设置FT
2023-06-10

虚拟主机设置二级域名的方法是什么

设置二级域名的方法如下:1. 首先在域名解析商处添加相应的DNS记录,将二级域名指向虚拟主机的IP地址。例如,添加一个名为"subdomain"的A记录,将其指向虚拟主机的IP地址。2. 登录虚拟主机的控制面板或使用FTP客户端,创建一个与
2023-09-07

linux虚拟主机创建和设置的方法是什么

要创建和设置Linux虚拟主机,可以按照以下步骤:1.选择虚拟主机服务提供商:首先,选择一个可靠的虚拟主机服务提供商,例如HostGator、Bluehost、DreamHost等。2.选择虚拟主机计划:根据您的需求选择一个合适的虚拟主机计
2023-05-31

云虚拟主机设置多站点的方法是什么

设置云虚拟主机多站点的方法如下:1. 确保你的云虚拟主机支持多站点功能。有些虚拟主机提供商可能限制了多站点的功能,因此你需要先确认你的虚拟主机是否支持。2. 创建一个新的网站目录。每个站点都应该有一个独立的目录来存放其文件和数据。你可以在虚
2023-08-16

Java虚拟机安装的方法是什么

Java虚拟机(JVM)安装方法安装JVM需满足系统要求,并下载相应安装包。Windows安装:运行安装程序,选择组件和路径。接受协议并单击“安装”。macOS安装:拖动JVM图标到“应用程序”文件夹。双击图标,按照向导提示操作。接受协议,输入密码,并单击“安装”。Linux安装:下载安装包并解压缩。移动目录到bin路径,并编辑~/.profile文件。设置JAVA_HOME环境变量。验证安装:java-version。设置环境变量:Windows:在“系统”控制面板中添加JAVA_HOME变量。macO
Java虚拟机安装的方法是什么
2024-04-09

注册虚拟主机的方法是什么

注册虚拟主机的方法通常如下:1、选择虚拟主机提供商首先需要选择一家可靠的虚拟主机提供商,根据自己的需求选择适合自己的虚拟主机服务。2、选择虚拟主机套餐根据自己的需求选择虚拟主机套餐,例如磁盘空间、带宽、数据库数量等。3、填写订单信息在虚拟主
2023-03-19

清空虚拟主机的方法是什么

清空虚拟主机的方法有以下几种:1. 删除虚拟主机的文件:通过登录到服务器,找到虚拟主机所在的目录,并删除该目录下的所有文件和文件夹。一般虚拟主机的文件目录在 /var/www/html 或类似的位置。2. 重置虚拟主机的数据库:如果虚拟主机
2023-08-22

虚拟主机的使用方法是什么

虚拟主机是一种将一台物理服务器分割成多个独立的虚拟服务器的技术,每个虚拟服务器可以拥有自己独立的操作系统、磁盘空间、带宽和其他资源。使用虚拟主机的方法如下:1.选择虚拟主机提供商:根据自己的需求和预算选择一个可靠的虚拟主机提供商。考虑到提供
2023-08-25

Java虚拟机安装的方法是什么

安装Java虚拟机(JVM)的方法如下:1. 下载Java Development Kit(JDK):首先,你需要下载适用于你操作系统的JDK版本。你可以在Oracle官方网站上下载JDK。2. 安装JDK:一旦下载完成,双击安装程序并按照
2023-09-14

win7虚拟内存设置的方法是什么

这篇文章主要讲解了“win7虚拟内存设置的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“win7虚拟内存设置的方法是什么”吧!win74g内存虚拟内存设置多少合适答:一般虚拟内存可
2023-07-01

虚拟主机共享一个ip设置的方法是什么

虚拟主机共享一个IP的设置方法主要包括以下步骤:1. 配置虚拟主机:在Web服务器中,通过配置虚拟主机来指定每个域名或网站的根目录和其他相关设置。具体的配置方法取决于所使用的Web服务器软件,如Apache、Nginx等。2. 绑定域名:将
2023-08-25

tomcat虚拟主机的配置方法是什么

要配置Tomcat虚拟主机,您可以按照以下步骤进行操作:1、在Tomcat的conf/server.xml配置文件中添加一个Host元素。2、在Host元素中指定虚拟主机的名称和应用程序基础目录。在上面的例子中,虚拟主机名称为"www.ex
2023-03-21

mysql虚拟主机连接的方法是什么

要连接MySQL虚拟主机,需要使用以下步骤:1. 确认MySQL虚拟主机的IP地址和端口号。2. 使用MySQL客户端连接虚拟主机,输入用户名和密码。3. 如果连接成功,就可以使用MySQL客户端进行数据库操作了。需要注意的是,不同的虚拟主
2023-06-06

配置tomcat虚拟主机的方法是什么

配置Tomcat虚拟主机的步骤如下:1. 在Tomcat的conf/server.xml文件中添加Host元素,指定虚拟主机的名称和访问的IP地址或域名。2. 在虚拟主机的目录下添加WEB-INF目录和web.xml文件,用于配置虚拟主机的
2023-06-08

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录