python教程(5更新中)

常用内建模块

Python之所以自称“batteries included”,就是因为内置了许多非常有用的模块,无需额外安装和配置,即可直接使用。

本章将介绍一些常用的内建模块。

datetime

datetime是Python处理日期和时间的标准库。

获取当前日期和时间

我们先看如何获取当前日期和时间:

>>> from datetime import datetime

>>> now = datetime.now() # 获取当前datetime

>>> print(now)2015-05-18 16:28:07.198690

>>> print(type(now))

<class 'datetime.datetime'>

注意到datetime是模块,datetime模块还包含一个datetime类,通过from datetime import datetime导入的才是datetime这个类。

如果仅导入import datetime,则必须引用全名datetime.datetime

datetime.now()返回当前日期和时间,其类型是datetime

获取指定日期和时间

要指定某个日期和时间,我们直接用参数构造一个datetime

>>> from datetime import datetime>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime>>> print(dt)2015-04-19 12:20:00

datetime转换为timestamp

在计算机中,时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。

你可以认为:

timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00

对应的北京时间是:

timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00

可见timestamp的值与时区毫无关系,因为timestamp一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的,这就是为什么计算机存储的当前时间是以timestamp表示的,因为全球各地的计算机在任意时刻的timestamp都是完全相同的(假定时间已校准)。

把一个datetime类型转换为timestamp只需要简单调用timestamp()方法:

>>> from datetime import datetime>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime>>> dt.timestamp() # 把datetime转换为timestamp1429417200.0

注意Python的timestamp是一个浮点数,整数位表示秒。

某些编程语言(如Java和JavaScript)的timestamp使用整数表示毫秒数,这种情况下只需要把timestamp除以1000就得到Python的浮点表示方法。

timestamp转换为datetime

要把timestamp转换为datetime,使用datetime提供的fromtimestamp()方法:

>>> from datetime import datetime>>> t = 1429417200.0>>> print(datetime.fromtimestamp(t))2015-04-19 12:20:00

注意到timestamp是一个浮点数,它没有时区的概念,而datetime是有时区的。上述转换是在timestamp和本地时间做转换。

本地时间是指当前操作系统设定的时区。例如北京时区是东8区,则本地时间:

2015-04-19 12:20:00

实际上就是UTC+8:00时区的时间:

2015-04-19 12:20:00 UTC+8:00

而此刻的格林威治标准时间与北京时间差了8小时,也就是UTC+0:00时区的时间应该是:

2015-04-19 04:20:00 UTC+0:00

timestamp也可以直接被转换到UTC标准时区的时间:

>>> from datetime import datetime>>> t = 1429417200.0>>> print(datetime.fromtimestamp(t)) # 本地时间2015-04-19 12:20:00>>> print(datetime.utcfromtimestamp(t)) # UTC时间2015-04-19 04:20:00

str转换为datetime

很多时候,用户输入的日期和时间是字符串,要处理日期和时间,首先必须把str转换为datetime。转换方法是通过datetime.strptime()实现,需要一个日期和时间的格式化字符串:

>>> from datetime import datetime>>> cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')>>> print(cday)2015-06-01 18:19:59

字符串'%Y-%m-%d %H:%M:%S'规定了日期和时间部分的格式。详细的说明请参考Python文档

注意转换后的datetime是没有时区信息的。

datetime转换为str

如果已经有了datetime对象,要把它格式化为字符串显示给用户,就需要转换为str,转换方法是通过strftime()实现的,同样需要一个日期和时间的格式化字符串:

>>> from datetime import datetime>>> now = datetime.now()>>> print(now.strftime('%a, %b %d %H:%M'))

Mon, May 05 16:28

datetime加减

对日期和时间进行加减实际上就是把datetime往后或往前计算,得到新的datetime。加减可以直接用+-运算符,不过需要导入timedelta这个类:

>>> from datetime import datetime, timedelta>>> now = datetime.now()>>> now

datetime.datetime(2015, 5, 18, 16, 57, 3, 540997)>>> now + timedelta(hours=10)

datetime.datetime(2015, 5, 19, 2, 57, 3, 540997)>>> now - timedelta(days=1)

datetime.datetime(2015, 5, 17, 16, 57, 3, 540997)>>> now + timedelta(days=2, hours=12)

datetime.datetime(2015, 5, 21, 4, 57, 3, 540997)

可见,使用timedelta你可以很容易地算出前几天和后几天的时刻。

本地时间转换为UTC时间

本地时间是指系统设定时区的时间,例如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00时区的时间。

一个datetime类型有一个时区属性tzinfo,但是默认为None,所以无法区分这个datetime到底是哪个时区,除非强行给datetime设置一个时区:

>>> from datetime import datetime, timedelta, timezone>>> tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00>>> now = datetime.now()>>> now

datetime.datetime(2015, 5, 18, 17, 2, 10, 871012)>>> dt = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00>>> dt

datetime.datetime(2015, 5, 18, 17, 2, 10, 871012, tzinfo=datetime.timezone(datetime.timedelta(0, 28800)))

如果系统时区恰好是UTC+8:00,那么上述代码就是正确的,否则,不能强制设置为UTC+8:00时区。

时区转换

我们可以先通过utcnow()拿到当前的UTC时间,再转换为任意时区的时间:

# 拿到UTC时间,并强制设置时区为UTC+0:00:>>> utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)>>> print(utc_dt)2015-05-18 09:05:12.377316+00:00# astimezone()将转换时区为北京时间:>>> bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))>>> print(bj_dt)2015-05-18 17:05:12.377316+08:00# astimezone()将转换时区为东京时间:>>> tokyo_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))>>> print(tokyo_dt)2015-05-18 18:05:12.377316+09:00# astimezone()将bj_dt转换时区为东京时间:>>> tokyo_dt2 = bj_dt.astimezone(timezone(timedelta(hours=9)))>>> print(tokyo_dt2)2015-05-18 18:05:12.377316+09:00

时区转换的关键在于,拿到一个datetime时,要获知其正确的时区,然后强制设置时区,作为基准时间。

利用带时区的datetime,通过astimezone()方法,可以转换到任意时区。

注:不是必须从UTC+0:00时区转换到其他时区,任何带时区的datetime都可以正确转换,例如上述bj_dttokyo_dt的转换。

小结

datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。

如果要存储datetime,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关。

练习

假设你获取了用户输入的日期和时间如2015-1-21 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp:

# -*- coding:utf-8 -*-



import re

from datetime import datetime, timezone, timedelta

def to_timestamp(dt_str, tz_str):

    pass



# 测试:

t1 = to_timestamp('2015-6-1 08:10:30', 'UTC+7:00')

assert t1 == 1433121030.0, t1



t2 = to_timestamp('2015-5-31 16:10:30', 'UTC-09:00')

assert t2 == 1433121030.0, t2



print('ok')

collections

collections是Python内建的一个集合模块,提供了许多有用的集合类。

namedtuple

我们知道tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:

>>> p = (1, 2)

但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。

定义一个class又小题大做了,这时,namedtuple就派上了用场:

>>> from collections import namedtuple>>> Point = namedtuple('Point', ['x', 'y'])>>> p = Point(1, 2)>>> p.x1>>> p.y2

namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。

这样一来,我们用namedtuple可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用,使用十分方便。

可以验证创建的Point对象是tuple的一种子类:

>>> isinstance(p, Point)True>>> isinstance(p, tuple)True

类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:

# namedtuple('名称', [属性list]):Circle = namedtuple('Circle', ['x', 'y', 'r'])

deque

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。

deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:

>>> from collections import deque>>> q = deque(['a', 'b', 'c'])>>> q.append('x')>>> q.appendleft('y')>>> q

deque(['y', 'a', 'b', 'c', 'x'])

deque除了实现list的append()pop()外,还支持appendleft()popleft(),这样就可以非常高效地往头部添加或删除元素。

defaultdict

使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict

>>> from collections import defaultdict>>> dd = defaultdict(lambda: 'N/A')>>> dd['key1'] = 'abc'>>> dd['key1'] # key1存在'abc'>>> dd['key2'] # key2不存在,返回默认值'N/A'

注意默认值是调用函数返回的,而函数在创建defaultdict对象时传入。

除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的。

OrderedDict

使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。

如果要保持Key的顺序,可以用OrderedDict

>>> from collections import OrderedDict>>> d = dict([('a', 1), ('b', 2), ('c', 3)])>>> d # dict的Key是无序的

{'a': 1, 'c': 3, 'b': 2}>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])>>> od # OrderedDict的Key是有序的

OrderedDict([('a', 1), ('b', 2), ('c', 3)])

注意,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:

