In [1]:
x = 100
print(id(globals))
print(id(locals))
In [2]:
globals()
Out[2]:
In [4]:
def test():
x = "hello world"
print(locals())
print(id(globals))
print(id(locals))
test()
所以,我们可以直接修改名字空间建立关联引用
In [5]:
globals()["hello"] = "hello world"
hello
Out[5]:
并非所有时候都能直接操作名字空间,函数执行使用缓存机制,直接修改本地名字空间未必有效。正常编码时候尽量避免直接修改名字空间
在名字空间字典中,名字只是简单的字符串主键,所以,名字可以重新关联另一个对象,不用在乎类型是否相同
In [6]:
x = 100
print(id(x))
x = "hello" # 重新关联对象,而不是修改原对象
print(id(x))
一个对象可以用多个名字
In [7]:
x = 1234
y = x
y is x # 必须用 is 判断是否引用同一个对象,因为相等操作符是可以重载的,有时候只判断值
Out[7]:
命名规则建议:
- 类型名称使用 CapWords 格式
- 模块文件名、函数、方法成员等使用 lower_case_with_underscores 格式
- 全局变量使用 UPPER_CASE_WITH_UNDERSCORES 格式
- 避免与内置函数或标准库的常用类型同名,以免造成误解
以下划线开头的名字,代表特殊含义:
- 模块成员以单下划线开头 `(_x)`,属于私有成员,不会被星号导入
- 类型成员以双下划线开头,但无结尾 `(__x)` 属于自动命名私有成员
- 以双下划线开头和结尾 `(__x__)` 通常是系统成员,应避免使用
- 交互模式下,单下划线 `(_)` 返回最后一个表达式结果
In [9]:
1 + 2 + 3
Out[9]:
In [11]:
_
Out[11]:
In [15]:
import sys
a = 1234
b = a
print(sys.getrefcount(a)) # getrefcount() 也会通过参数引用目标对象,导致引用计数 +1
del a
print(sys.getrefcount(b))
In [26]:
import weakref
class X:
def __del__(self):
print(id(self), "dead.")
c = X()
sys.getrefcount(c)
Out[26]:
In [27]:
w = weakref.ref(c)
w() is c
Out[27]:
In [28]:
sys.getrefcount(c)
Out[28]:
In [29]:
del c
In [30]:
w() is None
Out[30]:
弱引用经常用来缓存,监控等 “外挂” 场景,不影响目标对象,也不能阻止它们被回收,弱引用另一个典型应用是实现 Finalizer,也就是在对象被回收时执行额外的 “清理” 操作
In [36]:
d = X()
def callback(w):
print(w, w() is None)
w = weakref.ref(d, callback) # 创建弱引用时设置回调函数
In [37]:
del d
这里不用析构方法的原因是析构函数作为目标成员,用途是完成对象的内部资源清理,它并应该处理与之无关的外部场景,所有用 Finalizer 是一个合理的选择
弱引用与普通名字最大区别在于类函数的调用和语法。可以用 proxy 改进,使其和名字引用语法保持一致
In [38]:
a = X()
a.name = "kaka"
w = weakref.ref(a)
w.name
In [39]:
w().name
Out[39]:
In [40]:
p = weakref.proxy(a)
p
Out[40]:
In [41]:
p.name
Out[41]:
In [43]:
p.age = 60 # 可以直接赋值
p.age
Out[43]:
In [45]:
class X: pass
x = X()
x.data = [1, 2]
import copy
x2 = copy.copy(x)
x2 is x
Out[45]:
In [46]:
x2.data is x.data # 成员 data 仍然指向原列表,仅仅复制了引用
Out[46]:
In [47]:
x3 = copy.deepcopy(x)
x3 is x
Out[47]:
In [48]:
x3.data is x.data
Out[48]:
In [54]:
class X:
def __del__(self):
print(self, "dead.")
import gc
gc.disable() # 在性能测试时,要关闭 gc, 避免垃圾回收对执行器计时造成影响
a = X()
b = X()
a.x = b
b.x = a # 构建循环引用
del a
del b # 删除所有名字后对象并未回收,引用计数失效
gc.enable()
gc.collect()
Out[54]:
In [1]:
def add(x, y):
return x + y
add.__code__
Out[1]:
In [2]:
dir(add.__code__)
Out[2]:
In [3]:
add.__code__.co_varnames
Out[3]:
In [4]:
add.__code__.co_code
Out[4]:
我们无法直接阅读机器码,可以反编译
In [5]:
import dis
dis.dis(add)
某些时候,需要手工编译
In [8]:
source = """
print("hello, world")
print(1 + 2)
"""
code = compile(source, "demo", "exec") # 提供一个文件名用于输出提示
dis.show_code(code)
In [9]:
dis.dis(code)
In [11]:
import py_compile, compileall
path = '/home/kaka/kaka/udacity/1-LaneLines/tune.py'
py_compile.compile(path)
Out[11]:
In [12]:
compileall.compile_dir('.') # python -m compileall .
Out[12]:
In [14]:
import collections
User = collections.namedtuple("User", "name, age", verbose=True)
In [15]:
User
Out[15]:
In [16]:
u = User("kaka", 30)
u
Out[16]:
且不管代码如何生成,最终都要以模块导入执行,要么调用 eval,exec 函数执行。eval 执行单个表达式,exec 对应代码块执行,接受字符串或已编译好的代码对象(code) 作为参数。如果是字符串,就会检查是否符合语法规则
In [17]:
s = "1 + 2 + 3"
eval(s)
Out[17]:
In [18]:
s = """
def test():
print("hello world")
test()
"""
exec(s)
无论哪种方式,都必须有对应的上下文环境,默认直接使用 当前全局和本地名字空间
In [19]:
x = 100
def test ():
y = 200
print(eval("x + y")) # 从上下文空间获取 x, y
test()
In [23]:
def test():
print("test:", id(globals), id(locals))
exec('print("exec:", id(globals), id(locals))')
test()
有了操作上下文名字空间能力,我们就可以向外部环境注入新的成员,新的类型算法等等。最终达到动态逻辑或结果融入,成为当前体系组成的设计目标
In [25]:
s = """
class X: pass
def hello():
print("hello, world")
"""
exec(s)
In [26]:
X
Out[26]:
In [27]:
X()
Out[27]:
In [28]:
hello()
某些时候,动态代码来源不确定,基于安全考虑,必须对执行过程进行隔离,阻止其直接读写环境数据。如此,就必须传入容器对象作为动态代码的专用名字空间,以类似简易沙箱(sandbox)的方式执行
根据需要,分别提供 globals,locals参数,也可共用同一空间字典
为保证代码正确执行,解释器会自动导入 __builtins__
模块。以便导入内置函数
In [29]:
g = {"x": 100}
l = {"y": 200}
eval("x+y", g, l) # 为 globals 和 locals 分别指定字典
Out[29]:
In [31]:
ns = { }
exec("class X: pass", ns) # globals 和 locals 共用一个字典
# ns 太多了,不打印了
同时提供两个名字空间参数时,默认总是 locals 优先,除非在动态代码中明确指定使用 globals
In [35]:
s = """
print(x) # locals
global y # globals
y += 100
z = x + y # locals
"""
g = {"x": 10, "y": 20}
l = {"x": 1000}
exec(s, g, l)
在函数作用域内,locals 函数总是返回执行栈帧(stack frame) 名字空间。因此,即便显示提供 locals 名字看完我,也无法将其注入到动态代码中
In [38]:
s = """
print(id(locals()))
def test():
print(id(locals()))
test()
"""
ns = {}
id(ns)
Out[38]:
In [40]:
exec(s, ns, ns) # test.locals() 和 ns.locals() 不同
In [ ]: