递归
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
栈
迭代
Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
闭包
在高阶函数中,内部的函数可以引用外部函数的参数及局部变量,并且返回内部函数,而不是函数的值,这种情况,称为闭包。
def count(): # 这种形式要避免!
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count() # 结果f1,f2,f3 等于9, 9, 9 . 且不能直接写 f4 = count() 这样的赋值式,必须3个变量一起
全部都是 9!原因就在于返回的函数引用了变量 i,但它并非立刻执行。 等到 3 个函数都返回时,它们所引用的变量 i 已经变成了 3,因此最终 结果为 9。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后 续会发生变化的变量。
模块
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代 码就会越来越长,越来越不容易维护。
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里, 这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织 代码的方式。在 Python 中,一个.py
文件就称之为一个模块(Module)。
使用模块有什么好处?
最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开 始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的 时候,也经常引用其他模块,包括 Python 内置的模块和来自第三方的 模块。
使用模块还可以避免函数名和变量名冲突。
举个例子,一个abc.py
的文件就是一个名字叫abc
的模块,一个xyz.py
的文件就是一个名字叫xyz
的模块。
现在,假设我们的abc
和xyz
这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如mycompany
,按照如下目录存放:
mycompany
├─ init.py
├─ abc.py
└─ xyz.py
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py
模块的名字就变成了mycompany.abc
,类似的,xyz.py
的模块名变成了mycompany.xyz
。(⚠️有时候的模块引用出现大量xx.xx.xx.xx的形式可能是模块多重顶层包名,而不要想成是函数调用xxx.xxx)
像列表一样去读取斐波那契数列,用面向对象表示
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
定制类__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。
比如定义 Student 类:
class Student(object):
def __init__(self):
self.name = 'Michael'
# 调用 name 属性,没问题,但是,调用不存在的 score 属性,就有问题了:
>>> s = Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'score'
要避免这个错误,除了可以加上一个 score 属性外,Python 还有另一个 机制,那就是写一个__getattr__()
方法,动态返回一个属性。修改如下:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
# 当调用不存在的属性时,比如 score,Python 解释器会试图调用 __getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回 score 的值:
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
返回函数也是完全可以的:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25 # 只是调用方式要变为:
>>> s.age()
25
注意,只有在没有找到属性的情况下,才调用__getattr__
,已有的属性,比如 name,不会在__getattr__
中查找。
此外,注意到任意调用如 s.abc
都会返回 None,这是因为我们定义的 __getattr__
默认返回就是 None。要让 class 只响应特定的几个属性,我 们就要按照约定,抛出 AttributeError
的错误
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需 要任何特殊手段。
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全 动态的情况作调用。
现在很多网站都搞 REST API,比如新浪微博、豆瓣啥的,调用 API 的
URL 类似:
http://api.server/user/friends
http://api.server/user/timeline/list
如果要写 SDK,给每个 URL 对应的 API 都写一个方法,那得累死,而
且,API 一旦改动,SDK 也要改。
利用完全动态的__getattr__
,我们可以写出一个链式调用:
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__ 试试:
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
# 这里结果第一个 / 前应该有个值,是 self._path,但是Chain()没有传参,默认path = ' ',空的了