欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python十大基本语法总结

程序员文章站 2022-03-03 19:49:19
...
    Python 是一种代表简单思想的语言,其语法相对简单,很容易上手。不过,如果就此小视 Python 语法的精妙和深邃,那就大错特错了。本文精心筛选了最能展现 Python 语法之精妙的十个知识点,并附上详细的实例代码。如能在实战中融会贯通、灵活使用,必将使代码更为精炼、高效,同时也会极大提升代码B格,使之看上去更老练,读起来更优雅

1. for - else

什么?不是 if 和 else 才是原配吗?No,你可能不知道,else 是个脚踩两只船的家伙,for 和 else 也是一对,而且是合法的。十大装B语法,for-else 绝对算得上南无湾!不信,请看:

>>> for i in [1,2,3,4]:
	print(i)
else:
	print(i, '我是else')
	
1
2
3
4
4 我是else

如果在 for 和 else 之间(循环体内)有第三者 if 插足,也不会影响 for 和 else 的关系。因为 for 的级别比 if 高,else 又是一个攀附权贵的家伙,根本不在乎是否有 if,以及是否执行了满足 if 条件的语句。else 的眼里只有 for,只要 for 顺利执行完毕,else 就会屁颠儿屁颠儿地跑一遍:

>>> for i in [1,2,3,4]:
	if i > 2:
		print(i)
else:
	print(i, '我是else')

3
4
4 我是else

那么,如何拆散 for 和 else 这对冤家呢?只有当 for 循环被 break 语句中断之后,才会跳过 else 语句:

>>> for i in [1,2,3,4]:
	if i>2:
		print(i)
		break
else:
	print(i, '我是else')

2. 一颗星(*)和两颗星(**)

有没有发现,星(*)真是一个神奇的符号!想一想,没有它,C语言还有啥好玩的?同样,因为有它,Python 才会如此的仪态万方、风姿绰约、楚楚动人!Python 函数支持默认参数和可变参数,一颗星表示不限数量的单值参数,两颗星表示不限数量的键值对参数。
我们还是举例说明吧:设计一个函数,返回多个输入数值的和。我们固然可以把这些输入数值做成一个list传给函数,但这个方法,远没有使用一颗星的可变参数来得优雅:

>>> def multi_sum(*args):
	s = 0
	for item in args:
		s += item
	return s

>>> multi_sum(3,4,5)

Python 函数允许同时全部或部分使用固定参数、默认参数、单值(一颗星)可变参数、键值对(两颗星)可变参数,使用时必须按照前述顺序书写。

>>> def do_something(name, age, gender='男', *args, **kwds):
	print('姓名:%s,年龄:%d,性别:%s'%(name, age, gender))
	print(args)
	print(kwds)

>>> do_something('xufive', 50, '男', 175, 75, math=99, english=90)
姓名:xufive,年龄:50,性别:男
(175, 75)
{'math': 99, 'english': 90}

此外,一颗星和两颗星还可用于列表、元组、字典的解包,看起来更像C语言:

>>> a = (1,2,3)
>>> print(a)
(1, 2, 3)
>>> print(*a)
1 2 3
>>> b = [1,2,3]
>>> print(b)
[1, 2, 3]
>>> print(*b)
1 2 3
>>> c = {'name':'xufive', 'age':51}
>>> print(c)
{'name': 'xufive', 'age': 51}
>>> print(*c)
name age
>>> print('name:{name}, age:{age}'.format(**c))
name:xufive, age:51

3. 列表索引的各种骚操作

介绍了python 中如何获取列表的索引,在文中给大家提到了python 返回列表中某个值的索引,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
1.index方法

list_a= [12,213,22,2,32]
for a in list_a:
  print(list_a.index(a))

结果: 0 1 2 3 4

如果列表的没有重复的话那么用index完全可以的,那么如果列表中的元素有重复的呢?

list_a= [12,213,22,2,2,22,2,2,32]
for a in list_a:
  print(list_a.index(a))

结果:0 1 2 3 3 2 3 3 8<br><br>

很显然结果不是你想要的!!! 那么就看第二种方法>

2.enumerate 方法,将列表中的元素元组化

