zoukankan      html  css  js  c++  java
  • 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")
    启动文件
  • 相关阅读:
    这算什么?兴许是公告栏罢
    [考试总结]ZROI-21-NOIP冲刺-TEST1 总结
    [考试总结]ZROI-21-NOIP-CSP7连-DAY6 总结
    [考试总结]ZROI-21-十一集训-赠送赛 总结
    [考试总结]ZROI-21-CSP7连-DAY5 总结
    [考试总结]ZROI-21-CSP7连-DAY4 总结
    [考试总结]ZROI-21-CSP7连-EXTRA1 总结
    [考试总结]ZROI-21-CSP7连-DAY3 总结
    [题解]HDU6606 Distribution of books
    [题解]HDU6315 Naive Operations
  • 原文地址:https://www.cnblogs.com/root0/p/10430805.html
Copyright © 2011-2022 走看看