【有书共读01】《python学习手册》读书笔记十八

第24章  高级模块话题
本章研究一些模块相关的高级概念,
1.数据隐藏技巧
2.通过__future__模块启用新的语言特性
3.__name__使用模式变量
4.过渡性模块重载
5.由名称字符串的导入
6.探索总结模块设计的话题,发现模块相关的常见错误

1.数据隐藏的技巧:
在模块中隐藏数据,python模块会导出其文件顶层所赋值的所有变量名。
没有对某一个变量名进行声明,使其在模块内可间或不可见这种概念。是没有防止客户端修改模块内变量名的方法的
python中,可以通过破坏模块名称是这个模块不能工作,但是没有这样做的。
最小化from*的破坏:_X和__all__:
在特定的情况下,把下划线放在变量名前面(例如:_X),
可以防止客户端使用from*语句导入模块名时,把其中的哪些变量名复制出去。
from*会把所有变量名复制出去,导入者可能得到超出它所需的部分(包括会覆盖导入者内的变量名的变量名)。
下划线不是“私有”的声明,我们仍可以使用其他导入形式看见并修改这类变量名,如 import。
我们可以在模块顶层把变量名的字符串列表赋值给变量__all__,已达到,隐藏命名的效果,例如:
__all__=["Error","encode","decode"]
使用此功能时,from*语句只会列__all__列表中出现的这些变量名的复制出来。
与_X惯例相反,__all__是指出要复制的变量名,而_X是指出不被复制的变量名。
python会首先寻找__all__列表,如果没有定义的话,from*就会复制出开头没有单下划线的所有变量名。
像_X一样,__all__只对from*语句有效,并不是私有声明。

2.通过__future__模块启用新的语言特性:
启用以后的语言特性,可能破坏现有代码语言方面的变动会不断引进。
一开始,是以选用扩展功能的方式出现的,默认是关闭的,可以通过下面的语句开启:
from __future__ import featurename
这个语句一般应该出现在模块文件的顶层(也许docstring之后)
因为这是以每个模块为基础,开启特殊的代码编译。

3.__name__使用模式变量:
混合用法模式:__name__和__main__,是一个特殊的与模块相关的技巧,
可以把文件作为模块导入,并独立式程序的形式运行。
每个模块都有个名为__name__的内置属性,python会自动设置该属性:
a.如果文件是以顶层程序文件执行,在启动时,__name__就会设置为字符串“__main__".
b.如果文件被导入,__name__就会改为射程客户端所了解的模块名。
结果就是模块可以检测自己的__name__,来确定他是在执行还是在导入。
例如:我们建立一个模块文件,名为runme.py,它只导出一个名为tester的函数。
def tester():
    print("It's Christmas in Heaven...")
if __name__=='__main__':
    tester()
这个模块定义了一个函数,让用户可以正常的导入并使用:
>>>import runme
>>>runme.tester()
It's Christmas in Heaven...
然而,这个模块也在末尾包含了档次文件以程序执行时,就会调用该函数的代码:
我们点开runme.py这个模块,会立刻调用该函数:
It's Christmas in Heaven...
实际上一个模块的__name__变量充当一个使用模式标志允许它编写成一个可导入的库和一个顶层脚本。
__name__测试时最常见的自我测试代码。
我们可以在文件末尾加一个__name__测试,把测试模块导出的程序代码放在模块中。
编写既可以作为命令行工具也可以作为工具库使用的文件时,__name__技巧非常好用。

以__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

print(minmax(lessthan,4,2,1,5,6,3))
print(minmax(grtrthan,4,2,1,5,6,3))

这个脚本在末端包含了自我测试程序代码。
所以不用每次执行时,都得在交互模式命令行中重新输入搜友代码就可以惊醒测试
然而,这样写法的问题在于,每次这个文件被另一个文件作为工具导入时,
就会出现调用自我测试调用所得到的输出,现在我们用__name__来进行改进:
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))
    print(minmax(grtrthan,4,2,1,5,6,3))

在顶层打印__name__的值,目的是跟踪他的值。
当该文件作为顶层文件时,名字为__name__,所以运行结果为:
I am :__name__
1
6

但是我们导入这个文件时,就会:
>>>import min
I am : min
>>>min.minmax(min.lessthan.'s','p','a','m')
'a'

使用带有__name__的命令行参数:
例子见书P597

修改模块搜索路径:
之前我们知道通过环境变量python path以及可能的.pth路径文件进行定制。
现在我们介绍,python程序本身是如何修改搜索路径的。
修改sys.path(内置模块sys的path属性)的内置列表。
sys.path在程序启动时就会进行初始化,但在那之后,可以随意对其元素进行删除,附加和重设。
>>>import sys
>>>sys.payh
#我的电脑中出现了:
['', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\DLLs',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages']

>>> sys.path.append('C:\\sourcedir')
>>> import string
>>> sys.path
['', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\DLLs',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages',
'C:\\sourcedir']            #加了一个
>>>
一但做了这类修改,就会对python程序中将要导入的地方产生影响因为所有导入和文件都共享了同一个sys.path列表,
这个列表可以任意修改:
sys.path.append("")增加
sys.path=[r'd:\temp']#重置目录
但是注意,我们如果删除一些重要目录时,就无法获取一些关键性工具了,
如上面的例子sys.path=[]就无法获取string模块
另:sys方法设置只会在python会话或程序(进程)中才会存续,如果python结束,就不会保留下来,
而python path和.pth文件路径配置是保存在操作系统中,所以更为全局一点

