5. 流程控制、函数、高级特性

一.流程控制

学习Demo

1.1 if 语句

可能会有零到多个 elif 部分,else 是可选的。关键字 ‘elif’ 是 ’else if’ 的缩写,这个可以有效地避免过深的缩进。if … elif … elif … 序列用于替代其它语言中的 switchcase 语句。

age = 60
if age >= 60:
    print('老年人')
elif age >= 35:
    print('中年人')
elif age >= 18:
    print('青年')
else:
    print('未成年人')
1.2 for 语句

Python 中的 for 语句和 C 或 Pascal 中的略有不同。通常的循环可能会依据一个等差数值步进过程(如 Pascal),或由用户来定义迭代步骤和中止条件(如 C ),Python 的 for 语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代。例如(没有暗指):

words = ['cat', 'window', 'defenestrate']
#  倒序输出(切片的方式实现)
for w in words[::-1]:  #  参数1:起始索引(默认值:0);参数2:终止索引(默认值:len - 1);
                       # 参数3:步长值为-1,表示反向获取(若为2则代表取2的倍数的值)
    print(w)
1.3 while
n = 99
while n > 1:
    print(n)
    n -= 20
"""
while 循环使用 else 语句
如果else语句和while循环语句一起使用,则当条件变为 False 时,则执行else语句
"""
count = 0
while count < 5:
    print(count, '小于5')
    count += 1
else:  
    print(count, '大于或等于5')
1.4 range()

如果你需要一个数值序列,内置函数 range() 会很方便,它生成一个等差级数链表:

for i in range(5):
    print(i)  #  0 1 2 3 4

#  1.使用range指定区间的值
for i in range(3,5):
    print(i)  #  3 4

#  2.使range以指定数字开始并指定不同的增量(甚至可以是负数,有时这也叫做'步长')
for i in range(0,10,3):
    print(i)  #  0 3 6 9

#  3.结合range()和len()函数以遍历一个序列的索引
a = ['Google', 'Baidu', 'Runoob', 'Taobao']
for i in range(len(a)):
    print(i, a[i])  #  0 Google  1 Baidu  2 Runoob 3 Taobao

#  4.使用内置 enumerate 函数进行遍历
sequence = [12, 34, 34, 23]
for i, j in enumerate(sequence):
    print(i, j)
"""
0 12
1 34
2 34
3 23
"""
#  5.使用range()函数来创建一个列表
print(list(range(3)))  #  [0, 1, 2]
1.5 break

break 语句可以跳出 for 和 while 的循环体。如果你从 for 或 while 循环中终止,任何对应的循环 else 块将不执行

var = 10
while var > 0:
    print ('当期变量值为 :', var)
    var = var -1
    if var == 5:
        break  #  若不处理判断逻辑,去掉此句会报错
1.6 continue

continue语句被用来告诉Python跳过当前循环块中的剩余语句,然后继续进行下一轮循环

var1 = 5
while var1 > 0:
    var1 = var1 -1
    if var1 == 2:   # 变量为 2 时跳过输出
        continue
    print ('当前变量值 :', var1)
1.7 pass
  • Python pass是空语句,是为了保持程序结构的完整性。
  • pass 不做任何事情,一般用做占位语句。
  • 如果没有内容,可以先写pass,但是如果不写pass,就会语法错误
b = 6
if b > 1:
    pass # 必须要pass

#  示例如下
for letter in 'Runoob':
    if letter == 'o':
        pass  #  此句要不要无所谓
        print('执行 pass 块')
    print('当前字母 :', letter)
"""
当前字母 : R
当前字母 : u
当前字母 : n
执行 pass 块
当前字母 : o
执行 pass 块
当前字母 : o
当前字母 : b
"""

二.函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数

2.1 自定义函数规则
  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()
  • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
2.2 语法

Python 定义函数使用 def 关键字,默认情况下,参数值和参数名称是按函数声明中定义的的顺序匹配起来的。一般格式如下:

def 函数名(参数列表):
    函数体
#  1.无参数的函数
def hello():
    print('hello')
hello()  # 调用函数hello(),输出结果:hello

#  2.函数中带上参数变量
def area(width, height):
    return width * height
print(area(20,8))  #  调用函数area(),输出结果:160
2.3 参数

以下是调用函数时可使用的正式参数类型:

  • 必需参数
  • 关键字参数
  • 默认参数
  • 不定长参数
2.3.1 必需参数

必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。

