Python-base-general

Python-base-general

Python 基础的一些补充

生成器

概念

如果使用yield语句, 可以让函数生成一个结果序列,而不仅仅是一个值
如:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>>def countdown(n):
print("Counting down!")
while n > 0:
yield n
n -= 1
>>>countdown(5)
<generator object countdown at 0x00000289CCDB0888>
>>>c = countdown(5)
>>>c.__next__()
Counting down!
5
>>>c.__next__()
4
  • __next__()调用使生成器函数一直运行,到下一条语句为之。此时__next__()将返回传递给yield的值,而且函数将暂时中止执行,再次调用next时,将执行yield之后的语句的,持续到函数返回为止。
    通常不会手动调用__next__(),而是使用for in 循环

应用举例

生成器是编写基于处理管道,流或数据流程序的一种极强大的方式

  • 下面代码模拟了常用于监控日志文件的UNIX tail -f命令的行为:
    • tail 命令可用于查看文件的内容,有一个常用的参数 -f 常用于查阅正在改变的日志文件。
    • tail -f filename 会把 filename 文件里的最尾部的内容显示在屏幕上,并且不断刷新,只要 filename 更新就可以看到最新的文件内容。
1
2
3
4
5
6
7
8
9
import time
def tail(f):
f.seek(0, 2) # 移动到EOF
while True:
line = f.readline() # 尝试读取新的一行文本
if not line: # 如果没有内容,暂时休眠并尝试
time.sleep(0.1)
continue
yield line

下面代码用于在很多行中查找特定的子字符串,结果返回

1
2
3
4
def grep(lines, searchText):
for line in lines:
if searchText in line:
yield line
  • 这和你写个函数内的列表每次append一下,最后返回列表从结果看没啥区别
  • 每次__next__调用时,到下一条yield,也就是到下一个匹配的字符,即可以迭代获得所有有特定子字符串的行。
  • 生成器的微妙之处在于编写for in 语句时,in后面可以跟列表,文件生成器函数的结果,或者支持迭代的其他任意对象。扩大了程序可拓展性。

文档字符串

如果模块,类或者函数的第一条语句是一个字符串的话,该字符串会成为doc
通过函数名/类名/模块名.__doc__调用

类型与对象

type()&isinstance()

  • type() 函数如果只有第一个参数则返回对象的类型,三个参数返回新的类型对象。
  • isinstance() 与 type() 区别:
    • type() 不会认为子类是一种父类类型,不考虑继承关系。
    • isinstance() 会认为子类是一种父类类型,考虑继承关系。

如果要判断两个类型是否相同推荐使用 isinstance()。

语法
以下是 type() 方法的语法:
type(object)
type(name, bases, dict)
参数
• name – 类的名称。
• bases – 基类的元组。
• dict – 字典,类内定义的命名空间变量。
返回值
一个参数返回对象类型, 三个参数,返回新的类型对象。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>>type(1) 
<type 'int'>
>>>type('runoob')
<type 'str'>
>>>type([2])
<type 'list'>
>>>type({0:'zero'})
<type 'dict'>
>>>x = 1type( x ) == int
True


>>>class X(object):
a = 1
>>>X = type('X', (object,), dict(a=1)) # 产生一个新的类型 X
>>>X

>>><class '__main__.X'>

#type() 与 isinstance()区别:
class A:
pass

class B(A):
pass

isinstance(A(), A) # returns True
type(A()) == A # returns True
isinstance(B(), A) # returns True
type(B()) == A # returns False

垃圾回收机制

  • 在python中
  1. 变量无需事先声明
  2. 变量无需指定类型
  3. 程序员不用关心内存管理
  4. 变量名会被回收
  5. del语句可以直接释放资源
  • 变量定义

变量在使用前必须先声明,python中变量在第一次被赋值时自动声明
动态类型:python中变量无需声明类型,对象的类型和内存都是运行时确定的

  • 引用计数
    • 增加引用计数
      当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为1
      当同一个对象(的引用)又被赋值给其他变量时,或作为参数传递给函数,方法或类的实例时,该对象的引用计数自动+1

      x=3.14
      y=x (此时并没有为y新建一个对象,而是将该对象x的引用次数+1)
    • 减少引用计数
      当对象的引用被销毁时,引用计数会减少,最明显的例子是当引用离开其作用范围时,这种情况最经常出现在函数运行结束,局部变量自动销毁。
      当变量被赋值为另外一个对象时,原对象的引用计数也会自动-1
    • del删除
      引申:任何追踪或调试程序都会给一个对象增加一个额外的引用,可以推迟对象被回收的时间
  • 垃圾收集
    不再使用的内存会被一种称为垃圾收集的机制释放。
    当一个对象的引用计数变为0时,解释器会暂停,释放掉这个对象和仅有的这个对象可访问(可到达的)其他对象。

ps: 可以使用sys.getrefcount(t)来获取t的当前引用计数

深浅拷贝

  • 对每个对象的浅拷贝其实就是创建了一个类型跟原对象一样,其内容是原来对象的引用的对象。序列类型(list,tuple)对象的拷贝默认是浅拷贝,并可以以下几种方式实施:完全切片操作,工厂函数,copy模块的copy
  • 浅拷贝意味着如果修改原对象,拷贝的对象也会跟着改变
  • 使用copy.deepcopy()实现深拷贝(在copy标准库里)