list_a= [12,213,22,2,2,22,2,2,32]
print(list(enumerate(list_a)))

结果:

[(0, 12), (1, 213), (2, 22), (3, 2), (4, 2), (5, 22), (6, 2), (7, 2), (8, 32)]

这样可以解决列表中元素重复的问题,
ps:下面介绍下python 返回列表中某个值的索引

list = [5,6,7,9,1,4,3,2,10]
list.index(9)
out:3

同时可以返回列表中最大值的索引list.index(max(list))
最小值索引list.index(min(list))

4. yield 以及生成器和迭代器

背景
首先,我不会解释这两个名词,我看过很多遍解释,可还是看不懂,还是直接看使用情景吧。
我们以佩波拉契数列为例,当我们不知道迭代器的情况下,我们写出来的代码可能是这样子的:

'''这种方式计算fib(100)都很吃力'''
fib = lambda n:fib(n-2) + fib(n-1) if n>1 else 1

或者优化一下,变成这样子:

'''
将迭代结果存入temp中,避免重复的计算, 
这样可以提高计算速度,但是当计算fib(4000)时,会报:
“maximum recursion depth exceeded in comparison”
也就是说会超过最大迭代次数
'''
temp = {}
def fib(n):
    if n in temp:
        return temp[n]
    
    if n>1:
        _t = fib(n-1) +  fib(n-2)
        temp[n] = t
        return _t
    else:
        return 1

考虑到进一步的优化,我们就需要引进python的yield 语法

使用yield
yield的用法简单来说就 返回yield运行结果,当调用__next__时,再次运行至yield处,并返回 。所以我们可以利用这一特点,让fib函数每次只返回当前和前一个的值,再次运行时更新这两值, 并返回就好了。于是有了下面的代码:

'''
这次我们可以最大程度发挥计算机性能,
比上个版本能计算的更多也更快(我电脑能计算6位数的fib值)
'''
def fib_gen():
    '''
    这时一个佩波拉契数列的生成器,每次迭代返回当前值和前一个的值
    '''
    c , p = 1,1
    while True:
        yield p # 每次next运行到此,并返回p的值。当再次调用时,继续执行下面代码,
        p, c = c, c+p # 计算下次p的值
        
def fib(n):
    '''根据情况不断运行next(f),直到适合的条件'''
    f = fib_gen()
    for i in range(n+1):
        cur = next(f)
    return n

我们在看一个简单的生成器的例子

In [1]: array = [1,2,3,4]

In [2]: f = (i for i in array) # 是不是看起来很像“元组推倒式”,其实这是一个生成器。python并没有元组的推倒式

In [3]: f # 不信,我们来实际看看
Out[3]: <generator object <genexpr> at 0x10882df10>

In [4]: [i for i in f] # 生成器是可以迭代的,所以列表推到能取出值
Out[4]: [1, 2, 3, 4]

In [5]: [i for i in f]
Out[5]: [] # 此时,f中的数据全部取出,调用next(f) 取不出来值了

总结
从这个例子中我们可以发现,当使用yield后,函数并不是立刻执行,而是调用next的时候,我们才取出值,没调用的时候,它只是一个生成器。相当于保存了上次运行的现场。推荐一个

当调用next时,没有找到yield返回时,便会终止,并报StopIteration错误, 本例子中是死循环,不会发生这种情况。感兴趣的可以把while True改成while p < 100之类的试试

5. 三元表达式

要介绍Python的三元表达式,可以先看看其他编程语言比如C,JAVA中应用:

public class java {
 public static void main(String[] args){
 int x = 100;
 int y = 101;
 int MAX = (x > y)? x: y;
 System.out.println("MAX:" + MAX);
 }
}

上面的例子可以很好的说明了其他语言的格式:

判段的条件 ? 条件为真时的结果:条件为假时的结果

而在Python中尽管也有三元表达式,但格式不同,先看下在Python中简单示例

>>> x = 4
>>> y = 99 if x > 3 else 999
>>> y
99

从上面的Python是示例可以看出,Python的三元表达式格式如下:

条件为真时的结果 if 判段的条件 else 条件为假时的结果
适用场景:变量赋值时,要做条件判断时,简化代码时使用。上面就是一个示例。
先定义变量:

