python之面向对象编程(九)反射——超强必学技(下篇)!
关于命名空间
昨天讲反射的内容主要是getattr、hasattr、setattr、delattr、callable这五个函数。其中有一个关键词——命名空间,这是个非常非常重要的概念,深入学python一定要对命名空间有非常清楚的理解和认识。
命名空间是python程序在运行时向操作系统申请的一块有名字的内存空间。我们把内存想象成一幢大楼,那么python解释器执行脚本(当前执行的py文件)时会申请一层楼存放当前执行的py文件中的数据和代码块,这块命名空间的名字叫__main__;若在这个文件中定义了类,那么会在楼层里给这个类单独开辟一个房间用来存放这个类内部的数据和代码块,房间的名字就是类名;创建类的实例时也会给实例单独开辟一个房间,里面存放了实例的属性和类指针,访问实例的属性时在实例的命名空间中找,访问实例的绑定方法时通过类指针到类的房间里找,房间的名字就是实例的名字。假设我们导入了其他的模块,那么python解释器会向系统新申请一层楼来存放被导入的py文件,同理可以推导出新导入的py文件中也可能定义了类和创建了类的实例。那些类和它的实例都会存放在新的楼层,这个新的楼层名字就是py文件名。
以下通过案例和图例进行详细讲解:
有一个animal_file.py文件,内容如下:
class Animal:
kind = '动物'
def __init__(self, name):
self.name = name
def eat(self):
print(f'{self.name}正在进食!')
另有一个run.py文件,内容如下:
import animal_file
class Cat(animal_file.Animal):
def __init__(self, name, count=0):
self.count = count
animal_file.Animal.__init__(self, name)
def mousing(self):
print(f'猫咪“{self.name}”捉住了{self.count}只老鼠')
white_cat = Cat('大白猫')
white_cat.mousing()
out:
猫咪“大白猫”捉住了0只老鼠
实例图如下:
执行run.py文件时会先申请一块名字叫__main__的命名空间;执行import animal_file语句时会新申请一块名字叫animal_file的命名空间,导入文件内容时会在animal_file的命名空间内开辟一块名字叫Animal类的名称空间,内部有kind变量和__init__方法以及eat方法,导入完毕后返回run.py文件继续执行;执行class Cat(animal_file.Animal):语句时会__main__的命名空间内开辟一块名字叫Cat的名称空间,内部有指向Animal类的父类指针、__init__方法和mousing方法;创建white_cat实例时会__main__的命名空间内开辟一块名字叫white_cat的名称空间,内部有指向本类的指针、name属性和count属性。
以上就是关于命名空间的内容和代码实例、图例详细讲解,对命名空间概念还不清楚的朋友一定不要嫌啰嗦,耐心看完并理解,最好自己尝试画图,一定要把命名空间概念搞得清清楚楚。
反射的实例1——登录验证
代码如下:
class Authentic:
lst = [('登录', 'login'), ('注册', 'register'), ('列表', 'table')]
def __init__(self, name, pwd):
self.name = name
self.pwd = pwd
self.table = '测试列表'
def register(self):
name = input('请输入用户名:')
pwd1 = input('请输入密码:')
pwd2 = input('请再输入一次:')
if pwd1 == pwd2 and len(pwd1) > 5:
self.name = name
self.pwd = pwd1
print(f'注册账号"{name}"成功')
else:
print('两次密码不一致或密码长度小于6,注册失败')
def login(self):
name = input('请输入用户名:')
pwd = input('请输入密码:')
if pwd == self.pwd and name == self.name:
print(f'登录账号"{name}"成功')
else:
print('用户名或密码不对,登录失败!')
user = Authentic('abc', '123456')
while 1:
for j, i in enumerate(Authentic.lst):
print(f'{j+1}.{i[0]}', '\t', end='')
print()
ret = input('请输入你的选择:')
if ret.upper() == 'Q':
break
else:
try:
choice = Authentic.lst[int(ret)-1][1]
if hasattr(user, choice):
if callable(ret := getattr(user, choice)):
ret()
else:
print(ret)
except (IndexError, ValueError):
pass
以上是一个登录验证功能的极简示例,在Authentic类中定义了login、register两个方法,对应的菜单选项有3条;接下来初始化实例user,然后进入循环,循环体内开始先打印选项列表,再接受用户输入;判断输入内容是’q’或’Q’则退出循环;否则则尝试对输入内容转成数字并用它对列表取值(尝试的时候发生索引越界或不是数字类型的错误均忽略),取值成功后choice的内容是字符串’login’或’register’或’table’之中的一种,然后对choice值判断,可执行的则执行它,不可执行的则打印它。
以上是使用反射的一个简单案例。粗看平平无奇,请对比一下不使用反射实现同样功能的代码:
user = Authentic('abc', '123456')
while 1:
for j, i in enumerate(Authentic.lst):
print(f'{j+1}.{i[0]}', '\t', end='')
print()
ret = input('请输入你的选择:')
if ret.upper() == 'Q':
break
elif ret == '1':
user.login()
elif ret == '2':
user.register()
elif ret == '3':
print(user.table)
请仔细思考一下,目前选项列表是3项,使用了3次elif,如果选项多达几十项,那么代码是不是要随着选项列表的增加而新增几十项elif?回头再看使用反射实现的代码,无论选项列表是3项、30项、甚至300项都不需要增加elif判断。
反射的实例2——文件操作
3行代码实现文件操作的脚本,现在尝试新建一个py文件,内容如下,文件名叫f.py
import shutil
from sys import argv
getattr(shutil, argv[1].lower())(argv[2], argv[3])
再打开cmd窗口,进入f.py文件所在的目录。
执行下面的命令:
python f.py copy f.py fpy.bak
python f.py move fpy.bak f1.py
python f.py rename f1.py f2.py
每次执行完可以检查一下命令是否正常执行。
如何获取当前脚本的名字内容
关于反射,大家都知道了用getattr可获得指定命名空间中指定名字的内容,但是如何获取当前脚本的名字内容呢?首先import sys,然后使用sys.modules[’__main__’]可获得当前脚本的命名空间。
假设有一个小需求,a变量值为123,b变量值为’abc’,判断用户输入a,则打印123,判断输入是b则打印’abc’。
import sys
a = 123
b = 'abc'
ret = input('请输入要打印的名字:')
if hasattr(sys.modules['__main__'], ret):
print(getattr(sys.modules['__main__'], ret))
想象一下如果变量名字有几十个,用通常的if elif else的写法是不是需要写几十行?用反射简单的2行代码即搞定一大堆if eilf else。