import语句和from语句的as扩展:
书中P599-P600

模块是对象:元程序:
例子见书P601

4.过渡性模块重载:
当我们重载一个模块时,python只重新载入特殊模块的文件,它不会自动重载那些为了导入要重载文件的模块。
例如:
如果要重载模块A,并且A导入模块B和C,重载只适用于A,而不适用于B和C。
A中导入B和C的语句在重载的时候重新运行,
为了都重载,要写很多个reload(),这会使代码复杂,工程量大,所以,我们有一个更好的办法:
编写一个通用的工具来自动进行过渡性重载,
通过扫描模块的__dict__属性并检查每一项的type以找到要重新载入的嵌套模块。
这个工具函数应该递归的调用自己。
下面举一个例子:
模块reloadall.py有一个reload_all函数来自动的重载一个模块,以及该模块导入的每个模块等,
所有通往每个导入链条最低端的通路都被考虑到。
他使用字典来记录已经重载的模块,递归的遍历导入链条,以及标准库的types模块,
该模块直接为内置类型预定义type结果。
访问字典的技术在这里用来在导入是递归或冗余的时候避免循环,因为模块对象可以是一个字典键
"""
reloadall.py:transitively relaod nested modules
"""

import types
from imp import reload

def status(module):
    print('reloading'+module.__name__)

def transitive_reload(module,visited):
    if not module in visited:
        status(module)
        reload(module)
        visited[module]=None
        for attrobj in module.__dict__.values():
            if type(attrobj)==types.ModuleType:
                transitive_reload(attrobj,visited)

def reload_all(*args):
    visited={}
    for arg in args:
        if type(arg)==types.ModuleType:
            transitive_reload(arg,visited)


if __name__=='__main__':
    import reloadall
    reload_all(reloadall)

#输出:
reloading reloadall
reloading types
要使用这一工具,导入其reload_add 函数并将一个已经载入的模块的名称传递给它。
当文件独立的运行,其自测试代码将会测试自己,
它必须导入自己,因为他自己的名字并没有 在没有一个导入的文件中定义。
下面是这个模块对于python3.0中没写标准库模块工作的情况。
注意,os是如何由tkinter导入的,但tkinter在os之前已经导入了sys。

>>> from reloadall import reload_all        #首先我想调用我这里的模块
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>       #不行
ModuleNotFoundError: No module named 'reloadall'
>>> import sys
>>> sys.path
['', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\DLLs',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37',
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages']
#发现没有这个目录
>>> sys.path.append('C:\\Users\\Administrator\\PycharmProjects\\untitled\\venv')    #手动添加目录
>>> from reloadall import reload_all        #重新调用模块  成功
>>> import os, tkinter                      #调用模块
>>>
>>> reload_all(os)                          #使用该函数
reloading os
reloading abc
reloading sys
reloading stat
reloading ntpath
reloading genericpath
>>>
>>> reload_all(tkinter)                      #使用该函数
reloading tkinter
reloading enum
reloading sys
reloading _tkinter
reloading tkinter.constants
reloading re
reloading sre_compile
reloading _sre
reloading sre_parse
reloading functools
reloading _locale
reloading copyreg
>>>

例二:见书P606

5.用名称字符串导入模块:
程序在运行时以一个字符串的形式获取想到导入的模块名称:
>>> import "string"
  File "<stdin>", line 1
    import "string"
                  ^
SyntaxError: invalid syntax
python 期待的不是一个字符串,而是一个变量名称
直接把该字符赋值给一个变量名称也是无效的:
x="string"
import x    #此时会找x.py文件
Error!
为了解决这个问题,我们需要使用特殊工具,从运行时生成一个字符串来动态载入一个模块。
例:
>>> modname="string"
>>> exec("import "+modname)
>>> string
<module 'string' from
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\string.py'>
调用成功!
exec唯一的,真正的缺点时,每次运行时它必须编译import语句,
如果他运行多次,如果使用内置的__import__函数来从一个名称字符串载入的话,代码可能会运行的更快。
效果时类似的,但是,__import__运行模块对象,因此,在这里将其赋给一个名称以保存他:
>>> modname="string"
>>> string=__import__(modname)
>>> string
<module 'string' from
'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\string.py'>

6.探索总结模块设计的话题,发现模块相关的常见错误:
模块设计理念:
a.总是在python的模块内编写代码
b.模块的耦合性要降到最低:全局变量
c.最大模块的粘合性:统一目标
d.模块应该少去修改其他模块的变量

模块陷阱:
顶层代码的语句次序的重要性:
书P608
from复制变量名,而不是连接
书P609
from*会让变量语义模糊
书P610
reload不会影响from导入
书P610
reload,from以及交互模式测试
书P611
递归形式的from导入无法工作
书P612

#笔记##读书笔记#
全部评论

相关推荐

安全劝退第二人:给我发个
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客企业服务