a = 1
b = 2

第一种写法:

erroStr = "More" if a > b else "Less"
print(erroStr) # 运行结果为:Less

第二种写法:

print({True: "More", False: "Less"}[a > b]) # 运行结果为:Less

第三种写法:

print(("FalseValue", "TrueValue")[a > b]) # 运行结果为:FalseValue

其中我们比较常见的是第一种。

第二三种是挺简洁的,但是写在项目里怕是接手的同事要抓狂了。

其他示例:

>>> def Max(x, y):
...   return x if x > y else y
>>> Max(99, 98)
99
>>> Max(78, 88)
88
>>> L = []
>>> x = 2
>>> L.append(100 if x > 1 else 0)
>>> L
[100]
>>> def fib(n):
...   return 1 if n == 1 else fib(n-1) + n
>>> fib(1)
1
>>> fib(5)
15
>>> L = [-2, -4, 0, 2, 5, 8]
>>> list(filter(lambda x:True if x > 0 else False, L))
[2, 5, 8]

6. 装饰器

python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。简单的说装饰器就是一个用来返回函数的函数。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
二、为什么需要装饰器
1、先来看一个简单例子:

def foo():
print('i am foo')

2、增加需求
现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def foo():
  print('i am foo')
  print("foo is running")

3、又有需求
假设现在有100个函数需要增加这个需求,并且后续可能还要对这一百个函数都增加执行前打印日志的需求,怎么办?还一个个改吗?
当然不了,这样会造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码。

def use_logging(func):
  print("%s is running" % func.__name__)
  func()
def bar():
  print('i am bar')
use_logging(bar)
#result:
#bar is running
#i am bar

通过以上use_logging函数我们增加了日志功能,不管以后有多少函数需要增加日志或者修改日志的格式我们只需要修改use_logging函数,并执行use_logging(被装饰的函数)就达到了我们想要的效果。

def use_logging(func):
  print("%s is running" % func.__name__)
  return func
@use_logging
def bar():
  print('i am bar')
bar()

三、基础装饰器入门
1、装饰器语法糖
python提供了@符号作为装饰器的语法糖,使我们更方便的应用装饰函数。但使用语法糖要求装饰函数必须return一个函数对象。因此我们将上面的func函数使用内嵌函数包裹并return。

装饰器相当于执行了装饰函数use_loggin后又返回被装饰函数bar,因此bar()被调用的时候相当于执行了两个函数。等价于use_logging(bar)()

def use_logging(func):
  def _deco():
    print("%s is running" % func.__name__)
    func()
  return _deco
@use_logging
def bar():
  print('i am bar')
bar()

2、对带参数的函数进行装饰

现在我们的参数需要传入两个参数并计算值,因此我们需要对内层函数进行改动传入我们的两个参数a和b,等价于use_logging(bar)(1,2)

def use_logging(func):
  def _deco(a,b):
    print("%s is running" % func.__name__)
    func(a,b)
  return _deco
@use_logging
def bar(a,b):
  print('i am bar:%s'%(a+b))
bar(1,2)

我们装饰的函数可能参数的个数和类型都不一样,每一次我们都需要对装饰器做修改吗?这样做当然是不科学的,因此我们使用python的变长参数*args和**kwargs来解决我们的参数问题。

3、函数参数数量不确定
不带参数装饰器版本,这个格式适用于不带参数的装饰器。
经过以下修改我们已经适应了各种长度和类型的参数。这个版本的装饰器已经可以任意类型的无参数函数。

def use_logging(func):
  def _deco(*args,**kwargs):
    print("%s is running" % func.__name__)
    func(*args,**kwargs)
  return _deco
@use_logging
def bar(a,b):
  print('i am bar:%s'%(a+b))
@use_logging
def foo(a,b,c):
  print('i am bar:%s'%(a+b+c))
bar(1,2)
foo(1,2,3)

4、装饰器带参数

带参数的装饰器,这个格式适用于带参数的装饰器。

