ch24高级模块话题(python)
在模块中隐藏数据:
在模块中隐藏数据是一种惯例,不是一种约束
最小化from *的破坏:_X和__all__
在变量名前面加入_ (比如_X),可以防止客户端使用from *语句导入模块时,
把其中的那些变量名复制出去。但是这种变量名对于import语句来说无法起到隐藏效果。
在模块顶层把变量名的字符串列表赋值给变量__all__,如下
__all__ = ["Error", "encode", "decode"] # Export these only
客户端使用from *的时候,只会复制__all__列表中的这些变量名。
工作机制:python先搜索模块内的__all__列表,如果没有定义的话,from *就会复制出开头没有单下划线的所有变量名。
_X和__all__只对from *有效。模块编写者可以使用任何一种技巧实现模块,
在碰上from *的时,能良好地运行(参考第23章中有关包__init__.py文件内__all__列表的讨论,
那些列表中声明了由form *所加载的子模块)
启用以后的语言特性:
要开启扩展功能使用如下的语句形式:featurename 替换为具体特性
from __future__ import featurename
混合用法模式:__name__和__main__
每个模块都有个名为__name__的内置属性,python会自动设置改属性:
1:如果文件是以顶层程序文件执行,在启动的时,__name__就会设置为"__main__"
2:如果文件被导入,__name__就会改成设置成客户端所了解的模块名。
#runme.py只定义了一个函数
### file: runme.py
def tester():
print("It's Christmas in Heaven...")
if __name__ == '__main__': # Only when run
tester() # Not when imported
#作为模块
% python
>>> import runme
>>> runme.tester()
It's Christmas in Heaven...
#作为顶层文件
% python runme.py
It's Christmas in Heaven...
# __name__常用来测试代码。
以__name__进行单元测试:
### file: min.py
def minmax(test, *args):
res = args[0]
for arg in args[1:]:
if test(arg, res):
res = arg
return res
def lessthan(x, y): return x < y
def grtrthan(x, y): return x > y
print(minmax(lessthan, 4, 2, 1, 5, 6, 3)) # Self-test code
print(minmax(grtrthan, 4, 2, 1, 5, 6, 3))
#上面的代码修改为如下:
### file: min.py (modified)
print('I am:', __name__)
def minmax(test, *args):
res = args[0]
for arg in args[1:]:
if test(arg, res):
res = arg
return res
def lessthan(x, y): return x < y
def grtrthan(x, y): return x > y
if __name__ == '__main__':
print(minmax(lessthan, 4, 2, 1, 5, 6, 3)) # Self-test code
print(minmax(grtrthan, 4, 2, 1, 5, 6, 3))
#作为顶层文件使用
% python min.py
I am: __main__
1
6
#作为被导入文件使用
>>> import min
I am: min
>>> min.minmax(min.lessthan, 's', 'p', 'a', 'm')
'a'
#使用带有__name__的命令行参数:
#在python中,sys.argv列表中包含了命令行参数,第一项是将要运行的脚本的名称。
### file: formats.py
"""
Various specialized string display formatting utilities.
Test me with canned self-test or command-line arguments.
"""
def commas(N):
"""
format positive integer-like N for display with
commas between digit groupings: xxx,yyy,zzz
"""
digits = str(N)
assert(digits.isdigit())
result = ''
while digits:
digits, last3 = digits[:-3], digits[-3:]
result = (last3 + ',' + result) if result else last3
return result
def money(N, width=0):
"""
format number N for display with commas, 2 decimal digits,
leading $ and sign, and optional padding: $ -xxx,yyy.zz
"""
sign = '-' if N < 0 else ''
N = abs(N)
whole = commas(int(N))
fract = ('%.2f' % N)[-2:]
format = '%s%s.%s' % (sign, whole, fract)
return '$%*s' % (width, format)
if __name__ == '__main__':
def selftest():
tests = 0, 1 # fails: -1, 1.23
tests += 12, 123, 1234, 12345, 123456, 1234567
tests += 2 ** 32, 2 ** 100
for test in tests:
print(commas(test))
print('')
tests = 0, 1, -1, 1.23, 1., 1.2, 3.14159
tests += 12.34, 12.344, 12.345, 12.346
tests += 2 ** 32, (2 ** 32 + .2345)
tests += 1.2345, 1.2, 0.2345
tests += -1.2345, -1.2, -0.2345
tests += -(2 ** 32), -(2**32 + .2345)
tests += (2 ** 100), -(2 ** 100)
for test in tests:
print('%s [%s]' % (money(test, 17), test))
import sys
if len(sys.argv) == 1:
selftest()
else:
print(money(float(sys.argv[1]), int(sys.argv[2])))
C:\misc> python formats.py 999999999 0
$999,999,999.00
C:\misc> python formats.py -999999999 0
$-999,999,999.00
C:\misc> python formats.py 123456789012345 0
$123,456,789,012,345.00
C:\misc> python formats.py -123456789012345 25
$ -123,456,789,012,345.00
C:\misc> python formats.py 123.456 0
$123.46
C:\misc> python formats.py -123.454 0
$-123.45
C:\misc> python formats.py
...canned tests: try this yourself...
#使用from 语法测试:
>>> from formats import money, commas
>>> money(123.456)
'$123.46'
>>> money(-9999999.99, 15)
'$ -9,999,999.99'
>>> X = 99999999999999999999
>>> '%s (%s)' % (commas(X), X)
'99,999,999,999,999,999,999 (99999999999999999999)'
#help函数
>>> import formats
>>> help(formats)
Help on module formats:
NAME
formats
FILE
c:\misc\formats.py
DESCRIPTION
Various specialized string display formatting utilities.
Test me with canned self-test or command-line arguments.
FUNCTIONS
commas(N)
format positive integer-like N for display with
commas between digit groupings: xxx,yyy,zzz
money(N, width=0)
format number N for display with commas, 2 decimal digits,
leading $ and sign, and optional padding: $ -xxx,yyy.zz
#更多的命令行处理,参考python标准库和手册中的getopt和optparse模块
#修改模块搜索路径
#python启动时,sys.path机会初始化,之后在程序中可以任意修改sys.path路径
>>> import sys
>>> sys.path
['', 'C:\\users', 'C:\\Windows\\system32\\python30.zip', ...more deleted...]
>>> sys.path.append('C:\\sourcedir') # Extend module search path
>>> import string # All imports search the new dir last
#可以任意重新设置,修改
>>> sys.path = [r'd:\temp'] # Change module search path
>>> sys.path.append('c:\\lp4e\\examples') # For this process only
>>> sys.path
['d:\\temp', 'c:\\lp4e\\examples']
>>> import string
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named string
#sys.path的设置只在修改的python回话或者程序(进程)中才会存续
#import语句和from语句的as扩展
import modulename as name
#下面的三行相当于上面的as语句
import modulename
name = modulename
del modulename # Don't keep original name
from modulename import attrname as name
import reallylongmodulename as name # Use shorter nickname
name.func() #使用更短的名字
from module1 import utility as util1 # Can have only 1 "utility"
from module2 import utility as util2 # as用于避免变量冲突
util1(); util2()
import dir1.dir2.mod as mod # Only list full path once
mod.func() # as也可以用于包
#模块是对象
#管理程序(可以很容易的编写程序来管理其他程序)称为元程序。
#多种方法获取M模块内的name属性
M.name # Qualify object
M.__dict__['name'] # Index namespace dictionary manually
sys.modules['M'].name # Index loaded-modules table manually
getattr(M, 'name') # Call built-in fetch function
#实现定制版本的内置dir函数:遍历模块的__dict__,然后使用getattr函数
### file: mydir.py
"""
mydir.py: a module that lists the namespaces of other modules
"""
seplen = 60
sepchr = '-'
def listing(module, verbose=True):
sepline = sepchr * seplen
if verbose:
print(sepline)
print('name:', module.__name__, 'file:', module.__file__)
print(sepline)
count = 0
for attr in module.__dict__: # Scan namespace keys
print('%02d) %s' % (count, attr), end = ' ')
if attr.startswith('__'):
print('<built-in name>') # Skip __file__, etc.
else:
print(getattr(module, attr)) # Same as .__dict__[attr]
count += 1
if verbose:
print(sepline)
print(module.__name__, 'has %d names' % count)
print(sepline)
if __name__ == '__main__':
import mydir
listing(mydir) # Self-test code: list myself
#提供了文档字符串
>>> import mydir
>>> help(mydir)
#自测
C:\misc> c:\Python30\python mydir.py
#测试tkinter模块
>>> import mydir
>>> import tkinter
>>> mydir.listing(tkinter)
------------------------------------------------------------
name: tkinter file: c:\PYTHON30\lib\tkinter\__init__.py
------------------------------------------------------------
...
#
#用名称字符串导入模块
>>> import "string"
File "<stdin>", line 1
import "string"
^
SyntaxError: invalid syntax
#把字符串赋值给变量名,也是无效的
x = "string"
import x #会import x.py
#使用exec内置函数(exec在python26中是一个语句)
#exec会编译代码字符串,并且将其传递给python解释器已执行
>>> modname = "string"
>>> exec("import " + modname) # Run a string of code
>>> string # Imported in this namespace
<module 'string' from 'c:\Python30\lib\string.py'>
#__imort__()函数的使用,该函数比exec函数高效
>>> modname = "string"
>>> string = __import__(modname)
>>> string
<module 'string' from 'c:\Python30\lib\string.py'>
#过渡性模块重载
#模块重载,这是选择代码中的修改而不需要停止或重新启动一个程序的一种方式。
# A.py代码如下
import B # Not reloaded when A is
import C # Just an import of an already loaded module
#重载模块A
% python
>>> . . .
>>> from imp import reload
>>> reload(A)
#默认情况下,不能依赖于重载来过渡性的选择程序中的所有模块中的修改,
相反,必须使用多次reload调用来独立地更新子部分。
#编写一个通用的工具来自动进行过渡性重载,
#通过扫描模块的__dict__属性并检查每一项的type以找到要重新载入的嵌套模块。该工具函数应该递归调用自己。
### file: reloadall.py
"""
reloadall.py: transitively reload nested modules
"""
import types
from imp import reload # from required in 3.0
def status(module):
print('reloading ' + module.__name__)
def transitive_reload(module, visited):
if not module in visited: # Trap cycles, duplicates
status(module) # Reload this module
reload(module) # And visit children
visited[module] = None
for attrobj in module.__dict__.values(): # For all attrs
if type(attrobj) == types.ModuleType: # Recur if module
transitive_reload(attrobj, visited)
def reload_all(*args):
visited = {} #存放已经访问过的模块,visited是字典
for arg in args:
if type(arg) == types.ModuleType:
transitive_reload(arg, visited)
if __name__ == '__main__':
import reloadall # Test code: reload myself
reload_all(reloadall) # Should reload this, types
#独立运行文件,测试代码会运行,测试自身
C:\misc> c:\Python30\python reloadall.py
reloading reloadall
reloading types
#测试os和tkinter模块
>>> from reloadall import reload_all
>>> import os, tkinter
>>> reload_all(os)
reloading os
reloading copyreg
reloading ntpath
reloading genericpath
reloading stat
reloading sys
reloading errno
>>> reload_all(tkinter)
reloading tkinter
reloading _tkinter
reloading tkinter._fix
reloading sys
reloading ctypes
reloading os
reloading copyreg
reloading ntpath
reloading genericpath
reloading stat
reloading errno
reloading ctypes._endian
reloading tkinter.constants
#除非使用过渡性重载工具,否则重载不会选取对两个嵌套的文件的修改
### file names in comments
import b # a.py
X = 1
import c # b.py
Y = 2
Z = 3 # c.py
C:\misc> C:\Python30\python
>>> import a
>>> a.X, a.b.Y, a.b.c.Z
(1, 2, 3)
# Change all three files' assignment values and save
>>> from imp import reload
>>> reload(a) # Normal reload is top level only
<module 'a' from 'a.py'>
>>> a.X, a.b.Y, a.b.c.Z
(111, 2, 3)
>>> from reloadall import reload_all
>>> reload_all(a)
reloading a
reloading b
reloading c
>>> a.X, a.b.Y, a.b.c.Z # Reloads all nested modules too
(111, 222, 333)
#模块的设计理念:
1:总是在模块内编写代码。没有办法写出不在摸个模块之内的程序代码。
在交互提示下的代码存在于内置模块 __main__之内。
2:模块耦合要降到最低:全局变量。模块尽可能和其他模块的全局变量无关,除了与从模块导入的函数和类
3:最大化模块的粘合行:统一目标。
4:模块应该少去修改其他模块的变量。
#模块的陷阱
#顶层代码的语句次序的重要性
当模块被导入的时候,python会从头到尾执行语句。这里有些和前向引用相关的含义。
1:在导入时候,模块文件顶层的程序代码(不在函数内),一旦python运行到时,就会立刻执行。
所以,该语句是无法引用文件后面位置赋值的变量名
2:位于函数主体内的代码直到函数被调用后才会运行。因为函数内的变量名在函数执行前都不会解析,通常可以引用文件内任意地方的变量
func1() # Error: "func1" not yet assigned
#错误,对应于第1条
def func1():
print(func2()) # OK: "func2" looked up later
#正确,函数内可以引用文件内任意位置变量,对应于第2条
func1() # Error: "func2" not yet assigned
#错误,func2未定义
def func2():
return "Hello"
func1() # Okay: "func1" and "func2" assigned
#正确
#原则:如果需要把立即执行的代码和def一起使用,就要把def放在文件前面,把顶层代码放在后面。
这样的话,你的函数在使用的代码运行的时候,可以保证他们都已经定义并赋值过了。
#from复制变量名,而不是连接
#from语句其实是在导入者的作用域内对变量名的赋值语句,也就是变量名拷贝运算,而不是变量名的别名机制。
#
# nested1.py
X = 99
def printer():
print(X)
# nested2.py
from nested1 import X, printer # Copy names out X是nested1模块中X的复制
X = 88 # Changes my "X" only! 修改的只是自己作用域内的X
printer() # nested1's X is still 99
% python nested2.py
99
#如果是import的话,就是获得了整个模块,.运算符就会修改nested1.py中的变量名
# nested3.py
import nested1 # Get module as a whole
nested1.X = 88 # OK: change nested1's X
nested1.printer()
% python nested3.py
88
#from *会让变量语义模糊
>>> from module1 import * # Bad: may overwrite my names silently
>>> from module2 import * # Worse: no way to tell what we get!
>>> from module3 import *
>>> . . .
>>> func() # Huh???
#建议:在from语句中明确列出想要的属性,并且限制在每个文件中最多只有一个被导入的模块使用from *形式
#reload不会影响from导入
#重载被导入者对于使用from导入模块变量名的客户端没有影响。
from module import X # X may not reflect any module reloads!
. . .
from imp import reload
reload(module) # Changes module, but not my names
X # Still references old object X依然是原来的值
#为了保证重载有效,可以使用import以及点号运算,来取代from。
因为点号运算总是会回到模块,这样就找到模块重载后变量名的新的绑定值
import module # Get module, not names
. . .
from imp import reload
reload(module) # Changes module in-place
module.X # Get current X: reflects module reloads
#reload,from以及交互模式测试:
from module import function
function(1, 2, 3)
from imp import reload
reload(module)
from imp import reload
import module
reload(module)
function(1, 2, 3)
from imp import reload
import module
reload(module)
from module import function # Or give up, and use module.function()
function(1, 2, 3)
#递归形式的from导入无法工作
### file names in comments
# recur1.py
X = 1
import recur2 # Run recur2 now if it doesn't exist
Y = 2
# recur2.py
from recur1 import X # OK: "X" already assigned
from recur1 import Y # Error: "Y" not yet assigned 变量名Y是在导入recur1后赋值的
C:\misc> C:\Python30\python
>>> import recur1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "recur1.py", line 2, in <module>
import recur2
File "recur2.py", line 2, in <module>
from recur1 import Y
ImportError: cannot import name Y
原则:小心设计,通常可以避免这种导入循环:最大化模块的聚合性,同时最小化模块间的耦合性。
如果无法完全断开循环,就要使用import和点号运算(而不是from)
评论
发表评论