教你如何开发XBMC(KODI)插件,怎么入门及个人能的Python基础和实用技巧。
原文作者: wurenji
原文链接:https://www.hao4k.cn/thread-23835-1-9.html
Python是目前xbmc插件开发使用的唯一编程语言,要是想自己做个插件玩玩,Python这玩意儿多少要会点。python老鸟可以直接无视本文。
本文不作为完整的python学习教程,但可以帮助你更快入门和掌握要点。如果能在您日后的开发过程中起到一丁点作用,那将是我的荣幸!
话说磨刀不误砍柴工。下面几楼分块跟大家分享一下我的python学习使用心得
(一)环境搭建
为了更好的学习Python,建议搭建一些软件环境来提高学习开发效率。
Python是一门开源的程序设计语言,是一种可交互执行的解释性脚本语言,非常适合简单应用和插件开发。如果只是编写xbmc插件,并且在xbmc里面进行调试的话,可以不用下载安装python软件包。在官方下载地址上有多个版本下载,因为XBMC内置的解释器是基于python2的,所以请大家下载2.7.3版本进行安装。特别注意,python 3在很多地方与python 2是不兼容的。
DreamPie是一个很好的Python Shell,我经常拿它来进行交互开发,或者作为高级计算器。Python有个优势,不像其他编译性语言一样,一定要等到程序写完,编译通过以后才能运行得到结果。而Python却可以在Python Shell中单独允许一条语句。比如说,你不知道字符串去空格的函数是不是strip了,那么很简单,直接到pytho shell中跑一句 ' abc '.strip() 看得到的结果是不是'a'就知道了,不对再去查文档也不迟。很多时候,一个很大的py文件里面某地方出错了,你完全可以把其中某一些语句挑出来单独在python shell里面去跑,省去反复不停的执行整个文件,而且减少依赖。
我是用的最多的Python开发环境是Ulipad,因为本身是python写的,跨平台,windows/linux/mac os都可以用。另外一款PyScripter是针对windows平台开发的优秀IDE。两者都具有语法高亮、自动缩进、类型浏览等多种特性,可以提高代码编制效率。当然不嫌麻烦的话,也可以使用记事本来写代码。
(二)变量
Python的变量是没有类型的,可以赋任意类型的值。变量不需单独定义,一旦赋值即可使用。print函数可以输出变量内容。
>>> url = 'http://www.baidu.com'
>>> page = 4
>>> print url
http://www.baidu.com
>>> print page
4
>>> page += 5
>>> url += '?page=' + str(page)
>>> print url
http://www.baidu.com?page=9
上面的示例都是在python shell中交互运行的结果,以>>>开头的是输入的语句,其他的部分是显示结果。可以看到不需要像有些语言用var声明变量,也不需要int/string/char []*之类的类型定义符来进行定义。很简单,你想用的时候,直接赋值就行了,然后就可以对变量进行任意操作,比如用+=进行自加/连接,作为print函数的参数来输出变量的值。
>>> a = 123
>>> print a
123
>>> a = 1.24
>>> print a
1.24
>>> a = "I'm a fine"
>>> print a
I'm a fine
>>> a = (1, 2.4, "hello", {'a': 0, 'b': 1})
>>> print a
(1, 2.4, 'hello', {'a': 0, 'b': 1})
在上面的示例中,可以看到同一个变量可以赋不同的值。最后一个看起来稍微复杂点,将在后面的数据类型中一一讲到。
(三)数字
Python中的数字分整数和浮点数。python的整数有int和long,但是我们使用的时候不用管他。python中整数长度是没有限制的,这和别的很多语言不同,也就是说可以轻松的在python中完成大整数的运算
>>> 2**30
186: 1073741824
>>> 9**99
187: 29512665430652752148753480226197736314359272517043832886063884637676943433478020332709411004889L
>>> 2358321783728157823*23594389258432 + 29512665430652752148753480226197736314359272517043832886063884637676943433478020332709411004889
210: 29512665430652752148753480226197736314359272517043832886063884693320105595399861474399840518425L
>>> (1+2)*(39-13)
复制代码
在Python中,long类型的数值会在最后加上一个大写的L,但是你在输入的时候完全可以不用写。这是内部的类型转换,是无需关注和进行显示转换的。整数和浮点数进行运算时,会自动将整数转化为浮点后进行运算,得到浮点数结果。用int函数将浮点数转换为整数时,不进行四舍五入,而是简单的抛弃小数部分,这点需要注意。我们可以用int(x+0.5)的方式来进行四舍五入,当然也可以直接使用round()函数。
还有一点需要注意的是,/在python 2.x中,对于整数而言跟//的用法是相同的,都是取整数部分,这点很容易被忽略。%符号是用于取余数的运算符。divmod可以同时获得商和余数。
>>> 5/3
221: 1
>>> 5%3
222: 2
>>> 5/3.0
223: 1.6666666666666667
>>> 5//3
224: 1
>>> 5//3.0
225: 1.0
>>> divmod(5, 3)
226: (1, 2)
对于变量加1,没有像c语言里面那样的x++,一般用x=x+1,或者简化为x+=1。
(四)字符串
字符串是我们在程序中使用最多的类型。Python中没有字符类型,只有字符串。字符串可以用单引号或者双引号包围起来。可以用\符号在行的末尾进行换行,这是一种语法形式的换行,在很多地方使用,比如这行代码太长了。\换行的字符串实际并不包括换行符。
236: 'bbb'
>>> print 'hello'
hello
>>> print "hello"
hello
>>> 'hello' == "hello"
237: True
>>> print 'hello \
... world'
hello world
单引号和双引号可以嵌套使用。这点在使用中很方便,比如一个html代码片段,里面有双引号,在字符串里面就必须进行转义。但是使用单引号就可以省却这个麻烦。
>>> "test"
238: 'test'
>>> 'test'
239: 'test'
把几个字符串放到一起,Python会自动进行拼接,也可以用+号进行显示拼接。
>>> print 'hello' "world"
helloworld
>>> print 'hello' +"world"
helloworld
字符串中包含换行符的,需要用\n进行表示。但是Python有一种很方便的方法,就是使用连续三个’或者"将字符串包围起来,则其中所有字符都原封不动的保留,包括空格和回车。这在构造一段html代码的时候很有用。
>>> print 'Dear J:\n Hi.\n yours.'
正则表达式是我们在编写插件过程中最常见的技巧,但是正则表达式本身有\符号进行转义。比如\\表示\,\*表示*。但是\本身在python字符串中也是起转移作用的,那么要么你多转义几次,要么使用raw string,就是在字符串加上一个r,表示字符串内不需要进行转义。下面的示例在正则表达式中表示三个字符:^\*
>>> print '\\^\\\\\\*'
\^\\\*
>>> print r'\^\\\*'
\^\\\*
字符串是只读的,不能修改其中的某些字符。如果需要修改,必须重新构造一个字符串。
>>> s='abb'
>>> s[0]='b'
Traceback (most recent call last):
File "", line 1, in
s[0]='b'
TypeError: 'str' object does not support item assignment
>>> s='b' + s[1:]
>>> s
236: 'bbb'
上例中的s[1:]用法在python中叫做切片(slice)。不仅对于字符串,对于后面讲到的tuple和列表都是同样的用法。基本的用法是s[start:end:step],start是切片开始的位置,python中从0开始,end表示结束的位置,注意s[end]这个字符本身不包含在内。这样end-start就是最后切片的长度,当然如果end小于start,得到的是空字符串''。step表示切片的步长,默认为1。简单来说,就是从start开始取,依次是start+step、start+step*2、...一致到小于end的所有字符。start、end、step都可以为负数,start和end负数表示从字符串后面开始数起,-1表示最后一个字符,step为负数就从后往前切片,这要求start大于end。len()函数用来获取字符串长度。几个参数都可以省略,start省略表示从0开始,end省略表示取到最后,step默认就是1了。说起来有点犯迷糊,看下面的例子就容易理解了。
>>> a='hello world'
>>> len(a)
240: 11
>>> a[:] #copy of string
241: 'hello world'
>>> a[2:] #from the third
242: 'llo world'
>>> a[2:-1] #except the last one
243: 'llo worl'
>>> a[::2] # the even chars
244: 'hlowrd'
>>> a[::-1] #reverse of string
245: 'dlrow olleh'
>>> 'magnet:?xt=urn:btih:8fcffdf6062379a6a1a0505bb809919870d240eb&dn=%5B%E8%A5%BF%E6%B8%B8%E9%99%8D%E9%AD%94%E7%AF%87%5D.2013.HDTV.720p.x264.AAC-iSCG%5B%E5%9B%BD%E8%AF%AD%E4%B8%AD%E8%8B%B1%E5%AD%97%E5%B9%951.7G%5D'[20:60]
246: '8fcffdf6062379a6a1a0505bb809919870d240eb'
下面的例子里面有一些字符串常见操作:
>>> 'hello world'.upper() #大写
247: 'HELLO WORLD'
>>> 'HELLO WORLD'.lower() #小写
248: 'hello world'
>>> 'hello world'.capitalize() #首字母大写
249: 'Hello world'
>>> ' ab '.strip() #去空格
250: 'ab'
>>> ' ab '.lstrip() #去除左侧空格
251: 'ab '
>>> ' ab '.rstrip() #取出右侧空格
252: ' ab'
>>> 'bc' in 'abcd' #判断是否包含某字符串
253: True
>>> '中文test123'.encode('base64') #base64编码
262: '5Lit5paHdGVzdDEyMw==\n'
>>> print '5Lit5paHdGVzdDEyMw==\n'.decode('base64') #base64解码
中文test123
>>> '中文test123'.encode('hex').upper() #十六进制编码
264: 'E4B8ADE6968774657374313233'
>>> print 'E4B8ADE6968774657374313233'.decode('hex')
中文test123
>>> 'abcdabc'.replace('ab', '**') #替换
265: '**cd**c'
>>> 'ab|cd|ef'.split('|') #按指定符号分割字符串
267: ['ab', 'cd', 'ef']
>>> 'ab cd ef'.split() #按空格分割字符串
268: ['ab', 'cd', 'ef']
>>> 'http://www.baidu.com'.partition('://') #按制定字符串分割成两部分,比split效率更高
271: ('http', '://', 'www.baidu.com')
>>> ' , '.join(['hello', 'world', '!']) #连接字符串
270: 'hello , world , !'
上面提到用join来将一个列表拼接成字符串的方法经常要用到,而且是效率最高的方法。一些需要动态拼接的字符串,都先append到一个列表,最后用join来形成最终的字符串。
检查字符串是否以xx开头或者结尾,分别用'abc'.startswith('ab')和'abc'.endswith('bc')的函数来校验。更复杂的需要用到正则表达式,在Python中有re模块对正则表达式进行支持。由于正则是一个非常庞大的话题,在此不做详解。
转换和格式化
>>> str(10)
272: '10'
>>> str(1.5)
273: '1.5'
>>> int('335')
274: 335
>>> 'htt://%s/test/?page=%d' % ('www.baidu.com', 11)
275: 'htt://www.baidu.com/test/?page=11'
(五)列表和tuple
列表(list)在Python中的地位也非常重要,在插件开发过程中更是经常用到。list有点像C语言的数组,可以按索引遍历访问其中的每一个元素,可以对其进行修改。但是list与c的数组有天壤之别,便利之处也是数组遥不可及的。
首先,list长度不固定,可以任意追加、插入、删除元素,也可以一个元素都没有,即经常用到的空列表[]。从某种意义上说,list更像数据结构里面的链表,在内存中并不占有连续的空间。list元素也不限定数据类型,可以是任意Python类型,数字、字符串、字典...,甚至是另一个list,或者一个函数。
list可以用索引进行访问,比如x[2];可以切片,比如x[:-3];还可以迭代for item in aList: print item。下面来看看list长什么样子吧。
>>> urls = []
>>> urls.append('www.baidu.com')
>>> urls.append('www.google.com')
>>> urls.insert(0, 'xbmc.org') #注意顺序,在最前方插入
>>> urls.extend(['a', 'b', 'c'])
>>> urls
278: ['xbmc.org', 'www.baidu.com', 'www.google.com', 'a', 'b', 'c']
>>> urls.pop() #pop the last one and return
279: 'c'
>>> urls
280: ['xbmc.org', 'www.baidu.com', 'www.google.com', 'a', 'b']
>>> urls.pop(0) # pop the first
281: 'xbmc.org'
>>> urls
282: ['www.baidu.com', 'www.google.com', 'a', 'b']
>>> urls[2:]
283: ['a', 'b']
>>> urls[2] = [1,2,3]
>>> urls
284: ['www.baidu.com', 'www.google.com', [1, 2, 3], 'b']
>>> urls[2:2] = [4, 5, 6]
>>> urls
285: ['www.baidu.com', 'www.google.com', 4, 5, 6, [1, 2, 3], 'b']
>>> len(urls)
286: 7
从上面的示例可以看到,可以在列表中任意追加、插入、替换元素。请大家不要误会,list不是一定要从空列表[]开始。你完全可以一开始就 urls = ['a', 'b','c']。切片的操作和字符串类似,只不过字符串不能修改,而列表是可以修改的。urls[2:2]=[4,5,6]就利用这个技巧,在2的位置上加了三个元素,注意和urls[2]=进行区别。extend函数直接将另一个list直接追加到最后,省的一个一个append。
列表可以查找和删除指定元素,不仅仅是通过索引位置,还可以根据元素的值进行定位和删除,分别是index和remove函数。需要注意的是,如果多个元素值相同的话,只针对第一个出现的元素。sort和reverse函数分别对list进行排序和反转,这两个函数都不返回值,这点需要注意一下。如果需要排序结果,但不影响原list的话,就使用sorted函数。
>>> urls.append(4)
>>> urls.count(4)
287: 2
>>> urls.index(4)
288: 2
>>> urls.remove(4)
>>> urls
289: ['www.baidu.com', 'www.google.com', 5, 6, [1, 2, 3], 'b', 4]
>>> urls.sort()
>>> urls
290: [4, 5, 6, [1, 2, 3], 'b', 'www.baidu.com', 'www.google.com']
>>> urls.reverse()
>>> urls
291: ['www.google.com', 'www.baidu.com', 'b', [1, 2, 3], 6, 5, 4]
>>> sorted(urls)
292: [4, 5, 6, [1, 2, 3], 'b', 'www.baidu.com', 'www.google.com']
range函数返回一个数字列表。参数和切片有些类似,可以指定起至值和step。看下面的例子就明白了:
>>> range(10)
293: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(5, 10)
294: [5, 6, 7, 8, 9]
>>> range(10, -1, -2)
295: [10, 8, 6, 4, 2, 0]
因为返回的结果是一个list,是需要在内存中真实存在的。所以如果你需要一个从1到一百万的序列,请不要使用range函数,而是用xrange,得到的是一个迭代器(iterator),不会一次性在内存中生成。
下面要讲一下关于迭代的问题。对于任意可以迭代的数据,我们用 for 变量 in 数据 的语法形式来进行访问。我们已经知道,list可以根据索引访问。那么类似c语言数组的做法,很容易想到的是下面的用法:
>>> aList = ['a', 'b', 3, 4, 'e', 'f', 3.9]
>>> for i in range(len(aList)):
... print aList[i]
a
b
3
4
e
f
3.9
但是,这种访问方式很明显多此一举了。按照Python的思想,要用最简单的语句最多的事情。
>>> for item in aList:
... print item
a
b
3
4
e
f
3.9
即简单,又简洁。如果同时确实元素的索引值,可以用enumerate函数
>>> for i, item in enumerate(aList):
... print i, item
0 a
1 b
2 3
3 4
4 e
5 f
6 3.9
enumerate返回的一个元素为tuple的list,可能类似[(0, 'a'), (1, 'b'), ...]这样的形式。我们在for里面用到了两个变量,这叫做unpack(好像是这么称呼),就是将一个序列解开到多个变量。顺便先提一下, a,b这样的形式就叫做tuple,它和(a,b)是一样的,类似list,区别在于它不能修改。我们来看看upack怎么用。
>>> a,b = [1,2]
>>> a, b
296: (1, 2)
>>> print a, b
1 2
>>> a, b = ('hello', 'world')
>>> print a,b
hello world
>>> (a, b, c) = [3, 4, 5]
>>> print a, b, c
3 4 5
>>> name, url = ('百度', 'www.baidu.com')
>>> print name
百度
>>> print url
www.baidu.com
>>> name, url = url, name
>>> print name
www.baidu.com
>>> print url
百度
可以看到,unpack在变量赋值的过程中是非常有用的。name, url = aList 相当于name=aList[0] url=aList[1]。显然前面的方式更加简洁明了。甚至我们用name, url = url, name这样的语句,简单的交换了两个变量的值,这在c语言里面不用第三个变量中转是无法做到的。
list的迭代使用的确很方便,很强大。但是很多情况下还不需要这么复杂,因为有list comprehension,就是用一种表达式将原来的list进行运算变形,得到新的表达式。比如我需要得到1-10这10个数的平方,保存为一个列表。采用传统的方法:
>>> aList = []
... for i in range(11):
... aList.append(i*i)
>>> aList
298: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
你再看看下面更简洁的方法:
298: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> aList = [i*i for i in range(11)]
>>> aList
299: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
别看这个例子很简单,其实在我们写程序的过程中经常会用到,大大简化我们的程序。有兴趣也可以翻翻我写的插件源代码,到处充斥这这样的应用。
我们还可以用if来对产生的结果进行过滤。比如上例中,我要挑出3次方被3除余2的数字的平方(有点绕口)。
>>> [i*i for i in range(11) if i**3 % 3 == 2]
再来说说tuple,前面已经大致提到过。形如(1,2,3)用括号和逗号构造的序列就叫做tuple,有的翻译为元组,我觉得不习惯。它和list很相似,可以用索引进行访问,可以进行迭代,可以切片。tuple和list最大的区别就是它和字符串一样,不能对它进行任何修改。字符串从某种意义来讲,可以认为是单个字符组成的tuple。
tuple用于某些不希望别人修改它的场合,比如作为函数参数传入,作为字典的key等等。构造tuple很简单,需要注意的是,如果一个tuple只有一个元素,不是(1)这样的形式,这样会当作括号符进行运算,得到1这个整数。正确的语法是(1,),就是后面一定要有一个逗号,虽然有点怪异,但是习惯就好了。讲到这里,顺便提一下,tuple、list和我们后面要讲到的dict(字典),最后一个元素后面都可以带一个逗号,不会出现语法错误。我们经常会这么干,便于追加记录,比如
a = [
'a',
'b',
]
tuple也可以用+连接,构造新的tuple。下面的例子介绍一些常见的tuple形式和运算
>>> (1, 2, 'a', 'b')
300: (1, 2, 'a', 'b')
>>> (1, 2, 'a', 'b') + (4, 5)
301: (1, 2, 'a', 'b', 4, 5)
>>> (1, 2, 'a', 'b') + (6, )
302: (1, 2, 'a', 'b', 6)
>>> ()
303: ()
>>> (1,)
304: (1,)
tuple和list可用tuple和list内置函数进行相互转换
>>> aList = range(10)
>>> aList
307: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> t = tuple(aList)
>>> t
308: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> list(aList)
309: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
迭代同样适用于tuple
>>> ''.join(str(i) for i in t)
311: '0123456789'
上例中,t是一个10个数字的tuple。通过list comprehension表达式,将t里面的每一个数字用str函数转换成字符串。最后的得到一个generator用''.join连接在一起,形成最终的字符串。中间的表达式的结果实际上是一个generator,不用了解太深,知道可以这么用就可以了。
>>> (str(i) for i in t)
312: at 0x3144140>
我们可以用in来判断某个指定的元素在list/tuple中是否存在。比如 'a' in ('a', 'b'),'b' not in ['a', 'b']
最后,来看一个稍微复杂点的例子吧,将文件中以LOG: 开头的行显示出来,并且去掉LOG:。
print ''.join([line[4:] for line in open('a.txt') if line.startswith('LOG:')])
复制代码
其中open函数打开一个文件,返回一个每行数据的迭代,基本上你可以认为返回一个list,每个元素表示一行。
(六)字典
字典也是Python中非常重要的数据类型。它是一种key-value对,以hash表方式存储的数据结构。dict只能用key进行访问,它是无序的。网页API经常用到的JSON数据格式,经常见到dict的身影。比如说豆瓣电影上 西游降魔篇的介绍 http://api.douban.com/v2/movie/subject/5308265
{
"rating": {
"max": 10,
"average": 7.2,
"stars": "40",
"min": 0
},
"reviews_count": 3673,
"wish_count": 25322,
"collect_count": 236509,
"douban_site": "",
"year": "2013",
"images": {
"small": "http:\/\/img3.douban.com\/spic\/s24423024.jpg",
"large": "http:\/\/img3.douban.com\/lpic\/s24423024.jpg",
"medium": "http:\/\/img3.douban.com\/mpic\/s24423024.jpg"
},
"alt": "http:\/\/movie.douban.com\/subject\/5308265\/",
"id": "5308265",
"mobile_url": "http:\/\/movie.douban.com\/subject\/5308265\/mobile",
"title": "西游降魔篇",
"do_count": null,
"seasons_count": null,
"schedule_url": "http:\/\/movie.douban.com\/subject\/5308265\/cinema\/",
"episodes_count": null,
"genres": ["喜剧", "奇幻", "冒险"],
"countries": ["中国大陆", "香港"],
"casts": [{
"avatars": {
"small": "http:\/\/img3.douban.com\/img\/celebrity\/small\/1218.jpg",
"large": "http:\/\/img3.douban.com\/img\/celebrity\/large\/1218.jpg",
"medium": "http:\/\/img3.douban.com\/img\/celebrity\/medium\/1218.jpg"
},
"alt": "http:\/\/movie.douban.com\/celebrity\/1011513\/",
"id": "1011513",
"name": "文章"
}, {
"avatars": {
"small": "http:\/\/img3.douban.com\/img\/celebrity\/small\/1268.jpg",
"large": "http:\/\/img3.douban.com\/img\/celebrity\/large\/1268.jpg",
"medium": "http:\/\/img3.douban.com\/img\/celebrity\/medium\/1268.jpg"
},
"alt": "http:\/\/movie.douban.com\/celebrity\/1138320\/",
"id": "1138320",
"name": "舒淇"
}, {
"avatars": {
"small": "http:\/\/img3.douban.com\/img\/celebrity\/small\/1656.jpg",
"large": "http:\/\/img3.douban.com\/img\/celebrity\/large\/1656.jpg",
"medium": "http:\/\/img3.douban.com\/img\/celebrity\/medium\/1656.jpg"
},
"alt": "http:\/\/movie.douban.com\/celebrity\/1274242\/",
"id": "1274242",
"name": "黄渤 "
}, {
"avatars": {
"small": "http:\/\/img3.douban.com\/img\/celebrity\/small\/3083.jpg",
"large": "http:\/\/img3.douban.com\/img\/celebrity\/large\/3083.jpg",
"medium": "http:\/\/img3.douban.com\/img\/celebrity\/medium\/3083.jpg"
},
"alt": "http:\/\/movie.douban.com\/celebrity\/1274317\/",
"id": "1274317",
"name": "罗志祥"
}
],
"current_season": null,
"original_title": "西游降魔篇",
"summary": "大唐年间妖魔横行,一小渔村因为饱受鱼妖之害请来道士(冯勉恒 饰)除妖,年轻驱魔人陈玄奘(文章 饰)前来帮忙却被误认为骗子,幸亏职业赏金驱魔人段小姐(舒淇 饰)帮助玄奘制服了鱼妖真身(李尚正 饰)。二人又在高家庄为制服猪妖猪刚鬣(陈炳强 饰) 而再次相遇,这次除妖没有成功 ,但是段小姐却对玄奘二见钟情。玄奘求助师父,得知除妖的办法是去找被压在五指山下的孙悟空(黄渤 饰)帮忙,于是他准备前往五指山,途中又遇到段小姐和手下五煞,段小姐连蒙带哄想与玄奘在一起却屡次遭拒,在四妹(周秀娜 饰)调教下想变得更有女人味却适得其反。二人决裂后玄奘独自上路,与此同时降魔师(释延能 饰)、天残脚(张超理 饰)、空虚公子(罗志祥 饰)也一同前往除妖。经过千辛万苦玄奘终于找到孙悟空,段小姐又再次出现并交给玄奘一件重要的东西,猪妖终于被降服,但是更大的危机又出现在了玄奘面前,原来孙悟空与传闻中不一样,玄奘的除魔之路能否继续?\n本片是周星驰多年之后的力作,演员阵容延续了以往,除了主演外,还请了诸如卢正雨、杨迪等网络红人加盟。©豆瓣",
"subtype": "movie",
"directors": [{
"avatars": {
"small": "http:\/\/img3.douban.com\/img\/celebrity\/small\/281.jpg",
"large": "http:\/\/img3.douban.com\/img\/celebrity\/large\/281.jpg",
"medium": "http:\/\/img3.douban.com\/img\/celebrity\/medium\/281.jpg"
},
"alt": "http:\/\/movie.douban.com\/celebrity\/1048026\/",
"id": "1048026",
"name": "周星驰"
}, {
"avatars": {
"small": "http:\/\/img3.douban.com\/img\/celebrity\/small\/13093.jpg",
"large": "http:\/\/img3.douban.com\/img\/celebrity\/large\/13093.jpg",
"medium": "http:\/\/img3.douban.com\/img\/celebrity\/medium\/13093.jpg"
},
"alt": "http:\/\/movie.douban.com\/celebrity\/1274244\/",
"id": "1274244",
"name": "郭子健"
}
],
"comments_count": 104398,
"ratings_count": 204027,
"aka": ["除魔传奇", "西游", "大话西游之除魔传奇", "三藏伏魔", "大话西游3", "西游·降魔篇", "Journey to the West: Conquering the Demons", "Odyssey"]
}
看看这些数据,基本上就是dict和list的组合。dict的表达能力很强,它的key可以是整数、字符串、tuple,以字符串居多。value可以是任意类型。上面的示例中可以看到很多dict和list相互嵌套使用。
看看字典一些常见操作:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['b']
314: 2
>>> d['v']
Traceback (most recent call last):
File "", line 1, in
d['v']
KeyError: 'v'
>>> d.get('v')
>>> d.get('v', 'default value')
315: 'default value'
>>> d.get('b', 'default value')
316: 2
>>> len(d)
317: 3
>>> 'a' in d
318: True
>>> 'v' in d
319: False
>>> 'v' not in d
320: True
>>> d.keys()
321: ['a', 'c', 'b']
>>> d.values()
322: [1, 3, 2]
>>> d.items()
323: [('a', 1), ('c', 3), ('b', 2)]
>>> d['b'] = 100
>>> d
324: {'a': 1, 'b': 100, 'c': 3}
>>> del d['b']
我们注意到,在尝试访问不存在的key的时候,会抛出KeyErro异常,但是用get函数却返回None。后者效率更高,而且可以指定缺省值。比如我们从参数列表中取页码,就可以params.get('page', '1'),这样如果参数page存在则直接返回,不存在就给默认值1。keys、values、items函数分别返回key、value和tuple的列表,可用于遍历。del将数据从字典中删除。
get函数,有些类似于下面的代码,但是更简洁、高效。
d.get(key, default)
if key in d:
return d['key']
else:
return default
再演示一个具体的应用,比如我要统计一个字符串中间,出现的每个字符的频次。
>>> result = {}
非常的简洁。另外一个类似的函数setdefault,也是在key存在时返回相应的value,不存在则返回默认值。不同的是,如果key不存在,它会将default值写到dict里面去。上面的例子,如果变一下需求,只是统计一下出现的字符,而不需要频次,可以这么做
>>> result = {}
setdefault可以随便给个缺省值,因为我们要的只是key。当然你可一个把 result.setdefault(char, None) 替换成result[char] = None 来达到同样的目的。但是这样每次都需要去写dict,效率上应该会低一些,也没有前面的语句意思更加明确。
另外一个pop函数,跟get和setdefault有异曲同工之妙,它的作用是通过key删除一个元素,并返回它的value,如果不存在则返回默认值,但是不指定default参数则会抛出异常。这个函数用到的几率也很高。比如你从一些准备发送post的数据中间,把page参数挑出来,放到url后面的params参数中间,就可以使用pop函数。
>>> data = {'a': 1, 'b':2, 'page': 15}
>>> page = data.pop('page', 1)
>>> page
325: 15
>>> data
326: {'a': 1, 'b': 2}
注意不要将pop函数和popitem函数混淆了。popitem是从dict中随机删除一条记录,返回(key, value)。
如果你希望建立一个学生成绩表,并且将成绩初始化为0,不需要一个一个对dict赋值,你可以这么做
>>> students = ['Jack', 'Tom', 'Jerry']
>>> dict.fromkeys(students, 0)
327: {'Jack': 0, 'Jerry': 0, 'Tom': 0}
清空dict只需要调用clear()函数。copy()函数可以将dict复制一份,不过需要注意的是浅拷贝(shadow copy),就是只复制value引用。看下面的示例就明白了。
>>> d = {'a': 1, 'b': 2, 'c': (1,2), 'd': [1,2], 'e': {'hi': 'boy'}}
>>> d2 = d.copy()
>>> d2
332: {'a': 1, 'b': 2, 'c': (1, 2), 'd': [1, 2], 'e': {'hi': 'boy'}}
>>> d['a'] = 10
... d['c'] = (3,4)
... d['d'][0] = 100
... d['e']['evil'] = True
>>> d2
333: {'a': 1, 'b': 2, 'c': (1, 2), 'd': [100, 2], 'e': {'evil': True, 'hi': 'boy'}}
>>> d
334: {'a': 10, 'b': 2, 'c': (3, 4), 'd': [100, 2], 'e': {'evil': True, 'hi': 'boy'}}
d2是d的一份copy,但是我们修改d['d']里面的元素,却导致了d2被改变。可以看到,d['d']是被copy到d2['d']来了,但是只是引用,它们都指向同一个list。而整数、tuple等其他不可修改的数据则不存在类似的问题。一定要引起注意,这是很容易犯的错误,而且很不容易被发现。
最后就是dict的遍历,比较容易懂,注意是in d.items()而不是in d就行了
>>> for key, val in {'a': 1, 'b': 2, 'c': 100}.items():
... print '%s=%s' % (key, val)
a=1
c=100
b=2
(七)函数
函数在Python以def关键词定义,python函数可以指定缺省值。python函数调用的时候,不仅可以通过位置传递参数,也可以通过参数名称来进行传递,命名参数传递时,不需要指定参数顺序。
>>> def add(a, b=1):
... return a+b
>>> add(1,2)
335: 3
>>> add(1)
336: 2
>>> add(b=2, a=3)
339: 5
函数本身是一种数据类型,可以赋值给变量,然后通过变量来调用。
>>> def add(a, b):
... return a+b
... def sub(a, b):
... return a-b
>>> data = [(1,2), (7,3)]
>>> [[f(a,b) for f in (add, sub)] for a,b in data]
341: [[3, -1], [10, 4]]
不知道大家看明白了没有。首先定义了add和sub两个函数,分别用于加、减传入的两个参数。后面是一个list comprehension,data是一个元素为两个元素tuple的list,for a,b 将tuple进行unpack,对于list的第一个元素(1,2),得到的是a=1, b=2。返回另外一个list:[f(a,b) for f in (add, sub)]。这里又是一个list comprehension,对(add, sub)这个tuple进行遍历,我们说过,函数也是一种数据,所以for f in (add, sub)实际上是先后给f这个变量赋上add和sub两个函数,然后f就可以当作相应的函数来调用了。当f 取 add时,f(a,b)就是add(a,b)。所以最后得到的就是data中每个pair的和与差。没那么复杂,想想就明白了。
还一种常用的形式,就是把函数当作dict的value。根据key 进行选择。比如说,你需要根据一个url的协议来选择相应的函数来运行,你可以这么做
if protocol == 'http':
handle_http(url)
elif protocol == 'ftp':
handle_ftp(url)
elif ...
...
else
handle_default(url)
有点类似其他语言中的switch,但是Python中没有switch,因为根本就不需要这种语法,直接用dict就可以实现,而且更清晰。
handlers = {
'http': handle_http,
'ftp': handle_ftp,
...
}
handler = handlers.get(protocol, handle_default)
handler(url)
带来的另外一个好处,就是把协议和对应的处理函数放在一个字典里面,整个handlers可以作为数据管理,你甚至可以从配置文件里面加载这样的隐射关系!好处不言而喻了。
最后简单介绍一下lambda表达式。它是Python里面的匿名函数。比如 lambda x:x+1 就是一个接受一个参数,并且返回x+1值的函数。注意冒号后面的只能是一个表达式,而不能是多个语句,你可以使用条件表达式,比如 abs = lambda x: x if x>=0 else -x 。这样就简单定义了一个取绝对值的函数。配合dict,你可以这么做:
operator = 'add'
data = (1, 3)
{'add': lambda x,y: x+y,
'sub': lambda x,y: x-y
}[operator](*data)
你能猜到它的结果吗?对了,就是4!需要说明一点的是,在传递参数过程中,使用*号,是将后面的数据展开。f(*(1,2,3))就等同于f(1,2,3)。类似的用法,**可以解开dict,用于命名参数。比如f(a,b),你可以这么调用 f(**{'a':1, 'b':2})。是不是觉得Python非常强大呢?!
(八)模块、package
模块是什么?简单来说,模块就是一个.py文件,虽然这种说法不一定很确切。package差不多就是一个包含.py模块的目录。Python内置了大量的模块,除了Python语法本身的强大以外,其他的功能都依赖Python丰富的模块和第三方模块。比如你要处理图像,就有堪比PhotoShop的PIL包,要访问网页就有httplib、urllib、urllib2、requests等等一系列好的模块等你来挑。
假如你有一个utils.py文件,里面包含了大量的通用函数。而同目录的插件执行文件addon.py需要调用这些函数,怎么办呢,很简单,只要在文件前面使用import utils语句,你就可以用utils.myfunc来调用utils模块的myfunc函数了。当然如果你不想写那么长的名称,你也可以使用from utils import myfunc的语法来引入myfunc函数,调用就更简单了,直接myfunc()。需要注意的是,如果另外一个模块也有myfunc函数,而且都要在addons里面使用,那么from .. import myfunc就会造成名字空间冲突了,但是import 模块名的形式不会出现这样的问题。一般来说from .. import 的运行效率更好一些。
Python的文档非常完善,虽然都是english。在你不清楚一个模块或者一个函数怎么使用的时候,最直接的办法就是查Python的docs。我想很少有人能够丢开python docs来编写一个大型程序吧。
你的utils.py不一定每次都在跟addon.py相同的目录下,很可能他放到了common目录下面了,那么import utils肯定是找不到模块的。怎么办呢?两种办法,一是构造一个common的包,然后用 import common.utils引入;另一种是增加Python的搜索路径,然后import utils。
我们先说第一种方法,更通用的方法。前面说过,package就是包含py模块的目录,Python怎么知道这就是一个package呢?成为package的先决条件是,这个目录下必须有一个叫做__init__.py的文件,哪怕是个空文件都行。事实上,很多package的__init__.py都是空文件,什么都没做,这个文件是在你访问package的时候自动执行的,如果你不是非常了解它是什么样的运作机制,那么就让它永远空着吧。package是可以嵌套的,其实就是目录层级关系嘛。Python自带的有一个模块就是xml.sax.saxutils,你必须import xml.sax.saxutils,或者from xml.sax.saxutils import *来使用它。就这么简单。
Python是怎么寻找这些module和package的呢?很明显,当前目录是排在最前面的,如果在当前目录下搜索不到的话,它会在sys.path指定的目录里面去查找。
>>> import sys
... print sys.path
['', '/usr/local/lib/python2.7/dist-packages', '/usr/local/lib/python2.7/dist-packages/dreampielib/data', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.7', '/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode']
你的显示结果跟我可能不同。Python会按照list里面目录的顺序,挨个挨个去搜索我们要import的module或package,找不到就抛出ImportError异常。你可以自己修改sys.path,不过就是一个列表嘛!
sys.path.append('common')
这样就将common目录加到了搜索路径当中,你再使用import utils肯定就能找到你的utils模块了。
Python有一个package库 https://pypi.python.org/pypi,第三方的package绝大多数都在这里提交,你可以在这里找到绝大多数你所需要的功能。在Python的安装环境中,如果你需要安装使用第三方package,一般使用easy_install或者pip来安装。在这里不多说。
在插件开发过程中,我们也往往会用到很多第三方模块,一般放到resources/lib目录下面,然后增加sys.path里面的搜索路径,或者新建__init__.py,以resources.lib.xxx的形式来import。具体放置的位置不做强制限定。还有一种情况,就是已经有人将Python package做成了xbmc的插件供大家使用,我们只需要在addon.xml的requires节中增加相应的依赖关系即可。
(九)中文编码
如果你不用中文,这节你就甭看了 ^_^
首先要介绍一下,Python里面有str和unicode两种数据类型。前者说白了就是一些有序的字节串,每个字节可以从0~255没有任何限制,Python本身也不明白你这些字节序列是什么含义,到底是英文字符,还是中文字符,是gbk编码的中文,还是utf-8的编码。所以我们谈到的编码只有在I/O的时候才有意义。
unicode是一种特殊的字符串,它采用两个字节来代表一个字符,无一例外。随着计算机系统软件的国际化,很多程序内部都使用unicode编码,它可以涵盖世界上任何民族语言和方言的字符,不会产生任何奇异。像windows在2000以后吧,API内部都是使用unicode来表示字符串的。对于unicode而言,肯定是没有字符集而言的。
那么什么是字符集?字符集其实就是一种读取str内部自己序列的规范。拿GBK来说,ascii值在某个阀值以下的当作普通ascii处理,超过的需要跟后面一个字节连在一起来识别字符,中文都是占用两个字节。而UTF-8中的中文是以三个字节来表示的,英文也只占用一个字节。GBK能表示的字符数量有限,甚至有些生僻汉字都可能不被包含在内。但是UTF-8是以Unicode为基础的,它可以涵盖所有Unicode字符。因为计算机世界的标准大都还是说english的国家制定的,可能考虑到编码效率等诸多原因,一般在网络传输或者以文件形式存储时,采用UTF-8编码,而不是Unicode的双字节表示模式(UTF-16好像就是这个)。
XBMC采用的是UTF-8字符集,可以测试,传utf-8编码的中文串给xbmc显示正常。当然你传unicode过去也没问题,它内部应该做了转换的。
我们一般需要注意字符集的地方主要有HTTP数据、数据库数据和文件内容这几类。基本上都是在I/O部分,读取网络数据、数据库里面的记录、读取文件内容,还有GET/POST网页、写数据库和写文件的时候。还有一种I/O,就是用input函数要求用户输入和使用print在屏幕打印数据的时候,也涉及到字符集的问题。
回到编写XBMC插件的话题上来说,需要把握的原则是尽可能的在程序中使用unicode,这样最大可能的避免中文字符集造成的问题。.py文件是文本文件,那么它本身也是有字符集编码的,你用windows的记事本,很可能就是GBK,有些编辑软件设置的默认字符集是UTF-8。在这里我建议大家都是用utf-8编码保存.py文件,避免出现不必要的麻烦,和可能丢失数据的风险(GBK不能表示的字符有很多)。
大家可能注意到了,在.py文件的第一行或者第二行可能都有这么一条语句 #coding=utf-8。看起来是注释,但是python解释器会根据这个来识别字符集,有点类似html里面的编码指定。事实上,你如果没有指定coding,而包含了中文字符,在执行的时候肯定会报错的。像前面讲到的一样,建议大家都是用utf-8编码保存python源代码,然后在最前面加上#coding=utf-8指明编码。
我们来看看,在.py代码中使用中文字符串的时候,会是一个什么样的情况。比如 a = '中文',python解释器注意到这里是一个str类型的字符串,因为文件编码是utf-8,那么在内存里面,变量a就是一个编码为utf-8的字符串,说白了就是6个字节(一个汉字占3个字节)。但是如果你的编码是GBK,并且用#coding=gbk表明,那么得到的变量a就是一个4个字节的GBK编码的字符串。
试想一下,如果我的文件保存的时候使用utf-8编码,但是前面的申明却写成了gbk,会出现什么样的结果?我们来做下实验,新建一个a.py文件,内容如下:
#coding=gbk
print repr('中文')
文件以utf-8编码保存,python a.py执行看看会出现什么状况?
$ python a.py
File "a.py", line 2
SyntaxError: 'gbk' codec can't decode bytes in position 14-15: illegal multibyte sequence
嗯,发现什么没有?python解释器首先根据你指定的编码来识别文件内容,你看你让它用gbk编码来读取文件内容,前面的英文字符部分没问题,一旦碰到“中文”就出问题了,明明文件是6个字节保存的utf-8编码,你强制让它用GBK解码,能成功吗?好比你用密码abc压缩的rar文件,你一定要用密码123来解密,可能吗?这说明文件保存的编码一定要与声明的编码一致!
既然同一个python文件,在使用不用编码保存的时候,中文字符串会出现完全不同的结果,我们怎么保证程序运行的正确性呢?答案是设置非ascii字符的一律使用unicode字符串。
a = u'中文'
这样,不管你的源代码是gbk编码也好,是utf-8编码也好,还是什么别的乱七八糟的编码也好,python解释器都会忠实的给你转换成unicode。哇哦,这下可以松口气了!所以这里提到另一个重要的原则,就是.py源码中出现的的中文字符串一律使用unicode字符串!我们可以在需要的时候再进行编码。
下面要提到的是系统默认编码
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
python解释器使用的默认编码是ascii,这个默认编码什么时候使用呢?在你进行编码转换,但是没有明确指定需要转换的编码时就使用这个默认编码(有时候好像也参考了操作系统指定的编码)。
>>> a = '中文'
>>> print a
中文
>>> print repr(a)
'\xe4\xb8\xad\xe6\x96\x87'
>>> unicode(a)
Traceback (most recent call last):
File "", line 1, in
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>> b = a.decode()
Traceback (most recent call last):
File "", line 1, in
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>> b = a.decode('utf-8')
>>> print b
中文
我的linux系统采用了utf-8编码,就是说我在交互环境下输入的字符串是utf-8编码的,print输出utf-8字符串,显示正常。当我使用decode要求转换成unicode的时候,你明显看到python在尝试使用ascii编码进行解码,结果当然会失败。后面我指定字符集utf-8转换到unicode就正确了。然后print b的时候,python可能使用了系统的默认字符集转换,而不是ascii,所以显示正常。如果以后碰到print打印的内容是乱码的话,尝试转换成unicode再看。
在前面也看到了,str和unicode之间转换可以用str(u'unicode string', encoding)和unicode('str string', encoding),也可以使用encode和decode进行转换。
下面具体说一说,在处理网页的的时候,怎么处理编码问题。常见的网页一般采用GBK或者UTF-8这两种字符集。最简单的办法,就是根据html代码里面指定的编码或者http header指定的编码对获取的数据进行decode。有些模块,比如requests甚至自动给你进行了这样的转换。python内置的urllib2.urlopen(url).read()获取的是原始数据,服务器传过来的字节流,你只要根据对应的字符集进行decode,就可以得到unicode字符串,然后你针对这个unicode字符串进行处理,然后传给xbmc,一定不会出现编码问题。当然,如果网页本身是utf-8编码,你也可以偷懒,不用先decode成unicode字符串,反正xbmc也认识。
其他的编码问题其实还有很多,但是要具体问题具体分析,比如获取的json或者xml里面也存在字符集的问题。但是,编码问题万变不离其宗,在python interpreter中多实验,多想想,然后多google,问题总能得到完美解决。了解了编码的本质和python内部编码处理的机制,处理起中文编码问题起来都会得心应手。祝你好运!^_^