某些情况我们需要让装饰器带上参数,那就需要编写一个返回一个装饰器的高阶函数,写出来会更复杂。比如:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = "TKQ"
def use_logging(level):
  def _deco(func):
    def __deco(*args, **kwargs):
      if level == "warn":
        print "%s is running" % func.__name__
      return func(*args, **kwargs)
    return __deco
  return _deco
@use_logging(level="warn")
def bar(a,b):
  print('i am bar:%s'%(a+b))
bar(1,3)
# 等价于use_logging(level="warn")(bar)(1,3)

5、functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,先看例子:

def use_logging(func):
  def _deco(*args,**kwargs):
    print("%s is running" % func.__name__)
    func(*args,**kwargs)
  return _deco
@use_logging
def bar():
  print('i am bar')
  print(bar.__name__)
bar()
#bar is running
#i am bar
#_deco
#函数名变为_deco而不是bar,这个情况在使用反射的特性的时候就会造成问题。因此引入了functools.wraps解决这个问题


使用functools.wraps:

import functools
def use_logging(func):
  @functools.wraps(func)
  def _deco(*args,**kwargs):
    print("%s is running" % func.__name__)
    func(*args,**kwargs)
  return _deco
@use_logging
def bar():
  print('i am bar')
  print(bar.__name__)
bar()
#result:
#bar is running
#i am bar
#bar ,这个结果是我们想要的。OK啦!

6、实现带参数和不带参数的装饰器自适应

import functools
def use_logging(arg):
  if callable(arg):#判断参入的参数是否是函数,不带参数的装饰器调用这个分支
    @functools.wraps(arg)
    def _deco(*args,**kwargs):
      print("%s is running" % arg.__name__)
      arg(*args,**kwargs)
    return _deco
  else:#带参数的装饰器调用这个分支
    def _deco(func):
      @functools.wraps(func)
      def __deco(*args, **kwargs):
        if arg == "warn":
          print "warn%s is running" % func.__name__
        return func(*args, **kwargs)
      return __deco
    return _deco
@use_logging("warn")
# @use_logging
def bar():
  print('i am bar')
  print(bar.__name__)
bar()

三、类装饰器
使用类装饰器可以实现带参数装饰器的效果,但实现的更加优雅简洁,而且可以通过继承来灵活的扩展.

1、类装饰器

class loging(object):
  def __init__(self,level="warn"):
    self.level = level
  def __call__(self,func):
    @functools.wraps(func)
    def _deco(*args, **kwargs):
      if self.level == "warn":
        self.notify(func)
      return func(*args, **kwargs)
    return _deco
  def notify(self,func):
    # logit只打日志,不做别的
    print "%s is running" % func.__name__
@loging(level="warn")#执行__call__方法
def bar(a,b):
  print('i am bar:%s'%(a+b))
bar(1,3)

2、继承扩展类装饰器

class email_loging(Loging):
  '''
  一个loging的实现版本,可以在函数调用时发送email给管理员
  '''
  def __init__(self, email='[email protected]', *args, **kwargs):
    self.email = email
    super(email_loging, self).__init__(*args, **kwargs)
  def notify(self,func):
    # 发送一封email到self.email
    print "%s is running" % func.__name__
    print "sending email to %s" %self.email
@email_loging(level="warn")
def bar(a,b):
  print('i am bar:%s'%(a+b))
bar(1,3)

总结:给大家推荐一个特别牛逼python辅导学习扣扣学习群:774 数字711 数字191欢迎爱好学习,爱专研的伙伴加入,私信管理员,有系统学习课程领取

7. with - as

With语句是什么?
有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。
如果不用with语句,代码如下:

file = open("/tmp/foo.txt")
data = file.read()
file.close()

这里有两个问题。一是可能忘记关闭文件句柄;二是文件读取数据发生异常,没有进行任何处理。下面是处理异常的加强版本:

file = open("/tmp/foo.txt")
try:
    data = file.read()
finally:
    file.close()

虽然这段代码运行良好,但是太冗长了。这时候就是with一展身手的时候了。除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。下面是with版本的代码:

with open("/tmp/foo.txt") as file:
    data = file.read()

with如何工作?

这看起来充满魔法,但不仅仅是魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。

紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。

下面例子可以具体说明with如何工作:

#!/usr/bin/env python
# with_example01.py
 
class Sample:
    def __enter__(self):
        print "In __enter__()"
        return "Foo"
 
    def __exit__(self, type, value, trace):
        print "In __exit__()"
 
def get_sample():
    return Sample()
 
with get_sample() as sample:
    print "sample:", sample

运行代码,输出如下

In __enter__()
sample: Foo
In __exit__()

正如你看到的,

  1. enter()方法被执行
  2. enter()方法返回的值 - 这个例子中是"Foo",赋值给变量’sample’
  3. 执行代码块,打印变量"sample"的值为 “Foo”
  4. exit()方法被调用
    with真正强大之处是它可以处理异常。可能你已经注意到Sample类的__exit__方法有三个参数- val, type 和 trace。 这些参数在异常处理中相当有用。我们来改一下代码,看看具体如何工作的。
#!/usr/bin/env python
# with_example02.py
 
class Sample:
    def __enter__(self):
        return self
 
    def __exit__(self, type, value, trace):
        print "type:", type
        print "value:", value
        print "trace:", trace
 
    def do_something(self):
        bar = 1/0
        return bar + 10
 
with Sample() as sample:
    sample.do_something()

这个例子中,with后面的get_sample()变成了Sample()。这没有任何关系,只要紧跟with后面的语句所返回的对象有__enter__()和__exit__()方法即可。此例中,Sample()的__enter__()方法返回新创建的Sample对象,并赋值给变量sample。
代码执行后:

bash-3.2$ ./with_example02.py
type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x1004a8128>
Traceback (most recent call last):
  File "./with_example02.py", line 19, in <module>
    sample.do_something()
  File "./with_example02.py", line 15, in do_something
    bar = 1/0
ZeroDivisionError: integer division or modulo by zero

实际上,在with后面的代码块抛出任何异常时,exit()方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给__exit__()方法,因此抛出的ZeroDivisionError异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在__exit__方法当中。

8. 列表推导式

列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表。

它的结构是在一个中括号里包含一个表达式,然后是一个for语句,然后是 0 个或多个 for 或者 if 语句。那个表达式可以是任意的,意思是你可以在列表中放入任意类型的对象。返回结果将是一个新的列表,在这个以 if 和 for 语句为上下文的表达式运行完成之后产生。

列表推导式的执行顺序:各语句之间是嵌套关系,左边第二个语句是最外层,依次往右进一层,左边第一条语句是最后一层。

[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]

他的执行顺序是:

for x in range(1,5)
    if x > 2
        for y in range(1,4)
            if y < 3
                x*y

9. yield 以及生成器和迭代器

1.可迭代:
在Python中如果一个对象有__iter__()方法或__getitem__()方法,则称这个对象是可迭代的(iterable)。
其中__iter__()方法的作用是让对象可以用“for … in…”方式来循环遍历,getitem()方法是让对象可以通过“实例名[index]”的方式访问实例中的元素。换句话说,两个条件只要满足一条,就可以说对象是可迭代的。
python中的可迭代对象有:
(1)序列:字符串、列表、元组
(2)非序列:字典、文件
(3)自定义类:用户自定义的类实现了__iter__()或__getitem__()方法的对象
2.迭代器:
在Python中如果一个对象有__iter__()方法和__next__()方法,则称这个对象是迭代器(Iterator);其中__iter__()方法是让对象可以用for … in …循环遍历,next()方法是让对象可以通过next(实例名)访问下一个元素。这两个方法必须同时具备,才能称之为迭代器。
列表List、元组Tuple、字典Dictionary、字符串String等数据类型虽然是可迭代的,但都不是迭代器,因为他们都没有__next__()方法。

from collections import Iterable,Iterator

# 字符串、列表、元组、集合、字典都是可迭代对象
print(isinstance('123',Iterable))   # True
print(isinstance([1,2,3],Iterable))   # True
print(isinstance((1,2,3),Iterable))   # True
print(isinstance({1,2,3},Iterable))   # True
print(isinstance({"one":1,"two":2,"three":3},Iterable))   # True

# 字符串、列表、元组、集合、字典都不是迭代器
print(isinstance('123',Iterator))   # False
print(isinstance([1,2,3],Iterator))   # False
print(isinstance((1,2,3),Iterator))   # False
print(isinstance({1,2,3},Iterator))   # False
print(isinstance({"one":1,"two":2,"three":3},Iterator))   # False

