Python基础
字符串和编码
在计算机内存中,统一使用
Unicode
编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8
编码。用记事本编辑的时候,从文件读取的UTF-8
字符被转换为Unicode
字符到内存里,编辑完成后,保存的时候再把Unicode
转换为UTF-8
保存到文件;浏览网页的时候,服务器会把动态生成的Unicode
内容转换为UTF-8
再传输到浏览器。
一共有三种编码方式:ASCII
,UTF-8
,Unicode
。ASCII
只能表示英语,不能表示中文。Unicode
全世界都在用,不会存在乱码问题,但是占得地方比较大。UTF-8
可以兼容ASCII
码,也可以表示其他语言。
字符串与正则表达式
常用的关于字符串的函数:
1 | str1 = 'hello, world!' |
关于正则表达式,这里有一篇正则表达式30分钟入门教程,大家可以看一下。
函数的参数
实参与形参
在自己写程序的时候,遇到过这样一个问题:
1 | def mean_plus(lst): |
本来以为可以输出[1, 102, 3, 104, 5]
,可实际上却只输出了[1, 2, 3, 4, 5]
。
先看这一段代码:
1 | def swap(x, y): |
本来想用这段代码来交换a
和b
的值,可是却还是输出了他们之前的值。要想解答这个问题,先得知道实参与形参的概念。下面一幅图来解释参与形参,以及他们的关系。
比如这个例子中a
和b
就是实参,他们有着对应的值15
和50
。而swap(x, y)
中的x
和y
是形参,他们只是一个记号,并没有指向任何实际的值。
在执行swap(a, b)
时,形参(x, y)把a和b的值(15, 50)拿过来使用,并没有改变a对15,b对50的指向。所以像图上看到的一样,在执行print()语句时,结果并没有调换, 还仍然是15, 50。但是,当你执行print(swap(a, b))
时,你得到的结果将是50, 15,这两个值时由经过函数运算值之后的形参得来的,与实参没有什么关系。
以上就是在编写以及运行函数时需要注意的点,回到最开始的问题,其实都是大同小异的:
执行for i in list1:
时,判断条件在数组中找到了复数2,并且将i赋予了新的值。如果你在这时执行print(i)
,将会得到i = 102
,但是i
的值对整个数组毫无影响,当i++
时,i
又指向了3。所以更改i
并不能改变原数组的任何一个元素。如果需要类似功能可以使用map
函数。
位置参数
1 | def power(x, n): |
(x, n)
就是位置参数。
默认参数
1 | def power(x, n=2): |
n
就是默认参数。
可变参数
1 | def calc(numbers): |
这个函数可以传入多个参数,比如calc(1, 2, 3)
或者calc(1, 2, 3, 4, 5)
,那传入已有的list或者是tuple不是更方便🐎?要是想将元组numbers = (1, 2, 3, 4, 5)
中的各个元素传入calc()
的参数怎么办呢?所以Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去:
1 | numbers = (1, 2, 3, 4, 5) |
关键字参数
这个函数:
1 | def person(name, age, **kw): |
这个kw
是一个dict,你可以为此函数传入其他key以及他的value。它可以扩展函数的功能:比如,在person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。
当然,关键字参数也可以像可变参数*numbers
一样传入已有的dict:
1 | extra = {'city': 'Beijing', 'job': 'Engineer'} |
命名关键字参数
命名关键字参数就像是关键字参数的限制版本,如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数:
1 | def person(name, age, *, city, job): |
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。命名关键字参数必须传入参数名,没有传入时调用将报错。所以命名关键字参数可以有缺省值,从而简化调用。
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
生成器与杨辉三角
与list不同,生成器储存的不是一个个的元素,而是生成这个元素的算法。在generator执行过程中,遇到yield
就中断,下次执行时就会执行yield
之后的代码。
杨辉三角
这个也太难了,答案思路还都不太一样。我在网上搜到了一个挺好理解的答案:
1 | def triangle(): |
这个答案的重点在与N.append(0)
,就是将每一排的最右面一个1
的右边加了一个0
。
1 | [1] |
以第三排[1, 2, 1]
为例:第一个元素1 = L[0] + L[-1]
; 第三个元素1 = L[2] + L[1]
,这样就将看似特例的最两边的1
给普遍化了起来。
其实这样还是有点难想,还可以用insert()方法在前一行list首位插入一个0:
1 | def triangle(): |
再以第三排为例:第一个元素1 = L[0] + L[1]
; 第三个元素1 = L[2] + L[3]
,这样就好看多了。
1 | [0, 1, 0] |
斐波那契数列
回过头再看一下斐波那契数列(0, 1, 1, 2, 3, 5, 8, 13…):
1 | def fib(): |
斐波那契数列中的第3个数是前两个元素之和,所以说一个循环涉及到的变量是一定是只有3个元素的,而循环结束时,第2, 3个元素要在下个循环开始前又变成了下个循环的第1, 2个元素(a, b = b, a + b
)。
Python进阶
map&reduce函数
1 | def f(x): |
r
的数据类型类型type(r)
是map,如果想要list结果需要加上list(r)
.
1 | from functools import reduce |
reduce()使用时需要加载functools
库,而且reduce使用的函数一定(?)要有两个位置参数。
将字符串转换为浮点数
作业叭算是,弄了好久才弄明白
1 | from functools import reduce |
下面看到了更短的答案:
1 | def str2float(s): |
我的结果思路是把.
去掉在乘以小数确定小数点的位置,而这个答案用了str.split()
函数来将字符串分成了在list
中的两个部分,还用了ord(x)-48
替换掉了我的s2i(str)
函数。
关于split()
1 | str = "Line1-abcdef \nLine2-abc \nLine4-abcd"; |
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
1 | 'a b c'.split(' ') |
嗯,无法识别连续的空格,用正则表达式试试:
1 | re.split(r'\s+', 'a b c') # ['a', 'b', 'c'] |
filter()函数
Python内建的filter()
函数用于过滤序列。filter()
接收一个函数和一个序列,返回一个迭代器对象。这个函数接收的序列是一个可迭代对象:可以是list,也可以是生成器。
下面是一道求素数的练习:
1 | def _odd_iter(): |
这个练习用的计算素数的方法是埃氏筛法,大致原理就是_odd_iter()
函数生成的第一个数,然后判断并扣除这列数中是否有可以被它整除的数,如此往复循环(而不是只到7就结束了)。
求素数
在网上搜到的求素数还有其他的方法:
1 | from math import sqrt |
判断一个整数N是否是素数,其实不需要对区间$[2, N-1]$所有的元素进行判断,只需要判断区
间$[2,\sqrt{N}]$里是否存在元素能够整除N即可。
反证法证明如下:
设:$i * p = N$ , 且$i, p$均大于$\sqrt{N}$,那么不妨设:$\sqrt{N}<p<i<N$
则:
与$p<i$矛盾。
今天是九月十号,我又看到了一种更简单的方法,感觉只有老油条才能做出来:
1 | a = [i for i in range(2,101)] |
闭包&装饰器
闭包
先从闭包的作用说起:
var
变量写在了函数外面,是全局变量,可以被函数引用,运行不会报错:
1 | var = 100 # var全局变量 |
var
可以在函数内部被重新赋值,但是函数内部的var
变量是另起炉灶,跟第一行的var = 100
没有一丁点关系。当函数完成他的工作时,函数内部的var
变量会被删除掉。也就是说,在这个程序中,没有变量等于200了。所以print(var)
的结果是100。
1 | var = 100 # var全局变量 |
在下面的例子中,会抛出错误。原因其实跟上面的是一样的,函数模块中的var = var + 100
跟上面的全局变量没有一点关系。function()
函数根本够不到var = 100
,也不知道自己肚子里这个var
已经有值了。如果把函数模块中的var = var + 100
代码修改为大sb = 大sb + 100
同样也会报一样错误。
1 | var = 100 # var全局变量 |
那我就想用一下全局变量var
,那怎么办呢?可以用global var
语句,让function()
函数知道自己肚子里的这个var
到底是谁:
1 | var = 100 # var全局变量 |
这个例子结束了,再来看看下面的例子:
1 | def maker(step): #外部函数 |
前面说到了,当函数完成他的工作时,函数内部的变量会被删除掉。但是我就是想留着他以后用,怎么办呢?闭包的作用就出现了:保存函数的状态信息,使函数的局部变量信息依然可以保存下来。
分析一下这个例子:maker(step)
是外部函数,num
是外部函数的临时变量。nonlocal num
的作用是让内部函数认识num
变量。maker(step)
函数的返回值是fun
,而fun
只是一个函数而已,需要将maker(step)
赋值给一个变量func
,再通过调用func
来调用fun
函数。
我通过调用了四次func()
来调用fun
函数,得到的答案不同:1+3=4、4+3=7、7+3=10、10+3=13。你的每次调用,num
都被记录了下来,而不是像之前例子中的var= 200
一样,调用完就不见了。
练习:廖雪峰老师的练习再这样看来就很简单了:
1 | def createCounter(): |
如果还有不会的地方,可以参考这个博客,讲的很详细。
装饰器
理解了闭包,装饰器就很简单了:
1 | def func(fun): |
装饰器主要是用来为现有函数增加功能,而不破坏现有函数。其中@func
被称作装饰器。两层的装饰器很好观察与理解,注意的是层函数返回的是函数,而内层函数返回的是函数调用:@func
相当于func(myprint)()
。
如果装饰器需要传入参数,可就麻烦了,可以看看下面的例子:
1 | def func(sex): |
如果需要装饰器根据装饰的函数不同进行不同的判断,就需要三层的闭包结构。看起来很复杂,其实就是多一层外面的函数来进行参数接收而已。
如果还有不会的地方,可以参考这个视频教程,讲的很详细。