第八章 模块&虚拟环境

模块

一、模块基础

1、概述

  • 命名空间

    由于在Python中一切皆为对象(Object), 想要好好理解Module和Package,一定要先理解Namespace的概念。 所谓Namespace,是指标示符的可见范围。对于Python而言,常见的Namespace主要有以下几种

    • Build-in Namespace (内建命名空间)
    • Global Namespace (全局命名空间)
    • Local Namespace (局部命名空间)

    有了命名空间的概念,可以有效的解决函数或者是变量重名的问题。不同的命名空间中允许出现相同的函数名或者 是变量名。它们彼此之间不会相互影响,例如在Namespace A和B中同时有一个名为var的变量,对A.var赋值并不 会改变B.var的值。

  • 为什么使用模块?

    在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越难以维护

  • 模块概述

    为了编写可维护性的代码,我们会把很多函数进行分组,分别放到不同的文件里去。这样,每个文件包含的代码就相对较少,大多数编程语言都是采用这种方式来组织代码的,在python中,一个.py文件就称之为一个模块

    Python中的一个Module对应的就是一个.py文件。其中定义的所有函数或者是变量都属于这个Module。这个Module 对于所有函数而言就相当于一个全局的命名空间。而每个函数又都有自己局部的命名空间。

  • 优点

    • 提高了代码的可维护性
    • 提高了代码的复用度,编写代码不必从零开始,当一个模块编写完成,就可以在其他地方引用
    • 引用其他模块,包含python内置模块和第三方模块
    • 避免函数名和变量名等命名的冲突
  • 模块分类

    • 标准库模块

    • 第三方模块

    • 自定义模块

2、使用标准库中的模块

  • time

    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S")  # 获取当前时间
    print(now)
    
  • random

    import random
    random_num = random.randrange(3)  # 返回012的随机数
    print(random_num)
    

3、使用自定义模块

  • 新建一个名为speak.py文件

    # speak.py 
    '''
    This is only a test module
    '''
    name = 'lucky'
    age = 18
    
    def speak():
      print("lucky is a very good man!")
    
  • import 语句

    • 作用

      导入模块/包

    • 格式

      import module1[, module2[, module3[, ……]]]

      import module1 as 别名[, module2[, module3[, ……]]] 起别名

    • 注意

      一个模块只会被导入一次,不管你执行了多少次import,有效的防止导入模块被一次次的执行

    • 使用模块中的内容

      module.方法/类/变量

    不起别名实例

    import speak  # 导入speak模块 
    >>> speak.name  # 获取模块speak中name的值
    lucky
    >>> speak.age  # 获取模块speak中age的值
    18
    >>> speak.speak()  # 调用模块中speak方法
    lucky is a very good man!
    >>> print speak.__doc__  # 获取模块说明
    This is only a test module
    

    起别名实例

    >>> import speak as s  # 导入speak模块 并起别名为s
    >>> s.name  # 获取模块speak中name的值
    lucky
    >>> s.age  # 获取模块speak中age的值
    18
    >>> s.speak()  # 调用模块中speak方法
    lucky is a very good man!
    >>> print s.__doc__  # 获取模块说明
    This is only a test module
    
  • from ... import 语句

    • 作用

      从模块中导入一些指定的部分

    • 格式

      from module import name1[, name2[, name3[, ……]]]

    实例

    >>> from speak import name,age,speak  # 从speak模块导入 name,age,speak
    >>> name  # 获取模块speak中name的值
    lucky
    >>> age  # 获取模块speak中age的值
    18
    >>> speak()  # 调用模块中speak方法
    lucky is a very good man!
    
  • from ... import * 语句

    • 概述

      将模块中所有非下划线开头的成员都导入

    • 作用

      把一个模块中所有的内容全部导入当前命名空间

    • 格式

      from modulename import *

    • 注意

      不应该过多使用,很可能造成变量名的冲突

    实例

    >>> from speak import *  # 会将speak模块中非下划线开头的成员都导入当前命名空间中
    >>> name  # 获取模块speak中name的值
    lucky
    >>> age  # 获取模块speak中age的值
    18
    >>> speak()  # 调用模块中speak方法
    lucky is a very good man!
    

4、__all__接口暴露

  • 概述

    代码中是不提倡用 from xxx import * 的写法的,但是在 console 调试的时候图个方便还是很常见的。如果一个模块 spam 没有定义 __all__,执行 from spam import * 的时候会将 spam 中非下划线开头的成员都导入当前命名空间中,这样当然就有可能弄脏当前命名空间。如果显式声明了 __all__import * 就只会导入 __all__ 列出的成员。如果 __all__ 定义有误,列出的成员不存在,还会明确地抛出异常,而不是默默忽略。

  • 格式

    __all__ = ["name1", "name2"...]
    
  • 作用

    Python不像 Ruby 或者 Java,Python 没有语言原生的可见性控制,而是靠一套需要大家自觉遵守的”约定“下工作。比如下划线开头的应该对外部不可见。同样,__all__ 也是对于模块公开接口的一种约定,比起下划线,__all__ 提供了暴露接口用的”白名单“。一些不以下划线开头的变量(比如从其他地方 import 到当前模块的成员)可以同样被排除出去。

  • 新建test_all.py

    # test_all.py
    '''
    This is only a test __all__ module
    '''
    
    __all__ = ["name", "speak"]  # 排除了 age
    
    name = 'lucky'
    age = 18
    
    def speak():
      print("lucky is a very good man!")
    

5、模块循环引用

  • 概述

    出现循环引用其实就是模块之间发生了相互依赖,A依赖B,B依赖A,这样他们直接相互依赖,引用的时候就会出现者循环引用(交叉引用)

  • 现象

    有两个模块moduleA 和 moduleB

    moduleA.py

    from moduleB import b
    
    def a():
        print('我是A模块的a方法')
        moduleB.b()
    def c():
        print('我是A模块的c方法')
    
    if __name__ == '__main__':
        a()
    

    moduleB.py

    from moduleA import c
    
    def b():
      print('我是B模块的b方法')
      c()
    
  • 导入的实质

    导入其实是要将 被导入模块所有的顶格代码都执行一遍,遇到函数和类的定义会作申明
    如果b模块中有这么一句

    print('我是B模块')
    

    你在a模块impot b时就会 执行 print('bbb')这一句

    回到循环引用中,首先导入B,进入B中,发现B中又导入了A又回到A中,但是A又导入B这就形成了循环引用

  • 解决方式1(直接导入模块名,通过模块调用其中的函数)

    moduleA.py

    import moduleB
    
    def a():
        print('我是A模块的a方法')
        moduleB.b()
    def c():
        print('我是A模块的c方法')
    
    
    if __name__ == '__main__':
        a()
    

    moduleB.py

    import moduleA
    
    def b():
        print('我是B模块的b方法')
        moduleA.c()
    
    
    if __name__ == '__main__':
        b()
    
  • 解决方式2(使用延迟导入(lazy import))

    内部导入

    """
    moduleB.py
    """
    def b():
        from moduleA import c
        print('我是B模块的b方法')
        c()
    

6、__name__属性

  • 概述

    每个模块都有一个name属性,当其值为“main”时表明该模块自身在运行,否则是被当做模块导入,此时值为模块的名字

  • 实例

    # speak.py 
    '''
    This is only a test module
    '''
    name = 'lucky'
    age = 18
    
    def speak():
      print("lucky is a very good man!")
      
    if __name__ == '__main__': 
       speak()
    
  • __name__作用

    模块就是一个可执行的python文件,一个模块被另一个模块导入,想让模块中的某一段代码不执行,可以使用__name__属性来使程序隐藏该段代码,当自身执行时在执行该块代码。一般作为判断是否是作为主运行文件

  • 扩展

    以后主要用于程序入口使用(项目启动文件中使用)

"""
Main.py
"""
def main():
    pass

if __name__ == '__main__':
    main()

7、包

  • 需求

    如果不同的人编写的模块名相同怎么办?

  • 解决

    为了避免模块名的冲突,python又引入了按目录来组织模块的方法,称为包(package)

  • 特点

    引入包以后,只要顶层包名不与别人冲突, 那么所有的模块都不会与别人冲突

  • 注意

    每个包目录下都会有一个名为__init__.py的文件,说明这个目录是个python包,还可以导出包中的内容

  • 建包

    新建文件夹名称为lucky_package文件夹

    目录结构

    project/
      lucky_package/
          __init__.py  # 声明lucky_package为一个包
        speak.py  # 模块speak
      test.py  # 用于测试lucky_package包的使用
    

    实现

    • 方式一 通过pycharm直接创建Python包

      选择模块 -> New -> Python Package