3.总结:

迭代器都是可迭代的,但可迭代的不一定是迭代器;可用for … in …循环的都是可迭代的,可用next()遍历的才是迭代器;
next()是单向的,一次只获取一个元素,获取到最后一个元素后停止;
在可迭代的对象中提前存储了所有的元素,而迭代器是惰性的,只有迭代到了某个元素,该元素才会生成。
4.延迟计算或惰性求值 (Lazy evaluation)

迭代器不要求你事先准备好整个迭代过程中所有的元素。仅仅是在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合。

迭代之前元素可以是不存在的,迭代之后元素也可以被销毁,因此迭代器在处理大量数据甚至无限数据时具有加载数据快、占用内存小等优势。

5.创建迭代器:

(1)使用内建的工厂函数iter(iterable[, sentinel])

iter()函数只传入一个参数时,参数必须为可迭代对象;当使用第二个参数sentinel(哨兵)时,第一个参数必须是一个可调用对象。

当有第二个参数sentinel传入时,参数iterable应是一个可调用对象,这时候迭代器它会重复地调用第一个参数,当枚举到的值等于哨兵时,就会抛出异常StopIteration。

s = "abcdefgh"
iter1 = iter(s)
print(isinstance(iter1,Iterator))   # True
使用第二个参数的情况此处不描述了

(2)自己写类,其中实现__iter__()方法和__next__()方法

在__next__()方法中必须对迭代进行检查,超出范围则触发 StopIteration 异常。注意避免死循环。

class MyIterator(object):
    def __init__(self, data):
        self.data = data
    def __iter__(self):
       return self
    def __next__(self):
        if self.data <= 20:
           self.data += 2
            return self.data
        else:
           raise StopIteration

myIter = MyIterator(5)

print(isinstance(myIter,Iterator))  # True
print(next(myIter))     # 7
print(next(myIter))     # 9
print(next(myIter))     # 11
...

二、生成器(generator)
在Python中,不会一次性的生成所有的值,而是调用一次,生成一个值,再调用一次,生成下一个值。
即生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常时结束。

创建生成器的两种基本方法
1.生成器表达式:

通列表解析语法,在写列表生成式时,把[]换成()即可

实例:

# 列表生成式
>>> l = [i for i in range(5)]
>>> l
[0, 1, 2, 3, 4]

# 生成器表达式
>>> g = (i for i in range(5))
>>> g
<generator object <genexpr> at 0x000001A5108D5938>
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2

2.生成器函数

在 Python 中,使用了(一个或多个)yield语句的函数,就叫做生成器函数。

解释器处理这种函数定义的时候将创建特殊的生成器函数对象,而不是普通的函数对象。调用生成器函数的时候,返回值就是一个生成器对象。它只能用于迭代操作,更简单点理解生成器就是一种迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

实例:

# 实现的斐波那契数列
def fib_gen(n):
    f0, f1 = 0, 1
    for n in range(n):
        yield f0
        f0, f1 = f1, f0+f1

print(fib_gen(5))   # <generator object fib_gen at 0x0000023A5BB78830>

g = fib_gen(6)  # g就是一个生成器对象(也是一种迭代器)
print(next(g))  # 0
print(next(g))  # 1
print(next(g))  # 1
print(next(g))  # 2

# 生成器是包含有__iter__()和__next__()方法的,所以可以直接使用for来迭代
for x in fib_gen(5):
    print(x, end=', ')
# 0, 1, 1, 2, 3, 

2.1.yield 与 return

在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration异常。

>>> def gen():
...     for i in range(2)
...         yield 1
...
>>> g=gen()
>>> next(g)    #第一次调用next(g)时,会在执行完yield语句后挂起,此时程序并没有执行结束。
0
>>> next(g)    #第二次调用next(g),同样会在执行完yield语句后挂起,此时程序并没有执行结束。
1
>>> next(g)    #程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

生成器函数有return,如果return语句没有返回值,则直接抛出StopIteration异常。如果return语句有返回值,那么这个返回值会成为抛出StopIteration异常的一个说明。