调用printme()函数,你必须传入一个参数,不然会出现语法错误:

#可写函数说明
def printme(str):
   """打印任何传入的字符串"""
   print (str);
   return;
 
#调用printme函数
printme();
"""
Traceback (most recent call last):
  File "test.py", line 10, in 
    printme();
TypeError: printme() missing 1 required positional argument: 'str'
"""
2.3.2 关键字参数

关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

#可写函数说明
def printme( str ):
   """打印任何传入的字符串"""
   print (str);
   return;
 
#调用printme函数
printme( str = "test")  #  test

#  2.以下实例中演示了函数参数的使用不需要使用指定顺序
#可写函数说明
def printinfo( name, age ):
   "打印任何传入的字符串"
   print ("名字: ", name);
   print ("年龄: ", age);
   return;
 
#调用printinfo函数
printinfo( age=50, name="jack" );
"""
名字:  jack
年龄:  50
"""
2.3.3 默认参数

调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:

#可写函数说明
def printinfo( name, age = 35 ):
   "打印任何传入的字符串"
   print ("名字: ", name);
   print ("年龄: ", age);
   return;
 
#调用printinfo函数
printinfo( age=50, name="jack" );
print ("------------------------")
printinfo( name="jack" );
"""
名字:  runoob
年龄:  50
------------------------
名字:  runoob
年龄:  35
"""
2.3.4 不定长参数

你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述2种参数不同,声明时不会命名。基本语法如下:

def functionname([formal_args,] *var_args_tuple ):
   """函数_文档字符串"""
   function_suite
   return [expression]
  • *args是可变参数,args接收的是一个tuple;
  • **kw是关键字参数,kw接收的是一个dict。
  • 使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法
  • 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值
  • 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。
  • 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

加了星号(*)的变量名会存放所有未命名的变量参数。如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。

# 可写函数说明
def printinfo( arg1, *vartuple ):
   """打印任何传入的参数"""
   print ("输出: ")
   print (arg1)
   for var in vartuple:
      print (var)
   return;
 
# 调用printinfo 函数
printinfo( 10 );
printinfo( 70, 60, 50 );
"""
输出:
10
输出:
70
60
50
"""

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

f1(1, 2, 3, 'a', 'b', x=99)  #  调用
"""输出结果:a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}"""

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
f2(1, 2, d=99, ext=None)  #  调用
"""输出结果:a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}  """
2.4 参数传递

在 python 中,类型属于对象,变量是没有类型的

a=[1,2,3]
a="Runoob"

以上代码中,[1,2,3] 是 List 类型,"Runoob" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是 List 类型对象,也可以指向 String 类型对象。

可更改(mutable)与不可更改(immutable)对象
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。

可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

不可变类型: 类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。

可变类型: 类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响

python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

#  1.python 传不可变对象实例
def ChangeInt( a ):
    a = 10

b = 2
ChangeInt(b)
print(b) # 结果是 2
"""
实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变
量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 
指向它
"""

#  2.python 传可变对象实例
# 可写函数说明
def changeme(mylist):
   """修改传入的列表"""
   mylist.append([1,2,3,4]);
   print ("函数内取值: ", mylist)
   return
 
# 调用changeme函数
mylist = [10,20,30];
changeme( mylist );
print ("函数外取值: ", mylist)
#  传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:
"""
函数内取值:  [10, 20, 30, [1, 2, 3, 4]]
函数外取值:  [10, 20, 30, [1, 2, 3, 4]]
"""
2.5 匿名函数
2.5.1 匿名函数特点

python 使用 lambda ['læmdə]来创建匿名函数。

所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。

  • lambda 只是一个表达式,函数体比 def 简单很多。
  • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
  • lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
  • 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
2.5.2 匿名函数语法

lambda 函数的语法只包含一个语句,如下:

lambda [arg1 [,arg2,.....argn]]:expression
2.5.3 匿名函数实例
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
 
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))
"""
相加后的值为 :  30
相加后的值为 :  40
"""
2.6 return语句

return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。之前的例子都没有示范如何返回数值,以下实例演示了 return 语句的用法:

# 可写函数说明
def sum( arg1, arg2 ):
   # 返回2个参数的和."
   total = arg1 + arg2
   print ("函数内 : ", total)
   return total;

# 调用sum函数
total = sum( 10, 20 );
print ("函数外 : ", total)
"""
函数内 :  30
函数外 :  30
"""
2.7 变量作用域

Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