屏幕快照 2019-12-25 下午2.52.15.png
输入包名
屏幕快照 2019-12-25 下午2.52.44.png
点击OK
屏幕快照 2019-12-25 下午2.52.54.png
  • 方式二 手动创建

    • 模块 -> New -> Directory
屏幕快照 2019-12-25 下午2.56.17.png
- 输入 lucky_package

![屏幕快照 2019-12-25 下午2.52.44-7256895.png](https://upload-images.jianshu.io/upload_images/11222021-d7744dfe5c180b83.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


- 点击OK

  ![屏幕快照 2019-12-25 下午2.56.48.png](https://upload-images.jianshu.io/upload_images/11222021-354042fa73daaf93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


  

- lucky_package -> New -> Python File
屏幕快照 2019-12-25 下午2.56.52.png
- 输入文件名称为 `__init__.py`
屏幕快照 2019-12-25 下午2.57.00.png
  - 点击 OK

  ![屏幕快照 2019-12-25 下午2.57.00.png](https://upload-images.jianshu.io/upload_images/11222021-aa49d984a7fc1c40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 包内创建模块

    speak.py

    # speak.py 
    '''
    This is only a test module
    '''
    name = 'lucky'
    age = 18
    
    def speak():
      print("lucky is a very good man!")
      
    if __name__ == '__main__': 
       speak()
    
  • 使用

    test.py

    • 第一种导入方式使用

      >>> from lucky_package import speak  # 从lucky_package包导入speak模块
      >>> speak.name  # 获取模块speak中name的值
      lucky
      >>> speak.age  # 获取模块speak中age的值
      18
      >>> speak.speak()  # 调用模块中speak方法
      lucky is a very good man!
      

      导包重命名

      >>> from lucky_package import speak as s  # 从lucky_package包导入speak模块并重命名为s
      >>> s.name  # 获取模块speak中name的值
      lucky
      
    • 第二种导入方式

      >>> from lucky_package.speak import name,age,speak  # 从lucky_package包speak模块导入 name,age,speak
      >>> name  # 获取模块speak中name的值
      lucky
      >>> age  # 获取模块speak中age的值
      18
      
    • 第三种导入方式

      >>> from lucky_package.speak import *  # 从lucky_package包speak模块导入 所有成员
      >>> name  # 获取模块speak中name的值
      lucky
      >>> age  # 获取模块speak中age的值
      18
      
    • 第四种导入方式

      >>> import lucky_package.speak  # 导入lucky_package里speak包
      >>> lucky_package.speak.name  # 获取name的值
      lucky
      
    • 第五种导入方式

      >>> import lucky_package.speak as s  # 导入lucky_package里speak包
      >>> s.name  # 获取name的值
      lucky
      
  • 填充包__init__.py代码

    • 方式一 在init.py中 导入模块

      __init__.py

      from . import speak
      

      使用

      >>> from lucky_package import speak   # 导入lucky_package里speak包
      >>> speak.name  # 获取name的值
      lucky
      
    • 方式二

      在init.py中 导入模块所有成员

      __init__.py

      from .speak import * 
      

      使用

      >>> from lucky_package import *   # 导入lucky_package里speak包
      >>> name  # 获取name的值
      lucky
      
    • 注意

      不建议这样使用方式

二、常用模块

1、time模块

  • 名词
    UTC(世界协调时间):格林尼治时间,世界标准时间,北京时间1970年01月01日08时00分00秒)起至现在的总秒数,在中国为UTC+8

    DST(夏令时):是一种为了节约能源而人为规定地方时间的制度,一般在天亮早的夏季将时间提前一小时

  • 时间的表示形式

    • 时间戳
      以整型或浮点型表示的是一个以秒为单位的时间间隔,这个时间的基础值是1970年1月1号零时开始算

    • 元组
      一种python的数据结构表示方式,这个元组有9个整数元素,分别表示不同的时间含义
      year 年
      month(1-12) 月
      day(1-31) 日
      hours(0-23) 时
      minutes(0-59) 分
      seconds(0-59) 秒
      weekday(0-6,0表示星期一) 周
      Julian day(1-366):表示当前日期在本年度是第几天
      DST flag(-1 or 0 or 1):夏令时格式,0表示正常格式,1表示夏令时格式,-1表示根据当前的时间格式来判定

    • 导入

import time
  • 格式化日期的函数

    | 函数 | 函数说明 |
    | :-------------------------------------------------------- | ------------------------------------------------------------ |
    | time.time() | 当前时间戳(秒数) UNIX和Windows只支持到2038年。 |
    | time.localtime() | 接收时间辍(1970纪元后经过的浮点秒数)并返回当地时间下的时间元组 (0是周一) |
    | time.strftime("%Y-%m-%d %H:%M:%S") | 函数接收以时间元组,并返回以可读字符串表示的当地时间,格式由参数 format 决定。第二个参数可有可无 |
    | time.asctime() | 返回格式化后的英文文本的时间 |
    | time.mktime(tupletime) | 接受时间元组并返回时间辍 |
    | time.sleep(secs) | 推迟调用线程的运行,secs指秒数。 |
    | time.clock() | 用以浮点数计算的秒数返回当前的CPU时间。用来衡量不同程序的耗时,比time.time()更有用。 |
    | time.strptime("2013-10-10 23:40:00", "%Y-%m-%d %H:%M:%S") | 将给定时间转换为时间元组 |

  • 时间日期格式化符号

    符号 说明 符号 说明
    %Y 4位的年 %y 2位的年
    %m 月份(01-12) %d 月内中的一天(0-31)
    %H 24小时制小时数(0-23) %I 12小时制小时数(01-12)
    %M 分钟数(00=59) %S 秒(00-59)
    %a 本地简化星期名称 英文文本 简化 %A c本地简化星期名称 英文文本 完整
    %j 年内的一天(001-366) %w 星期(0-6),星期天为星期的开始 (0是周一)
    %x 本地相应的日期表示 (08/02/17) %X 本地相应的时间表示 23:48:34
  • time()

    返回当前时间的时间戳,无需参数,返回值为浮点型

    t1 = time.time()
    print(t1)
    
  • localtime([t])

    将给定的时间戳转为本地时间元组格式,如果没有参数默认转换当前时间戳

    t3 = time.localtime()
    print(t3)
    
  • mktime(tt)

    将本地时间元组转为时间戳

    t4 = time.mktime(t3)
    print(t4)
    
    import time
    
    t = (2016, 2, 17, 17, 3, 38, 1, 48, 0)
    secs = time.mktime( t )
    print ("time.mktime(t) : %f" %  secs)#time.mktime(t) : 1455699818.000000
    time.strptime(“2013-10-10 23:40:00”, "%Y-%m-%d %H:%M:%S")
    time = time.mktime(time.strptime("2013-10-10 23:40:00", "%Y-%m-%d %H:%M:%S"))
    print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time)))
    
  • asctime([tt])

    将时间元组格式转为指定格式的字符串形式,如果没有参数默认使用localtime时间的时间元组

    t5 = time.asctime(t3)
    print(t5, type(t5))
    
  • strftime(format[, tt])

    将时间元组以指定的格式转为字符串,如果没有tt参数默认使用当前本地时间元组

      t7 = time.strftime("%Y-%m-%d %H:%M:%S", t3)
      print(t7)
    
  • strptime(st, format)

    将指定格式的字符串解析为时间元组,是strftime的逆过程

    注意:format表示的格式要与st一致

    t8 = time.strptime("2001-10-01 08:08:08", "%Y-%m-%d %H:%M:%S")
    print(t8)
    
  • sleep()

    延迟一个时间段,接收整型或者浮点型

    time.sleep(2)
    
  • clock()

    返回当前程序执行时间,Unix系统始终返回全部运行时间,而Windows系统从第二次开始都是以第一次调用此函数的时间戳为基准,而不是以程序开始的时间为基准

    #                   windows   Unix
    print(time.clock()) # 0         1
    time.sleep(1)
    print(time.clock()) # 1         2
    time.sleep(1)
    print(time.clock()) # 2         3
    
  • 案例

    计算程序运行时间

    import time
    
    def procedure():
        for i in range(10000):
           pass
    
    # time.clock
    t0 = time.clock()
    procedure()
    print (time.clock() - t0)
    
    # time.time
    t0 = time.time()
    procedure()
    print (time.time() - t0)
    

    计算一个人活了多久

    import time
    # 将其转换为时间元组
    time_tup = time.strptime("1993-10-10 23:40:00", "%Y-%m-%d %H:%M:%S")
    before_time = time.mktime(time_tup)  # 根据实际元组返回秒数
    now_time = time.time() - before_time
    print(now_time/60/60,'小时')
    print(now_time/60/60/24,'天')
    print(now_time/60/60/24/365,'年')
    print(round(now_time/60/60/24/365, 2),'年')
    