>>> def ff():
...    for i in range(2):
...        yield i
...    return 'i finished.'
...
>>> g = ff()
>>> next(g)
0
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: i finished.

2.2.yield from语法

yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。

yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。

yield from 主要用于生成器的嵌套,重点是帮我们自动处理内外层之间的异常问题。

这种用法参考博客:深入理解yield from语法

3.生成器支持的方法

① generator.send(expr)

启动新生成器或唤醒处于挂起状态的生成器,把参数expr的值传送给它,使之成为当时挂起的那个yield表达式的值。

本操作导致generator继续执行到下一个yield表达式(或者语句),返回yield值。对新生成器调用send时参数只能是None,因为当时不存在等待值的yield表达式。

def gen():
    value=0
    while True:
        receive=yield value
        if receive=='e':
            break
        value = 'i got: %s' % receive

g=gen()
print(g.send(None))   # 这里用next(g)也是一样
print(g.send('aaa'))
print(g.send(3))
print(g.send('e'))

执行流程:

通过g.send(None)或者next(g)可以启动生成器函数,并执行到第一个yield语句结束的位置,yield value会输出初始值0。此时,执行完了yield语句,但是没有给receive赋值,因为执行完yield程序就处于挂起状态了,还没有来得急给receive赋值。

通过g.send(‘aaa’),会传入值aaa作为yield表达式的值,然后继续执行程序,此时把yield表达式的值赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句又暂停。此时yield value会输出"got: aaa",然后挂起。

通过g.send(3),会重复第2步,最后输出结果为"got: 3"

当我们g.send(‘e’)时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。

最后的执行结果如下:

0
i got: aaa
  File "生成器.py", line 104, in <module>
i got: 3
    print(g.send('e'))
StopIteration

② :generator.close()

手动关闭生成器函数,如果继续调用next()获取下一个值会直接抛出StopIteration异常。

def gen():
    value=0
    while True:
        receive=yield value
        if receive=='e':
            break
        value = 'i got: %s' % receive

g=gen()
print(next(g))
g.close()
print(next(g))  # 继续调用抛出StopIteration异常

③ :generator.throw(type[,value[,traceback]])

启动新生成器或唤醒处于挂起状态的生成器,在当时挂起的yield表达式(语句)处抛出type类型的异常,该异常可以附带value作为参数说明,还可以有相应的traceback对象。

这个调用也返回generator生成器的下一个值,如果此时生成器退出或没有产生yield值,会抛出StopIteration异常。简单说,会抛出异常并结束程序,或者消耗掉一个yield值,或者在没有下一个yield的时候直接进行到程序的结尾。

def gen():
    while True:
        try:
            yield 'normal value1'
            yield 'normal value 2'
            print('here')
        except ValueError:
            print('ValueError : ...... ')
        except TypeError as e:
            print('TypeError : ',e)
            break

g=gen()
print(next(g))
print(g.throw(ValueError))
print(next(g))
print(g.throw(TypeError,'i raised a TypeError'))

执行结果:

normal value1
Traceback (most recent call last):
ValueError : ...... 
normal value1
  File "生成器.py", line 121, in <module>
normal value 2
    print(g.throw(TypeError,'i raised a TypeError'))
TypeError :  i raised a TypeError
StopIteration

10. 巧用断言assert

在开发一个程序时候,与其让它运行时崩溃,不如在它出现错误条件时就崩溃(返回错误)。这时候断言assert 就显得非常有用。
python assert断言是声明布尔值必须为真的判定,如果发生异常就说明表达式为假。
可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。
assert的语法格式:

assert expression

它的等价语句为:

if not expression:
  raise AssertionError

这段代码用来检测数据类型的断言,因为 a_str 是 str 类型,所以认为它是 int 类型肯定会引发错误。

>>> a_str = 'this is a string'
>>> type(a_str)
<type 'str'>
>>> assert type(a_str)== str
>>> assert type(a_str)== int
 
Traceback (most recent call last):
 File "<pyshell#41>", line 1, in <module>
  assert type(a_str)== int
AssertionError

总结
以上就是本文关于Python断言assert的用法代码解析的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!