Chapter-4 语句结构

重点总结

  • for 循环并修改集合类对象时,先用 .copy()
  • 一次性终止多个循环时可用 flag
  • 没有元组推导式
  • 推导式的三种基础写法
  • 推导式中用 else 不能写在结尾
  • zip()与双值推导式的取值方式不同,结果不同
  • Python 寻找变量的顺序 LEGB

1 | 语句 Clause

1-1 | if-else 判断

if a == 1:
    print('a is 1')
elif a == 2:
    print('a is 2')
else: 
    print('a')

📋if 后的条件表达式在底层会转换为 True/False ,因此可以简写:

d = {}
if d:
  # d 不是空字典
else:
  # d 是空字典

1-2 | while 循环

count = 1
while count <= 5:
    print('still looping')
    count += 1
else:
    print('After all the loop, this will be print')

与 if 结构相同,while 的条件表达式会转换为 True/False

1-3 | for 循环

for target in iterable_object:
    pass
else:
    print('After all the loop, this will be print')

💡提示:循环字典会循环字典的所有键

⚠️注意不要一边循环一边修改被循环对象,简单的解决办法是复制一个新的再循环

l = [2, 2, 2, 2, 2]  # 删除所有值为 2 的元素

# 错误方式 X, 实际只循环了 3 次
for i in l:
    if i == 2:
        l.remove(i)
    print(l)
[2, 2, 2, 2]
[2, 2, 2]
[2, 2]

# 正确方式1 √
for i in l.copy():
    if i == 2:
        l.remove(i)  
    
# 正确方式2 √
while 2 in l:
    l.remove(2)

1-4 | 循环控制

  • break 强制跳出循环
  • continue 跳出本次循环,直接执行下一次

⚠️无论是 break 还是 continue只能中止一层循环!!

for _ in range(10):
	for _ in range(10):
		break
	print("This will be print !!!!!")	

📋需要中止整个循环可以使用标识 flag

is_looping = True
for _ in range(10):
	if is_looping:
		for _ in range(10):
            # 处于某些原因,需要终止整个循环,将 flag 设为 False
			is_looping = False
			break
	else:
		break

⚠️当心有坑,Python 历史遗留问题,循环变量实际和体外的同名变量是同一个

>>> x = 1
>>> for x in range(10):
...         pass
>>> x
9

1-5 | try 异常捕捉结构

一个 try 语句可以对应多个 except

结构:try – except – <else> – <finaly>

try:
    pass
except IndexError: 
    pass
except Exception as other:  # 将异常绑定给变量other
     print('Something Wrong:', other)
except RuntimeError, TypeError, NameError:  # 一个 except 可以包含多个 Error
    pass   
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise  # 最后一个 except 子句可省略异常名称捕捉所有类型的异常
else:
    file.close() 
    # 当 try 语句没有抛出异常时,需要执行的代码可写在这里
finally:
    print('Try ends here, Goodbye')
    # 无论是否发生异常,此处定义的代码都一定会执行,可用于兜底

抛出异常

语法关键字 raise 抛出一个异常实例或异常类(继承自 Exception)

>>> raise NameError('Hi there')  # 自定义错误信息
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: Hi there

1-6 | with 结构

预定义清理行为,with 下的语句执行后,文件资源 f 总会被释放(即使出错)

with open("myfile.txt") as f:
    for line in f:
        print(line)

不用 with 语句则需要手写 f.close() ,如果忘写则导致该文件资源一直被占用

1-7 | match 结构

🆕Python 3.10

使用关键字 match case 类似于 C 语言的 switch 结构,💡_可匹配任意情况,可用于兜底

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 403 | 404:
            return "Error"
        case _:
            return "Something's wrong with the internet"

除了普通值,还可比较对象、对象的属性

class Point:
    x: int
    y: int

def where_is(point):
    match point:
        case Point(x=0, y=0):     # 比较属性值
            print("Origin")
        case Point():             # 比较对象    
            print("Somewhere else")    
        case ['a', b, c]:         # 比较数量与开头元素,其余两个作为变量使用   
            print(b, c)

2 | 推导式 Comprehension

通用语法

# 普通
exp for item in iterable

# 普通 + if条件
exp for item in iterable (if con)  # 结尾无法使用 else!

# 普通 + if条件 + else条件
exp (if con else exp) for item in iterable

# filter(function, iterable)
item for item in iterable if function(item)

# map(function, iterable, *iterables)
function(item) for item in iterable  

三种数据类型的推导式:

[exp for item in iterable]  # listcomps
{key_exp: value_exp for item in iterable}  # dictcomps
{exp for exp in iterable}  # setcomps
  • 双值推导式

    💡相当于笛卡尔积,注意 x 与 y 前后顺序不同时,结果也不同

    # 普通
    [(x, y) for x in arr1 for y in arr2]
    # 普通 + if 条件
    [(x, y) for x in arr1 if con for y in arr2 if con]
    
    >>> [(x, y) for x in [1,2] for y in [3,4]]
    [(1, 3), (1, 4), (2, 3), (2, 4)]
    

    💡靠前的迭代对象相对“静态”,逐个乘以后面的迭代对象的元素

  • 嵌套推导式

    表达式也可以是一个循环语句,如 student for class_group in school注意循环的前后顺序 ⚠️

    school = [["Mary", "Jack", "Tiffany"], 
              ["Brad", "Claire"],
              ["Molly", "Andy", "Carla"]]
    # 普通写法
    arr = []
    for group in school:
    	for student in group:
            arr.append(student)
    
    # 错误顺序X,NameError
    [student for student in group for group in school]
    # 正确顺序√
    [student for group in school for student in group]
    ['Mary', 'Jack', 'Tiffany', 'Brad', 'Claire', 'Molly', 'Andy', 'Carla']
    

😂不存在元组推导式~

可变类型(列表、字典、集合)具有推导式。不可变类型(字符串、元组)则需使用其他方法创建

❓什么时候不要用列表推导式

如果被迭代对象过大,应采用生成器

# To cal sum from 0 to 1000000000
>>> sum(i * i for i in range(1000000000))
333333332833333333500000000

📋以上写法为 lazy evaluate,会逐个计算,内存大小不会影响, map() 方法同样采取该策略

推导式的局部作用域

列表推导式、生成器表达式、集合推导式、字典推导式的 for 循环有自己的局部作用域,不影响全局作用域中的同名变量

x = 1
>>> [x for x in range(9)]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> x # 1,不影响全局作用域中的同名变量

3 | 函数 Function

  • 命名规则:与变量一致,属于 identifier

  • 作用:复用代码、利于测试、程序易读

  • 创建:

    def func_name(parameters):
        """Doc String, which describe what this function do"""
        pass
    

⚠️ 如果函数不通过关键字 return 显式声明返回值 , 则默认返回 None

⚠️ return 只能写在函数里,写在其他地方则 SyntaxError

3-0 | 文档字符串

DocString

在函数、类中,定义三元引号中的文字,是函数说明的文本,输出可用 print(function.__doc__)

⚠️ 注意:Python 解析器保留文档字符串中的所有空格

def a():
    """
    This is a test class
    aaa
    """

>>> print(a.__doc__)

    This is a test class
    aaa

3-1 | 实参 & 形参

def hello_world(name):
    pass
hello_world('xxx')

定义时, name 叫做形参 Parameter, 函数完成工作所需的信息

调用时,'xxx' 叫做实参 Argument, 函数调用时传给函数的信息

3-2 | 特殊参数

  • / 表示入参必须使用位置参数形式传入,定义函数时不能作为第一个入参,否则 SyntaxError

  • * 表示入参必须采用关键字形式传入,定义函数时不能作为最后一个入参,否则 SyntaxError

    def pos_only_arg(arg, /):
    	...
    pos_only_arg(arg=1)  # TypeError
    def pos_only_arg(/, arg):  # SyntaxError
    
        
    def kwd_only_arg(*, kwarg):
    	...
    kwd_only_arg(3)  # TypeError
    def kwd_only_arg(kwarg, *):  # SyntaxError
    

作用:防止未来修改函数的形参名时引起 Bug,防止函数使用者依赖位置传递参数

两种特殊参数可混合使用

# standard 既可以采用位置参数,也可采用关键字参数
def combined_example(pos_only, /, standard, *, kwd_only):
    print(pos_only, standard, kwd_only)
    
combined_example(1, 2, 3)  # TypeError
combined_example(1, 2, kwd_only=3)  # 1 2 3
combined_example(1, standard=2, kwd_only=3)  # 1 2 3
combined_example(pos_only=1, standard=2, kwd_only=3)  # TypeError

🧲适用场景:位置参数与实际传入的关键字参数重名

# 常规写法
def foo1(name, **kwds): 
    pass
# 特殊写法
def foo2(name, /, **kwds):
    print(name, kwds['name'])

foo1('Jax', **{'name': 'May'})  # TypeError, 位置参数 name 与实际传入的关键字参数 name 冲突会报错
foo2('Jax', **{'name': 'May'})  # Jax May