2、datetime模块

  • 概述

datetime比time高级,可以理解为datetime基于time进行了封装,提供了更简单的函数接口,datetime模块的接口更直观、更容易调用

  • 模块中的类

    • time 只关注时间
    • date 只关注日期
    • datetime 同时关注日期和时间
    • timedelta 主要用于计算时间跨度
    • tzinfo 时区相关
  • 导入

    import datetime
    
  • 方法

    • 获取当前时间

      t1 = datetime.datetime.now()
      print(t1, type(t1))
      
    • 获取指定日期时间

      t2 = datetime.datetime(2001, 10, 1, 8, 8, 8)
      print(t2)
      
    • 将datetime时间转对象为字符串

      t3 = t1.strftime("%X %x")
      print(t3, type(t3))
      
    • 将字符串转为datetime对象

      t4 = datetime.datetime.strptime(t3, "%X %x")
      print(t4, type(t4))
      
    • 时间相减,返回一个时间间隔

      t5 = datetime.datetime(2001, 10, 1, 8, 8, 8)
      t6 = datetime.datetime(2001, 10, 2, 9, 8, 9)
      t7 = t6- t5
      print(t7, type(t7))
      #获取时间间隔天数
      print(t7.days)
      #获取去除间隔天数以外的间隔秒数
      print(t7.seconds)
      
    • Python 获取昨天日期

      # 引入 datetime 模块
      import datetime
      def getYesterday(): 
          today=datetime.date.today() 
          oneday=datetime.timedelta(days=1) 
          yesterday=today-oneday  
          return yesterday
       
      # 输出
      print(getYesterday())
      

3、calendar模块

  • 概述

    日历模块

  • 导入

    import calendar
    
  • 方法

    • 返回指定年的某月

      print(calendar.month(2019, 2))
      
    • 返回指定年的日历

    print(calendar.calendar(2018))
    
    • 判断是否是闰年

      print(calendar.isleap(2000))
      
    • 返回某个月的weekday的第一天和这个月的所有天数

      print(calendar.monthrange(2019, 4))
      
    • 返回某一个月以一周为周期的元素序列

      print(calendar.monthcalendar(2019, 2))
      

4、Pillow模块

  • 概述

    PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。

    由于PIL仅支持到Python 2.7,加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫Pillow,支持最新Python 3.x,又加入了许多新特性,因此,我们可以直接安装使用Pillow。

  • 安装Pillow

    如果安装了Anaconda,Pillow就已经可用了。否则,需要在命令行下通过pip安装:

    $ pip install pillow
    

    如果遇到Permission denied安装失败,请加上sudo重试。

    • 操作图像

    来看看最常见的图像缩放操作,只需三四行代码:

    from PIL import Image
    
    # 打开一个jpg图像文件,注意是当前路径:
    im = Image.open('test.jpg')
    # 获得图像尺寸:
    w, h = im.size
    print('Original image size: %sx%s' % (w, h))
    # 缩放到50%:
    im.thumbnail((w//2, h//2))
    print('Resize image to: %sx%s' % (w//2, h//2))
    # 把缩放后的图像用jpeg格式保存:
    im.save('thumbnail.jpg', 'jpeg')
    

    其他功能如切片、旋转、滤镜、输出文字、调色板等一应俱全。

    比如,模糊效果也只需几行代码:

    from PIL import Image, ImageFilter
    
    # 打开一个jpg图像文件,注意是当前路径:
    im = Image.open('test.jpg')
    # 应用模糊滤镜:
    im2 = im.filter(ImageFilter.BLUR)
    im2.save('blur.jpg', 'jpeg')
    

    验证码实现

    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    
    import random
    
    # 随机字母:
    def rndChar():
        return chr(random.randint(65, 90))
    
    # 随机颜色1:
    def rndColor():
        return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))
    
    # 随机颜色2:
    def rndColor2():
        return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
    
    # 240 x 60:
    width = 60 * 4
    height = 60
    image = Image.new('RGB', (width, height), (255, 255, 255))
    # 创建Font对象:
    # windwos下
    font = ImageFont.truetype(r'C:\Windows\Fonts/Arial.ttf', 36)
    # Mac下
    # font = ImageFont.truetype('Arial.ttf', 36)
    # 创建Draw对象:
    draw = ImageDraw.Draw(image)
    # 填充每个像素:
    for x in range(width):
        for y in range(height):
            draw.point((x, y), fill=rndColor())
    # 输出文字:
    for t in range(4):
        draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())
    # 模糊:
    image = image.filter(ImageFilter.BLUR)
    image.save('code.jpg', 'jpeg')
    

    作业:自己写一个实现等比缩放的函数

    4、uuid模块

  • 概述

    是128位的全局唯一标识符,通常由32字节的字母串表示,它可以保证时间和空间的唯一性,也称为GUID

  • 作用

    随机生成字符串,在token、账号、订单号等需要唯一标识的地方使用

  • 原理

    通过Mac地址、时间戳、命名空间、随机数、伪随机数来保证产生的id的唯一性

  • 算法:

    • uuid1()基于时间戳
      有MAC地址、当前时间戳、随机数字,可以保证全球范围内的唯一性。但是由于MAC地址的使用会带来安全问题,局域网中可以使用IP来代替MAC

    • uuid2()基于分布式计算环境DCE
      算法和uuid1()相同,不同的是把时间戳的前4位换成POSI的UID,实际当中很少使用

      注意:python中没有这个函数

    • uuid3()基于名字和MD5散列值
      通过计算名和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,和不同命名空间的唯一性,但是同一命名空间的相同名字生成相同的uuid

    • uuid4()基于随机数
      由伪随机数得到的,有一定的重复概率,这个重复概率是可以通过计算得到的

    • uuid5()基于名字和SHA1散列值
      算法和uuid3()相同,不同的是使用SHA1算法

  • 使用经验:

    • python中没有基于DCE的,所以uuid2()可以忽略
    • uuid4()存储概率性重复,由于无映射性所以最好不用
    • 如果在全局的分布式环境下,最好使用uuid1()
    • 若名字的唯一性要求,最好使用uuid3()或者uuid5()
  • 导入

    import uuid
    
  • 使用

    a = uuid.uuid1()
    print(a, type(a))
    b = str(a)
    print(b, type(b))
    # print(uuid.uuid2())
    print(uuid.uuid3(uuid.NAMESPACE_DNS, "lucky"))
    print(uuid.uuid3(uuid.NAMESPACE_DNS, "lucky"))
    print(uuid.uuid3(uuid.NAMESPACE_OID, "lucky"))
    print(uuid.uuid3(uuid.NAMESPACE_DNS, "lucky"))
    print(uuid.uuid5(uuid.NAMESPACE_DNS, "lucky"))
    

5、collections模块

  • 概述

    python中内建的一个集合模块,提供了许多有用的集合类

  • namedtuple

    • 概述

      命名元组,本质是一个函数,用他来创建一个自定义的tuple类型

      规定tuple元素的格式,并可以用属性而不是索引引用tuple中的元素

      用namedtuple实际上是创建一个新的数据类型

    • 导入

      from collections import namedtuple
      
    • 使用

      #假设这是一个点的坐标,但是没有写注释,时间久了就忘了该有含义
      p = (1, 2)
      
      
      # 定义一个新的数据类型,
      Point = namedtuple("point", ["x", "y"])
      # 定义一个Point类型的变量,保存一个元组数据
      p2 = Point(1, 2)
      print(p2, isinstance(p2, Point), isinstance(p2, tuple))
      #访问命名元组元素的值
      print(p2[0], p2[1])
      print(p2.x, p2.y)
      
  • deque

    • 概述

      使用list存储数据,按索引访问元素,但是插入和删除元素会根据元素的个数增多个降低效率。因为list是线性存储,数据量大插入和删除的效率就会低。

      deque就是为了高效实现插入和删除操作的双向列表,适用于队列和栈。并且deque是线程安全的

    • 导入

      from collections import deque
      
    • 使用

      q = deque([1,2,3,4,5])
      
      q.append(6)
      q.appendleft(0)
      
      print(q.pop())
      print(q.popleft())
      
      print(q)
      
  • defaultdict

    • 概述

      使用dict时,如果引用的key不存在,如果使用的[]方式则会报KeyError异常,如果使用的get()方式则会得到None。
      如果希望key不存在时也能得到一个默认的值就使用defaultdict

    • 导入

      from collections import defaultdict
      
    • 使用

      d1 = {"a": 1, "b": 2, "c": 3}
      # print(d1["d"])
      # print(d1.get("d"))
      
      
      d2 = defaultdict(lambda :"键值对不存在")
      d2["a"] = 1
      d2["b"] = 2
      print(d2["c"])
      print(d2.get("c"))
      print(d2, type(d2), isinstance(d2, dict))
      
  • OrderedDict

    • 概述

      使用dict是,key是无序的。对dict做迭代时无法保证key的顺序。如果需要key有顺序,就可以使用OrderDict

    • 导入

      from collections import OrderedDict
      
    • 使用

      d1 = {"a": 1, "b": 2, "c": 3}
      for key in d1:
          print(key)
      print("----------------------")
      
      
      
      d2 = OrderedDict([("a", 1),("b", 2),("c", 3)])
      print(d2)
      print(d2["a"])
      print(d2.get("b"))
      
  • Counter

    • 概述

      是一个简单的计数器,本质上是dict的一个子类

    • 导入

      from collections import Counter
      
    • 使用

      需求:计算集合中每个字符出现的次数

      s = "lucky is a good man"
      c = Counter()
      print(c, type(c), isinstance(c, dict))
      for ch in s:
          c[ch] = c[ch] + 1
      print(c)
      for key in c:
          print(key, c[key])
      

6、base64模块

  • 概述

    用记事本打开图片等文件,看到一坨乱码,因为二进制文件包含很多无法显示的内容。所以想让记事本能处理二进制数据,就需要将二进制字符串转换,base64是一种比较常见的二进制编码方式

  • 作用

    适用于小段内容的编码,比如数字证书签名、cookie、网页中传输的少量二进制数据

  • 编码原理

    一个包含64个字符的列表
    ["A", "B", ……, "Z", "a", "b", ……, "z", "0", "1", ……, "9", "+", "/"]
    对二进制数据进行处理,每是三个字节一组,一组就是3x8=24bit,划分为4组,每组正好6bit。得到4个数字作为索引,然后查表,获取相应的4个字符,就是编码后的字符串

  • 注意

    base64是一种通过查表的编码方法,不能用于加密,即使修改了字符对照表也不行

  • 使用

    • b64encode

      s1 = b"lucky is a good man"
      print(base64.b64encode(s1))
      s2 = b'c3VuY2sgaXMgYSBnb29kIG1hbg=='
      print(base64.b64decode(s2))
      

      由于=字符也可能出现在base64编码中,但是=在url、cookie里会造成歧义,所以很多base64编码会把编码后的=去掉

      s6 = b"abcd"
      s7 = base64.b64encode(s6)
      print(s7)
      '''
      'YWJjZA=='
      'YWJjZA'
      '''
      s8 = b'YWJjZA=='
      print(base64.b64decode(s8))
      

      注意

      由于标准base64编码后可能出现字符+或/,在URL中就不能直接作为参数

    • urlsafe_b64encode

      提供urlsafe_b64encode编码,保证url的安全,将+和/替换为-和_,提供urlsafe_b64decode进行url安全解码

      s3 = b"http://www.xialigang.com"
      print(base64.urlsafe_b64encode(s3))
      s4 = b"aHR0cDovL3d3dy5zdW5jay53YW5n"
      print(base64.urlsafe_b64decode(s4))
      
      s5 = b"lucky is a good m~"
      print(base64.b64encode(s5))
      print(base64.urlsafe_b64encode(s5))
      

7、hashlib模块

  • 概述

    该模块提供了常见的摘要算法,如MD5、SHA1

    摘要算法(又称哈希算法、散列算法):它通过一个函数,把任意长度的数据转为一个长度固定的数据串(通常用16进制的字符串表示)

  • 作用

    用于加密

  • MD5
    最常见的摘要算法,速度快,生成的结构是128位字节,通常用32位16进制字符串表示

  • 使用

    s1 = b"lucky is a good man"
    m1 = hashlib.md5()
    m1.update(s1)
    ret = m1.hexdigest()
    print(ret)
    
    #如果数据量比加大,可以分多次调用update,最终结果是一样的
    m2 = hashlib.md5()
    m2.update(b"lucky is a")
    m2.update(b" good man")
    ret2 = m2.hexdigest()
    print(ret2)
    
  • SHA1
    调用SHA1与调用MD5完全一样,SHA1的结果是160字节,通常用40位16进制字符串表示

  • 使用

    s2 = b"lucky is a good man"
    sh1 = hashlib.sha1()
    sh1.update(s2)
    ret3 = sh1.hexdigest()
    print(ret3)
    

    注意:数据量大同md5使用相同

  • 更安全的
    SHA256
    SHA512
    越安全的算法不见越慢,而且摘要越长

  • 应用:
    任何允许用户登录的网站都会存储用户登录的用户名和密码(存储在数据库中),那么密码一般存储的是原密码的摘要值
    lucky-666666明文存储到数据库中,如果数据库泄露,所有用户信息就会暴露

    正确的保存口令方式不是存储明文,而是存储口令的摘要,当用户登录时,首先计算用户输入的明文的摘要,和数据库中的对比,如果一致说明口令正确,否则一定错误

8、hmac模块

  • 概述

    实现了HMAC算法,是用一个key对数据进行“杂凑”后在进行的hash,是用hmac比hash算法更安全,不同的key会产生不同的hash值

  • 导入

    s = b"lucky is a good man"
    key = b"good"
    h = hmac.new(key, s, digestmod="MD5")
    ret = h.hexdigest()
    print(ret)
    

三、第三方模块

1、安装

  • Windows
    pip install 模块名
    pip install 模块名==版本号

  • linux(root用户)
    pip install 模块名
    pip install 模块名==版本号

  • linux(普通用户)
    sudo pip install 模块名
    sudo pip install 模块名==版本号

  • 注意

    公司里基本你不会让你用root用户

2、卸载

  • Windows
    pip uninstall 模块名
  • linux(root用户)
    pip uninstall 模块名
  • linux(普通用户)
    sudo pip uninstall 模块名

3、查看

  • 当前所有的三方模块
    pip list
  • pip的版本
    pip --version

4、实际操作

  • pillow

    处理图像

  • 安装

    pip install pillow

  • 实例

    from PIL import Image
    
    #打开图片
    im = Image.open("timg.jpg")
    # 查看图片信息
    print(im.format, im.size, im.mode)
    #修改图片大小
    im.thumbnail((102, 68))
    #生成新图片
    im.save("timg2.jpg", "JPEG")
    

四、virtualenv虚拟环境

1、概述

在开发Python应用程序的时候,系统安装的Python3只有一个版本:3.5。所有第三方的包都会被pip安装到Python3的site-packages目录下。

如果我们要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python 3。如果应用A需要jinja 2.7,而应用B需要jinja 2.6怎么办?

这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。

2、安装

我们用pip安装virtualenv:

$ pip3 install virtualenv

然后,假定我们要开发一个新的项目,需要一套独立的Python运行环境,可以这么做:

3、操作

  • 第一步,创建目录:
Mac:~ xialigang$ mkdir myproject
Mac:~ xialigang$ cd myproject/
Mac:myproject xialigang$
  • 第二步,创建一个独立的Python运行环境,命名为venv
Mac:myproject xialigang$ virtualenv --system-site-packages venv
Using base prefix '/usr/local/.../Python.framework/Versions/3.4'
New python executable in venv/bin/python3.4
Also creating executable in venv/bin/python
Installing setuptools, pip, wheel...done.

命令virtualenv就可以创建一个独立的Python运行环境,我们还加上了参数--no-site-packages,这样,已经安装到系统Python环境中的所有第三方包都不会复制过来,这样,我们就得到了一个不带任何第三方包的“干净”的Python运行环境。

新建的Python环境被放到当前目录下的venv目录。有了venv这个Python环境,可以用source进入该环境:

Mac:myproject xialigang$ source venv/bin/activate
(venv)Mac:myproject xialigang$

注意到命令提示符变了,有个(venv)前缀,表示当前环境是一个名为venv的Python环境。

  • 安装各种第三方包,并运行python命令:
(venv)Mac:myproject xialigang$ pip install jinja2
...
Successfully installed jinja2-2.7.3 markupsafe-0.23
(venv)Mac:myproject xialigang$ python myapp.py
...

venv环境下,用pip安装的包都被安装到venv这个环境下,系统Python环境不受任何影响。也就是说,venv环境是专门针对myproject这个应用创建的。

  • 退出当前的venv环境,使用deactivate命令:
(venv)Mac:myproject xialigang$ deactivate 
Mac:myproject xialigang$

此时就回到了正常的环境,现在pippython均是在系统Python环境下执行。

完全可以针对每个应用创建独立的Python运行环境,这样就可以对每个应用的Python环境进行隔离。

virtualenv是如何创建“独立”的Python运行环境的呢?原理很简单,就是把系统Python复制一份到virtualenv的环境,用命令source venv/bin/activate进入一个virtualenv环境时,virtualenv会修改相关环境变量,让命令pythonpip均指向当前的virtualenv环境。

你可能感兴趣的:(第八章 模块&虚拟环境)