>>> od = OrderedDict()>>> od['z'] = 1>>> od['y'] = 2>>> od['x'] = 3>>> list(od.keys()) # 按照插入的Key的顺序返回

['z', 'y', 'x']

OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key:

from collections import OrderedDict

class LastUpdatedOrderedDict(OrderedDict):



    def __init__(self, capacity):

        super(LastUpdatedOrderedDict, self).__init__()

        self._capacity = capacity



    def __setitem__(self, key, value):

        containsKey = 1 if key in self else 0

        if len(self) - containsKey >= self._capacity:

            last = self.popitem(last=False)

            print('remove:', last)

        if containsKey:

            del self[key]

            print('set:', (key, value))

        else:

            print('add:', (key, value))

        OrderedDict.__setitem__(self, key, value)

ChainMap

ChainMap可以把一组dict串起来并组成一个逻辑上的dictChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找。

什么时候使用ChainMap最合适?举个例子:应用程序往往都需要传入参数,参数可以通过命令行传入,可以通过环境变量传入,还可以有默认参数。我们可以用ChainMap实现参数的优先级查找,即先查命令行参数,如果没有传入,再查环境变量,如果没有,就使用默认参数。

下面的代码演示了如何查找usercolor这两个参数:

from collections import ChainMapimport os, argparse

# 构造缺省参数:

defaults = {

    'color': 'red',

    'user': 'guest'

}

# 构造命令行参数:

parser = argparse.ArgumentParser()

parser.add_argument('-u', '--user')

parser.add_argument('-c', '--color')

namespace = parser.parse_args()

command_line_args = { k: v for k, v in vars(namespace).items() if v }

# 组合成ChainMap:

combined = ChainMap(command_line_args, os.environ, defaults)

# 打印参数:

print('color=%s' % combined['color'])

print('user=%s' % combined['user'])

没有任何参数时,打印出默认参数:

$ python3 use_chainmap.py

color=red

user=guest

当传入命令行参数时,优先使用命令行参数:

$ python3 use_chainmap.py -u bob

color=red

user=bob

同时传入命令行参数和环境变量,命令行参数的优先级较高:

$ user=admin color=green python3 use_chainmap.py -u bob

color=green

user=bob

Counter

Counter是一个简单的计数器,例如,统计字符出现的个数:

>>> from collections import Counter>>> c = Counter()>>> for ch in 'programming':...     c[ch] = c[ch] + 1

...>>> c

Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})>>> c.update('hello') # 也可以一次性update>>> c

Counter({'r': 2, 'o': 2, 'g': 2, 'm': 2, 'l': 2, 'p': 1, 'a': 1, 'i': 1, 'n': 1, 'h': 1, 'e': 1})

Counter实际上也是dict的一个子类,上面的结果可以看出每个字符出现的次数。

小结

collections模块提供了一些有用的集合类,可以根据需要选用。

​​​​​​​

argparse

在命令行程序中,经常需要获取命令行参数。Python内置的sys.argv保存了完整的参数列表,我们可以从中解析出需要的参数:

# copy.pyimport sys

print(sys.argv)

source = sys.argv[1]

target = sys.argv[2]# TODO...

运行上述copy.py,并传入参数,打印如下:

['copy.py', 'source.txt', 'copy.txt']

这种方式能应付简单的参数,但参数稍微复杂点,比如可以使用-d复制目录,使用--filename *.py过滤文件名等,解析起来就非常麻烦。

为了简化参数解析,我们可以使用内置的argparse库,定义好各个参数类型后,它能直接返回有效的参数。

假设我们想编写一个备份MySQL数据库的命令行程序,需要输入的参数如下:

  • host参数:表示MySQL主机名或IP,不输入则默认为localhost
  • port参数:表示MySQL的端口号,int类型,不输入则默认为3306
  • user参数:表示登录MySQL的用户名,必须输入;
  • password参数:表示登录MySQL的口令,必须输入;
  • gz参数:表示是否压缩备份文件,不输入则默认为False
  • outfile参数:表示备份文件保存在哪,必须输入。

其中,outfile是位置参数,而其他则是类似--user root这样的“关键字”参数。

argparse来解析参数,一个完整的示例如下:

# backup.py

import argparse

