数据类型和变量
Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来。对变量赋值x = y是把变量x指向真正的对象,该对象是变量y所指向的。随后对变量y的赋值不影响变量x的指向。
-
python能直接处理的数据类型:整数、浮点数、字符串、布尔值、空值(None)、变量、常量
**注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在-2147483648-2147483647。Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大). -
python中可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,这种变量本身类型不固定的语言称之为动态语言;静态语言在定义变量时必须指定变量类型,如果赋值时类型不匹配,就会报错,例如Java就是静态语言。
-
不可变数据对象:整型数值、浮点型数值、字符串、常量、布尔型 可变数据对象:list、dict
注意元组是不可变对象,但其可以包含可变对象如tup = (1,2,[1,2,3])
包含了可变对象[1,2,3]
函数的参数
注意:定义默认参数要牢记一点:默认参数必须指向不变对象!
原因:不变对象一旦创建,对象内部的数据就不可修改,减少了由于数据修改而造成的错误。
def add_end(L = []):
L.append("end")
return L
# 第一次调用函数
print(add_end())
# 第二次调用函数
print(add_end())
# 第三次调用函数
print(add_end())
结果:
[‘end’]
[‘end’, ‘end’]
[‘end’, ‘end’, ‘end’]
如果改成 L= None 即 形参指向不可变对象
def add_end(L = None):
if L == None:
L = []
L.append("end")
return L
# 第一次调用函数
print(add_end())
# 第二次调用函数
print(add_end())
# 第三次调用函数
print(add_end())
结果为:
[‘end’]
[‘end’]
[‘end’]
可变参数
*nums表示将nums所表示的列表或者元组中的元素分别做为形参传入函数,形参前加 * 表示将参数组合成列表或者元组传入函数中
def cals(*nums):
s = 0
for i in nums:
s = s + i
return s
nums = [1,2,3,4]
print(cals(1,2,3,4))
print(cals(*nums))
关键字参数
可变参数允许传入0个或任意个参数,这些可变参数自动组装成tuple,而关键字参数允许传入0个或任意个带参数名的参数,这些参数自动组装成dict。
def person(name, age, **kw):
print('name', name, 'age', age, 'other', kw)
other = {'city': 'china', 'skill': 'python'}
person('Adam', 30, **other)
结果
name Adam age 30 other {‘city’: ‘china’, ‘skill’: ‘python’}
**other表示把other这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是other的一份拷贝,对kw的改动不会影响到函数外的other。
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
递归
- 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
- 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。因此要尽量写成尾递归的形式。
- 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
# 斐波那契数列 1,1,2,3,5,8.... 求其第 n 项
# 普通递归
def fi(n):
if n == 1 or n == 2:
return 1
return fi(n-1) + fi(n-2)
print(fi(7))
# 尾递归 注意两个出口条件 num1, num2
def fi(n, num1, num2):
if n == 1:
return num1
if n == 2:
return num2
return fi(n-1, num2, num1 + num2)
print(fi(7, 1, 1))
迭代
python中采用for… in…来迭代可迭代对象,如list, dict, tuple
如何判断是否为可迭代对象:
from collections.abc import Iterable
print(isinstance([1,2,3], Iterable))
print(isinstance((1,2,3), Iterable))
print(isinstance('123', Iterable))
print(isinstance(123, Iterable))
True
True
True
False
生成器
- generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误
>> g = (x for x in range(3)) # 将生成式[]变成()即变成一个生成器对象
>>> g # 不断调用next可获得生成器对象的元素,当所有元素取完后,继续调用next将报StopIteration
<generator object <genexpr> at 0x000001BFC1CBDE08>
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
generator函数
# 以斐波那契数列为例,打印斐波那契数列,普通函数
def fi(max):
n, a, b = 0, 0, 1
while(n<=max):
print(b)
a, b = b, a+b
n = n+1
return 'done'
# generator函数
def fi1(max):
n, a, b = 0, 0, 1
while(n<=max):
yield b
a, b = b, a+b
n = n+1
return 'done'
调用生成器函数产生一个生成器对象o,调用next(o) 获取该生成器对象的值,遇到yield就中断并返回一个值,下次继续调用next 获得下一个值
**注意:**调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
def fi1(max):
n, a, b = 0, 0, 1
while(n<=max):
yield b
a, b = b, a+b
n = n+1
return 'done'
f = fi1(4)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
>>> 1
1
2
3
5
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-13-8408b21d6099> in <module>()
12 print(next(f))
13 print(next(f))
---> 14 print(next(f))
StopIteration: done
例子
杨辉三角定义如下:
1
/ \
1 1
/ \ / \
1 2 1
/ \ / \ / \
1 3 3 1
/ \ / \ / \ / \
1 4 6 4 1
把每一行看做一个list,试写一个generator,不断输出下一行的list:
# 原来的写法
def tri(n):
i = 0
while i < 2:
yield [1]*(i+1)
i = i+1
lis = [[1]*i for i in range(1, n+1)] # 注意定义二维数组的位置
for i in range(2, n):
for j in range(i+1):
if i > j and j != 0:
lis[i][j] = lis[i-1][j-1] + lis[i-1][j]
yield lis[i]
return 'done'
# 更加优雅的写法
def triangles(max):
n = 0
l = [1]
while n < max:
yield l
a = [1] # 必须在yield之后
b = [l[i] + l[i + 1] for i in range(len(l) - 1)]
a.extend(b)
a.append(1)
l = a
n += 1
return 'done'
迭代器
-
凡是可作用于for循环的对象都是Iterable类型;
-
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
-
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
可迭代对象如list、dict、tuple、set是可迭代的(iterable)但是他们不是迭代器(iterator),即他们不能用next()函数返回下一个数据元素,原因是Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数,即将一个函数作为参数传给另一个函数。把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
返回函数
继承和多态
不同于静态语言的继承和多态,动态语言具有鸭子类型特性,比如如下方法在接收一个对象作为参数时,静态语言只接受实现了read()方法的此对象及其子类,而python中却没有如此严格的规定,只要一个对象且实现了read()方法,way()方法都能被正确执行:
def way(object):
object.read()
实例属性和类属性
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object):
count = 0
def __init__(self, name):
self.name = name
# class内部的变量不是简单的引用关系,
# 用global或nonlocal声明都无效,
# 类属性调用必须使用类名.属性名进行调用,
# 无论是在封装的内部还是在外部都一概如此。
Student.count += 1
使用@property
Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property # 将方法变成实例属性 score
def score(self):
return self._score # 注意返回的实例变量不能和方法名一致,否则会导致无限递归
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 100
s.score
>>> 100
属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
s = Student()
s.birth
>>> RecursionError: maximum recursion depth exceeded while calling a Python object
这是因为调用s.birth时,首先转换为方法调用,在执行return self.birth时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError。