2007-08-06

 

Python Cookbook 3.0 Python时间和货币概述

概述:

今天,上周末,明年.这些名词听起来很平常.你可能曾经感觉到奇怪,也许仅仅一次,关于时间是如何深刻的影响着我们的生活.时间的概念围绕着我们,它也大量的存在于软件工程中.任何一个简单的程序都可能要处理时间戳,延迟,超时,速度测量,日历等等.如果一个语言拥有"Batteries Included"(便携电池)特性,将会非常方便.Python的标准库为这些应用程序提供了稳定的支持 ,也可以从第三方包中获得更多的支持.
在计算机处理中,货币问题是另外一个比较有趣的话题,因为它和我们的生活息息相关.Python2.4后引入了对带小数点数的支持,使得Python成为要处理小数点的一个不错的选择,而且它避免使用浮点数.
本章主要讨论这两个方面,时间和货币.对于传统的说法,其实文章只介绍了一个问题,因为大家都知道:"时间就是金钱!"

时间模块:

Python标准库的time模块使得Python应用程序能很方便的使用Python所运行平台上的时间函数.你的系统文档中的C语言库文档也是很有用的 ,其它一些平台也会对Python有用.
time模块中最常用的函数就是获得当前时间:time.time.这个函数的返回值有些隐晦难懂:它是一个浮点数,表示从一个叫做epoch的固定时间到当前时间的秒数.那个时间可能因为系统的不同而不同,不过大多数系统都是1970年1月1号的午夜.
要判断你的系统使用什么epoch,使用下面的代码:

>>> import time
>>> print time.asctime(time.gmtime(0))

请注意我们传递了参数0给time.gmtime函数,表示从epoch后过0秒,time.gmtime将任何时间戳(从epoch到现在的秒数)转换为一个元组,该元组用人们可以理解的方式表示时间,而且不做时区转换(GMT表示格林威治时间,UTC表示国际标准时间).你也可以传递一个时间戳给time.localtime,它将使用本地的时区来转换时间.
需要主意二者的区别,如果你的时间戳已经是对应于本地时区了,, 那么再把它传递给time.localtime将不会返回期望的值,除非你的时区恰好和UTC一致.
下面提供一个分解时间元组的方法:

year, month, mday, hour, minute, second, wday, yday = time.localtime( )

这种方法虽然有效,但是不提倡使用,因为它很不美观.这样的写法可以完全摒弃 ,因为你可以使用跟可读的字段来获取元素的值,比如获取当前月的信息:

time.localtime( ).tm_mon

请注意我们调用localtime的时候并没有传递任何参数,当我们使用localtime,gmtime或asctime的时候,如果不传递任何参数,它们会默认使用当前时间.
在time模块中比较有用的两个方法是strtime和strptime,前者能从一个时间元组获得字符串, 而后者通过一个字符串获得一个时间元组.两个方法都接收格式化字符串为参数,从而获得你需要的字符串结果.关于传递的格式化字符串的具体信息,可以参见http://docs.python.org/lib/module-time.html.
在time模块中最后一个比较重要的方法是time.sleep函数,它可以让Python程序延迟.尽管函数POSIX计数器部分只接受整数为参数 ,Python支持了小数并可以实现半秒,如:

for i in range(10):
    time.sleep(0.5)
    print "Tick!"

这段代码将执行5秒钟,每秒输出2个Tick.

时间和日期对象:

尽管time模块十分强大,Python标准库还提供了datetime模块,它里面提供的类型更好的抽象了日期和时间的概念,它的类型包括:time,date, datetime.它们构造的方法相同:

  today = datetime.date.today( )
  birthday = datetime.date (1977, 5, 4)      #May 4
  currenttime = datetime.datetime.now( ).time( )
  lunchtime = datetime.time(12, 00)
  now = datetime.datetime.now( )
  epoch = datetime.datetime(1970, 1, 1)
  meeting = datetime.datetime(2005, 8, 3, 15, 30)

同时,这些对象也提供了很多方便的方法来访问它的属性.下面的语句创建了date类型的实例 ,表示今天,然后获取明天的今天,最后以点分格式输出:

  today = datetime.date.today( )
  next_year = today.replace(year=today.year+1 ).strftime("%Y.%m.%d")
  print next_year

注意是怎样增加一年的,使用了replace方法,直接给time实例复制是很方便的,而它的属性确实不可修改的(这样的好处是,你可以把属性看成字典来访问),所以要创建新实例next_year.

>>> import datetime
>>> NewYearsDay = datetime.date(2005, 01, 01)
>>> NewYearsEve = datetime.date(2004, 12, 31)
>>> oneday = NewYearsDay - NewYearsEve
>>> print oneday
1 day, 0:00:00

一个timedelta实例在内部是使用天,秒和毫秒来表示的,但你可以通过传递任意形式的时间参数来构造timedelta,比如分钟,小时, 星期等.别的形式的时间差默认是不支持的,因为容易它的操作和结果可能产生歧义(有第三方包支持这些特征,如dateutil).
datetime可以称为小心谨慎的设计,它不会实现不明确的任务,或者和平台依赖性太强的任务,和别的模块的实现思路一样.
该模块另一个设计的特点是时区的支持.尽管datetime提供了查询和设置时区的功能,没有外界类继承tzinfo类的话还是用处不大.有两个第三方扩展提供了对时区的支持,如dateutil和pyTZ.

十进制数:

decimal是Python标准库的一部分,自Python2.4后添加入的.它将十进制算数引入了Python.有了decimal,我们现在有了十进制小数类型,它同时保证了精度和浮点数的特征.让我们先来介绍三个概念:

十进制小数类型(decimal numeric)
      数字不是用二进制来存储的,是用十进制数字存储的.

定点小数
      数字的小数位数是固定的.

浮点小数
      小数点没有固定.(换一个说法:数字的总位数是固定的,但小数点后面的位数是不固定的)

这样的数据类型有很多用处(最大的用处就是计算货币),尤其是decimal.Decimal提供了比float更多的更高级的方法,最主要的好处是用户输入的数字能完整的保留精度(也就是数,使用二进制保存的浮点数不能保证有些数字的精度):

>>> import decimal
>>> 1.1
1.1000000000000001
>>> 2.3
2.2999999999999998
>>> decimal.Decimal(" 1.1")
Decimal("1.1")
>>> decimal.Decimal(" 2.3")
Decimal("2.3")

在算术中能保持准确性.以浮点数为例:

>>> 0.1 + 0.1 + 0.1 - 0.3
5.5511151231257827e-17

上面的差值很接近于0,但它们不能通过等于测试,如:

>>> 0.1+0.1+0.1-0.3==0.0
False

而且这些偏差会积累下去,因此,在需要保持准确性的场合,应该使用decimal:

>>> d1 = decimal.Decimal(" 0.1")
>>> d3 = decimal.Decimal("0.3")
>>> d1 + d1 + d1 - d3
Decimal("0.0")

decimal.Decimal实例可以从整数,字符串或者元组构造.要从浮点数构造decimal,首先应该把浮点数转换为字符串.Decimal数字包含很多特殊数字,比如NaN(表示非数字),正的和负的无穷大, 还有-0.一旦构造好后,decimal数字是不能修改的,和Python中别的数字一样.
decimal模块实现了一些学校里面教过的基本运算法则.为了保证精度,小数位数尽可能的给出最多.

>>> 0.9 / 10
0.089999999999999997
>>> decimal.Decimal("0.9") / decimal.Decimal(10)
Decimal("0.09")

当小数位数超过允许的精度后,它使用当前的保留方式来保留小数.有很多保留方式,默认的是round-half-even.
decimal模块采用保留位数0的方式,比如:1.30+1.20=2.50.这在一些金融应用程序中是有用的,对于乘法,也保留了位数0:

>>> decimal.Decimal("1.3") * decimal.Decimal("1.2")
Decimal("1.56")
>>> decimal.Decimal("1.30") * decimal.Decimal("1.20")
Decimal("1.5600")

decimal除了有内建数字类型的通用方法外,它还有自己特有的方法,你可以自己查看帮助.
decimal依赖于上下文环境工作,也就是说,你可以设置它的工作方式(集合).每一个线程也有自己的上下文(也就是说,你可以改变一个线程,而不影响其它的);修改线程上下文的方式就是使用decimal模块的getcontext和setcontext函数.
和由机器决定精度的浮点数不同,decimal可以有用户设置精度(默认是28为),它可以被设置到需要的大小:

>>> decimal.getcontext( ).prec = 6            # set the precision to 6...
>>> decimal.Decimal (1) / decimal.Decimal(7)
Decimal("0.142857")
>>> decimal.getcontext( ).prec = 60           # ...and to 60 digits
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal("0.142857142857142857142857142857142857142857142857142857142857")

当然,并不是所有的decimal函数都像前面举例的那样简单.基本上,decimal实现了十进制数标准,你可以参考 http://www2.hursley.ibm.com/decimal/. 在实际中,decimal实现了信号,信号表示一些不寻常的计算结果(比如:1/0,0/0,无穷大/无穷大等).如果你的程序不需要信号,它可以被忽略或者做为信息,甚至异常.每一个信号,都有一个标志和陷阱.当信号出现的时候,标志加一,陷阱被设置为1,一个异常就抛出了.这给了程序员很大的方便来使用decimal.
既然decimal有这么多的好处,为什么还有人坚持使用float?的确,有什么原因让Python把float做为(也是唯一)非整数的数据类型?当然有很多原因可以列出来,但最关键的就是速度,比如:

$ python -mtimeit -s'from decimal import Decimal as D' 'D("1.2")+D("3.4")'
10000 loops, best of 3: 191 usec per loop
$ python -mtimeit -s'from decimal import Decimal as D' '1.2+3.4'
1000000 loops, best of 3: 0.339 usec per loop

简单的说:在这台计算机上(Athlon 1.2GHz,Linux),Python每秒能运行3百万次float加法,但是只能运行5000多次Decimal加法.
基本上,如果你的程序要计算成千上万的非整数,你最好坚持使用float!因为...(一系列原因),所以还是使用浮点数吧.
当然,并不是所有的程序都要进行大量的非整数运算,所以我们还是可以方便的使用decimal的.


相关说明:

Batteries Included:

Plone对Batteries Included的注解是:

Supports Linux, Windows, Mac OS X, FreeBSD, Solaris. The installers get
you up and running within minutes. No complex set-up procedures.

round-half-even

如果舍弃部分左边的数字为奇数,则作   ROUND_HALF_UP   ;如果它为偶数,则作   ROUND_HALF_DOWN

标签:


Comments: 发表评论



<< Home

This page is powered by Blogger. Isn't yours?