Python函数属性和PyCodeObject
函数属性
python中的函数是一种对象,它有属于对象的属性。除此之外,函数还可以自定义自己的属性。注意,属性是和对象相关的,和作用域无关。
自定义属性
自定义函数自己的属性方式很简单。假设函数名称为myfunc,那么为这个函数添加一个属性var1:
myfunc.var1="abc"
那么这个属性var1就像是全局变量一样被访问、修改。但它并不是全局变量。
可以跨模块自定义函数的属性。例如,在b.py中有一个函数b_func()
,然后在a.py中导入这个b.py模块,可以直接在a.py中设置并访问来自b.py中的b_func()
的属性。
import b
b.b_func.var1="hello"
print(b.b_func.var1) # 输出hello
查看函数对象属性
python函数是一种对象,是对象就会有对象的属性。可以通过如下方式查看函数对象的属性:
dir(func_name)
例如,有一个属性__name__
,它表示函数的名称:
def f(x):
y=10
def g(z):
return x+y+z
return g
print(f.__name__) # 输出f
还有一个属性__code__
,这个属性是本文的重点,它表示函数代码对象:
print(f.__code__)
输出:
<code object f at 0x0335B180, file "a.py", line 2>
上面的输出结果已经指明了__code__
也是对象,既然是对象,它就有自己的属性:
print( dir(f.__code__) )
现在,就可以看到函数代码对象相关的属性,其中有一类属性都以co_
开头,表示字节码的意思,后文会详细解释这些属性的意义。实际上,并非只有函数具有这些属性,所有的代码块(code block)都有这些属性。
[...省略其它非co_属性...
'co_argcount', 'co_cellvars',
'co_code', 'co_consts',
'co_filename', 'co_firstlineno',
'co_flags', 'co_freevars',
'co_kwonlyargcount', 'co_lnotab',
'co_name', 'co_names', 'co_nlocals',
'co_stacksize', 'co_varnames']
如何查看这些__code__
的属性?使用f.__code__.co_XXX
即可。由于dir()
返回的是属性列表,所以下面使用循环将co_
开头的属性都输出出来:
for i in dir(f.__code__):
if i.startswith("co"):
print(i+":",eval("f.__code__."+i))
输出结果:
co_argcount: 1
co_cellvars: ('x', 'y')
co_code: b'd\x01\x89\x01\x87\x00\x87\x01f\x02d\x02d\x03\x84\x08}\x01|\x01S\x00'
co_consts: (None, 10, <code object g at 0x02FB7338, file "g:/pycode/b.py", line 3>, 'f.<locals>.g')
co_filename: g:/pycode/b.py
co_firstlineno: 1
co_flags: 3
co_freevars: ()
co_kwonlyargcount: 0
co_lnotab: b'\x00\x01\x04\x01\x0e\x02'
co_name: f
co_names: ()
co_nlocals: 2
co_stacksize: 3
co_varnames: ('x', 'g')
此外,还可以使用dis
模块的show_code()
函数来输出这些信息的整理:
import dis
def f(x):
y=10
def g(z):
return x+y+z
return g
print(dis.show_code(f))
输出结果:
Name: f
Filename: g:/pycode/b.py
Argument count: 1
Kw-only arguments: 0
Number of locals: 2
Stack size: 3
Flags: OPTIMIZED, NEWLOCALS
Constants:
0: None
1: 10
2: <code object g at 0x00A89338, file "g:/pycode/b.py", line 4>
3: 'f.<locals>.g'
Variable names:
0: x
1: g
Cell variables:
0: x
1: y
None
__code__属性的解释
这些属性定义在python源码包的Include/code.h
文件中,如有需要,可自行去查看。
另外,这些属性是代码块(code block)的,不限于函数。但此处以函数为例进行说明。
由于这些属性中涉及到了闭包属性(或者嵌套函数的属性),所以以下面这个a.py文件中的嵌套函数为例:
import dis
x=3
def f(a,b,*args,c):
a=3
y=10
print(a,b,c,x,y)
def g(z):
return a+b+c+x+z
return g
以下是查看函数f()和闭包函数g()的方式:
# f()的show_code结果
dis.show_code(f)
# f()的co_XXX属性
for i in dir(f.__code__):
if i.startswith("co"):
print(i+":",eval("f.__code__."+i))
# 闭包函数,注意,传递了*args参数
f1=f(3,4,"arg1","arg2",c=5)
# f1()的show_code结果
dis.show_code(f1)
# f1()的co_XXX属性
for i in dir(f1.__code__):
if i.startswith("co"):
print(i+":",eval("f1.__code__."+i))
下面将根据上面查看的结果解释各属性:
co_name
函数的名称。
上例中该属性的值为外层函数f和闭包函数g,注意不是f1。
co_filename
函数定义在哪个文件名中。
上例中为a.py
。
co_firstlineno
函数声明语句在文件中的第几行。即def关键字所在的行号。
上例中f()的行号为3,g()的行号为7。
co_consts
该函数中使用的常量有哪些。python中并没有专门的常量概念,所有字面意义的数据都是常量。
以下是show_code()得到的f()中的常量:
Constants:
0: None
1: 3
2: 10
3: <code object g at 0x0326B7B0, file "a.py", line 7>
4: 'f.<locals>.g'
而内层函数g()中没有常量。
co_kwonlyargcount
keyword-only的参数个数。
f()的keyword-only的参数只有c,所以个数为1
g()中没有keyword-only类的参数,所以为0
co_argcount
除去*args
之外的变量总数。实际上是除去*
和**
所收集的参数以及keyword-only类的参数之后剩余的参数个数。换句话说,是*
或**
前面的位置参数个数。
f()中属于此类参数的有a和b,所以co_argcount数值为2
g()中只有一个位置参数,所以co_argcount数值为1
co_nlocals
co_varnames
本地变量个数和它们的名称,变量名称收集在元组中。
f()的本地变量个数为6,元组的内容为:('a', 'b', 'c', 'args', 'y', 'g')
g()的本地变量个数为1,元组的内容为:('z',)
co_stacksize
本段函数需要在栈空间评估的记录个数。换句话说,就是栈空间个数。
这个怎么计算的,我也不知道。以下是本示例的结果:
f()的栈空间个数为6
g()的栈空间个数为2
co_names
函数中保存的名称符号,一般除了本地变量外,其它需要查找的变量(如其它文件中的函数名,全局变量等)都需要保存起来。
f()的co_names:
Names:
0: print
1: x
g()的co_names:
Names:
0: x
co_cellvars
co_freevars
这两个属性和嵌套函数(或者闭包有关),它们是互相对应的,所以内容完全相同,它们以元组形式存在。
co_cellvars
是外层函数的哪些本地变量被内层函数所引用
co_freevars
是内层函数引用了哪些外层函数的本地变量
对外层函数来说,co_freevars
一定是空元组,对内层函数来说,co_cellvars
则一定是空元组。
如果知道自由变量的概念,这个很容易理解。
f()的co_cellvars
内容: ('a', 'b', 'c')
g()的co_freevars
内容: ('a', 'b', 'c')
co_code
co_flags
co_lnotab
这3个属性和python函数的源代码编译成字节码有关,本文不解释它们。
属性和字节码对象PyCodeObject
对于python,通常都认为它是一种解释型语言。但实际上它在进行解释之前,会先进行编译,会将python源代码编译成python的字节码(bytecode),然后在python virtual machine(PVM)中运行这段字节码,就像Java一样。但是PVM相比JVM而言,要更"高级"一些,这个高级的意思和高级语言的意思一样:离物理机(处理机器码)的距离更远,或者说要更加抽象。
源代码被python编译器编译的结果会保存在内存中一个名为PyCodeObject的对象中,当需要运行时,python解释器开始将其放进PVM中解释执行,执行完毕后解释器会"根据需要"将这个编译的结果对象持久化到二进制文件*.pyc
中。下次如果再执行,将首先从文件中加载(如果存在的话)。
所谓"根据需要"是指该py文件是否只运行一次,如果不是,则写入pyc文件。至少,对于那些模块文件,都会生成pyc二进制文件。另外,使用compileall模块,可以强制让py文件编译后生成pyc文件。
但需要注意,pyc虽然是字节码文件,但并不意味着比py文件执行效率更高,它们是一样的,都是一行行地读取、解释、执行。pyc唯一比py快的地方在导入,因为它无需编译的过程,而是直接从文件中加载对象。
py文件中的每一个代码块(code block)都有一个属于自己的PyCodeObject对象。每个代码块除了被编译得到的字节码数据,还包含这个代码块中的常量、变量、栈空间等内容,也就是前面解释的各种co_XXX
属性信息。
pyc文件包含3部分:
- 4字节的Magic int,表示pyc的版本信息
- 4字节的int,是pyc的产生时间,如果与py文件修改时间不同,则会重新生成
- PycodeObject对象序列化的内容
参考文章:https://blog.csdn.net/efeics/article/details/9255193
Python函数属性和PyCodeObject的更多相关文章
- Python 学习 第八篇:函数2(参数、lamdba和函数属性)
函数的参数是参数暴露给外部的接口,向函数传递参数,可以控制函数的流程,函数可以0个.1个或多个参数:在Python中向函数传参,使用的是赋值方式. 一,传递参数 参数是通过赋值来传递的,传递参数的特点 ...
- Python基本语法_函数属性 & 参数类型 & 偏函数的应用
目录 目录 前言 软件环境 Python Module的程序入口 函数的属性 Python函数的创建 函数的参数 必备参数 缺省参数 命名参数 不定长参数 匿名参数 偏函数的应用 前言 Python除 ...
- Python函数参数默认值的陷阱和原理深究"
本文将介绍使用mutable对象作为Python函数参数默认值潜在的危害,以及其实现原理和设计目的 本博客已经迁移至: http://cenalulu.github.io/ 本篇博文已经迁移,阅读全文 ...
- Python入门笔记(18):Python函数(1):基础部分
一.什么是函数.方法.过程 推荐阅读:http://www.cnblogs.com/snandy/archive/2011/08/29/2153871.html 一般程序设计语言包含两种基本的抽象:过 ...
- Day03 - Python 函数
1. 函数简介 函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.Python提供了许多内建函数,比如print():也可以自己创建函数,这 ...
- python函数参数的pack与unpack
python函数参数的pack与unpack 上周在使用django做开发的时候用到了mixin(关于mixin我还要写一个博客专门讨论一下,现在请参见这里),其中又涉及到了一个关于函数参数打包(pa ...
- Python 3.X 调用多线程C模块,并在C模块中回调python函数的示例
由于最近在做一个C++面向Python的API封装项目,因此需要用到C扩展Python的相关知识.在此进行简要的总结. 此篇示例分为三部分.第一部分展示了如何用C在Windows中进行多线程编程:第二 ...
- 使用可变对象作为python函数默认参数引发的问题
写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...
- 【转】Python函数默认参数陷阱
[转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...
随机推荐
- U盘安装centos7 关键步骤
出现安装界面 按tab键或e键进行修改 一般默认第二行为: vmlinuz intrd=initrd.img inst.stage2=hd:LABEL=CentOS\x207\x20x86_64 rd ...
- 洛谷 质因子分 p2043
#include <iostream>#include <algorithm>#include <cstring>using namespace std; cons ...
- B树/[oracle]connect BY语句
读大神的书,出现很多没有见过的函数和便捷操作,特此记录 connect by 之前没有接触过,为了学习这个语句,先了解一下B树数据类型是最好的方法. [本人摘自以下博客] https://www.cn ...
- 简述Oracle IOT(Index Organized Table)
转:http://blog.itpub.net/17203031/viewspace-744477 对关系型数据库产品(RDBMS)而言,一个重要特性就是:数据信息都被组织为二维数据表,信息的表达可以 ...
- python insert所用 插入到自定的位置
a = list(range(50)) b = list(range(50)) c = [] for x in a: c.insert(x, [a[x], b[x]]) print(c)
- jsp获取当前项目跟路径
在jsp中获取当前项目的根路径: <% String basePath = request.getScheme() + "://"+ request.getServerNam ...
- python网络编程 双人多人聊天
在学习网路编程时,我们首先要考虑的是其中的逻辑,我们借助打电话的形式来了解网络编程的过程, 我们打电话时属于呼叫方,接电话的属于被呼叫方,那么被呼叫方一直保持在待机状态,等待主呼叫方 呼叫,只有在被呼 ...
- 关于oracle数据库的小知识
--查询语句:select 列名/通配符/列别名/表达式 from 表名 (修饰/限制语句)select * from tab;select tname from tab;--指定的列select t ...
- J2EE导论 | 疑惑篇
J2EE是Java程序员从新手进阶的一个必经之路.要体会所谓的工业级代码,就必须要融入和经历更为复杂的开发.部署环境,需要同更多的模块.组件做信息流交换,比较和使用不同的框架,逐一去琢磨和考察它们的必 ...
- 初探系列 — Pharbers用于单点登录的权限架构
一. 前言 就职公司 法伯科技是一家以数据科技为驱动, 专注于医药健康领域的循证咨询公司. 以数据科学家身份, 赋能医药行业. 让每位客户都能享受数据带来的价值, 洞察业务, 不止于数据, 让决策更精 ...