1. Python中列表赋值的问题
在看官网时候,我个人写了如下的代码练习切割操作:
In[68]: letters
Out[68]: ['a', 'b', 'C', 'D', 'E', 'f', 'g']
In[69]: letters[2:5] = ['c', 'd', 'e']
In[70]: letters
Out[70]: ['a', 'b', 'c', 'd', 'e', 'f', 'g']
In[71]: letters[2:5] = ('C', 'D', 'E')
In[72]: letters
Out[72]: ['a', 'b', 'C', 'D', 'E', 'f', 'g']
我当时的第一个疑问是:
In[71]: letters[2:5] = ('C', 'D', 'E')
In[72]: letters
Out[72]: ['a', 'b', 'C', 'D', 'E', 'f', 'g']
这里letters为什么不会被修改为:['a', 'b', ('C', 'D', 'E'), 'f', 'g'
].
然后想明白了对于切割操作,外层的[]是语法糖,代表的里面包含的元素要替换letters中的列表,这时('C', 'D', 'E')实际上被解释为['C', 'D', 'E'],则上述的赋值操作,可以解释如下:
letters = ['a', 'b'] + ['c', 'd', 'e'] + ['f', 'g']
letters[2:5] = ('C', 'D', 'E') ==> ['a', 'b'] + ['C', 'D', 'E'] + ['f', 'g'] ==> ['a', 'b', 'c', 'd', 'e', 'f', 'g']
如果我们非要赋值元祖,可以提供一个列表,里面的数据是元祖即可:
In[74]: letters[2:5] = [('c', 'd', 'e')]
In[75]: letters
Out[75]: ['a', 'b', ('c', 'd', 'e'), 'f', 'g']
这也解释了为什么我们清空列表可以这样编写:
In[76]: letters[:] = []
In[77]: letters
Out[77]: []
2. for循环的基本使用
C++的STL支持迭代器,而Python中也直接类似的操作.例如我们遍历一个列表,通常有以下两种方法:
# -*- coding:utf-8 -*-
nums = range(0, 10)
#类似迭代器
for num in nums:
print num,
print
#使用下标索引
for i in range(0, len(nums)):
print nums[i],
print
如果我们想要修改列表,通过索引则可以很简单实现:
#使用索引进行列表的修改
for i in range(0, len(nums)):
nums[i] += 10
#[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
print nums
而实际上通过迭代器无法实现,我们可以测试如下:
nums = [1, 2]
#[140520361897032, 140520361897008]
print [id(num) for num in nums]
#使用迭代实现修改元素
#140520361896792 140520361896768
for num in nums:
num += 10
print id(num),
print
#[1, 2]
print nums
for循环中的每个num为数值,是不可改变的,所以每次执行+操作,会新建一个num,然后存储新值.这也是为什么这里的id会完全不同的原因.
而且使用迭代器操作改变原列表有一个很隐藏的BUG!:
# -*- coding:utf-8 -*-
words = ['a', 'bb', 'ccc']
for w in words:
print w
if len(w) > 2:
words.insert(0, w)
print words
这段代码会死循环直到内存溢出~_~
所以一般我们会修改成:
# -*- coding:utf-8 -*-
words = ['a', 'bb', 'ccc']
for w in words[:]:
if len(w) > 2:
words.insert(0, w)
#['ccc', 'a', 'bb', 'ccc']
print words
for循环还有一个非常好用的属性:判断for循环是否完整运行:
# -*- coding:utf-8 -*-
nums = range(0, 3)
#类似迭代器
for num in nums:
print num,
else:
print '完整运行一遍'
for num in nums:
if num > 1:
break
else:
print '这句话不会输出'
#输出如下: 0 1 2 完整运行一遍
在循环中还有两个函数特别有用:enumerate和zip:
In[52]: for i, v in enumerate(['a', 'b', 'c']):
... print i, v
...
0 a
1 b
2 c
而zip可以将多个列表进行操作:
In[53]: for key, value in zip([0, 1, 2], ['a', 'b', 'c']):
... print key, '=>', value
...
0 => a
1 => b
2 => c
3. 函数的基本使用
函数传参的基本原则是:传递一个对象,而非副本:
# -*- coding:utf-8 -*-
nums = [1, 2]
def func(nums):
nums += [11, 12]
func(nums)
#[1, 2, 11, 12]
print nums
函数名也只是一个对象,所以存在赋值操作:
# -*- coding:utf-8 -*-
def func():
print 'hello world'
f = func
#hello world
f()
#4408107568
print id(f)
#4408107568
print id(func)
函数传参时要遵循以下原则:
保证所传递的参数不被函数所修改,被修改的变量当做函数返回值,而非函数的参数.
例如:
# -*- coding:utf-8 -*-
def func(words, nums = []):
newStr = words + ",".join(nums)
return newStr
nums = ['1', '2']
newStr = func("hello", nums)
#['1', '2']
print nums
#hello1,2
print newStr
我之前曾经思考过所传递的参数必须为元祖,例如传递参数时候将列表转换为元祖,在函数内部将元祖再转换为列表等,但是对于字典则很难办,而且代码会非常的繁琐.所以就按照上面的原则进行函数的编写.
缺省参数的写法可以让我们完成类似C++的重载操作:
# -*- coding:utf-8 -*-
def func(words, nums = []):
print words
if nums:
print nums
#hello
func('hello')
#hello
#[1, 2, 3]
func('hello', [1, 2, 3])
4. 列表的基本使用
1. 基本函数
list.append(x): 向后插入一个元素,等价于:a[len(a):] = [x]
list.extend(L): 通过插入列表L来达到扩展的目的,等价于a[len(a):] = L
list.insert(index, x): 在当前索引index前插入元素x
list.remove(x): 移除元素值等于x的元素.如果此元素不存在,则报错.
list.pop([i]): 移除指定索引的元素并返回删除元素的值.如果不带参数索引,则默认删除最后一个元素并返回它.
list.index(x): 返回值等于x的元素的索引,如果不存在则报错.
list.count(x): 返回x在列表中出现的次数
list.sort(cmp = None, key = None, reverse = False): 排序列表
list.reverse(): 反转列表
# -*- coding:utf-8 -*-
nums = [1, 2]
#[1, 2, 3]
nums.append(3)
#[1, 2, 3, 4, 5]
nums.extend([4, 5])
#[1, 1.11, 2, 3, 4, 5]
nums.insert(1, 1.11)
#[1, 2, 3, 4, 5]
nums.remove(1.11)
#3
print nums.pop(2)
#5
print nums.pop()
#2
print nums.index(4)
#1
print nums.count(1)
nums.reverse()
#[4, 2, 1]
print nums
2. 对列表有用的三个内建函数
filter(function, sequence): 对序列sequence中每个元素item执行function(item),返回为True组合的序列.除了str, unicode和tuple,其余的都返回列表
# -*- coding:utf-8 -*-
#[1, 3, 5, 7, 9]
print filter(lambda x: x % 2, range(0, 10))
#defg
print filter(lambda x: x > 'c', 'abcdefg')
#(4, 5)
print filter(lambda x: x > 3, tuple(range(0, 6)))
map(function, sequence): 与filter类似,但是function需要几个参数,则就需要几个序列
# -*- coding:utf-8 -*-
#[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print map(lambda x: x * x, range(0, 10))
#[0, 2, 6, 12, 20, 30, 42, 56, 72, 90]
print map(lambda x, y: x * y, range(0, 10), range(1, 11))
reduce(function, sequence): 读取序列的前两个元素进行function,然后生成的结果作为第一个元素,和第三个元素继续组成两个元素,然后继续function.如果提供初始值,则以初始值作为第一个元素:
# -*- coding:utf-8 -*-
def add(x, y):
return x + y
#55
print reduce(add, range(1, 11))
def mul(x, y):
return x * y
#240
print reduce(mul, range(1, 5), 10)
5. 推导式的基本使用
最常用的是列表推导式:
In[35]: [(x + 1) for x in range(0, 10)]
Out[35]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
但是如果一个序列很大,例如我们通过推导式来读取一个文件,则我们需要生成器表达式(个人理解为元祖推导式)来完成:
In[38]: ss = ((x + 1) for x in range(0, 1000))
In[39]: ss
Out[39]: <generator object <genexpr> at 0x108764cd0>
In[40]: for i in ss:
... if i > 10:
... break
... print i,
...
1 2 3 4 5 6 7 8 9 10
假设是集合set,那么使用推导式则需要花括号:
In[42]: a = {x for x in 'abracadabra' if x not in 'abc'}
In[43]: a
Out[43]: {'d', 'r'}
In[44]: set(['d', 'r'])
Out[44]: {'d', 'r'}
6. 模块化
在大型项目中,模块化是非常重要的.但是我们导入模块时候有两种方式,假设我们定义了一个模块文件:
__author__ = 'lgt'
def fib(n):
a, b = 0, 1
while b < n:
print b,
a, b = b, a + b
def fib2(n):
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a + b
return result
一般情况下我们会这样导入:
In[15]: import fibo
In[16]: fibo.fib(100)
1 1 2 3 5 8 13 21 34 55 89
In[17]: fibo.fib2(100)
Out[17]: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
但是有时候为了书写方便,我们往往会这样导入:
In[18]: from fibo import fib, fib2
In[19]: from fibo import *
In[20]: fib(100)
1 1 2 3 5 8 13 21 34 55 89
In[21]: fib2(100)
Out[21]: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
但是这会隐藏一个BUG.例如我们在另一个文件fibo1.py中定义了相同的fib函数:
__author__ = 'lgt'
def fib(n):
print 'hello world'
那么那个模块最后导入,就使用那个模块:
In[22]: from fibo import *
In[23]: from fibo1 import *
In[24]: fib(100)
hello world
In[25]: from fibo1 import *
In[26]: from fibo import *
In[27]: fib(100)
1 1 2 3 5 8 13 21 34 55 89
所以,具体取舍还是得靠程序员自己.
__name__ == '__main__'的妙用
一般情况下,我们是需要对一个模块进行单元测试的编写的.这时我们往往可以在模块中添加类似如下指令:
if __name__ == '__main__':
import sys
fib(int(sys.argv[1]))
因为只有运行当前模块情况下,__name__才会等于'__main__',而如果其它模块调用它,则__name__等于'fibo'.
7. 序列化json
序列化用代码就可以说明:
# -*- coding:utf-8 -*-
import json
f = open('test.txt', 'w')
json.dump([1, 'simple', 'list'], f)
f.close()
with open('test.txt', 'r') as f:
for line in f:
print line
print type(line)
with open('test.txt', 'r') as f:
x = json.load(f)
print x
print type(x)
输出:
[1, "simple", "list"]
<type 'str'>
[1, u'simple', u'list']
<type 'list'>
8. 异常处理
异常通常使用try ... catch 来捕获.而else表示没有产生异常时必须执行的语句,finally表示无论是否发生异常,都会被执行的语句:
# -*- coding:utf-8 -*-
import sys
try:
a = 1 / 1
except (RuntimeError, TypeError, NameError) as e:
print 'handler error', e
else:
print 'no error'
finally:
print 'finish'
#输出:
#no error
#finish
而产生异常的代码:
# -*- coding:utf-8 -*-
import sys
try:
a = 1 / 0
except (RuntimeError, TypeError, NameError, ZeroDivisionError) as e:
print 'handler error', e
else:
print 'no error'
finally:
print 'finish'
#输出:
#finish
9. Classes
1. Python的作用域和命名空间
命名空间就是一堆对象的名字集合,而对象通常具有不同的生命周期.而作用域就是命名空间可以直接访问的文本域,且作用域通常是动态生成的(Python是解释性语言).
例如,我们定义一个函数func,则产生一个作用域:
# -*- coding:utf-8 -*-
def func():
x = 1
del x
这是,执行x = 1实际上就是将变量x绑定到函数func所产生的作用域中,而del x就是接触绑定.
2. 类对象
类对象支持两种操作: 属性引用和实例化.假设我们定义一个类:
class MyClass:
"""A simple example class"""
i = 12345
def __init__(self, i):
self.i = i
def f(self):
return 'hello world'
属性引用:
print MyClass.i #12345
MyClass.i = 10000
print MyClass.i #10000
而我们一般都使用实例化:
x = MyClass(12345)
print x.f() #hello world
print x.i #12345
3. 实例化变量
类变量针对类而言,而实例化变量针对具体对象而言.假设我们编写如下代码:
# -*- coding:utf-8 -*-
class Dog:
kind = 'canine'
def __init__(self, name):
self.name = name
d = Dog('Fido')
e = Dog('Buddy')
print d.kind #canine
print e.kind #canine
print d.name #Fido
print e.name #Buddy
这里类变量和实例变量互不影响.但假设我们这样编写代码:
# -*- coding:utf-8 -*-
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
print d.tricks #['roll over', 'play dead']
这不是新需求,是BUG......
所以我们一般都是这样设计:
# -*- coding:utf-8 -*-
class Dog:
def __init__(self, name):
self.name = name
self.tricks = []
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
print d.tricks #['roll over']
4. 继承
Python中的继承机制如C++的虚拟继承,即所有的函数都可以被重写,可直接被调用,或者根本不调用:
# -*- coding:utf-8 -*-
class BaseClass(object):
def __init__(self, name):
self.baseName = name
def show(self):
print self.baseName
def getName(self):
return self.baseName
def baseFunc(self):
print "I'm baseClass"
class SubClass(BaseClass):
def __init__(self, name, num):
super(SubClass, self).__init__(name)
self.subNum = num
def show(self):
super(SubClass, self).show()
print self.subNum
def getName(self):
return self.baseName + str(self.subNum)
C = SubClass('lcj', 26)
#继承父函数
C.show()
#调用自身函数
print C.getName()
#调用父函数
C.baseFunc()
输出:
lcj
26
lcj26
I'm baseClass
而Python中私有变量往往以__(双下划线开头),这在设计类的时候很有用:
# -*- coding:utf-8 -*-
class Mapping:
def __init__(self, iterable):
self.items_list = []
#私有变量
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update
class MappingSubclass(Mapping):
def update(self, keys, values):
for item in zip(keys, values):
self.items_list.append(item)
S = MappingSubclass([11, 12])
S.update(['a', 'b'], [1, 2])
#[11, 12, ('a', 1), ('b', 2)]
print S.items_list