python学习笔记(二) ——控制流工具

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 中使用,而不产生歧义。

  1. 使用仅限位置形参,可以让用户无法使用形参名。形参名没有实际意义时,强制调用函数的实参顺序时,或同时接收位置形参和关键字时,这种方式很有用。

  2. 当形参名有实际意义,且显式名称可以让函数定义更易理解时,阻止用户依赖传递实参的位置时,才使用关键字。

  3. 对于 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() 原本的文档字符串将会丢失。

七、编码风格

  1. 缩进,用 4 个空格,不要用制表符。

  2. 4 个空格是小缩进(更深嵌套)和大缩进(更易阅读)之间的折中方案。制表符会引起混乱,最好别用

  3. 换行,一行不超过 79 个字符。
    这样换行的小屏阅读体验更好,还便于在大屏显示器上并排阅读多个代码文件。

  4. 用空行分隔函数和类,及函数内较大的代码块。

  5. 最好把注释放到单独一行。

  6. 使用文档字符串。

  7. 运算符前后、逗号后要用空格,但不要直接在括号内使用: a = f(1, 2) + g(3, 4)。

  8. 类和函数的命名要一致;按惯例,命名类用 UpperCamelCase,命名函数与方法用 lowercase_with_underscores。命名方法中第一个参数总是用 self (类和方法详见 初探类)。

  9. 编写用于国际多语环境的代码时,不要用生僻的编码。Python 默认的 UTF-8 或纯 ASCII 可以胜任各种情况。

  10. 同理,就算多语阅读、维护代码的可能再小,也不要在标识符中使用非 ASCII 字符。
    参考资料:FishC PEP8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值