Python开发【第七篇】: 面向对象和模块补充

内容概要

  1.   特殊成员
  2.  反射
  3.  configparser模块
  4.  hashlib模块
  5.  logging模块
  6.  异常处理
  7.  模块
  8.  包

 

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)
View Code

 

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} 在内存中都被删掉了
View Code

 

反射当前模块成员:

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
View Code

导入其他模块,利用反射查找该模块是否存在某个方法

#!/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"
master
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("没有这个功能")
导入master

 

# 对象中的反射,类本身也是对象(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())
View Code

 

 

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')]
View Code

 

增删改操作:

# 先读取. 然后修改. 最后写回文件
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"))
View Code

 

 

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()
View Code

 

文件的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 
View Code

 

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
View Code

 

 # 多文件日志系统

# 创建一个操作日志的对象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("医院丢了")
View Code

 

即输出到屏幕,又写入到文件

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
View Code

 

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
View Code

 

  什么错误呢. 除法中除数不能是0. 那如果真的出了这个错. 你把这一堆信息抛给客户么? 肯定不能. 那如何处理呢? 

def chu(a, b):
    return a/b

try:
    ret = chu(10, 0)
    print(ret)
except Exception as e:
    print("除数不能是0")

# 执行结果:
除数不能是0
View Code

  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("不管有没有异常,最后要执行我")
# 执行结果:
# 没有异常,正常执行
# 不管有没有异常,最后要执行我
View Code

 

上面是处理异常. 我们在执行代码的过程中如果出现了一些条件上的不对等. 根本不符合我的代码逻辑. 比如. 参数. 我要求你传递一个数字. 你非得传递一个字符串. 那对不起. 我没办法帮你处理. 那如何通知你呢? 两个方案.
  方案一. 直接返回即可.
  方案二. 抛出一个异常.


第一种不够好,无法起到警示作用,所以直接抛一个错误出去. 那怎么抛呢? 我们要用到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("报错了.自己处理去吧")
# 报错了.自己处理去吧
View Code

  当程序运行到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("反正报错了")
View Code

 

执行结果:

当测试代码的时候把堆栈信息打印出来. 但是当到了线上的生产环境的时候把这个堆栈去掉即可. 

 

异常信息记录日志:

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())
View Code

 

 

日志内容:

 

 

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

 

引用 my_module 模块

# 导入模块
import my_module
print(my_module.money)  # 使用模块中定义好的变量

my_module.read1()       # 调用模块中的函数
View Code

 

在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
View Code

 

导入模块的时候都做了些什么? 首先. 在导入模块的一瞬间. 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
View Code

 

为模块名起别名,相当于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() # 报错
View Code

 

最后. 看一下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)
View Code

 

#文件内容
#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']
api目录下的__init.py

 

 

此时我们在于 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
test.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/api/version.py

 

测试结果:在于glance同级的 test.py 文件中测试

 

 

# 启动文件
from glance.api import versions

if __name__ == '__main__':
    versions.create_resource("config")
启动文件

 

posted @ 2019-02-25 14:42  LiShiChao  阅读(298)  评论(0编辑  收藏  举报