3-2 | 传入实参

  • **位置参数 ** Postional Argument

    每个入参位置固定传参时需要对应好顺序*args* 号创建元组,并将多余的位置参数封装入其中(位置参数数量可变化)

  • 关键字参数 Keyword Argument

    必须放在位置参数之后,传参顺序可与定义的不一致, **kwargs 可将额外关键字参数收集到一个字典中(关键字参数数量可变化)

    def args_func(a, b, c, *args):
    def kwargs_func(a='a', b='b', c='c', **kwargs)
    
    def all_args(*args, **kwargs):
        print('args is ---', args)
        print('kwargs is ---', kwargs)
    
    >>> all_args(1, 2, first="primer", second="segundo")
    args is --- (1, 2)
    kwargs is --- {'first': 'primer', 'second': 'segundo'}
    
  • 默认参数

    func(b=3)
    

    ⚠️ 默认值**不能是可变类型!**否则出错,因为默认参数值在函数定义时就已经计算得出,而不是程序运行时

    def bug(arg, l=[]):
          l.append(arg)
          print(l)
    
    >>> bug('a')  # ['a']
    >>> bug('b')
    ['a','b'] # 预期结果是['b'],但其实是....
    

3-3 | 注释

Annotations

定义函数时的可选信息, 用于表明入参与返回值的类型,以字典形式存储在属性 __annotations__

def anno_example(prefix: str, suffix: int=1) -> list:
  print('Annotations is:', anno_example.__annotations__)
  return [prefix, suffix]

 >>> anno_example('a')
Annotations is: {'prefix': <class 'str'>, 'suffix': <class 'int'>, 'return': <class 'list'>}
['a', 1]

📋PEP8 建议:类型周围加空格

🆕Python 3.10,新增标准库方法 inspect.get_annotations() 方便获取注释

3-4 | 可变函数

函数可作为参数传入其他函数,即将函数作为入参传给另一个参数

def run_a_func(func):
     func()

也可从其他函数中返回值

def run_with_arg(func, arg1, arg2):
    func(arg1, arg2)

3-5 | 内嵌函数

可避免循环和代码重复,在函数内部执行,复杂任务时使用

def outer(a, b):
    def inner(c, d):
        return c + d
    return inner(a, b)

3-6 | 闭包

内嵌函数可看作一个闭包,一个被动态创建的可以记录外部变量的函数

def outer(a, b):
    def inner():
        return a+b
    return inner # 返回函数而不是调用!

>>> a = outer(1, 2)
>>> a  # 记录着外部变量 1,2 的一个方法
<function outer. < locals > .inter at 0x00000224A47ACAE8 >
>>>a()
3

3-7 | 匿名函数

语法:lambda params: expression

lambda 是用一条语句表达的匿名函数,不用起名,可用于创建微型函数,不能写 return

a = lambda x:x.capitalize()
>>> a('a')
'A'

a = lambda:'Python'  # 也可以没有入参
>>> a()
'Python'

4 | 迭代函数

💻zip(*iterables)

返回一个可迭代对象,对多个序列并行迭代,若迭代对象长短不一,则以最短的为基准!

⚠️注意:迭代器用一次后会失效 !!

arr1, arr2 = [1,2], [3,4]

>>> z = zip(arr1, arr2)
>>> z  # <zip object at 0x000001EB1D8AC8C0>
>>> [item for item in z]
[(1, 3), (2, 4)]
>>> [item for item in z]
[]

list(zip(arr1, arr2))  
dict(zip(arr1, arr2))  

set()list()均可传入迭代对象,dict(**kwargs)也可传入

list(zip(arr1, arr2))  # [(1, 3), (2, 4)]
dict(zip(arr1, arr2))  # {1: 3, 2: 4}
set(zip(arr1, arr2))   # {(2, 4), (1, 3)}

注意:双值推导式是递归配对zip()只循环一次

>>> [(x, y) for x in arr1 for y in arr2]
[(1, 3), (1, 4), (2, 3), (2, 4)]

>>> list(zip(arr1, arr2))
[(1, 3), (2, 4)]

💻range(start, end, step)

range 其实是 sequence 中的一种不可变类型,而不是一个单纯的内置方法

r = range(0)
issubclass(r.__class__, Sequence)
True

普通用法

list(range(10))  # 相当于(0,9),有头没尾
list(range(1, 3))
>>> list(range(1, 10, 2))
[1, 3, 5, 7, 9]

特殊用法

# 双值或三值对象将被解析为 start,end,step
>>> start_end = [3, 10, 2]
>>> [x for x in range(*start_end)]
[3, 5, 7, 9]

💻enumerate(iterable, start=0)

在循环体中提供索引值元素值start 参数方便自定义索引初始的数值,否则还要手写 i += n

# 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')]

5 | 迭代器 Iterator

一种表示数据流的对象,重复调用next方法会连续返回数据流中的数据,没有返回值时则抛出 StopIteration 异常

An object representing a stream of data. Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream. When no more data are available a StopIteration exception is raised instead

💻iter()

语法:iter(object[, sentinel])

简便写法,返回一个 Iterator 对象

>>> s = 'ab'
>>> it = iter(s)
>>> it
<str_iterator object at 0x000001F3520FB198>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
StopIteration

自定义

定义 __iter_____next___ 方法,如自定一个反向迭代

class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self  # call this func will output <__main__.Reverse object at 0x00....>

    def __next__(self):
        if self.index == 0:
            raise StopIteration 
        self.index = self.index - 1
        return self.data[self.index]  # return element from the end

>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
      print(char)
m a p s  # 反向输出字符串

6 | 生成器 Generator

一个 Python 序列生成的对象

一个通过关键字 yield 返回值的函数,可以在循环中逐个返回,也可以方法 next() 逐个取

创建方式

# M1. yield 创建
def my_range(first=0, last=10, step=1):
    number = first
    while number < last:
        yield number  # yield 返回值,但不会中断运行,与 return 不同
        number += step
# 注意区分调用前后的类型!!
>>> type(my_range())
generator
>>> type(my_range)
function        
        
# M2. 用括号创建
gen = (number for number in range(1,6))
>>>type(gen)
<class 'generator'>

⚠️一个生成器只能用一次,再次使用失效!生成器是动态生成值,并不像变量存储在内存中

>>> [x for x in gen]
[1, 2, 3, 4, 5]
>>> [x for x in gen]
[]      

📋使用 next() 手动取出

gen = (number for number in range(1,6))
>>> next(gen)
1
>>> next(gen)
2

什么时候使用生成器

需要迭代庞大数据量时使用,可节省大量内存,避免 MemoryError

💡 没有内存限制时,选用普通方法更快

# 内存占用对比
>>> nums_list_comprehension = [i * i for i in range(100_000_000)]
>>> nums_generator = (i * i for i in range(100_000_000))
>>> import sys
>>> sys.getsizeof(nums_generator)
104
>>> sys.getsizeof(nums_list_comprehension)
835128600

6-1 | 空间占用对比

💻 sys.getsizeof(object)

返回的占用空间单位为字节

sys.getsizeof(range(10000))
48
sys.getsizeof(range(100000))
48
sys.getsizeof(range(1000000))
48
sys.getsizeof(list(range(10000)))
80056
sys.getsizeof((x for x in range(10000)))
200 	

7 | 装饰器 Decorators

本质是一个闭包,入参必须是一个函数,返回内嵌函数的名称,可在不修改原函数的情况下扩展功能

def deco(func):                         # 只传一个入参即可,名称可不叫 func
    def inner(a, b, *args, **kwargs):   # 加上 *args, **kwargs 可增加兼容性
        print(f'Sum of {a} and {b} is')
        re = func(a, b, *args, **kwargs)
        print('End....')
        return re                       # 被装饰函数没有返回值则不用return
    return inner

应用装饰器

# 方法一:用 @,函数的定义与使用不分离
@deco
def add(a, b):
    print(a+b)

# 方法二:直接传入装饰器,函数的定义与使用不是同时的
add = deco(add) 

>>> add(1, 99)
Sum of 1 and 99 is
100
End....

⚠️多个装饰器存在时,执行顺序从上到下

def deco1(func):
    def inner(*args):
        print("This is deco1")
        func(*args)
    return inner

def deco2(func):
    def inner(*args):
        print("This is deco2")
        func(*args)
    return inner
    
@deco1
@deco2
def add(a, b):
    print(a+b)
    
>>> add(1,23)
This is deco1
This is deco2
24

8 | 作用域 Scope

名称所在的空间

LEGB Rule:Python 解释器按照以下顺序查找一个变量:

  1. Locals. Variables defined within the function body and not declared global.
  2. Enclosing. Names of the local scope in all enclosing functions from inner to outer.
  3. Globals. Names defined at the top-level of a module or declared global with a global keyword.
  4. Built-in. Any built-in name in Python.

每个程序都定义了全局命名空间,其中的变量是全局变量 global 关键字可以访问全局空间中的变量

locals()  #  以字典形式返回局部命名空间的变量
globals()  # 以字典形式返回全局命名空间的变量
  • 局部作用域

    函数内定义的变量,函数外无法获取

    def test():
        b = 1
    >>> test()
    >>> b
    NameError: name 'b' is not defined
    

    即使与全局作用域内的重名也不会相互影响

    b = 2
    def test():
         b = 1
    >>> test()
    >>> b
    2
    
  • 全局作用域

    global 关键字可以访问或创建全局空间中的变量

  • nonlocal
    nonlocal 可以在局部的外部 赋值变量 (外部不是全局!)

spam = "test spam"
def scope_test():
  def do_local():
      spam = "local spam"

  def do_nonlocal():
      nonlocal spam
      spam = "nonlocal spam"

  def do_global():
      global spam
      spam = "global spam"
  spam = "test spam"
  do_local()
  print("After local assignment:", spam)
  do_nonlocal()
  print("After nonlocal assignment:", spam)
  do_global()
  print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

# 输出 
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

9 | 异常 Exception

  • SyntaxError:语法错误,也被称作解析错误(parsing error)

  • Exception:运行期间检测到的错误称为异常

9-1 | 异常链

错误堆栈中记录的所有异常

💡阻止异常链,可将 raise RuntimeError("error") 改为 raise RuntimeError("error") from None

9-2 | 自定义异常

若一个新建模块中需抛出几种不同的错误,通常为该模块定义一个异常基类,然后针对不同的错误类型派生出对应的异常子类

class MyException(Exception):
  def __init__(self, message):
      self.message = message  # explanation  

try: 
    raise MyException('Something break down....') #显示抛出异常
except MyException as err:
    print(err)

9-3 | 一次抛出多个异常

ExceptionGroup 实现

def f():
    excs = [OSError('error 1'), SystemError('error 2')]
    raise ExceptionGroup('there were problems', excs)

In [7]: f()
ExceptionGroup: there were problems (2 sub-exceptions)

使用 except* 可以捕捉 Exception 组里的某个异常,*可理解为继续捕获异常不终止

try:
    f()
except* OSError as e:
    print("There were OSErrors")
except* SystemError as e:
    print("There were SystemErrors")

# 执行结果
There were OSErrors
There were SystemErrors

9-4 | 增加错误信息

Exception 类有一个 add_note() 方法接受字符串,并将其按顺序在错误堆栈中输出

try:
    raise TypeError('bad type')
except Exception as e:
    e.add_note('Add some information')
    e.add_note('Add some more information')
    raise
# 输出
TypeError: bad type
Add some information
Add some more information

10 | 运算符 Operators

  • **比较运算符 ** Comparision Operators

    返回值均为 True / False

    ==  # 相等 
    !=  # 不相等
    in  # 属于,成员关系运算符
    >,<  # 大于等于
    >=,<=  # 小于等于
    
  • 布尔(逻辑)运算符

    and 
    or 
    not
    

    ⚠️ 运算优先级低于比较运算符

  • 海象运算符 🆕3.8

    walrus operator

    语法:name := expression

    允许一个表达式同时求值并赋值变量,也称为赋值表达式

    import random
    def get_weather_data():
        return random.randrange(90, 110)
    
    # 用途一:简化推导式
    >>> hot_temps = [temp for _ in range(20) if (temp := get_weather_data()) >= 100]
    >>> hot_temps
    [107, 102, 109, 104, 107, 109, 108, 101, 104]
    
    # 用途二:简化赋值
    if (n := len(s)) > 10 :
        print(f'The string has {n} chars')
    # 直接赋值 SyntaxError:    
    if (n=len(s)) > 10:
    # 没有海象运算符,则需分两步写:
    n = len(s)
    if n > 10
    

关键字 is 与运算符 ==

  • == 判断是否相等

  • is 判断两个对象是否是同一内存对象

>>> a1 = [1,2,3]
>>> a2 = [1,2,3]
>>> a1 == a2
True
>>> a1 is a2
False

11 | 语法关键字 Keywords

💻 查看所有关键字

# 方法一
>>> help("keywords")
Here is a list of the Python keywords.  Enter any keyword to get more help.
False               class               from            or
None                continue            global          pass
True                def                 if              raise
and                 del                 import          return
as                  elif                in              try
assert              else                is              while
async               except              lambda          with
await               finally             nonlocal        yield

# 方法二
>>> import keyword
>>> keyword.kwlist

🆕 Python 3.7 新增 async await

💻 检测是否为保留字

from keyword import iskeyword
>>> iskeyword('True')
True
>>> iskeyword('aa')
False

12 | 软关键字 Soft Keywords

🆕 Python 3.10

某些关键字仅在特殊情况下被 Python 预留,如 match, case, _

另外使用这些软关键字作为 identifier 也不会出错

match a:
     case 100:
         match = 1
         case = 2
         print(match, case)
1 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值