Python开发【第七篇】: 面向对象和模块补充
内容概要
- 特殊成员
- 反射
- configparser模块
- hashlib模块
- logging模块
- 异常处理
- 模块
- 包
1. 特殊成员
什么是特殊成员呢? __init_()就是个特殊的成员. 带双下划线的都是特殊方法. 这些方法在特殊的场景的时候会被自动的执行. 比如
1. 类名() 会自动执行__init__()
2. 对象() 会自动执行__call__()
3. 对象[key] 会自动执行__getitem__()
4. 对象[key] = value 会自动执行__setitem__()
5. del 对象[key] 会自动执行 __delitem__()
6. 对象+对象 会自动执行 __add__()
7. with 对象 as 变量 会自动执行__enter__ 和__exit__
8. 打印对象的时候 会自动执行 __str__
9. 干掉可哈希 __hash__ == None 对象就不可哈希了.
创建对象的真正步骤:
首先, 在执行类名()的时候. 系统会自动先执行__new__()来开辟内存. 此时新开辟出来的内存区域是空的. 紧随其后, 系统自动调用__init__()来完成对象的初始化⼯作. 按照时间轴来算.
1. 加载类
2. 开辟内存(__new__)
3. 初始化(__init__)
4. 使用对象干xxx
类似的操作还有很多很多. 我们不需要完全刻意的去把所有的特殊成员全都记住. 实战中也用不到那么多. 用到了查就是了.
单例模式:
class Foo(object): _instance = None # 实例 # 先 def __new__(cls, *args, **kwargs): if Foo._instance == None: Foo._instance = object.__new__(cls) # 开辟内存 return Foo._instance # 后 def __init__(self): print("我是一个简单的__init__") f1 = Foo() # 第一步先执行__new__分配内存, 第二步执行__init__初始化这段内存 f2 = Foo() print(f1) print(f2)
2. 反射
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
关于反射, 一共有4个函数:
1. hasattr(obj, str) 判断obj中是否包含str成员
2. getattr(obj,str) 从obj中获取str成员
3. setattr(obj, str, value) 把obj中的str成员设置成value. 注意. 这里的value可以是值, 也可以是函数或者方法
4. delattr(obj, str) 把obj中的str成员删除掉
注意, 以上操作都是在内存中进行的. 并不会影响源代码
四个方法的使用示例:
class Foo: f = "类的静态变量" def __init__(self,name,age): self.name = name self.age = age def func1(self): print("hi, %s" %self.name) obj = Foo("zhouxingxing",18) # 拿着功能的名字,去对象或者模块里找对应的功能 print(hasattr(obj,'name')) # True 存在返回True print(hasattr(obj,'func1')) # True print(hasattr(obj,'gender')) # False 不存在返回False # 获取属性 # getattr(object, name, default=None) n = getattr(obj,"name") print(n) # lishichao func = getattr(obj,"func1") func() # hi, lishichao # print(getattr(obj,"aaaa")) # AttributeError: 'Foo' object has no attribute 'aaaa' 报错 # 第三个参数:默认值,如果该属性不存在,则使用默认值 print(getattr(obj,"aaaa","不存在啊")) # 不存在啊 # 设置属性 # setattr(object,name,value) setattr(Foo,"f1","类的静态变量") # 设置类变量 setattr(obj,"sb",True) # 设置实例变量 setattr(obj,"show_name",lambda self:self.name+"sb") # 设置属性 print(obj.__dict__) # {'name': 'lishichao', 'age': 18, 'sb': True, 'show_name': <function <lambda> at 0x00000000021E1730>} print(obj.show_name(obj)) # zhouxingxingsb # 删除属性 delattr(obj,"age") delattr(obj,"show_name") # delattr(obj,"name111") #不存在,则报错。 AttributeError: name111 print(obj.__dict__) # {'name': 'zhouxingxing', 'sb': True} 在内存中都被删掉了
反射当前模块成员:
import sys def s1(): print('s1') def s2(): print('s2') this_mod = sys.modules[__name__] # <module '__main__' from 'E:/python-25期课上代码/day07/课上代码/02 反射.py'> print(hasattr(this_mod,"s1")) # True func = getattr(this_mod,"s2") func() # s2
导入其他模块,利用反射查找该模块是否存在某个方法
#!/usr/bin/env python3 # _*_ coding:utf-8 _*_ def t1(): print('from the t1') def t2(): print('from the t2') def t3(): print('from the t3') money = "25000"
import master while 1: # 根据用户输入的功能进行调用 tool = input("请输入你要执行的功能的名字:") # chi # 拿着功能的名字,去对象或者模块里找对应的功能 # has 有 attr 属性 if hasattr(master, tool): # 把这个功能拿出来 fn = getattr(master, tool) if callable(fn): # 判断是否可以被调用 fn() # 调用函数 else: print(fn) # 打印变量 else: print("没有这个功能")
# 对象中的反射,类本身也是对象(python 一切皆对象)
class Foo(object): staticField = "old boy" def __init__(self): self.name = 'wupeiqi' def func(self): return 'func' @staticmethod def bar(): return 'bar' obj = Foo() print(getattr(Foo, 'staticField')) func1 = getattr(Foo, 'func') print(func1(obj)) func2 = getattr(Foo, 'bar') print(func2())
3. configparser模块
该模块适用于配置文件件的格式与windows ini文件类似,可以包含一个或多个节(section)每个节可以有多个参数(键=值). 首先, 我们先看一个xxx服务器的配置文件
[DEFAULT] ServerAliveInterval = 45 Compression = yes CompressionLevel = 9 ForwardX11 = yes [server] User = hg Bind = 0.0.0.0 Port = 8888 [client] IP = 127.0.0.0 Port = 50022 ForwardX11 = no
我们用configparser就可以对这样的文件进行处理.首先, 是初始化
# 1. 初始化 import configparser config = configparser.ConfigParser() config["DEFAULT"] = { "sleep": 1000, "session-time-out": 30, "user-alive": 999999 } config["TEST-DB"] = { "db_ip": "192.168.17.189", "port": "3306", "u_name": "root", "u_pwd": "123456" } config['168-DB'] = { "db_ip": "152.163.18.168", "port": "3306", "u_name": "root", "u_pwd": "123456" } f = open("config.ini",mode="w") config.write(f) # 写入文件 f.flush() f.close() # 读取文件信息: config = configparser.ConfigParser() config.read("config.ini") # 读取文件 print(config.sections()) # 获取到所有section(章节)。 [DEFAULT]是默认信息,每个章节都有[DEFAULT]中的信息 # 执行结果: ['TEST-DB', '168-DB'] # 从章节中获取到任意的数据,get 方法Section下的key对应的value print(config.get("TEST-DB","db_ip")) # 执行结果: 192.168.17.189 # 也可以像字典一样操作 print(config["TEST-DB"]['db_ip']) print(config["168-DB"]["db_ip"]) # 执行结果: # 192.168.17.189 # 152.163.18.168 # 循环TEST-DB章节下的所有key for k in config["TEST-DB"]: print(k) # 所有key:value for k,v in config["TEST-DB"].items(): print(k,v) print(config.options("TEST-DB")) # 同for循环,找到 "TEST-DB"下所有键 # ['db_ip', 'port', 'u_name', 'u_pwd', 'sleep', 'session-time-out', 'user-alive'] print(config.items("TEST-DB")) # 找到 "TEST-DB"下所有键值对 # [('sleep', '1000'), ('session-time-out', '30'), ('user-alive', '999999'), ('db_ip', '192.168.17.189'), ('port', '3306'), ('u_name', 'root'), ('u_pwd', '123456')]
增删改操作:
# 先读取. 然后修改. 最后写回文件 config = configparser.ConfigParser() config.read("config.ini") # 读取文件 # 添加一个章节 config.add_section("172-DB") config["172-DB"] = { "db_ip": "172.168.12.11", "port": "3306", "u_name": "root", "u_pwd": "123456" } # 修改信息 config.set("168-DB","db_ip","10.0.3.26") # 删除章节 config.remove_section("TEST-DB") # 删除元素信息 config.remove_option("168-DB","db_ip") # 写回文件 config.write(open("config.ini",mode="w"))
4. MD5加密
MD5是一种不可逆的加密算法. 它是可靠的. 并且安全的. 在python中我们不需要手写这一套算法. 只需要引入hashlib模块就能搞定MD5的加密工作
import hashlib
# 1. 创建对象 obj = hashlib.md5() # 2. 把要加密的内容写入对象 obj.update("lishichao".encode("utf-8")) # 加密的必须是字节 # 3. 获取到MD5 miwen = obj.hexdigest() print(miwen) # 5f71293582408cc955d1a41fc434d29a
那这样的密文安全么? 其实是不安全的. 当我们用这样的密文去找一个所谓的MD5解密工具. 是有可能解密成功的.
MD5不是不可逆么? 注意. 这里有一个叫撞库的问题. 就是. 由于MD5的原始算法已经存在很久了. 那就有一些人用一些简单的排列组合来计算MD5. 然后当出现相同的MD5密文的时候就很容易反推出原来的数据是什么. 所以并不是MD5可逆,而是有些别有用心的人把MD5的常见数据已经算完并保留起来了.
那如何应对呢? 加盐就行了. 在使用MD5的时候. 给函数的参数传递一个byte即可.
import hashlib # 1. 创建对象,2. 加盐 obj = hashlib.md5(b"asjdkanoiwhdwbiohjixvzx") # 3. 把要加密的内容写入对象 obj.update("lishichao".encode("utf-8")) # 加密的必须是字节 # 4. 获取到MD5 miwen = obj.hexdigest() print(miwen) # 4404442131473d354913e82270dad0a1
md5的应用:
def my_md5(s): obj = hashlib.md5(b"asjdkanoiwhdwbiohjixvzx") obj.update(s.encode("utf-8")) miwen = obj.hexdigest() return miwen username = "" password = "" # 用户登录 def login(): uname = input("请输入用户名:") upwd = input("请输入密码:") if uname == username and my_md5(upwd) == password: print("登录成功") else: print("登录失败") # 用户注册 def reg(): global username global password uname = input("请输入用户名:") upwd = input("请输入密码:") username = uname password = my_md5(upwd) # 密码放密文 reg() login()
文件的MD5,用来校验文件是否传输完整:
obj = hashlib.md5(b"asjdkanoiwhdwbiohjixvzx") # 文件获取MD5值 f = open("test.txt",mode="r",encoding="utf-8") for i in f: obj.update(i.encode("utf-8")) miwen = obj.hexdigest() print(miwen) # 5fcda51b94a2e76d0322881d457c5c4f # 修改文件内容后MD5值会有变化: # be6e5639222ad8a950eaa98e7e66dc73
5. 日志
论日志的重要性,统计数据,报错信息,程序运行记录都由日志记录。在python中创建日志系统:
1. 导入logging模块.
2. 简单配置logging
3. 出现异常的时候(except). 向日志中写错误信息.
# 对日志系统进行配置
# 单日志系统
1 # filename: 文件名 2 # format: 数据的格式化输出. 最终在日志文件中的样子 3 # 时间-名称-级别-模块: 错误信息 4 # datefmt: 时间的格式 5 # level: 错误的级别权重, 当错误的级别权重大于等于leval的时候才会写入文件 6 7 logging.basicConfig(filename='x1.txt', # 日志文件名 8 # %(asctime) 时间 9 # %(name) root 10 # %(levelname)s 事件的严重性 11 # %(module)s 不用管 12 # %(message) 13 format='%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s', 14 datefmt='%Y-%m-%d %H:%M:%S', # 时间格式 15 level=30) # 记录日志的最低级别 16 17 # 如何使用(重点) 18 logging.critical("我是critical 我最牛B") # 级别最高的日志 50 19 logging.error("我是error, 我第二牛B") # 程序员的错 40 20 logging.warning("我是warning, 警告") # 警告. 不一定会出错 30 21 logging.info("我是info, 普通日志") # 不是问题. 如果输出的量很大. 也很麻烦 20 22 logging.debug("比屁都小的事儿都记录") # 级别最低. 粒度最大 10 23 logging.log(999, "皮炎平") 24 25 26 # CRITICAL = 50 27 # FATAL = CRITICAL 28 # ERROR = 40 29 # WARNING = 30 30 # WARN = WARNING 31 # INFO = 20 32 # DEBUG = 10 33 # NOTSET = 0
# 多文件日志系统
# 创建一个操作日志的对象logger(依赖FileHandler) file_handler1 = logging.FileHandler('l1.log', 'a', encoding='utf-8') file_handler1.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s")) logger1 = logging.Logger('汽车融资租赁', level=logging.ERROR) logger1.addHandler(file_handler1) file_handler2 = logging.FileHandler('l2.log', 'a', encoding='utf-8') file_handler2.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s")) logger2 = logging.Logger('IHOS医疗卫生综合服务系统', level=logging.ERROR) logger2.addHandler(file_handler2) logger1.error("我出错了. 我的车找不到了") logger2.error("医院丢了")
即输出到屏幕,又写入到文件
import logging # 创建 logging 对象 logger = logging.getLogger() # 创建文件对象 fh1 = logging.FileHandler("a1.log",encoding="utf-8") # 创建屏幕对象 sh = logging.StreamHandler() # 日志格式 formater1 = logging.Formatter( fmt='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s', # 显示格式 datefmt='%Y-%m-%d %H:%M:%S',) formater2 = logging.Formatter( fmt='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s', # 显示格式 datefmt='%Y-%m-%d %H:%M:%S',) # 给对象绑定格式 fh1.setFormatter(formater1) sh.setFormatter(formater2) # 给logger对象添加其他对象 logger.addHandler(fh1) logger.addHandler(sh) # 设置logger级别 logger.setLevel(10) fh1.setLevel(40) sh.setLevel(50) logging.debug('调试模式') # 10 logging.info('正常运行') # 20 logging.warning('警告') # 30 logging.error('错误') # 40 logging.critical('系统崩了') # 50
6. 异常处理
什么是异常? 异常是程序在运行过程中产生的错误. 我们先制造一个错误. 来看看异常长什么样.
def chu(a, b): return a/b ret = chu(10, 0) print(ret) # 执行结果: Traceback (most recent call last): File "E:/python-25期课上代码/day07/课上代码/06 异常处理.py", line 5, in <module> ret = chu(10, 0) File "E:/python-25期课上代码/day07/课上代码/06 异常处理.py", line 4, in chu return a/b ZeroDivisionError: division by zero
什么错误呢. 除法中除数不能是0. 那如果真的出了这个错. 你把这一堆信息抛给客户么? 肯定不能. 那如何处理呢?
def chu(a, b): return a/b try: ret = chu(10, 0) print(ret) except Exception as e: print("除数不能是0") # 执行结果: 除数不能是0
try ... except 是尝试着运行xxx代码. 出现了错误. 就执行except后面的代码. 在这个过程中. 当代码出现错误的时候. 系统会产生一个异常对象. 然后这个异常会向外抛. 被except拦截. 并把接收到的异常对象赋值给e. e就是异常对象.
Exception 是所有异常的基类, 也就是异常的根. 换句话说. 所有的错误都是Exception的子类对象. 我们看到的ZeroDivisionError 其实就是Exception的子类.
Exception 表示所有的错误. 太笼统了. 所有的错误都会被认为是Exception.当程序中出现多种错误的时候, 就不好分类了, 最好是出什么异常就用什么来处理.在try...execpt语句中.
还可以写更多的except:
try: print("各种操作....") except ZeroDivisionError as e: print("除数不能是0") except FileNotFoundError as e: print("⽂件不存在") except Exception as e: print("其他错误")
此时. 程序运行过程中. 如果出现了ZeroDivisionError就会被第一个except捕获. 如果出现了FileNotFountError就会被第二个except捕获. 如果都不是这两个异常. 那就会被最后的Exception捕获. 总之最后的Exception就是我们异常处理的最后一个守门员. 这时我们最常用的一套写法.
接下来. 给出一个完整的异常处理写法(语法):
try: '''操作''' except Exception as e: '''异常的父类,可以捕获所有的异常''' else: '''保护不抛出异常的代码, 当try中无异常的时候执行''' finally: '''最后总是要执行我'''
解读: 程序先执行操作, 然后如果出错了会走except中的代码. 如果不出错, 执行else中的代码. 不论处不出错. 最后都要执行finally中的语句. 一般我们用try...except就够用了. 顶多加上finally. finally一般用来作为收尾工作.
示例:
def chu(a, b): return a/b try: chu(10, 0) except Exception as e: print("其他错误") else: print("没有异常,正常执行") finally: print("不管有没有异常,最后要执行我") # 执行结果: # 其他错误 # 不管有没有异常,最后要执行我 try: chu(10, 2) except Exception as e: print("其他错误") else: print("没有异常,正常执行") finally: print("不管有没有异常,最后要执行我") # 执行结果: # 没有异常,正常执行 # 不管有没有异常,最后要执行我
上面是处理异常. 我们在执行代码的过程中如果出现了一些条件上的不对等. 根本不符合我的代码逻辑. 比如. 参数. 我要求你传递一个数字. 你非得传递一个字符串. 那对不起. 我没办法帮你处理. 那如何通知你呢? 两个方案.
方案一. 直接返回即可.
方案二. 抛出一个异常.
第一种不够好,无法起到警示作用,所以直接抛一个错误出去. 那怎么抛呢? 我们要用到raise关键字
def add(a, b): """ 求和运算 """ if not type(a) == int and not type(b) == int: # 当程序运行到这句话的时候. 整个函数的调用会被中断. 并向外抛出一个异常. raise Exception("不是整数, 无法运算") return a + b # 如果调用方不处理异常. 那产生的错误将会继续向外抛. 最后就抛给了用户 add("你好", "我叫赛利亚") # Exception: 不是整数, 无法运算 # 如果调用方处理了异常. 那么错误就不会丢给用户. 程序也能正常进行⾏ try: add("a", "b") except Exception as e: print("报错了.自己处理去吧") # 报错了.自己处理去吧
当程序运行到raise. 程序会被中断. 并实例化后面的异常对象. 抛给调用方. 如果调用方不处理. 则会把错误继续向上抛出. 最终抛给用户. 如果调用方处理了异常. 那程序可以正常的执行.
自定义异常:
自己写的代码中出现了一个无法用现有的异常来解决问题的时候,需要自定义异常
自定义异常: 写的类继承了Exception类. 那这个类就是一个异常类.
class GenderError(Exception): pass class Person: def __init__(self, name, gender): self.name = name self.gender = gender def Man(p): # 女 if p.gender != "男": raise GenderError("进错了,这里是男澡堂") # 抛出异常 else: print("欢迎光临") p1 = Person("alex", "男") p2 = Person("景女神", "女") Man(p2) # 报错,程序就停了 Man(p1) # 处理异常 try: Man(p2) Man(p1) except GenderError as e: print(e) # e => 进错了,这里是男澡堂⼦ except Exception as e: print("反正报错了")
我们在调试的时候, 最好是能看到错误源自于哪里,需要引入另一个模块traceback. 这个模块可以获取到我们每个方法的调用信息.又被成为堆栈信息. 这个信息用来排错是很有帮助的.
import traceback # 继承 Exception 就是异常类 class GenderError(Exception): pass class Person: def __init__(self,name,gender): self.name = name self.gender = gender def Man(person): if person.gender != "男": raise GenderError("性别不对") p1 = Person("周星星","男") p2 = Person("张敏","女") # Man(p1) # Man(p2) # 报错 会抛出异常: GenderError # 处理异常 try: Man(p1) Man(p2) except GenderError as e: val = traceback.format_exc() # 获取到堆栈信息 print(e) print(val) except Exception as e: print("反正报错了")
执行结果:
当测试代码的时候把堆栈信息打印出来. 但是当到了线上的生产环境的时候把这个堆栈去掉即可.
异常信息记录日志:
import logging import traceback logging.basicConfig(filename='error.log', # 日志文件名 # %(asctime) 时间 # %(name) root # %(levelname)s 事件的严重性 # %(module)s 不用管 # %(message) format='%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', # 时间格式 level=30) # 记录日志的最低级别 try: print(1/0) except Exception: logging.error(traceback.format_exc())
日志内容:
7. 模块
01. 模块
什么是模块. 模块就是一个包含了python定义和声明的文件, 文件名就是模块的名字加上.py后缀。我们写的py文件都可以看成是一个模块但是我们import加载的模块一共分成四个通用类别:
1. 使用pyhton编写的py文件
2. 已被变异为共享库或者DLL或C或者C++的扩展
3. 包好组模块的包.
4. 使用c编写并连接到python解释器的内置模块
为什么要使用模块? 为了我们写的代码可以重用. 不至于把所有的代码都写在一个文件内. 当项目规模比较大的时候. 就必须要把相关的功能进行分离. 方便维护和开发。
如何使用模块?导入模块有两种方式
1. import 模块
2. from xxx import xxx
02. import
示例文件:自定义模块my_module.py,文件名my_module.py,模块名my_module
# 自定义模快 my_module.py # 示例文件:文件名my_module.py,模块名my_module print('from the my_module.py') money=1000 def read1(): print('my_module->read1->money',money) def read2(): print('my_module->read2 calling read1') read1() def change(): global money money=0
引用 my_module 模块
# 导入模块
import my_module
print(my_module.money) # 使用模块中定义好的变量
my_module.read1() # 调用模块中的函数
在Python中模块是不能够重复导入的. 当重复导入模块时. 系统会根据 sys.modules 来判断该模块是否已经导入了. 如果已经导入. 则不会重复导入
import sys print(sys.modules.keys()) # 查看导入的模块. import my_module # 导入模块. 此时会默认执行该模块中的代码 import my_module # 该模块已经导入过了. 不会重复执行代码 import my_module import my_module import my_module import my_module # 执行结果: # dict_keys(['builtins', 'sys', '_frozen_importlib', '_imp', '_warnings', '_thread', '_weakref', '_frozen_importlib_external', '_io', 'marshal', 'nt', 'winreg', 'zipimport', 'encodings', 'codecs', '_codecs', 'encodings.aliases', 'encodings.utf_8', '_signal', '__main__', 'encodings.latin_1', 'io', 'abc', '_weakrefset', 'site', 'os', 'errno', 'stat', '_stat', 'ntpath', 'genericpath', 'os.path', '_collections_abc', '_sitebuiltins', 'sysconfig', 'sitecustomize']) # from the my_module.py
导入模块的时候都做了些什么? 首先. 在导入模块的一瞬间. python解释器会先通过sys.modules来判断该模块是否已经导入了该模块. 如果已经导入了则不再导入. 如果该模块还未导入过. 则系统会做三件事.
1. 为导入的模块创立新的名称空间
2. 在新创建的名称空间中运行该模块中的代码
3. 创建模块的名字. 并使用该名称作为该模块在当前模块中引用的名字.
我们可以使用globals来查看模块的名称空间
print(globals()) {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000001DBB518>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/python-25期课上代码/day07/课上代码/07 模块.py', '__cached__': None, 'sys': <module 'sys' (built-in)>, 'my_module': <module 'my_module' from 'E:\\python-25期课上代码\\day07\\课上代码\\my_module.py'>}
由于模块在导入的时候会创建其自己的名称空间. 所以. 我们在使用模块中的变量的时候一般是不会产生冲突的
import my_modlue money = 2000 print(my_module.money) # 模块中的变量 print(money) #自己的变量 # 执行结果: # 1000 # 2000
为模块名起别名,相当于m1=1;m2=m1
import my_module as sm print(sm.money)
在一行导入多个模块
import sys,os,re
模块搜索路径:
python解释器在启动时会自动加载一些模块,可以使用sys.modules查看
在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
# windows: python解释器模块搜索路径是项目根目录,和当前目录,通过 sys.path 查看模块搜索路径
# linux: 只有当前的目录,需要 sys.path.append() 把项目根目录添加进去
main是什么,main是程序的入口
我们可以通过模块的全局变量__name__来查看模块名: 当做脚本运行: __name__ 等于'__main__' 当做模块导入: __name__= 模块名 def main():
pass if __name__ == "__main__": # 启动文件, 被当做模块导入时 不执行。 main()
正确的导入模块的顺序:
1. 所有的模块导入都要写在最上⾯. 这是最基本的
2. 先引入内置模块
3. 再引入扩展模块
4. 最后引入你自己定义的模块
03. from xxx import xxx
在使用from的时候, python也会给我们的模块创建名称空间. 这一点和import是一样的. 但是from xxx import xxx的时候. 我们是把这个空间中的一些变量引入过来了. 说白了. 就是部分导入. 当这个模块中的内容过多的时候. 可以选择性的导入要使用的内容
from my_module import read1 read1()
此时是可以正常运行的. 但是我们省略了之前的模块.函数() 直接函数()就可以执行了, 并且from语句也支持一行语句导入多个内容.
from my_module import read1,read2,change read1() read2() change()
同样支持as
from my_module import read1 as rd rd()
from xxx import *
是把模块中的所有内容都导入. 注意, 如果模块中没有写出__all__ 则默认所有内容都导入. 如果写了__all__ 此时导入的内容就是在__all__列表中列出来的所有名字.
# haha.py __all__ = ["money", "chi"] money = 100 def chi(): print("我是吃") def he(): print("我是呵呵") # test.py from haha import * chi() print(money) # he() # 报错
最后. 看一下from的坑. 当我们从一个模块中引入一个变量的时候. 如果当前文件中出现了重名的变量时. 会覆盖掉模块引入的那个变量.
from my_module import money money = 10 print(money) # 10
所以. 不要重名. 切记. 不要重名! 不仅仅是变量名不要重复. 我们自己创建的py文件的名字不要和系统内置的模块重名. 否则. 引入的模块都是python内置的模块.
8. 包
什么是包?
包是一种通过 '.模块名' 来组织python模块名称空间的方式. 那什么样的东西是包呢? 我们创建的每个文件夹都可以被称之为包. 但是我们要注意, 在python2中规定. 包内必须存在__init__.py文件. 创建包的目的不是为了运行, 而是被导入使用. 包只是一种形式而已. 包的本质就是一种模块
为何要使包?
包的本质就是一个文件夹, 那么文件夹唯一的功能就是将文件组织起来,随着功能越写越多, 我们无法将所有功能都放在一个文件中, 于是我们使用模块去组织功能,随着模块越来越多, 我们就需要用文件夹将模块文件组织起来, 以此来提高程序的结构性和可维护性
首先, 我们先创建一些包. 用来作为接下来的学习. 包很好创建. 只要是一个文件夹, 有__init__.py就可以
创建目录结构:
import os os.makedirs('glance/api') os.makedirs('glance/cmd') os.makedirs('glance/db') l = [] l.append(open('glance/__init__.py','w')) l.append(open('glance/api/__init__.py','w')) l.append(open('glance/api/policy.py','w')) l.append(open('glance/api/versions.py','w')) l.append(open('glance/cmd/__init__.py','w')) l.append(open('glance/cmd/manage.py','w')) l.append(open('glance/db/__init__.py','w')) l.append(open('glance/db/models.py','w')) map(lambda f:f.close() ,l)
#文件内容 #policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine)
包的导入:
1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
3.对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。
import ,在与包 glance 同级别的文件中测试,test.py文件。
import glance.db.models glance.db.models.register_models("mysql")
from ... import ...
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
还是 test.py 与 glance 目录同级
from glance.db import models models.register_models("mysql") from glance.db.models import register_models register_models("redis")
__init__ 文件
不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。
from glance.api import *
在讲模块时,我们已经讨论过了从一个模块内导入所有*,此处我们研究从一个包导入所有*。
此处是想从包api中导入所有,实际上该语句只会导入包api下 __init__. py文件中定义的名字,我们可以在这个文件中定义__all___:
#在__init__.py中定义 print("我是api包下的__init__.py文件") x=10 def func(): print('from api.__init.py') __all__=['x','func','policy']
此时我们在于 glance 同级的 test.py 文件中执行 from glance.api import * 就导入__all__中的内容(versions仍然不能导入)。
from glance.api import * policy.get() print(x) func() versions.create_resource("config.ini") # 报错,没有导入 # 我是api包下的__init__.py文件 # from policy.py # 10 # from api.__init.py
绝对导入和相对导入
我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入:以glance作为起始
相对导入:用. 或者.. 的方式做为起始(只能在一个包中使用,不能用于不同目录内)
例如:我们在 glance/api/version.py 中想要导入 glance/cmd/manage.py
# 绝对导入 import sys # from glance.cmd import manage # 相对导入 # ValueError: attempted relative import beyond top-level package # versions不能作为启动文件, 启动文件要与glance在同级目录 from ..cmd import manage def create_resource(conf): manage.main() print('from version.py: ',conf)
测试结果:在于glance同级的 test.py 文件中测试
# 启动文件 from glance.api import versions if __name__ == '__main__': versions.create_resource("config")