Python系列文章目录
第一章 python学习笔记(一) ——变量
第二章 python学习笔记(二) ——控制流工具
第三章 python学习笔记(三) ——数据结构
第四章 python学习笔记(四) ——模块
文章目录
控制流工具
一、if
>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
... x = 0
... print('Negative changed to zero')
... elif x == 0:
... print('Zero')
... elif x == 1:
... print('Single')
... else:
... print('More')
More
二、for
Python 的 for 语句与 C 或 Pascal 中的不同。Python 的 for 语句不迭代算术递增数值(如 Pascal),或是给予用户定义迭代步骤和结束条件的能力(如 C),而是在列表或字符串等任意序列的元素上迭代,按它们在序列中出现的顺序。
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategy: Iterate over a copy
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategy: Create a new collection
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
三、range()函数
生成的序列绝不会包括给定的终止值;range(10) 生成 10 个值——长度为 10 的序列的所有合法索引,左包括右不包括,即0~9。range 可以不从 0 开始,且可以按给定的步长递增(即使是负数步长):
代码如下(示例):
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(-10, -100, -30))
[-10, -40, -70]
如果直接打印一个 range 会发生意想不到的事情:
>>> range(10)
range(0, 10)
range() 返回的对象在很多方面和列表的行为一样,但其实它和列表不一样。该对象只有在被迭代时才一个一个地返回所期望的列表项,**并没有真正生成过一个含有全部项的列表,**从而节省了空间。
四、enumerate(iterable, start=0)
返回一个枚举对象。iterable 必须是一个序列,或 iterator,或其他支持迭代的对象。 enumerate() 返回的迭代器的 next() 方法返回一个元组,里面包含一个计数值(从 start 开始,默认为 0)和通过迭代 iterable 获得的值。
>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
等价于
def enumerate(iterable, start=0):
n = start
for elem in iterable:
yield n, elem
n += 1
五、循环中的 break、continue 语句及 else 子句
-
break 语句将跳出最近的一层 for 或 while 循环。
-
for 或 while 循环可以包括 else 子句。
-
在 for 循环中,else 子句会在循环成功结束最后一次迭代之后执行。
-
在 while 循环中,else子句会在循环条件变为假值后执行。
-
无论哪种循环,如果因为 break 而结束,那么 else 子句就 不会 执行。
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
六、函数
(1) 默认值参数
默认值在 定义 作用域里的函数定义中求值,所以:
i = 5
def f(arg=i):
print(arg)
i = 6
f()
上例输出的是 5。
重要警告: 默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果。例如,下面的函数会累积后续调用时传递的参数:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
输出结果如下:
[1]
[1, 2]
[1, 2, 3]
不想在后续调用之间共享默认值时,应以如下方式编写函数:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
(2)关键字参数
-
函数调用时,关键字参数必须跟在位置参数后面。
-
所有传递的关键字参数都必须匹配一个函数接受的参数(比如,actor 不是函数 parrot 的有效参数),
-
关键字参数的顺序并不重要。这也包括必选参数,(比如,parrot(voltage=1000) 也有效)。
-
不能对同一个参数多次赋值,
-
最后一个形参为 **name 形式时,接收一个字典(详见 映射类型 — dict),该字典包含与函数中已定义形参对应之外的所有关键字参数。**name 形参可以与 *name 形参(下一小节介绍)组合使用(*name 必须在 **name 前面), *name 形参接收一个 元组,该元组包含形参列表之外的位置参数。例如,可以定义下面这样的函数:
def cheeseshop(kind,*arguments,**keywords):
print('--Do you have any',kind,"?")
print("I'm sorry, we're all out of",kind)
for arg in arguments:
print(arg)
print('-'*40)
for kw in keywords:
print(kw,":",keywords[kw])
cheeseshop("Limburger","It's very runny,sir.",
"It's really very,VERY runny,sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
--Do you have any Limburger ?
I'm sorry, we're all out of Limburger
It's very runny,sir.
It's really very,VERY runny,sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
默认情况下,参数可以按位置或显式关键字传递给 Python 函数。为了让代码易读、高效,最好限制参数的传递方式,这样,开发者只需查看函数定义,即可确定参数项是仅按位置、按位置或关键字,还是仅按关键字传递。
(3) 特殊参数
函数定义如下:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
/ 和 * 是可选的。这些符号表明形参如何把参数值传递给函数:位置、位置或关键字、关键字。关键字形参也叫作命名形参。
-
此处再介绍一些细节,特定形参可以标记为 仅限位置。仅限位置 时,形参的顺序很重要,且这些形参不能用关键字传递。仅限位置形参应放在 / (正斜杠)前。/ 用于在逻辑上分割仅限位置形参与其它形参。如果函数定义中没有 /,则表示没有仅限位置形参。
-
/ 后可以是 位置或关键字 或 仅限关键字 形参。
-
把形参标记为 仅限关键字,表明必须以关键字参数形式传递该形参,应在参数列表中第一个 仅限关键字 形参前添加 *。
下面的函数定义中,kwds 把 name 当作键,因此,可能与位置参数 name 产生潜在冲突:
def foo(name, **kwds):
return 'name' in kwds
调用该函数不可能返回 True,因为关键字 ‘name’ 总与第一个形参绑定。例如:
>>>foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
加上 / (仅限位置参数)后,就可以了。此时,函数定义把 name 当作位置参数,‘name’ 也可以作为关键字参数的键:
>>>def foo(name, /, **kwds):
... return 'name' in kwds
...
>>>foo(1, **{'name': 2})
True
*换句话说,**仅限位置形参的名称可以在 kwds 中使用,而不产生歧义。
-
使用仅限位置形参,可以让用户无法使用形参名。形参名没有实际意义时,强制调用函数的实参顺序时,或同时接收位置形参和关键字时,这种方式很有用。
-
当形参名有实际意义,且显式名称可以让函数定义更易理解时,阻止用户依赖传递实参的位置时,才使用关键字。
-
对于 API,使用仅限位置形参,可以防止未来修改形参名时造成破坏性的 API 变动。
(4) 任意实参列表
它们通常在形参列表的末尾。*args 形参后的任何形式参数只能是仅限关键字参数,即只能用作关键字参数,不能用作位置参数:
>>> def concat(*args, sep="/"):
... return sep.join(args)
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
(5) 解包实参列表
函数调用要求独立的位置参数,但实参在列表或元组里时,要执行相反的操作。例如,内置的 range() 函数要求独立的 start 和 stop 实参。如果这些参数不是独立的,则要在调用函数时,用 * 操作符把实参从列表或元组解包出来:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
同样,字典可以用 ** 操作符传递关键字参数:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
(6)Lambda表达式
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1]) #按字母升序排序
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
(7)文档字符串
- 第一行应为对象用途的简短摘要。为保持简洁,不要在这里显式说明对象名或类型,因为可通过其他方式获取这些信息(除非该名称碰巧是描述函数操作的动词)。这一行应以大写字母开头,以句点结尾。
- 文档字符串为多行时,第二行应为空白行,在视觉上将摘要与其余描述分开。后面的行可包含若干段落,描述对象的调用约定、副作用等。
- Python 解析器不会删除 Python 中多行字符串字面值的缩进,因此,文档处理工具应在必要时删除缩进。这项操作遵循以下约定:文档字符串第一行 之后 的第一个非空行决定了整个文档字符串的缩进量 (第一行通常与字符串开头的引号相邻,其缩进在字符串中并不明显,因此,不能用第一行的缩进),然后,删除字符串中所有行开头处与此缩进“等价”的空白符。不能有比此缩进更少的行,但如果出现了缩进更少的行,应删除这些行的所有前导空白符。转化制表符后(通常为 8 个空格),应测试空白符的等效性。
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
(8)函数注解
标注 以字典的形式存放在函数的 annotations 属性中而对函数的其他部分没有影响。 形参标注的定义方式是在形参名后加冒号,后面跟一个会被求值为标注的值的表达式。 返回值标注的定义方式是加组合符号 ->,后面跟一个表达式,这样的校注位于形参列表和表示 def 语句结束的冒号。 下面的示例有一个必须的参数、一个可选的关键字参数以及返回值都带有相应的标注:
def f(ham: str, eggs: str = 'eggs') -> str:
print("Annotations:", f.__annotations__)
print("Arguments:", ham, eggs)
return ham + ' and ' + eggs
f('spam')
输出:
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
(9)闭包
a. 利用嵌套函数的外层作用域具有记忆能力这个特效,让数据保存在外层函数的参数或者变量中;
b. 将内层函数作为返回值给返回,这样就可以从外部间接调用到内层的函数
(10)装饰器
把函数作为参数传递给其他函数。
import time
def time_master(func):
def call_func():
print('开始运行程序')
start = time.time()
func()
end = time.time()
print('程序运行了{}秒。'.format(end-start))
return call_func
@time_master
def myfunc():
time.sleep(2)
print('hello!')
#@time_master等价于在此处 myfunc=time_master(myfunc) ,本质上是
myfunc()#不是调用myfunc而是把myfunc作为参数塞到装饰器(time_master)里面,\
#然后去调用这个装饰器
可以使用多个装饰器,例如:
def add(func):
def inner():
x=func()
return x+1
return inner
def cube(func):
def inner():
x=func()
return x**3
return inner
def square(func):
def inner():
x=func()
return x**2
return inner
@add
@cube
@square
def test():
return 2
print(test())
通过嵌套给装饰器传递参数,如:
import time
def logger(msg):
def time_master(func):
def call_func():
print('开始运行程序')
start = time.time()
func()
end = time.time()
print('{}程序运行了{}秒。'.format(msg,end-start))
return call_func
return time_master
@logger(msg='A')
def funA():
time.sleep(1)
print('Hello!')
@logger(msg='B')
def funB():
time.sleep(1)
print('HelloWorld!')
# funA=logger(msg='A')(funA)
# funB=logger(msg='B')(funB)
funA()
funB()
(11)递归
函数自己调用自己
def fibIter(n):
a=1
b=1
c=1
while n > 2:
c = a + b
a = b
b = c
n -= 1
return c
print(f'迭代方法的结果:{fibIter(12)}')
def fibRecur(n):
if n==1 or n==2:
return 1
else:
return fibRecur(n-1)+fibRecur(n-2)
print(f'递归方法的结果是{fibRecur(12)}')
迭代的运算速度比递归要快很多。
我们使用递归实现汉诺塔:
def hanoi(n,x,y,z):
if n == 1:
print(x,'->',z)#如果只有1层,直接将金属片从x移到z
else:
hanoi(n-1,x,z,y)#将x上n-1个金属片移动到y
print(x,'->',z)#将x上1个金属片移动到z
hanoi(n-1,y,x,z)#将y上n-1个金属片移动到z
n = int(input('请输入汉诺塔的层数:'))
hanoi(n,'A','B','C')
输出
请输入汉诺塔的层数:3
A -> C
A -> B
C -> B
A -> C
B -> A
B -> C
A -> C
(12)高阶函数–functools
functools 模块应用于高阶函数,即参数或(和)返回值为其他函数的函数。 通常来说,此模块的功能适用于所有可调用对象。
- 偏函数
functools.partial(func, /, *args, **keywords)
返回一个新的 部分对象,当被调用时其行为类似于 func 附带位置参数 args 和关键字参数 keywords 被调用。 如果为调用提供了更多的参数,它们会被附加到 args。 如果提供了额外的关键字参数,它们会扩展并重载 keywords。 大致等价于:
def partial(func, /, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = {**keywords, **fkeywords}
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
partial() 会被“冻结了”一部分函数参数和/或关键字的部分函数应用所使用,从而得到一个具有简化签名的新对象。 例如,partial() 可用来创建一个行为类似于 int() 函数的可调用对象,其中 base 参数默认为二:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
- reduce函数
- 本质上就是闭包。
functools.reduce(function, iterable[, initializer])
将两个参数的 function 从左至右积累地应用到 iterable 的条目,以便将该可迭代对象缩减为单一的值。 例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 是计算 ((((1+2)+3)+4)+5) 的值。 左边的参数 x 是积累值而右边的参数 y 则是来自 iterable 的更新值。 如果存在可选项 initializer,它会被放在参与计算的可迭代对象的条目之前,并在可迭代对象为空时作为默认值。 如果没有给出 initializer 并且 iterable 仅包含一个条目,则将返回第一项。
大致相当于:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
请参阅 itertools.accumulate() 了解有关可产生所有中间值的迭代器。
- 函数装饰器
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
这是一个便捷函数,用于在定义包装器函数时发起调用 update_wrapper() 作为函数装饰器。 它等价于 partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)。 例如:
>>> from functools import wraps
>>> def my_decorator(f):
... @wraps(f)
... def wrapper(*args, **kwds):
... print('Calling decorated function')
... return f(*args, **kwds)
... return wrapper
>>> @my_decorator
def example():
"""Docstring"""
print('Called example function')
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
如果不使用这个装饰器工厂函数,则 example 函数的名称将变为 'wrapper',并且 example() 原本的文档字符串将会丢失。
七、编码风格
-
缩进,用 4 个空格,不要用制表符。
-
4 个空格是小缩进(更深嵌套)和大缩进(更易阅读)之间的折中方案。制表符会引起混乱,最好别用。
-
换行,一行不超过 79 个字符。
这样换行的小屏阅读体验更好,还便于在大屏显示器上并排阅读多个代码文件。 -
用空行分隔函数和类,及函数内较大的代码块。
-
最好把注释放到单独一行。
-
使用文档字符串。
-
运算符前后、逗号后要用空格,但不要直接在括号内使用: a = f(1, 2) + g(3, 4)。
-
类和函数的命名要一致;按惯例,命名类用 UpperCamelCase,命名函数与方法用 lowercase_with_underscores。命名方法中第一个参数总是用 self (类和方法详见 初探类)。
-
编写用于国际多语环境的代码时,不要用生僻的编码。Python 默认的 UTF-8 或纯 ASCII 可以胜任各种情况。
-
同理,就算多语阅读、维护代码的可能再小,也不要在标识符中使用非 ASCII 字符。
参考资料:FishC PEP8