页面导航:
英文教程的下载地址:
本篇文章是根据英文教程《Python Tutorial》来写的学习笔记。该英文教程的下载地址如下:
百度盘地址:
http://pan.baidu.com/s/1c0eXSQG
DropBox地址:
点此进入DropBox链接
Google Drive:
点此进入Google Drive链接
这是学习笔记,不是翻译,因此,内容上会与英文原著有些不同。以下记录是根据英文教程的第十三章来写的。(文章中的部分链接,包括下载链接,可能需要通过代理访问!)
本篇文章也会涉及到Python的C源代码,这些C源码都是2.7.8版本的。想使用gdb来调试python源代码的话,就需要按照前面
"Python基本的操作运算符"文章中所说的,使用configure
--with-pydebug命令来重新编译安装python。
如果Python的官方网站取消了Python-2.7.8.tgz的源码包的话,可以在以下两个链接中下载到:
DropBox:
Python-2.7.8.tgz的DropBox网盘链接
Google Drive:
Python-2.7.8.tgz的Google Drive网盘链接
文章里棕色的注释是作者额外添加的,在源码里并没有。本篇文章的例子主要是针对Linux系统下Python 2.7.8版本的。
calendar模块:
calendar是与日历相关的模块,该模块的源码定义在Python文件中:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar
<module 'calendar' from '/usr/local/lib/python2.7/calendar.pyc'>
>>> quit()
[email protected]:~$ ls /usr/local/lib/python2.7/calendar.py
/usr/local/lib/python2.7/calendar.py
[email protected]:~$
|
可以看到,在作者的系统里,calendar模块定义在
/usr/local/lib/python2.7/calendar.py文件里:
....................................................
class Calendar(object):
"""
Base calendar class. This class doesn't do any formatting. It simply
provides data to subclasses.
"""
................................................
class TextCalendar(Calendar):
"""
Subclass of Calendar that outputs a calendar as a simple plain text
similar to the UNIX program cal.
"""
................................................
class HTMLCalendar(Calendar):
"""
This calendar returns complete HTML pages.
"""
................................................
# Support for old module level interface
c = TextCalendar()
....................................................
monthcalendar = c.monthdayscalendar
prweek = c.prweek
week = c.formatweek
weekheader = c.formatweekheader
prmonth = c.prmonth
month = c.formatmonth
calendar = c.formatyear
prcal = c.pryear
....................................................
|
在该模块文件里定义了很多类型,主要有Calendar,
TextCalendar以及HTMLCalendar类型。其中,Calendar是
TextCalendar与HTMLCalendar的基类。
该模块文件还对外提供了很多方法,例如:
calendar,
month,
prcal,
prmonth之类的方法。这些方法其实就是
TextCalendar类里定义的方法,只不过有的改了些名字而已,例如:
calendar方法其实就是
TextCalendar类里的
formatyear方法等等。
下面会先对
TextCalendar里的这些方法进行介绍(该类主要用于生成普通的文本格式的日历信息)。
calendar方法:
calendar方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.calendar(2015)
' 2015\n\n January ...............'
>>> print calendar.calendar(2015)
2015
January February March
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 1 1
5 6 7 8 9 10 11 2 3 4 5 6 7 8 2 3 4 5 6 7 8
12 13 14 15 16 17 18 9 10 11 12 13 14 15 9 10 11 12 13 14 15
19 20 21 22 23 24 25 16 17 18 19 20 21 22 16 17 18 19 20 21 22
26 27 28 29 30 31 23 24 25 26 27 28 23 24 25 26 27 28 29
30 31
April May June
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 1 2 3 1 2 3 4 5 6 7
6 7 8 9 10 11 12 4 5 6 7 8 9 10 8 9 10 11 12 13 14
13 14 15 16 17 18 19 11 12 13 14 15 16 17 15 16 17 18 19 20 21
20 21 22 23 24 25 26 18 19 20 21 22 23 24 22 23 24 25 26 27 28
27 28 29 30 25 26 27 28 29 30 31 29 30
July August September
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 1 2 1 2 3 4 5 6
6 7 8 9 10 11 12 3 4 5 6 7 8 9 7 8 9 10 11 12 13
13 14 15 16 17 18 19 10 11 12 13 14 15 16 14 15 16 17 18 19 20
20 21 22 23 24 25 26 17 18 19 20 21 22 23 21 22 23 24 25 26 27
27 28 29 30 31 24 25 26 27 28 29 30 28 29 30
31
October November December
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 1 1 2 3 4 5 6
5 6 7 8 9 10 11 2 3 4 5 6 7 8 7 8 9 10 11 12 13
12 13 14 15 16 17 18 9 10 11 12 13 14 15 14 15 16 17 18 19 20
19 20 21 22 23 24 25 16 17 18 19 20 21 22 21 22 23 24 25 26 27
26 27 28 29 30 31 23 24 25 26 27 28 29 28 29 30 31
30
>>>
|
calendar方法本身返回的只是一段与日历相关的字符串信息,该字符串里包含了'
\n'之类的换行符。要让这段字符串能在终端里以较好的方式进行显示的话,可以使用print语句,如上面的print calendar.calendar(2015)语句,就可以将日历信息以较好的排版显示出来。
该方法对应的Python源码如下:
class TextCalendar(Calendar):
................................................
def formatyear(self, theyear, w=2, l=1, c=6, m=3):
"""
Returns a year's calendar as a multi-line string.
"""
w = max(2, w)
l = max(1, l)
c = max(2, c)
colwidth = (w + 1) * 7 - 1
v = []
a = v.append
a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
a('\n'*l)
header = self.formatweekheader(w)
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
# months in this row
months = range(m*i+1, min(m*(i+1)+1, 13))
a('\n'*l)
names = (self.formatmonthname(theyear, k, colwidth, False)
for k in months)
a(formatstring(names, colwidth, c).rstrip())
a('\n'*l)
headers = (header for k in months)
a(formatstring(headers, colwidth, c).rstrip())
a('\n'*l)
# max number of weeks for this row
height = max(len(cal) for cal in row)
for j in range(height):
weeks = []
for cal in row:
if j >= len(cal):
weeks.append('')
else:
weeks.append(self.formatweek(cal[j], w))
a(formatstring(weeks, colwidth, c).rstrip())
a('\n' * l)
return ''.join(v)
....................................................
c = TextCalendar()
....................................................
calendar = c.formatyear
|
可以看到,calendar方法本质上就是TextCalendar类里的
formatyear方法。
formatyear方法的第一个参数theyear表示需要返回哪一年的日历信息,第二个参数w用于控制每个星期的字符宽度,第三个参数l用于控制日历的行与行之间的换行数,第四个参数c用于控制月份之间的间隙宽度,最后一个参数m用于控制每排显示几个月。除了第一个参数外,取余的参数都是可选的,这些可选参数的具体含义,如下图所示:
图1
我们可以使用
pdb来调试分析TextCalendar类的
formatyear方法:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.calendar, 2015)
> /usr/local/lib/python2.7/calendar.py(338)formatyear()
-> w = max(2, w)
(Pdb) n
> ..................................................
-> l = max(1, l)
(Pdb) n
> ..................................................
-> c = max(2, c)
(Pdb) n
> ..................................................
-> colwidth = (w + 1) * 7 - 1
(Pdb) n
> ..................................................
-> v = []
(Pdb) n
> ..................................................
-> a = v.append
(Pdb) n
> ..................................................
-> a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
(Pdb) n
> ..................................................
-> a('\n'*l)
(Pdb) p v
[' 2015']
(Pdb) n
> ..................................................
-> header = self.formatweekheader(w)
(Pdb) n
> ..................................................
-> for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
(Pdb) p header
'Mo Tu We Th Fr Sa Su'
(Pdb) p self.yeardays2calendar(theyear, m)
[[[[(0, 0), (0, 1), (0, 2), (1, 3), (2, 4), (3, 5), (4, 6)],
[(5, 0), (6, 1), (7, 2), (8, 3), (9, 4), (10, 5), (11, 6)],
[(12, 0), (13, 1), (14, 2), (15, 3), (16, 4), (17, 5), (18, 6)],
[(19, 0), (20, 1), (21, 2), (22, 3), (23, 4), (24, 5), (25, 6)],
[(26, 0), (27, 1), (28, 2), (29, 3), (30, 4), (31, 5), (0, 6)]],
....................................................]]]]
(Pdb)
|
有关pdb调试的内容,可以参考
https://docs.python.org/2.7/library/pdb.html 的2.7.x版本的官方手册。
从上面的调试里可以看到,formatyear方法里的列表
v用于存储每一行的字符串信息,以及行与行之间的换行符。在此方法的最后,会将列表
v里的所有字符串都连接到一起来构成完整的字符串数据。
还可以看到,
self.formatweekheader(w)会根据宽度w来生成对应的星期名所组成的头部信息。例如当w为2时,得到的
header就会是
'Mo Tu We Th Fr Sa Su',当w为3时,得到的
header就会是
'Mon Tue Wed Thu Fri Sat Sun' 。
formatyear方法里面与日历相关的数据,都是由self.
yeardays2calendar(theyear, m)生成的。self.
yeardays2calendar返回的是一个列表,而这个结果列表中又包含了很多层别的列表,我们需要先理解这些层层包裹起来的列表的含义,才能去理解其他代码的作用。self.
yeardays2calendar所返回的列表的具体含义,如下图所示:
图2
在上图所示的一月份列表数据里,包含了很多(0,0),(1, 3),(2, 4)之类的元组。元组里的第一项表示一个月的第几天(如果为0则表示对应的元组无效,在日历的对应位置就会显示空白),元组里的第二项则表示星期几(0表示星期一,1表示星期二,2表示星期三,...... 6表示星期天)。
例如:一月份列表里的(1, 3)这个元组就表示1月1日,星期四。一月份里的(2, 4)则表示1月2日,星期五。
有了self.yeardays2calendar返回的列表数据,formatyear方法里的代码就可以根据上图所示的列表结构,来生成对应的日历字符串信息了。限于篇幅,请读者自行通过pdb调试器,配合上图的列表结构来分析formatyear方法里没介绍到的代码。
prcal方法:
prcal方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.prcal(2015)
2015
January February March
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 1 1
5 6 7 8 9 10 11 2 3 4 5 6 7 8 2 3 4 5 6 7 8
12 13 14 15 16 17 18 9 10 11 12 13 14 15 9 10 11 12 13 14 15
19 20 21 22 23 24 25 16 17 18 19 20 21 22 16 17 18 19 20 21 22
26 27 28 29 30 31 23 24 25 26 27 28 23 24 25 26 27 28 29
30 31
April May June
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 1 2 3 1 2 3 4 5 6 7
6 7 8 9 10 11 12 4 5 6 7 8 9 10 8 9 10 11 12 13 14
13 14 15 16 17 18 19 11 12 13 14 15 16 17 15 16 17 18 19 20 21
20 21 22 23 24 25 26 18 19 20 21 22 23 24 22 23 24 25 26 27 28
27 28 29 30 25 26 27 28 29 30 31 29 30
July August September
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 1 2 1 2 3 4 5 6
6 7 8 9 10 11 12 3 4 5 6 7 8 9 7 8 9 10 11 12 13
13 14 15 16 17 18 19 10 11 12 13 14 15 16 14 15 16 17 18 19 20
20 21 22 23 24 25 26 17 18 19 20 21 22 23 21 22 23 24 25 26 27
27 28 29 30 31 24 25 26 27 28 29 30 28 29 30
31
October November December
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 1 1 2 3 4 5 6
5 6 7 8 9 10 11 2 3 4 5 6 7 8 7 8 9 10 11 12 13
12 13 14 15 16 17 18 9 10 11 12 13 14 15 14 15 16 17 18 19 20
19 20 21 22 23 24 25 16 17 18 19 20 21 22 21 22 23 24 25 26 27
26 27 28 29 30 31 23 24 25 26 27 28 29 28 29 30 31
30
>>>
|
可以看到,上面的calendar.prcal(2015)其实就等效于print calendar.calendar(2015)语句。我们还可以从prcal方法的Python源码里看到这一点(下面的代码也定义在
/usr/local/lib/python2.7/calendar.py文件里):
class TextCalendar(Calendar):
................................................
def pryear(self, theyear, w=0, l=0, c=6, m=3):
"""Print a year's calendar."""
print self.formatyear(theyear, w, l, c, m)
....................................................
c = TextCalendar()
....................................................
prcal = c.pryear
|
从上面的代码里可以看到,calendar模块的
prcal方法,本质上就是TextCalendar类的
pryear方法。该方法会直接通过
print语句将
formatyear返回的指定年份的日历字符串信息给显示出来。
pryear也有5个参数,它会将这些参数直接传递给
formatyear,至于这些参数的具体含义,在之前介绍
formatyear方法时已经详细的讲解过了,读者可以参考前面的
图1。
month与prmonth方法:
这两个方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.month(2015, 9)
' September 2015\nMo Tu We Th Fr Sa Su\n .........'
>>> print calendar.month(2015, 9)
September 2015
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
>>> calendar.prmonth(2015, 9)
September 2015
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
>>>
|
可以看到:上面的calendar.prmonth(2015, 9)等效于print calendar.month(2015, 9) 。其中,calendar.month方法可以将指定的月份的日历信息,以字符串的形式作为结果返回。而calendar.prmonth方法则会通过print语句将month方法所返回的字符串,以较好的排版在终端里显示出来。
这两个方法对应的Python源代码如下(都定义在
/usr/local/lib/python2.7/calendar.py文件里):
class TextCalendar(Calendar):
................................................
def prmonth(self, theyear, themonth, w=0, l=0):
"""
Print a month's calendar.
"""
print self.formatmonth(theyear, themonth, w, l),
def formatmonth(self, theyear, themonth, w=0, l=0):
"""
Return a month's calendar string (multi-line).
"""
w = max(2, w)
l = max(1, l)
s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
s = s.rstrip()
s += '\n' * l
s += self.formatweekheader(w).rstrip()
s += '\n' * l
for week in self.monthdays2calendar(theyear, themonth):
s += self.formatweek(week, w).rstrip()
s += '\n' * l
return s
....................................................
c = TextCalendar()
....................................................
prmonth = c.prmonth
month = c.formatmonth
....................................................
|
从上面的代码里可以看到,calendar模块的
month方法本质上就是TextCalendar类的
formatmonth方法,而calendar模块的
prmonth方法对应就是TextCalendar类的
prmonth方法。
prmonth与
formatmonth方法具有相同的参数,
prmonth会直接将参数传递给
formatmonth,再将
formatmonth返回的日历字符串通过
print语句显示出来。这两个方法都可以接受四个参数:第一个参数theyear用于指定年份。第二个参数themonth用于指定月份。最后两个参数w与l是可选,w与l的含义可以参考前面的
图1。
我们可以通过pdb调试器来对
formatmonth方法进行调试分析:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.month, 2015, 9)
> /usr/local/lib/python2.7/calendar.py(322)formatmonth()
-> w = max(2, w)
(Pdb) n
> ..................................................
-> l = max(1, l)
(Pdb) n
> ..................................................
-> s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
(Pdb) n
> ..................................................
-> s = s.rstrip()
(Pdb) p s
' September 2015 '
(Pdb) n
> ..................................................
-> s += '\n' * l
(Pdb) n
> ..................................................
-> s += self.formatweekheader(w).rstrip()
(Pdb) p self.formatweekheader(w)
'Mo Tu We Th Fr Sa Su'
(Pdb) n
> ..................................................
-> s += '\n' * l
(Pdb) n
> ..................................................
-> for week in self.monthdays2calendar(theyear, themonth):
(Pdb) p self.monthdays2calendar(theyear, themonth)
[[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)],
[(7, 0), (8, 1), (9, 2), (10, 3), (11, 4), (12, 5), (13, 6)],
[(14, 0), (15, 1), (16, 2), (17, 3), (18, 4), (19, 5), (20, 6)],
[(21, 0), (22, 1), (23, 2), (24, 3), (25, 4), (26, 5), (27, 6)],
[(28, 0), (29, 1), (30, 2), (0, 3), (0, 4), (0, 5), (0, 6)]]
(Pdb) c
' September 2015\nMo Tu We Th Fr Sa Su\n .........'
>>>
|
可以看到,
formatmonth方法里的self.
formatmonthname会返回日历头部的年月信息,例如:
' September 2015 '的字符串。self.
formatweekheader则会返回星期名所组成的头部结构,例如:
'Mo Tu We Th Fr Sa Su'的字符串。
最后,self.
monthdays2calendar会返回一个包含了指定月份的所有日期数据的列表,该列表的数据结构,可以参考前面
图2里的一月份的列表数据,以及该列表数据的相关说明。
firstweekday与setfirstweekday方法:
这两个方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.firstweekday()
0
>>> calendar.prmonth(2015, 9)
September 2015
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
>>> calendar.setfirstweekday(1)
>>> calendar.firstweekday()
1
>>> calendar.prmonth(2015, 9)
September 2015
Tu We Th Fr Sa Su Mo
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
>>> calendar.setfirstweekday(2)
>>> calendar.firstweekday()
2
>>> calendar.prmonth(2015, 9)
September 2015
We Th Fr Sa Su Mo Tu
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
>>>
|
从上例中可以看到,firstweekday方法返回的值,用于控制在日历里哪个星期排在最前面,
0表示星期一(
Monday),
1表示星期二(
Tuesday),
2表示星期三(
Wednesday),以此类推。
要设置firstweekday方法所返回的值,就可以使用setfirstweekday方法。例如:setfirstweekday(
1)执行后,firstweekday就会返回
1。setfirstweekday(
2)执行后,firstweekday就会返回
2,等等。
如果没使用setfirstweekday方法进行过设置的话,firstweekday方法返回的缺省值会是0 。
这两个方法相关的Python源代码如下(都定义在
/usr/local/lib/python2.7/calendar.py文件里):
# Constants for weekdays
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
....................................................
class Calendar(object):
"""
Base calendar class. This class doesn't do any formatting. It simply
provides data to subclasses.
"""
def __init__(self, firstweekday=0):
self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
def getfirstweekday(self):
return self._firstweekday % 7
def setfirstweekday(self, firstweekday):
self._firstweekday = firstweekday
firstweekday = property(getfirstweekday, setfirstweekday)
................................................
class TextCalendar(Calendar):
"""
Subclass of Calendar that outputs a calendar as a simple plain text
similar to the UNIX program cal.
"""
................................................
# Support for old module level interface
c = TextCalendar()
firstweekday = c.getfirstweekday
def setfirstweekday(firstweekday):
try:
firstweekday.__index__
except AttributeError:
raise IllegalWeekdayError(firstweekday)
if not MONDAY <= firstweekday <= SUNDAY:
raise IllegalWeekdayError(firstweekday)
c.firstweekday = firstweekday
|
TextCalendar作为Calendar的子类,它继承了Calendar里的所有的属性,方法和成员。
在Calendar类里有一个
_firstweekday成员,该成员的值决定了日历中哪个星期需要排在最前面。
要访问
_firstweekday成员的值,可以使用Calendar类里的getfirstweekday方法,此方法会将_firstweekday成员的值与7进行取余运算,取余的结果作为返回值,这是因为_firstweekday的有效值是0到6的整数(0表示星期一,6表示星期天)。
在Calendar类中,还有一个firstweekday的
property(属性)。当对该属性进行读操作时,会自动调用getfirstweekday方法去读取_firstweekday成员的值。当对该属性进行设置操作时,则会自动调用setfirstweekday方法去设置_firstweekday成员的值。
从Calendar的__init__初始化函数里可以看到,firstweekday的默认初始值是
0,因此,缺省情况下,星期一会排在日历的最前面。
从上面的代码里还可以看到:calendar模块的firstweekday方法,本质上就是TextCalendar的基类即Calendar类里的getfirstweekday方法。
而calendar模块的setfirstweekday方法会先对参数进行检测,如果参数是0到6的整数的话,则将参数设置到Calendar类的firstweekday属性里,该属性会自动调用setfirstweekday方法将参数的值设置到_firstweekday成员里。
以下是pdb调试器的输出情况:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.firstweekday)
> /usr/local/lib/python2.7/calendar.py(136)getfirstweekday()
-> return self._firstweekday % 7
(Pdb) c
2
>>> pdb.runcall(calendar.setfirstweekday, 3)
> /usr/local/lib/python2.7/calendar.py(572)setfirstweekday()
-> try:
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(573)setfirstweekday()
-> firstweekday.__index__
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(576)setfirstweekday()
-> if not MONDAY <= firstweekday <= SUNDAY:
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(578)setfirstweekday()
-> c.firstweekday = firstweekday
(Pdb) s
--Call--
> /usr/local/lib/python2.7/calendar.py(138)setfirstweekday()
-> def setfirstweekday(self, firstweekday):
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(139)setfirstweekday()
-> self._firstweekday = firstweekday
(Pdb) c
>>>
|
可以看到,pdb调试器的输出结果与我们之前的分析是一致的:calendar.
firstweekday最终会通过Calendar类里的
getfirstweekday方法,去读取
_firstweekday成员的值。而calendar.
setfirstweekday则最终会通过Calendar类里的
setfirstweekday方法,去设置
_firstweekday成员的值。
isleap与leapdays方法:
这两个方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.isleap(2000)
True
>>> calendar.isleap(2004)
True
>>> calendar.isleap(2008)
True
>>> calendar.isleap(2012)
True
>>> calendar.isleap(2007)
False
>>> calendar.isleap(2500)
False
>>> calendar.leapdays(2000, 2016)
4
>>>
|
从上例里可以看到,isleap方法用于判断指定的年份是否是闰年,如果是闰年则返回True,否则返回False。因此,上面的2000、2004、2008及2012都是闰年,而2007与2500都不是闰年。
leapdays方法则可以计算出两个指定的年份之间,一共存在多少个闰年。例如上面的calendar.leapdays(2000, 2016)执行后返回4,就说明2000年到2016年(不包括2016在内)之间一共包含4个闰年,分别是2000、2004、2008以及2012年。
isleap与leapdays方法相关的Python源代码如下(也都定义在
/usr/local/lib/python2.7/calendar.py文件里):
def isleap(year):
"""Return True for leap years, False for non-leap years."""
# 如果某年份能被4整除,同时不能被100整除时,
# 那么该年份就是闰年,
# 例如:2004,2008以及2012年都是闰年。
# 如果某年份能被4整除,同时还能被400整除的话,
# 那么该年份也是闰年,例如:2000年也是闰年。
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
def leapdays(y1, y2):
"""Return number of leap years in range [y1, y2).
Assume y1 <= y2."""
y1 -= 1
y2 -= 1
# (y2//4 - y1//4)可以计算出y1与y2之间
# 一共有多少个数能被4整除,
# (y2//100 - y1//100)可以计算出y1与y2之间
# 一共有多少个数能被100整除,
# (y2//400 - y1//400)可以计算出y1与y2之间
# 一共有多少个数能被400整除,
# 因此,下面的计算公式就可以计算出
# y1到y2之间一共有多少个数
# 能被4整除,同时不能被100整除(或者能被400整除)。
# 也就计算出y1与y2之间
# 一共包含了多少个闰年了。
return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
|
timegm方法:
timegm方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> import calendar
>>> t = time.time()
>>> t
1442020787.238788
>>> tm = time.gmtime(t)
>>> tm
time.struct_time(tm_year=2015, tm_mon=9, tm_mday=12, tm_hour=1, tm_min=19, tm_sec=47, tm_wday=5, tm_yday=255, tm_isdst=0)
>>> calendar.timegm(tm)
1442020787
>>>
|
可以看到,time模块的gmtime方法可以将时间戳转为UTC标准时区的时间(该时间存储在time.struct_time类型的对象里)。而calendar模块的timegm方法则可以反过来,将时间转为UTC标准时区的时间戳。有关时间戳以及time模块相关的内容,请参考上一篇文章。
calendar模块的timegm方法对应的Python源代码如下(定义在
/usr/local/lib/python2.7/calendar.py文件里):
EPOCH = 1970
_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
def timegm(tuple):
"""Unrelated but handy function to calculate Unix timestamp from GMT."""
year, month, day, hour, minute, second = tuple[:6]
days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
hours = days*24 + hour
minutes = hours*60 + minute
seconds = minutes*60 + second
return seconds
|
上面的datetime.date(year, month, 1).toordinal()方法可以将年月日转换为以001年1月1日为起点的天数,例如:datetime.date(1,1,1).toordinal()会返回1表示第一天(以001年1月1日为起点),datetime.date(1,1,2).toordinal()则会返回2表示第二天,以此类推。
因此,上面的datetime.date(year, month, 1).toordinal()减去_EPOCH_ORD后,再加上day,并减去1,得到的结果就是year年month月day日距离1970年1月1日的天数。
在得到days天数后,将days乘以24(一天有24小时),再加上hour就可以得到对应的小时数。最后将小时转为分,分再转为秒,就可以得到UTC标准时区的时间戳了。
以下是pdb调试器的结果:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.timegm, (2015, 9, 12, 1, 19, 47))
> /usr/local/lib/python2.7/calendar.py(612)timegm()
-> year, month, day, hour, minute, second = tuple[:6]
(Pdb) n
> ..................................................
-> days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
(Pdb) n
> ..................................................
-> hours = days*24 + hour
(Pdb) p days
16690
(Pdb) n
> ..................................................
-> minutes = hours*60 + minute
(Pdb) p hours
400561
(Pdb) n
> ..................................................
-> seconds = minutes*60 + second
(Pdb) n
> ..................................................
-> return seconds
(Pdb) p seconds
1442020787
(Pdb) c
1442020787
>>>
|
datetime模块的C源码定义在
Modules/datetimemodule.c文件里,该模块的date类的toordinal方法对应的底层C函数为
date_toordinal:
[email protected]:~$ gdb -q python
....................................................
>>> import datetime
>>> datetime.date(2015,9,12).toordinal()
....................................................
Breakpoint 1, date_toordinal (self=0xb7ca0538)
at /mnt/zenglOX/Python-2.7.8/Modules/datetimemodule.c:2680
2680 GET_DAY(self)));
(gdb) s
2679 return PyInt_FromLong(ymd_to_ord(GET_YEAR(self), GET_MONTH(self),
(gdb) s
ymd_to_ord (year=2015, month=9, day=12)
at /mnt/zenglOX/Python-2.7.8/Modules/datetimemodule.c:330
330 return days_before_year(year) + days_before_month(year, month) + day;
(gdb) c
Continuing.
735853
>>>
|
限于篇幅,请读者自行通过gdb调试器对date_toordinal相关的C源码进行分析。
weekday与monthrange方法:
这两个方法的使用,如下所示:
[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46)
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.weekday(2015, 9, 12)
5
>>> calendar.monthrange(2015, 9)
(1, 30)
>>>
|
weekday方法所返回的值,用来表示指定的日期是星期几,0表示星期一,6表示星期天。例如上面的calendar.weekday(2015, 9, 12)返回的结果为5,就表示2015年9月12日是星期六。
monthrange方法则会返回一个元组,该元组的第一项表示指定月份的第一天是星期几(0表示星期一,6表示星期天),第二项则表示指定的月份有多少天。例如上面的calendar.monthrange(2015, 9)返回的结果为(1, 30),就表示2015年9月1日是星期二,并且2015年的9月份一共有30天。
这两个方法对应的Python源代码如下(都定义在
/usr/local/lib/python2.7/calendar.py文件里):
# Constants for months referenced later
January = 1
February = 2
# Number of days per month (except for February in leap years)
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
....................................................
def weekday(year, month, day):
"""Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
day (1-31)."""
return datetime.date(year, month, day).weekday()
def monthrange(year, month):
"""Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
year, month."""
if not 1 <= month <= 12:
raise IllegalMonthError(month)
day1 = weekday(year, month, 1)
ndays = mdays[month] + (month == February and isleap(year))
return day1, ndays
|
从上面的代码里可以看到,monthrange会先通过weekday方法得到指定月份的第一天是星期几,并将该星期作为结果元组的第一项。接着,monthrange方法会从mdays数组里得到指定的月份一共包含了多少天,并将其作为结果元组的第二项。当然,如果是闰年的二月份,则mdays里对应的值28,还需要再加上一天,即29天。
weekday方法则会通过datetime模块里的date类中的weekday方法去完成具体的操作。date类的weekday方法会调用的底层C函数为
date_weekday(该C函数定义在
Modules/datetimemodule.c文件里):
[email protected]:~$ gdb -q python
....................................................
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.weekday, 2015, 9, 12)
> /usr/local/lib/python2.7/calendar.py(113)weekday()
-> return datetime.date(year, month, day).weekday()
(Pdb) s
....................................................
Breakpoint 1, date_weekday (self=0xb7b715f8)
at /mnt/zenglOX/Python-2.7.8/Modules/datetimemodule.c:2686
2686 int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self));
(gdb) n
2688 return PyInt_FromLong(dow);
(gdb) p dow
$1 = 5
(gdb) c
Continuing.
--Return--
> /usr/local/lib/python2.7/calendar.py(113)weekday()->5
-> return datetime.date(year, month, day).weekday()
(Pdb) c
5
>>>
|
限于篇幅,这里不会对date_weekday相关的C源码进行介绍,请读者自行通过gdb调试器来进行分析。
结束语:
以上就是和calendar模块相关的内容,该模块里没介绍到的代码,请读者自行通过pdb调试器与gdb调试器来进行分析。
人的生存法则很简单,就是忍人所不忍,能人所不能。忍是一条线,能是一条线,两者的间距就是生存机会。
—— 天道