谨以此blog记录我的python学习之路和给其他初学者参考。
我看的书是像计算机科学家一样思考Python,看起来B格很高,但其实非常好入门,并没有很理论(大概名字高级好卖一点 )。
一、程序之路
安装好python3,设好环境变量后,在命令行敲个python就可以进入码代码的模式了。新的语言自然是从Hello World开始
>>> print('Hello,World!') #输出Hello,World!
这个程序体现了三个信息。
- print()是输出啦~
- #后面是注释啦~
- 字符串可以用单引号括起来~(也可以用双引号)
Then Py还可以做一些类似计算器的工作(从此不用电脑的计算器
>>> 2 * 3 ** 3
54
>>> qwq = 9
>>> qwq / 3
3.0
>>> qwq // 2
4
我们可以发现我们输入一个表达式,会求出最终值给我们。这里的**是指数运算的意思,//是向下整除法。其他运算基本就是c语言了。(我学过c,c++。所以跟c,c++相似的就不赘余了
二、变量、表达式和语句
赋值,变量名称要求和c语言一样。表达式和语句的区分:
>>> n = 10 #语句
>>> n + 4 #表达式
14
语句通常没有值,表达式有最终值。
前面这些都是interactive mode下运行python,也就是一行一行地在命令行写并执行。还可以在文件里写,也就是script mode。两者区别就是表达式在script mode下不会输出他的值,想输出要print()。
字符串操作,有两个,+ 和 *。+ 就是拼接。*就是重复字符串几次(必须 * 整数。
>>> 'Mr'+'chen116'
'Mrchen116'
>>> 'BBZ' * 4
'BBZBBZBBZBBZ'
三、函数
代码说话
>>> type('454')
<class 'str'>
>>> type(5.5)
<class 'float'>
>>> int('49')
49
>>> float('23.8')
23.8
>>> int(-16.7)
-16
type()输出参数的类型,类型转换则c++类似,int(),str(),float(),三者可以相互转。
要用数学函数就要有数学的模块(module)
>>> import math
>>> math
<module 'math' (built-in)>
>>> math.sqrt(49)
7.0
>>> math.sin(math.pi/6) #弧度制,瞧他还有浮点误差
0.49999999999999994
可以看到math是一个module object。用’.'可以使用module object里面的变量和函数。值得注意的是,三角函数用的是弧度制,而不是角度。有了以上的知识,python是一个很好的计算器了
写函数~
>>> def print3(mySTR):
... print(mySTR*3)
...
>>> print3 #可见定义其实就是创建函数对象,但是不直接执行函数体的语句。
<function print3 at 0x000002684965D1E0>
>>> print3('您好~')
您好~您好~您好~
Py中函数def定义函数,括号内是参数,然后:后面就是函数体,函数体用缩进来表示,相当于c的{ }。后面就可以调用这个函数了。
然后形参跟实参的关系跟c类似啦,但由于形参的类型并没有固定,所以会跟随实参的类型。然后函数里面的变量包括形参都是局部变量啦~
这里的函数是无返回值的,如果强行用它的值,会发现他是None。不是字符串,而是None type的。
>>> res = print3('~')
~~~
>>> print(res)
None
>>> type(res)
<class 'NoneType'>
四、案例研究:接口设计
这一章比较好玩,用来熟悉语法。这一章就开始用script mode了,也就是搞个IDE玩。
介绍第二个module:turtle,用来画图。就是一只乌龟,走到哪,哪就留下一条边。
import turtle
bob = turtle.Turtle()
运行这个代码就会弹出白窗口,箭头就是小乌龟了!这里bob就是一个Turtle对象了,也可以做很多个Turtle对象,也就是会出现很多个箭头。
下面是对?的一些操作:
bob.fd(10) #向前走10px
bob.lt(90) #左转90度
bob.rt(60) #右转60度
然后我就用这个做了方形,圆形,菊花。。。
import turtle
import math
bob = turtle.Turtle()
qq= turtle.Turtle()
def polygon(t,r):
'''plot半径为r圆形'''
n=r
len = 2*r*math.pi/n
for i in range(n):
t.fd(len)
t.lt(360/n)
def arc(t,r,angle):
'''plot半径为r角度为angle的圆弧'''
n=r
len = 2*r*math.pi/n
for i in range(int(angle/360*n)):
t.fd(len)
t.lt(360/n)
def petal(t,len,angle):
'''plot长度为len展角为angle的花瓣'''
r=int(len/math.sin(angle/180*math.pi/2))
arc(t,r,angle)
t.lt(180-angle)
arc(t,r,angle)
def flower(t,num_petal,len):
'''plot num_petal个长度为len花瓣的花朵'''
angle=int(360/num_petal)
for i in range(num_petal):
petal(t,len,angle)
t.lt(180)
def square(t,len):
'''plot方形'''
for i in range(4):
t.fd(len)
t.lt(90)
square(qq,30)
flower(bob,12,150)
#while 1:
# i = input()
# if i == 'a':
# bob.lt(90)
# elif i=='d':
# bob.rt(90)
# elif i=='w':
# bob.fd(10)
# elif i=='s':
# bob.rt(180)
# bob.fd(10)
上面的三点,是多行字符串的意思,也就是可以保存换行
mySTR = '''Hello
World'''
print(mySTR)
五、条件和递归
逻辑操作符: and or not 即与或非啦~
条件
i = 24
if i > 0:
print('qwq')
elif i < 0:
print('QAQ')
else:
print('GB')
递归
def print_num(n):
print(n)
if n > 0:
print_num(n-1)
print_num(8)
输入 input() 函数,参数可有可无,为输入提示信息。
ans_me = input("What's your problem?\n") #字符串有'用双引号,\n是换行
print("This's your problem. " + ans_me)
六、有返回值函数
就是加个return 啦~
由于函数参数并没有限定,而很多时候需要指定类型的参数,就可以用到一个判断类型是否为某类型的函数isinstance(),返回类型是bool。
>>> b = 6
>>> c = 'v'
>>> isinstance(c,str)
True
>>> isinstance(b,str)
False
>>> isinstance(b,int)
True
七、迭代
用一个平方根,介绍一下while,break的用法。这里用牛顿迭代法做一个开平方跟熟悉这两个语句。
a = float(input())
x = a
epsilon = 1e-6
while True:
print(x)
y = (x + a/x) / 2
if abs(y-x) < epsilon:
break
x = y
print(x)
我学到这就开始有点无聊了
八、字符串
字符串就非常好玩了~~~
>>> fruit = 'apple'
>>> type(fruit[0])
<class 'str'>
>>> fruit[-1]
'e'
>>> len(fruit)
5
字符串就是字符序列,于是我就好奇那里面的单个字符是什么类型呢?结果是仍然是str。同时Py中下标也是从0开始。因缺思汀的是负整数同为合法的下标,从右往左编号,第一个是-1。len函数就是字符的个数。
切片:
>>> fruit[0:3] #冒号前后表示一个区间,包含左边的,但不包含右边的,因此没有'l'
'app'
>>> fruit[:3] #省略第一个,就是从头开始
'app'
>>> fruit[2:] #省略第二个,就是到结尾
'ple'
>>> fruit[4:0] #第二个≥第一个,就会是空串
''
for循环遍历
>>> for cha in fruit:
... print(cha)
...
a
p
p
l
e
特殊:字符串不能改变里面某一个字符
>>> fruit[1] = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
表明str作为一个object,是一个整体,而里面字符只是一个item。
字符串方法
>>> fruit.lower() #返回字母都变为小写
'apple'
>>> fruit.upper() #变成大写
'APPLE'
>>> fruit.find('pl') #查找字符串在原串中的首位置
2
>>> fruit.find('p',2) #接受第二个参数,为开始查找的位置
2
>>> fruit.islower() #返回是否全为小写字母,同样还有isupper()
True
>>> fruit.capitalize() #若第一个字符是字母,则将其变成大写;其他字母变小写
'Apple'
布尔操作符in
返回第一个str是不是第二个的子串。
>>> 'pl' in fruit
True
>>> 'awp' in fruit
False
于是就可以写出这种类似英文的代码,找出两个串中相同的字符。
def in_both(word1,word2):
for letter in word1:
if letter in word2:
print(letter)
in_both('What','the')
九、案例分析:文字游戏
代码说话
fin = open('Py1.py') #读入文件的方法,返回一个file
for line in fin: #逐行读入
print(line.strip()) #strip()方法是去除行首末的指定字符,未指定则默认空格和换行
逐行读取还有一个函数
fin.readline()
十、列表
列表一般用方括号括起来
>>> li = ['xixi', 2.5, 6, [4, 7]] #列表可以存各种类型
>>>> li[0] = 'haha' #这是合法的
>>>> 6 in li
True
>>> li + li[-1]
['haha', 2.5, 6, [4, 7], 4, 7]
>>> li[2:4]
[6, [4, 7]]
>>> li[2:4]
[6, [4, 7]]
可以看到列表还可以嵌套,而且列表元素是可变的。而且可以用in操作符。遍历方法同str差不多,都可以用遍历元素或遍历下标。同样的,列表有加法和乘法,与str一样的用法。切片的用法也一样。
列表方法:
见注释
>>> li.append('DIAO') # 添加一个元素
>>> li
['haha', 2.5, 6, [4, 7], 4, 7, 'DIAO']
>>> li.extend(['Q','E']) # 添加列表所有元素到里面
>>> li
['haha', 2.5, 6, [4, 7], 4, 7, 'DIAO', 'Q', 'E']
>>> bb = ['a','E','c','1','A']
>>> bb.sort() # 对列表内元素排序,无返回值
>>> bb
['1', 'A', 'E', 'a', 'c']
>>> bb.pop() # 删除最后一个元素,同时返回它!
'c'
>>> bb.pop(0) # 删除下标是0的元素,并将其返回
'1'
>>> del bb[0] # 若不需要其值,可以直接del
>>> bb
['E', 'a']
列表和字符串
>>> STR = 'split me'
>>> li = list(STR) # 把字符串分成单个字符的列表
>>> li
['s', 'p', 'l', 'i', 't', ' ', 'm', 'e']
>>> t = STR.split() # 此方法可按空格分开
>>> t
['split', 'me']
>>> STR = 'Go!Euler!Oh!'
>>> STR.split('!') # 同样可按你要求的分隔符
['Go', 'Euler', 'Oh', '']
对象和值
来一段诡异的代码:
>>> s = 'are'
>>> b = 'are'
>>> s is b
True
>>> l1 = [1,2,3]
>>> l2 = [1,2,3]
>>> l1 is l2
False
这里显示出s和b是同一个字符串的引用,而l1,l2不是同一个对象,只是值相同。有没有想起str不能改变,而list可以改变这个特性!?相信你知道了!
别名
理解了上面那个,看这个就更加诡异了~
>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>> b[0] = 6
>>> a
[6, 2, 3]
刚说完不同,这又来了个True,这证明b只是a的别名。当我改变b的时候,a也改变了。
列表参数
def del_head(li):
del li[0]
t = [1,2,3]
del_head(t)
print(t) # [2, 3]
当以列表为函数参数时,传的是引用,即改变形参,会影响实参的值
然鹅。。。
def del_head(li):
li = li[1:]
t = [1,2,3]
del_head(t)
print(t) # [1, 2, 3]
这又是为什么呢?因为切片时其实新建了一个新的列表,所以最后li已经不是实参的引用了。同样,下面的加法也创造了一个新list。
def add_end(li):
li = li + [7]
t = [1,2,3]
add_end(t)
print(t) # [1, 2, 3]
十一、字典
字典就是映射。犹如c++ STL中的map一样,下标可以是任意类型。(回顾一下,下标->键,另外一个是值
>>> di = dict() # 创建空字典
>>> di['one'] = 1 # 添加key-value pair
>>> di['two'] = 2
>>> di
{'one': 1, 'two': 2}
>>> di['one'] # 查找某个值
1
>>> di['four'] # 若没有就Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'four'
与STL map不同,Py的dict是用hashtable的方式存的。具体后面再谈。下面用统计单词的各个字符出现的次数的函数来进一步熟悉dict。
def func(s):
d = dict()
for c in s:
if c in d: # 判断一个键是否在字典中
d[c] += 1
else:
d[c] = 1
return d
myS = 'Tencent'
di =func(myS)
for x in sorted(di): # sorted函数会把di的键进行排序,返回值是一个键的list
print(x,di[x])
dict还有一个方法get(),接受一个键和一个默认值,若找不到对应键就返回默认值,所以上面的func() 也可以这么写
def func(s):
d = dict()
for c in s:
d[c] = d.get(c,0)+1
return d
反向遍历,实际上并没有太好的方法。
def reverse_lookup(d, v):
for k in d:
if d[k] == v:
return k
raise LookupError('value does not appear in the dictionary!')
di = {'a':1,'b':2,'c':3}
print(reverse_lookup(di,2))
这里有个新语句raise,它会产生一个异常,这要生成的异常是LookupError 这是一个内置异常。那个描述错误细节的参数是可选的。当找不到对应value是程序就会输出
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in reverse_lookup
LookupError: value does not appear in the dictionary!
字典与列表
列表可以是字典的一个值,下面举一个反转字典的例子。
def invert_dict(d):
inverse =dict()
for key in d:
val = d[key]
if val not in inverse:
inverse[val] = [key]
else:
inverse[val].append(key)
return inverse
di = {'a':1,'b':2,'c':1}
print(invert_dict(di))
输出为{1: ['a', 'c'], 2: ['b']}
。但是列表并不能作为字典的键,毕竟列表无法hash,而且它是可变的。从而你也可以猜到字典也是无法作为键的,因为它也是可变的。
全局变量
并不懂作者为什么放全局变量在字典这一章,可能没地方放吧
这里主要是讨论全局变量在函数中的问题。
在函数内若未声明全局变量,仍然可以使用它的值:
c = '乌里妈查'
def p():
print(c) # 正确
p()
然而,你想改变它的值,那就不行了:
c = '乌里妈查'
def p():
c = 'M . M'
p()
因为Py会默认c是新建的一个局部变量,并不会改变全局变量的值。基于此,下面这段代码就会报错了,因为局部变量c压根还没定义。
c = '乌里妈查'
def p():
c = c + '!' # 错误
p()
所以当我们要在函数内修改全局变量的值时,就要声明,告诉编译器这是全局变量,不要给我新建一个局部变量!
c = '乌里妈查'
def p():
global c
c = c + '!'
p()
但是,若全局变量是可变的值,你也可以不用声明就修改它。所以可以添加、删除和替换一个全局的列表或字典的元素。
d = {0:'a',8:'w'}
def p():
d[2] = 'Q'
p()
print(d)
但是,要想给全局变量重新赋值,则需要声明啦~
d = {0:'a',8:'w'}
def p():
global d # 必须声明
d = dict()
p()
print(d)
这样听起来会特别乱。概括起来就是全局变量不声明时,可以作为右值访问,不能整个对象作为左值,而要是全局变量是可变的,就可以改变其值,但仍然不能作为左值进行重新赋值。不明白莫得关系,多试试
十二、元组
元组各方面跟列表差不多,但是元组是不可变的!创建元组的几种方式如下:
t = 'a','b','c'
t = ('a','b','c')
t = tuple('abc')
上面三种都是等价的。tuple()不带参数是新建空元组,参数是序列(str,list,tuple)就会生成包含序列的元素的元组。
Py序列比较大小,从第一个元素开始,逐个比较到不同元素为止。
>>> (0,1,123)<(0,3,-9)
True
元组赋值
优雅地交换两个变量的值
b,a = a,b
魔性吧!左边是变量的元组,右边是表达式的元组。每个值会一一对应。但是左右必须变量个数相同,不然就Value Error了。更一般的,右边可以是任何序列。
address = 'Mr.chen@qq.com'
name,domain = address.split('@') # 返回list
上面的代码就可以把用户名和域名分开了。
遍历元组列表
t = [(1,11),(2,22),(3,33)]
for index,value in t:
print(index,value)
作为返回值的元组
严格说,函数只能返回一个值。但这个返回值可以是元组,那就跟返回多个值差不多了。内置函数divmod()就是返回了(商,余数)这个元组。
divmod(11,3)
(3, 2)
你也可以用return a,b,c
来写自己的函数。
可变长参数元组
函数可以接受不定个参数。只要形参前面加个*,所有参数都会 收集(gather)到这个元组上,反操作是分散(scatter),在调用形参不是接受元组,而是接受多个参数时,在实参元组前面加*。
def print_all(*arg):
print(arg)
tu = 11,3
print_all(tu)
print(divmod(*tu))
结果是:
((11, 3),)
(3, 2)
下面介绍一下,内置函数中哪些是接受可变长元组,哪些是不接受
max(1,2,4,6) #正确
min(1,2,4,6) #正确
sum(1,2,4,6) #错误
列表和元组
zip() 接受两个或多个序列,并返回一个元组列表。每个元组包含一个元素,就像拉链一样把各个序列里的元素对应起来。例如:
s = {2:4,3:9,4:64}
t = [5,6,7,8]
q = "What???"
tmp = zip(s,t,q)
print('tmp:',tmp)
for e3 in tmp:
print(e3)
print('list(tmp): ',list(tmp))
print('zip(s,t,q): ',list(zip(s,t,q)))
输出
tmp: <zip object at 0x000001FE8ADA3888>
(2, 5, 'W')
(3, 6, 'h')
(4, 7, 'a')
list(tmp): []
zip(s,t,q): [(2, 5, 'W'), (3, 6, 'h'), (4, 7, 'a')]
这反映的几个特性
- zip函数返回的是一个zip对象
- zip对象是一种迭代器,跟列表类似,但不能下标访问,而且只能遍历一遍( for里遍历了tmp,所以tmp最后为空
- 元组的个数有len最小的序列决定
- 元组里元素的顺序由参数顺序决定
- 要是序列是字典,实际上只用了键,没用值
- zip对象可以转换成list对象
zip和for组合起来就可以同时遍历多个序列。例如寻找是否有s1[i] == s2[i] 的程序
def has_match(s1,s2):
for a,b in zip(s1,s2):
if a == b:
return True
return False
案例研究:选择数据结构
这章主要在于如何灵活地应用各种Py核心数据结构。
读入文件拆分为单词,并转成小写
这里用到了string module的whitespace(空白字符串),punctuation(所有标点符号)。
import string
fin = open("text.txt")
out = ""
for line in fin:
for char in line:
if char in string.punctuation+string.whitespace:
out+=' ';
else:
out+=char.lower();
out+='\n'
outlist=out.split()
print(outlist)
统计文件中单词总数以及每个单词使用的次数
用这个去www.gutenberg.org扒书统计还是很有意思的。
import string
fin = open("text.txt")
out = ""
for line in fin:
for char in line:
if char.isalpha():
out+=char.lower();
else:
out+=' ';
out+='\n'
outlist=out.split()
dic = dict()
for word in outlist:
dic[word] = dic.get(word,0)+1
moreThan20Word = dict()
for word in dic:
if dic[word] >= 20:
moreThan20Word[word] = dic[word]
print(moreThan20Word)
然而看了标程后,羞愧不已。
import string
def process_file(filename):
hist = dict()
fp = open(filename)
for line in fp:
process_line(line,hist)
return hist
def process_line(line,hist):
line = line.replace('-',' ')
for word in line.split():
word = word.strip(string.whitespace+string.punctuation)
word = word.lower()
hist[word] = hist.get(word,0)+1
hist = process_file("text.txt")
随机数
文件
类和对象
类和函数
类和方法
继承