x = int(2.9)  # 内建作用域
 
g_count = 0  # 全局作用域
def outer():
    o_count = 1  # 闭包函数外的函数中
    def inner():
        i_count = 2  # 局部作用域

Python 中只有模块(module),类(class)以及函数(deflambda)才会引入新的作用域,其它的代码块(如 if/elif/else/try/exceptfor/while等)是不会引入新的作用域的,也就是说这这些语句内定义的变量,外部也可以访问。

实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。
如果将 msg 定义在函数中,则它就是局部变量,外部不能访问

>>> def test():
...     msg_inner = 'I am from Runoob'
... 
>>> msg_inner
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'msg_inner' is not defined
>>> 
2.7.1 全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

total = 0; # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
    #返回2个参数的和
    total = arg1 + arg2; # total在这里是局部变量.
    print ("函数内是局部变量 : ", total)
    return total;

#调用sum函数
sum( 10, 20 );
print ("函数外是全局变量 : ", total)
"""
函数内是局部变量 :  30
函数外是全局变量 :  0
"""
2.7.2 global 和 nonlocal关键字

当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了

num = 1
def fun1():
    num = 100

fun1()
print(num)  
"""
输出结果:1
为什么是1,而不是100?因为第一行num = 1中的num是全局变量,fun1()中的num是局部变量,函数执行完毕
就num会销毁,而print(num)  中的num却是全局变量,两个num不是一个变量,因此不会改变num的值。
"""
num = 1
def fun1():
    global num  # 需要使用 global 关键字声明
    num = 100
    
fun1()
print(num)  # 输出结果:100,在此段代码中num是全局变量且是同一个变量,因此值会被改变。

修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,如下实例:

x = 0
def outer():
    x = 1

    def inner():
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)
"""
inner: 2
outer: 1
global: 0
"""
x = 0
def outer():
    x = 1

    def inner():
        nonlocal x   # 需要使用 nonlocal 关键字声明
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)
"""
inner: 2
outer: 2
global: 0
"""

三.高级特性

3.1 切片

取一个list或tuple的部分元素是非常常见的操作。比如,一个list如下:

>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']

取前3个元素,应该怎么做?
笨办法:

>>> [L[0], L[1], L[2]]
['Michael', 'Sarah', 'Tracy']

之所以是笨办法是因为扩展一下,取前N个元素就没辙了。

取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:

>>> r = []
>>> n = 3
>>> for i in range(n):
...     r.append(L[i])
... 
>>> r
['Michael', 'Sarah', 'Tracy']
3.2 迭代

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为:迭代(Iteration)
在Python中,迭代是通过for ... in来完成的

  • 判断一个对象是可迭代对象可通过collections模块的Iterable类型判断:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
  • 默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
...     print(key)
...
a
c
b
  • 字符串迭代:
>>> for ch in 'ABC':
...     print(ch)
...
A
B
C
  • list实现下标循环可用内置的enumerate函数可以把一个list变成索引元素对,这样就可以在for循环中同时迭代索引和元素本身:
>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C
  • for循环同时引用两个变量
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
...     print(x, y)
...
1 1
2 4
3 9
3.3 列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
如生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?方法一是循环:

>>> L = []
>>> for x in range(1, 11):
...    L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# 但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
3.4 生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(generator)

  • 创建一个generator(方法一)
    有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
>>> L = [x * x for x in range(3)]
>>> L
[0, 1, 4]
>>> g = (x * x for x in range(3))
>>> g
 at 0x1022ef630>

# 可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
# for循环获得generator的下一个返回值
>>> g = (x * x for x in range(3))
>>> for n in g:
...     print(n)
... 
0
1
4
  • yield关键字(方法二)
    yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器,当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象,当使用for进行迭代的时候,函数中的代码才会执行
# 比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个
# 数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

>>> fib(6)
1
1
2
3
5
8
'done'

# 上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改
为yield b就可以了,这就是定义generator的另一种方法:
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
>>> f = fib(6)
>>> f

  • generator的执行流程
    函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
# 调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
3.5 迭代器

集合数据类型,如listtupledictsetstr等和generator,包括生成器和带yieldgenerator function。可以直接作用于for循环的对象统称为可迭代对象:Iterable

  • isinstance()判断一个对象是否是Iterable对象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
  • 可被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator,如:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
  • listdictstr等Iterable变成Iterator可以使用iter()函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

你可能感兴趣的:(5. 流程控制、函数、高级特性)