def main():

    # 定义一个ArgumentParser实例:

    parser = argparse.ArgumentParser(

        prog='backup', # 程序名

        description='Backup MySQL database.', # 描述

        epilog='Copyright(r), 2023' # 说明信息

    )

    # 定义位置参数:

    parser.add_argument('outfile')

    # 定义关键字参数:

    parser.add_argument('--host', default='localhost')

    # 此参数必须为int类型:

    parser.add_argument('--port', default='3306', type=int)

    # 允许用户输入简写的-u:

    parser.add_argument('-u', '--user', required=True)

    parser.add_argument('-p', '--password', required=True)

    parser.add_argument('--database', required=True)

    # gz参数不跟参数值,因此指定action='store_true',意思是出现-gz表示True:

    parser.add_argument('-gz', '--gzcompress', action='store_true', required=False, help='Compress backup files by gz.')





    # 解析参数:

    args = parser.parse_args()



    # 打印参数:

    print('parsed args:')

    print(f'outfile = {args.outfile}')

    print(f'host = {args.host}')

    print(f'port = {args.port}')

    print(f'user = {args.user}')

    print(f'password = {args.password}')

    print(f'database = {args.database}')

    print(f'gzcompress = {args.gzcompress}')

if __name__ == '__main__':

    main()

输入有效的参数,则程序能解析出所需的所有参数:

$ ./backup.py -u root -p hello --database testdb backup.sql

parsed args:

outfile = backup.sql

host = localhost

port = 3306

user = root

password = hello

database = testdb

gzcompress = False

缺少必要的参数,或者参数不对,将报告详细的错误信息:

$ ./backup.py --database testdb backup.sql

usage: backup [-h] [--host HOST] [--port PORT] -u USER -p PASSWORD --database DATABASE outfile

backup: error: the following arguments are required: -u/--user, -p/--password

更神奇的是,如果输入-h,则打印帮助信息:

$ ./backup.py -h                          

usage: backup [-h] [--host HOST] [--port PORT] -u USER -p PASSWORD --database DATABASE outfile



Backup MySQL database.



positional arguments:

  outfile



optional arguments:

  -h, --help            show this help message and exit

  --host HOST

  --port PORT

  -u USER, --user USER

  -p PASSWORD, --password PASSWORD

  --database DATABASE

  -gz, --gzcompress     Compress backup files by gz.



Copyright(r), 2023

获取有效参数的代码实际上是这一行:

args = parser.parse_args()

我们不必捕获异常,parse_args()非常方便的一点在于,如果参数有问题,则它打印出错误信息后,结束进程;如果参数是-h,则它打印帮助信息后,结束进程。只有当参数全部有效时,才会返回一个NameSpace对象,获取对应的参数就把参数名当作属性获取,非常方便。

可见,使用argparse后,解析参数的工作被大大简化了,我们可以专注于定义参数,然后直接获取到有效的参数输入。

小结

使用argparse解析参数,只需定义好参数类型,就可以获得有效的参数输入,能大大简化获取命令行参数的工作。

相关推荐

  1. python教程(5更新)

    2024-04-24 17:16:02       30 阅读
  2. python教程(3更新)

    2024-04-24 17:16:02       34 阅读
  3. python教程(4更新)

    2024-04-24 17:16:02       32 阅读
  4. python教程(6更新)

    2024-04-24 17:16:02       33 阅读
  5. 更新Python 学习资料列表

    2024-04-24 17:16:02       58 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-24 17:16:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-24 17:16:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-24 17:16:02       87 阅读
  4. Python语言-面向对象

    2024-04-24 17:16:02       96 阅读

热门阅读

  1. python

    2024-04-24 17:16:02       30 阅读
  2. LeetCode //C - 16. 3Sum Closest

    2024-04-24 17:16:02       31 阅读
  3. 异步线程与RabbitMQ应该如何选择?

    2024-04-24 17:16:02       32 阅读
  4. 2、Flink DataStreamAPI 概述(下)

    2024-04-24 17:16:02       26 阅读
  5. 4.5 海思SS928开发 - uboot开发 - 镜像验证

    2024-04-24 17:16:02       34 阅读
  6. 机器学习常用评价指标的公式和含义

    2024-04-24 17:16:02       31 阅读
  7. 解决MemoryError的一些方法

    2024-04-24 17:16:02       28 阅读
  8. 本地使用docker-compse搭建nacos集群

    2024-04-24 17:16:02       33 阅读
  9. OneFlow概念清单、以及优缺点

    2024-04-24 17:16:02       38 阅读
  10. JUC与多线程基础详解

    2024-04-24 17:16:02       33 阅读