上下文管理协议

with语句支持在另一个成为上下文管理器的对象的控制下执行一系列语句。
with context [as var]
statement
其中context对象需要实现下表的中的方法:

方法 描述
__enter__(self) 进入上下文时调用,其返回值将被放入有with语句的as说明符指定的变量中
__exit__(self, type, value, tb) 离开上下文时调用。如果有异常,则type,value,tb分别为异常的类型,异常的值,跟踪信息。保证安全释放资源
  • 上下文管理接口的首要用途是简化涉及系统状态(打开文件,网络连接和锁定对象)的对象的控制。

  • 如果没有要处理的错误,所有三个值将被置成None

  • Attention!
    一般情况下,引发的异常可能导致控制流跳过负责释放关键资源(如锁)的语句,而使用with语句就会更加安全。

1
2
3
4
5
6
7
8
9
10
11
#sample-1
with open("debug_log", "a") as f:
f.write("Debugging\n")
#statement

#sample-2
import threading
lock = threading.Lock() #获取锁
with lock:
#statement

第一个例子中,当控制流离开with语句时,会自动关闭已经打开的文件
第二个例子中,当控制流进入with后的语句时,会自动请求锁 lock.acquire(),在控制流离开时会释放这个锁 lock.release()

  • 我们还可以自定义实现上下文管理器,看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# -*- coding:utf-8 -*-
#Transaction: 交易,办理,事务
class ListTransaction():
def __init__(self, list):
self.list = list
def __enter__(self):
self.copyList = list(self.list)
return self.copyList
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.list[:] = self.copyList
return False

items = [1, 2, 3]
with ListTransaction(items) as working:
working.append(4)
working.append(5)
print(items)


items = [1, 2, 3]
try:
with ListTransaction(items) as working:
working.append(4)
working.append(5)
raise RuntimeError("Toooooo Much!!")
except RuntimeError:
pass
print(items)

当exc_type 为None 时, 说明没有捕获到异常,将copyList赋值到list里
若不为None,说明出现异常,返回false(这会将异常传递出上下文,该函数的返回值表示产生的异常是否处理了)
由于返回false, 我们外面的except 才捕捉到了上下文传出的异常,而当有异常时,我们的self.list并不发生变化(没有赋值)
结果:
[1, 2, 3, 4, 5]
[1, 2, 3]

  • contextlib模块通过包装生成器函数,更容易实现自定义上下文
    下面是contextmanager源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
"""Helper for @contextmanager decorator."""

def __init__(self, func, args, kwds):
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = func, args, kwds
# Issue 19330: ensure context manager instances have good docstrings
doc = getattr(func, "__doc__", None)
if doc is None:
doc = type(self).__doc__
self.__doc__ = doc

def _recreate_cm(self):
return self.__class__(self.func, self.args, self.kwds)

def __enter__(self):
try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None

def __exit__(self, type, value, traceback):
if type is None:
try:
next(self.gen)
except StopIteration:
return False
else:
raise RuntimeError("generator didn't stop")
else:
if value is None:
# tell if we get the same exception back
value = type()
try:
self.gen.throw(type, value, traceback)
except StopIteration as exc:
# Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration
# raised inside the "with" statement from being suppressed.
return exc is not value
except RuntimeError as exc:
# Don't re-raise the passed in exception. (issue27122)
if exc is value:
return False
# Likewise, avoid suppressing if a StopIteration exception
# was passed to throw() and later wrapped into a RuntimeError
# (see PEP 479).
if type is StopIteration and exc.__cause__ is value:
return False
raise
except:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But throw()
# has to raise the exception to signal propagation, so this
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
#
if sys.exc_info()[1] is value:
return False
raise
raise RuntimeError("generator didn't stop after throw()")


def contextmanager(func):
"""@contextmanager decorator.

Typical usage:

@contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>

This makes this:

with some_generator(<arguments>) as <variable>:
<body>

equivalent to this:

<setup>
try:
<variable> = <value>
<body>
finally:
<cleanup>

"""
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func, args, kwds)
return helper

我们的关注点在__enter__和__exit__,虽然不能完全看懂,但可以看明白个大概:

  • __enter__中捕获可迭代对象的StopIteration(当迭代到最后一项时抛出),即进入时检测是否可以迭代
  • __exit__调用迭代函数的返回结果一次,看看到最后没,结束时检测是否迭代到最后,否则抛出RuntimeError,如果出现异常,则调用函数throw出异常(这里好多看不懂的…)

ps:当能看懂的时候我会回来完善的

另外一个关注点就是contextmanager修饰器的doc,有这个铺垫就可以看懂书上的这段代码:

1
2
3
4
5
6
7
8
from contextlib import contextmanager
@contextmanager
def list_transaction(items):
copy_list = list(items)
yield copy_list
#仅在没有出现错误时才会修改原始列表
items[:] = copy_list

当然最安全的写法是try list(items),防止无法转成列表,然后就可以调用yield了。
这个例子将传递给yield的值作用了__enter__方法的返回值,调用__exit__时,执行将在yield语句后恢复。

Author

Ctwo

Posted on

2019-07-09

Updated on

2020-10-25

Licensed under

Comments