好冷的Python~ 那些同名的家伙们(Python作用域)

原文链接: http://www.juzicode.com/python-cold-same-name/

前两天桔子菌在调试代码的时候发现自己写的一个方法怎么也执行不到,这个自定义的类大概是下面这样子的:

class ClassA(object):
    def __init__(self,filename):
        self.name = '公众号: 桔子code'
        self.filename = filename
        self.data = None
        
    def open_file(self):
        print('filename:',self.filename)
        with open(self.filename,'r',encoding='UTF-8') as p:
            self.data = p.read()
            
    def read_data(self):
        self.open_file()
        print(self.data)
    ...
    ...


if __name__ == '__main__':
    ca = ClassA('data.txt')
    ca.read_data()

在ClassA中read_data()方法中调用了open_file()方法,但是修改了open_file()方法内部的代码后,修改的内容并没有生效。查来查去最后发现居然是因为在同一个class内写了个重名的open_file()方法,导致自己一直在修改的第1个open_file()方法总是没有执行到。

我们通过增加print()观察下实际运行的情况:

'''
author: juzicode
address: www.juzicode.com
公众号: 桔子code/juzicode
'''

class ClassA(object):
    def __init__(self,filename):
        self.name = '公众号: 桔子code'
        self.filename = filename
        self.data = None
        
    def open_file(self):
        print('open_file(): 调用第1个open_file()')
        print('filename:',self.filename)
        with open(self.filename,'r',encoding='UTF-8') as p:
            self.data = p.read()
            
    def read_data(self):
        self.open_file()
        print('read_data(): self.data=',self.data)
    ...
    ...
    
    def open_file(self):
        print('open_file(): 调用第2个open_file()')

if __name__ == '__main__':
    ca = ClassA('data.txt')
    ca.read_data()


==========结果:
open_file(): 调用第2个open_file()
read_data(): self.data= None

如果按照上图的方法定义了第2个同名的open_file(),调用ClassA的open_file()方法时,前面那个open_file()就被屏蔽掉了,调用ClassA.open_file()时运行的永远是第2个open_file() 。

py文件内同名对象

根据class中的这个特点,我们也可以推测如果在同一个py文件中定义了2个同名的函数,第1个同名函数也是会被屏蔽的,我们写一段简短的代码验证下:


def func():
    print('func(): 调用第1个func()函数')
def func():
    print('func(): 调用第2个func()函数')    
    
if __name__ == '__main__':
    func()
==========结果:
func(): 调用第2个func()函数

和class中的第1个同名方法被屏蔽一样,在py文件中第1个函数也被屏蔽了,只有第2个同名函数才有效。

不同模块同名对象

在使用from module import * 方法导入模块的时候,并不需要指定模块名称就能使用模块内的函数、变量和类,如果多个模块内有重名函数、变量或者类名,会按照什么规则调用呢?下面我们设计个实验,在2个模块中定义2个同名函数,函数内部打印对应模块的名称并且执行不同的操作,看看调用时会发生什么。在mod1.py中定义了一个func_cal()函数,返回变量a+b的结果,同时在mod2.py中也定义了一个func_cal()函数,返回变量a-b的结果:

==========mod1.py
def func_cal(a,b):
    print('mod1: func_cal()')
    return a+b

==========mod2.py
def func_cal(a,b):
    print('mod2: func_cal()')
    return a-b

在第3个主文件main_mod_test.py中同时导入了mod1和mod2,并调用func_cal()函数,先导入mod1模块,再导入mod2模块,运行的结果如下:

==========main_mod_test.py
from mod1 import func_cal
from mod2 import func_cal

if __name__ == '__main__':
    res = func_cal(30,20)
    print('res:',res)

==========结果:
mod2: func_cal()
res: 10

如果在第3个主文件main_mod_test.py中先导入mod2模块,再导入mod1模块,再调用 func_cal()函数,运行的结果如下:

==========main.py
from mod2 import func_cal
from mod1 import func_cal

if __name__ == '__main__':
    res = func_cal(30,20)
    print('res:',res)

==========结果:
mod1: func_cal()
res: 50

从上面的实例代码可以看出,后导入模块的func_cal()函数得到了执行,先导入模块的同名函数也是被屏蔽了,这点和在同一个class中后定义的方法屏蔽先定义的方法、或者同一个py文件中后定义的函数屏蔽先定义的函数效果是一样的。

同名模块

再接下来我们看下如果定义了同名模块会发生什么事情,比如经常使用的time模块,其中的sleep()函数表示延时一定时长后继续执行,我们也写一个time.py文件,并且定义一个sleep()函数,只是打印一些内容,将这个time.py文件放置在main_time_test.py文件同一个目录下,在main_time_test.py中导入time模块,调用sleep()函数,看下会发生什么。

==========自定义的time.py
def sleep(a):
    print('自定义的time模块: sleep()')
    return a
==========main_time_test.py

import time  

if __name__ == '__main__':
    print('调用time.sleep()')
    time.sleep(1)
    print('调用time.sleep()结束')

运行python main_time_test.py,看到的结果:

==========运行结果:
调用time.sleep()
调用time.sleep()结束

从执行结果可以看到,Python内置的time模块屏蔽了我们自定义的time模块,是不是就可以得出结论系统自带的模块查找的优先级更高呢?继续往下看,这次我们的实验对象是hashlib模块,在我们当前的工作目标下编写了一个hashlib.py文件,并编写了一个main_hashlib_test.py文件导入hashlib模块:

==========自定义的hashlib.py
def md5():
    print('自定义的hashlib模块: md5()')
    return None
==========main_hashlib_test.py

import hashlib
if __name__ == '__main__':
    print('调用hashlib.sha256()')
    m = hashlib.sha256()
    print('调用hashlib.sha256()结束') 

运行python main_hashlib_test.py,看到的结果:

==========运行结果:
调用hashlib.sha256()
自定义的hashlib模块: sha256()
调用hashlib.sha256()结束

当有重名自定义的hashlib时,导入模块时使用的却是自定义的hashlib模块!

time模块和hashlib模块的差异在于time模块是内置的模块,而hashlib是python安装目录的lib文件夹下的模块,这点差异就是导致不同结果的原因。在python3的文档中确实也是这么解释的(https://docs.python.org/3/tutorial/modules.html#the-module-search-path):

解释器首先搜索是内建模块,再搜索sys.path变量指定的模块,而sys.path初始化时首先包含的是当前被执行脚本所在的目录,最后才是Python安装目录下的库文件目录。这也正好解释了time这种内建模块首先被找到并导入,而hashlib这种在安装目录下的库文件却被main_hashlib_test.py所在当前工作目录下的hashlib.py屏蔽了。

小结:1、同名函数、类、变量在同一个py文件中,后定义者会屏蔽先定义者;2、在不同的模块中如果存在同名的函数、类、变量,后导入的模块会屏蔽先导入着;3、同名的模块导入的优先级:内建模块->被执行py文件同目录模块(当前工作目录模块)->安装目录模块

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注