函数属性

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的更多相关文章

  1. Python 学习 第八篇:函数2(参数、lamdba和函数属性)

    函数的参数是参数暴露给外部的接口,向函数传递参数,可以控制函数的流程,函数可以0个.1个或多个参数:在Python中向函数传参,使用的是赋值方式. 一,传递参数 参数是通过赋值来传递的,传递参数的特点 ...

  2. Python基本语法_函数属性 & 参数类型 & 偏函数的应用

    目录 目录 前言 软件环境 Python Module的程序入口 函数的属性 Python函数的创建 函数的参数 必备参数 缺省参数 命名参数 不定长参数 匿名参数 偏函数的应用 前言 Python除 ...

  3. Python函数参数默认值的陷阱和原理深究"

    本文将介绍使用mutable对象作为Python函数参数默认值潜在的危害,以及其实现原理和设计目的 本博客已经迁移至: http://cenalulu.github.io/ 本篇博文已经迁移,阅读全文 ...

  4. Python入门笔记(18):Python函数(1):基础部分

    一.什么是函数.方法.过程 推荐阅读:http://www.cnblogs.com/snandy/archive/2011/08/29/2153871.html 一般程序设计语言包含两种基本的抽象:过 ...

  5. Day03 - Python 函数

    1. 函数简介 函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.Python提供了许多内建函数,比如print():也可以自己创建函数,这 ...

  6. python函数参数的pack与unpack

    python函数参数的pack与unpack 上周在使用django做开发的时候用到了mixin(关于mixin我还要写一个博客专门讨论一下,现在请参见这里),其中又涉及到了一个关于函数参数打包(pa ...

  7. Python 3.X 调用多线程C模块,并在C模块中回调python函数的示例

    由于最近在做一个C++面向Python的API封装项目,因此需要用到C扩展Python的相关知识.在此进行简要的总结. 此篇示例分为三部分.第一部分展示了如何用C在Windows中进行多线程编程:第二 ...

  8. 使用可变对象作为python函数默认参数引发的问题

    写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...

  9. 【转】Python函数默认参数陷阱

    [转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...

随机推荐

  1. php中连接mysql数据库的第一步操作

    <?phperror_reporting(E_ALL ^ E_DEPRECATED);//设置报警级别人$mylink = mysql_connect("localhost" ...

  2. OC重写init方法

    在创建一个对象的时候我们经常会用到init方法,单单是init只能是初始化,当我们在初始化的时候想要给这个对象加上默认的东西的时候, 系统提供的init方法就不能满足我们的需要,这时,就需要我们自己去 ...

  3. vue.js 2.0(1)

    1.点击一个按钮打开,关闭弹框 2.实现滚动监听,导航看顶置,实现某元素吸顶 路由

  4. Oracle数据库用EF操作的示例

    Using EF Oracle Sample Provider with EDM Designer  (from msdn) Many people are asking if it is possi ...

  5. 【CF429E】 Points and Segments(欧拉回路)

    传送门 CodeForces 洛谷 Solution 考虑欧拉回路有一个性质. 如果把点抽出来搞成一条直线,路径看成区间覆盖,那么一个点从左往右被覆盖的次数等于从右往左被覆盖的次数. 发现这个性质和本 ...

  6. HTML+CSS技术实现网页滑动门效果

    一.什么是滑动门 大家在网页中经常会见到这样一种导航效果,因为使用频率广泛,所以广大的程序员给它起了一个名字,叫做滑动门.在学习滑动门之前,首先你要了解什么是滑动门. 小米官网,网页滑动门效果 二.实 ...

  7. Android Studio 常见问题及解决方法

    一.Error:All flavors must now belong to a named flavor dimension 问题描述: Error:All flavors must now bel ...

  8. HTTP 协议常见的状态码

    HTTP状态码负责表示客户端HTTP请求的返回结果.标记服务器端的处理是否正常.通知出现的错误等工作. 状态码的类别: 记录在RFC2616上的HTTP状态码有40种,再加上WebDAV等的扩展,数量 ...

  9. 第59节:Java中的html和css语言

    欢迎到我的简书查看我的文集 前言: HTML 英文: HyperText Markup Language内容 html是超文本标记语言,是网页语言的基础知识,html是通过标签来定义的语言,所有代码都 ...

  10. Metasploit Framework(6)客户端渗透(上)

    文章的格式也许不是很好看,也没有什么合理的顺序 完全是想到什么写一些什么,但各个方面都涵盖到了 能耐下心看的朋友欢迎一起学习,大牛和杠精们请绕道 前五篇文章讲解了Metasploit Framewor ...