快乐学习
前程无忧、中华英才非你莫属!

Python-异步编程-gevent-8

zhangtongle阅读(2170)

锁和信号量

信号量是一种低级同步原语,它允许 greenlet 协调和限制并发访问或执行。信号量公开了两个方法,acquire和release的信号量已经被获取和释放被称为绑定的旗语的次数之间的差异。如果信号量边界达到 0,它将阻塞,直到另一个 greenlet 释放其获取。

from gevent import sleep
from gevent.__semaphore import BoundedSemaphore
from gevent.pool import Pool

sem = BoundedSemaphore(2)

def worker1(n):
    sem.acquire()
    print('Worker %i acquired semaphore' % n)
    sleep(0)
    sem.release()
    print('Worker %i released semaphore' % n)

def worker2(n):
    with sem:
        print('Worker %i acquired semaphore' % n)
        sleep(0)
    print('Worker %i released semaphore' % n)

pool = Pool()
pool.map(worker1, range(0, 2))
pool.map(worker2, range(3, 6))

# Worker 0 acquired semaphore
# Worker 1 acquired semaphore
# Worker 0 released semaphore
# Worker 1 released semaphore
# Worker 3 acquired semaphore
# Worker 4 acquired semaphore
# Worker 3 released semaphore
# Worker 4 released semaphore
# Worker 5 acquired semaphore
# Worker 5 released semaphore

线程局部变量

Gevent 还允许您指定 greenlet 上下文的本地数据。在内部,这是作为全局查找实现的,它寻址由 greenlet 的getcurrent()值作为键的私有命名空间。

import gevent
from gevent.local import local

stash = local()

def f1():
    stash.x = 1
    print(stash.x)

def f2():
    stash.y = 2
    print(stash.y)
    try:
        stash.x
    except AttributeError:
        print("x is not local to f2")

g1 = gevent.spawn(f1)
g2 = gevent.spawn(f2)

gevent.joinall([g1, g2, g2])

Python-异步编程-gevent-6

zhangtongle阅读(2126)

队列

队列是有序的数据集,它们具有通常的put/get 操作,但以一种可以在 Greenlets 之间安全操作的方式编写。

例如,如果一个 Greenlet 从队列中抓取一个项目,另一个同时执行的 Greenlet 将不会抓取同一个项目。

import gevent
from gevent.queue import Queue

tasks = Queue()

def worker(n):
    while not tasks.empty():
        task = tasks.get()
        print('Worker %s got task %s' % (n, task))
        gevent.sleep(0)

    print('Quitting time!')

def boss():
    for i in range(1, 25):
        tasks.put_nowait(i)

gevent.spawn(boss).join()

gevent.joinall([
    gevent.spawn(worker, 'steve'),
    gevent.spawn(worker, 'john'),
    gevent.spawn(worker, 'nancy'),
])

'''
输出结果

Worker steve got task 1
Worker john got task 2
Worker nancy got task 3
Worker steve got task 4
Worker john got task 5
Worker nancy got task 6
Worker steve got task 7
Worker john got task 8
Worker nancy got task 9
Worker steve got task 10
Worker john got task 11
Worker nancy got task 12
Worker steve got task 13
Worker john got task 14
Worker nancy got task 15
Worker steve got task 16
Worker john got task 17
Worker nancy got task 18
Worker steve got task 19
Worker john got task 20
Worker nancy got task 21
Worker steve got task 22
Worker john got task 23
Worker nancy got task 24
Quitting time!
Quitting time!
Quitting time!

Process finished with exit code 0

'''

从输出结果中,可以看出从队列取出来的资源,并没有重复。

队列也可以阻塞put或get在需要时阻塞。

每个put and get操作都有一个非阻塞对应物,put_nowait并且 get_nowait不会阻塞,而是在操作不可能时引发gevent.queue.Empty或 引发gevent.queue.Full。

在这个例子中,我们让老板同时运行到工人,并且对队列有一个限制,防止它包含三个以上的元素。此限制意味着put 操作将阻塞,直到队列上有空间。相反,get如果队列上没有要获取的元素,则操作将阻塞,它还需要一个超时参数,以允许队列退出,gevent.queue.Empty如果在超时的时间范围内找不到工作,则异常退出 。

import gevent
from gevent.queue import Queue, Empty

tasks = Queue(maxsize=3)  # 队列最大3

def worker(name):
    try:
        while True:
            task = tasks.get(timeout=1)  # 将队列大小减 1
            print('Worker %s got task %s' % (name, task))
            gevent.sleep(0)
    except Empty:
        print('Quitting time!')

def boss():
    """
    老板会等到个别工人下班
     free,因为任务队列的最大大小是 3。
    """

    for i in range(1, 10):
        tasks.put(i)  # 只能添加3次,被阻塞了
        print('我是迭代1')
    print('在迭代 1 中分配所有工作')

    for i in range(10, 20):
        tasks.put(i)
        print('我是迭代2')
    print('在迭代 2 中分配所有工作')

gevent.joinall([
    gevent.spawn(boss),
    gevent.spawn(worker, 'steve'),
    gevent.spawn(worker, 'john'),
    gevent.spawn(worker, 'bob'),
])

'''
我是迭代1
我是迭代1
我是迭代1
Worker steve got task 1
Worker john got task 2
Worker bob got task 3
我是迭代1
我是迭代1
我是迭代1
Worker steve got task 4
Worker john got task 5
Worker bob got task 6
我是迭代1
我是迭代1
我是迭代1
在迭代 1 中分配所有工作
Worker steve got task 7
Worker john got task 8
Worker bob got task 9
我是迭代2
我是迭代2
我是迭代2
Worker steve got task 10
Worker john got task 11
Worker bob got task 12
我是迭代2
我是迭代2
我是迭代2
Worker steve got task 13
Worker john got task 14
Worker bob got task 15
我是迭代2
我是迭代2
我是迭代2
Worker steve got task 16
Worker john got task 17
Worker bob got task 18
我是迭代2
在迭代 2 中分配所有工作
Worker steve got task 19
Quitting time!
Quitting time!
Quitting time!

Process finished with exit code 0

'''

Python 异步编程-Gevent-4

zhangtongle阅读(2180)

Gevent超时控制

超时是对代码块或 Greenlet 运行时的限制。

import gevent
from gevent import Timeout

seconds = 10

timeout = Timeout(seconds)
timeout.start()

def wait():
    gevent.sleep(10)

try:
    gevent.spawn(wait).join()
except Timeout:
    print('Could not complete')

它们还可以在with语句中与上下文管理器一起使用。

import gevent
from gevent import Timeout

time_to_wait = 5 # seconds

class TooLong(Exception):
    pass

with Timeout(time_to_wait, TooLong):
    gevent.sleep(10)

此外,gevent 还为各种 Greenlet 和数据结构相关调用提供超时参数。例如:

import gevent
from gevent import Timeout

def wait():
    gevent.sleep(2)

timer = Timeout(1).start()
thread1 = gevent.spawn(wait)

try:
    thread1.join(timeout=timer)
except Timeout:
    print('Thread 1 timed out')

# --

timer = Timeout.start_new(1)
thread2 = gevent.spawn(wait)

try:
    thread2.get(timeout=timer)
except Timeout:
    print('Thread 2 timed out')

# --

try:
    gevent.with_timeout(1, wait)
except Timeout:
    print('Thread 3 timed out')

Thread 1 timed out
Thread 2 timed out
Thread 3 timed out

Python 异步编程-Gevent-3

zhangtongle阅读(2158)

gevent执行状态判断

import gevent

def win():
    return 'You win!'

def fail():
    raise Exception('You fail at failing.')

# winner = gevent.spawn(win)
loser = gevent.spawn(fail)

print(winner.started) # True
print(loser.started)  # True

# 在 Greenlet 中引发的异常,留在 Greenlet 内。
try:
    gevent.joinall([winner, loser])
except Exception as e:
    print('This will never be reached')

print(winner.value) # 'You win!'
print(loser.value)  # None

print(winner.ready()) # True
print(loser.ready())  # True

print(winner.successful()) # True
print(loser.successful())  # False

# 在失败中引发的异常,不会传播到外面
# greenlet. 堆栈跟踪将打印到标准输出,但它
# 不会展开父级的堆栈。

print(loser.exception)

# 虽然有可能在外面再次引发异常
# raise loser.exception
# or with
# loser.get()

True
True
You win!
None
True
True
True
False
You fail at failing.

杀死僵尸进程

当主程序收到 SIGQUIT 时未能让步的 Greenlet 可能使程序的执行时间比预期的要长。这会导致所谓的“僵尸进程”,需要从 Python 解释器外部杀死它们。

一个常见的模式是在主程序上监听 SIGQUIT 事件并gevent.shutdown在退出前调用。

import gevent
import signal

def run_forever():
    gevent.sleep(1000)

if __name__ == '__main__':
    gevent.signal(signal.SIGQUIT, gevent.kill)
    thread = gevent.spawn(run_forever)
    thread.join()

Python 异步编程-Gevent-2

zhangtongle阅读(1447)

案例3:非确定性的task函数

import gevent
import random

def task(pid):
    """
    Some non-deterministic task
    """
    gevent.sleep(random.randint(0, 2) * 0.001)
    print('Task %s done' % pid)

def synchronous():  # 同步
    for i in range(1, 10):
        task(i)

def asynchronous():  # 异步
    threads = [gevent.spawn(task, i) for i in range(10)]
    gevent.joinall(threads)

print('Synchronous:')
synchronous()

print('Asynchronous:')
asynchronous()

'''
输出结果:
Synchronous:
Task 1 done
Task 2 done
Task 3 done
Task 4 done
Task 5 done
Task 6 done
Task 7 done
Task 8 done
Task 9 done
Asynchronous:
Task 0 done
Task 2 done
Task 4 done
Task 6 done
Task 8 done
Task 3 done
Task 9 done
Task 1 done
Task 5 done
Task 7 done

Process finished with exit code 0

'''

在同步的情况下,所有的任务都是顺序运行的,这会导致主程序在每个任务执行时阻塞(即暂停主程序的执行)。

该程序的重​​要部分是 gevent.spawn将给定函数包装在 Greenlet 线程中的部分。初始化的greenlets列表存储在数组中threads,该数组传递给gevent.joinall阻止当前程序运行所有给定greenlets的函数。只有当所有 greenlet 终止时,执行才会向前推进。

需要注意的重要事实是异步情况下的执行顺序基本上是随机的,并且异步情况下的总执行时间比同步情况少得多。事实上,同步案例完成的最长时间是每个任务暂停 0.002 秒导致整个队列暂停 0.02 秒。在异步情况下,最大运行时间大约为 0.002 秒,因为没有一个任务阻止其他任务的执行。


案例4:从服务器异步获取数据

import gevent.monkey
gevent.monkey.patch_socket()

import gevent
import urllib2
import simplejson as json

def fetch(pid):
    response = urllib2.urlopen('http://json-time.appspot.com/time.json')
    result = response.read()
    json_result = json.loads(result)
    datetime = json_result['datetime']

    print('Process %s: %s' % (pid, datetime))
    return json_result['datetime']

def synchronous():
    for i in range(1,10):
        fetch(i)

def asynchronous():
    threads = []
    for i in range(1,10):
        threads.append(gevent.spawn(fetch, i))
    gevent.joinall(threads)

print('Synchronous:')
synchronous()

print('Asynchronous:')
asynchronous()

在更常见的用例中,从服务器异步获取数据,fetch()请求之间的运行时会有所不同,具体取决于请求时远程服务器上的负载。

Python 异步编程-Gevent-1

zhangtongle阅读(1578)

1、gevent 介绍

小编介绍

gevent是通过协成封装实现的,实现起来让代码更加简洁,用于IO密集型的场景,如果
例如大规模网络并发请求的时候,可以使用Gevent,例如我们经常使用的requests 是Python 程序员经常拿来发出网络请求的模块,他是同步的,为了发出大量并发异步请求,我们可以使用grequest ,他是requests的作者,使用requests模块 结合gevent 构造而成,我们可以直接使用不必重新造轮子。

那另外说一点:而对应的计算密集型的需求,应当采用传统的多线程、多进程方案,就不适合使用gevent。

官方介绍

是一个基于libev的并发库。它为各种并发和网络相关任务提供了一个干净的 API。目标是为您提供使用 gevent 所需的工具,帮助您解决现有的并发问题并立即开始编写异步应用程序。

gevent 中使用的主要模式是Greenlet,这是一个作为 C 扩展模块提供给 Python 的轻量级协程。Greenlets 都在主程序的 OS 进程内运行,但都是协作调度的。这不同于由操作系统提供的任何真正的并行构造 multiprocessing或threading库,它们执行由操作系统调度并且真正并行的自旋进程和 POSIX 线程。

并发的核心思想是将一个更大的任务分解为一组子任务,这些子任务被安排同时或异步运行,而不是一次或同步运行一个。两个子任务之间的切换称为上下文切换。

gevent 中的上下文切换是通过yielding完成的。在这个例子中,我们有两个上下文,它们通过调用 gevent.sleep().

2、Gevent案例:

案例1:

import gevent

def foo():
    print('foo start')
    gevent.sleep(3)
    print('foo end')

def bar():
    print('bar start')
    gevent.sleep(2)
    print('bar end')

def eat():
    print('eat start')
    gevent.sleep(1)
    print('eat end ')

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
    gevent.spawn(eat)
])
#输出结果
'''
foo start
eat start
bar start
eat end 
bar end
foo end

Process finished with exit code 0

'''

通过输出结果可以得出结论,函数执行的顺序默认会按照joinall配置的执行,当函数遇到sleep阻塞的时候,就会执行其他函数,直到最后执行最堵塞的函数完毕。

案例2:

当我们将它用于可以协同安排的网络和IO绑定功能时,gevent的真正力量就来了。
Gevent已经处理了所有细节,以确保您的网络库尽可能隐式地产生greenlet上下文。
我不能强调这是一个强大的习语。但也许一个例子将说明。
在这种情况下,该select()函数通常是一个阻塞调用,它会轮询各种文件描述符。

import time
import gevent
from gevent import select

start = time.time()
tic = lambda: 'at %1.1f seconds' % (time.time() - start)

def gr1():
    # Busy waits for a second, but we don't want to stick around...
    print('Started Polling: %s' % tic())
    select.select([], [], [], 2)
    print('Ended Polling: %s' % tic())

def gr2():
    # Busy waits for a second, but we don't want to stick around...
    print('Started Polling: %s' % tic())
    select.select([], [], [], 2)
    print('Ended Polling: %s' % tic())

def gr3():
    print("Hey lets do some stuff while the greenlets poll, %s" % tic())
    gevent.sleep(1)

gevent.joinall([
    gevent.spawn(gr1),
    gevent.spawn(gr2),
    gevent.spawn(gr3),
])

'''
运行结果
Started Polling: at 0.0 seconds
Started Polling: at 0.0 seconds
Hey lets do some stuff while the greenlets poll, at 0.0 seconds
Ended Polling: at 2.0 seconds
Ended Polling: at 2.0 seconds
'''

Python 两大凶器-迭代器与生成器

zhangtongle阅读(1428)

1、前言

迭代器与生成器在Python 中非常有用的工具,可以帮助我们处理复杂数据问题的同时也能让我们的代码更加整洁性能更好。


2、迭代器概念

英文名:iterator,实现了方法 iternext 的对象是迭代器。

python 中的for 在底层就实现了迭代器这种技术。所以我们可以使用统一的for xxx in xxx: 的这种固定格式来迭代我们的数据。

例子:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

在代码的底层,for 语句会在容器对象上调用 iter()函数,该函数返回一个定义了 next() 方法的迭代器对象,此方法将逐一访问容器中的元素。 当元素用尽时,next() 将引发 StopIteration 异常来通知终止 for 循环。 你可以使用 next() 内置函数来调用 next() 方法;这个例子显示了它的运作方式:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

3、创建迭代器

3.1 官方案例:

看过迭代器协议的幕后机制,给你的类添加迭代器行为就很容易了。 定义一个 iter() 方法来返回一个带有 next() 方法的对象。 如果类已定义了 next(),则 iter() 可以简单地返回 self:

class Reverse:
    """用于向后循环序列的迭代器."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

3.2 自定义案例:

class MultiplyByTwo:
    def __init__(self, number):
        self.number = number
        self.counter = 0

    def __next__(self):
        self.counter += 1
        return self.counter, self.number * self.counter

mul = MultiplyByTwo(200)
print(next(mul))
print(next(mul))
print(next(mul))

'''
输出结果
(1, 200)
(2, 400)
(3, 600)
'''

让我们看看在Python 中迭代器是如何工作的,上面的代码,我们实现了一个MultiplyByTwo的类,该类有一个名字为next 函数,每当它调用时。都会返回一个新的迭代器,迭代器内部通过维护一个变量来记录序列中的位置,我们可以看到next 方法中使用了counter变量。但是我们使用for循环遍历这它就会出现异常:

for i in mul:
    print(i)

# TypeError: 'MultiplyByTwo' object is not iterable

类型错误,MultiplyByTwo对象是不可迭代。这里强调,MultiplyByTwo它是一个迭代器,但是它不是一个可迭代的对象,为了能让他可以通过for循环遍历,需要把它变成可迭代对象,需要在类里面添加iter方法。

例如:


class MultiplyByTwo:
    def __init__(self, number):
        self.number = number
        self.counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1
        return self.counter, self.number * self.counter

for i in mul:
    print(i)

'''
输出结果:会一直循环遍历下去成死循环

(1, 200)
(2, 400)
(3, 600)
此处为省略
(12368, 2473600)
此处为省略
'''

虽然我们的MultiplyByTwo迭代器,可以让for循环遍历了,但是它成死循环了,不是我们想要的,所以我们需要继续优化它。

例如:

class MultiplyByTwo:
    def __init__(self, number, bigValue):
        self.number = number
        self.counter = 0
        self.bigValue = bigValue

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1

        value = self.number * self.counter

        if value > self.bigValue:
            raise StopIteration
        else:
            return self.counter, value

mul = MultiplyByTwo(200, 1000)

for i in mul:
    print(i)

'''
输出结果为:返回有限数据的迭代器。
(1, 200)
(2, 400)
(3, 600)
(4, 800)
(5, 1000)
'''

当引发StopIteration 这个异常时,MultiplyByTwo对象收到已经达到限制的信号,for 循环会自动处理这个异常,并退出循环。


4、生成器概念

在 Python 中,使用了 yield 的函数被称为生成器(generator),它又叫做生成迭代器的工具

生成器 是一个用于创建迭代器的简单而强大的工具。 当它要返回数据时会使用 yield 语句。每次在生成器上调用 next() 时,它会从上次离开的位置恢复执行(它会记住上次执行语句时的所有数据值)。

生成器在读取大量数据或者大量文件时,非常有用,他可以被暂停和恢复。生成器返回一个像列表一样可被迭代的对象。

这里做重要的概念区分:

可迭代对象:

列表、字典、元组、字符串、文件对象、迭代器 他们都是可迭代对象
因为它们内部都包含iter函数。

列表、字典、元组、字符串、文件对象他们都不属于迭代器,因为他们并不包含 next函数。

看到这里有些小伙伴蒙圈了,如何区分呢:

第一种方式:

可以使用pycharm 快捷键,ctrtl+ 鼠标单击对应list、dict 、类对象来查看类内部是否包含iter和next 函数,来区分是迭代器,还是普通的可迭代对象。在这里我们可以把迭代器看成一种特殊的可迭代对象。

第二种方式

from collections.abc import Iterator # 迭代器
from collections.abc import Iterable # 可迭代对象

# rev 变量为3.1 案例中rev变量。
isinstance(rev, Iterator) # True 输出真 说明他是迭代器
isinstance(rev, Iterable) # True 输出真,说明它又是可迭代对象

isinstance([1, 2, 3], Iterable) # Ture ,他是可迭代对象
isinstance([1, 2, 3], Iterator) # False ,说明他不是迭代器。

# 我们通过第二种方式,来判断它到底是不是迭代器的一个好办法。

5、官方生成器的案例

可以用生成器来完成的操作同样可以用前一节所描述的基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建 iter() 和 next() 方法。

另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 这使得该函数相比使用 self.index 和 self.data 这种实例变量的方式更易编写且更为清晰。

除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发 StopIteration。 这些特性结合在一起,使得创建迭代器能与编写常规函数一样容易。

def reverse(data):
    print('开始')
    for index in range(len(data) - 1, -1, -1):
        print('for开始')

        yield data[index]  # 调用next停止的地方 ,

        print('for结束')  # 生成器从它停止的地方恢复

    print('结束')

rev = reverse('golf')
next(rev)

这里不断调用next 来输出对应的值,小编在例子中加了打印语句,可以方便大家可以看到在哪里暂停,又是在哪里恢复。

我们在对rev 进行判断,看看它到底是什么类型:


rev = reverse('golf')
from collections.abc import Iterator  # 迭代器

isinstance(rev, Iterator)  # True

总结:生成器的最终概念,生成迭代器。想要用好yield 你可以把它当做return 使用即可,但是它不会停止程序哦,并且要记住当再次遍历它时,它会从上次离开的位置恢复执行(并且会记住上次执行语句时的所有数据值),


6、生成器表达式

某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,但外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

官方案例:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

7、生成器案例

当我们想要生成一个0.5 到6.5 步长为0.2 的序列,使用range函数会报错,因为它不支持浮点数:

range(0.5, 6.5, 0.2)
# TypeError: 'float' object cannot be interpreted as an integer

那我们使用生成器技术,来优化range函数,自定义一个来符合我们的要求

# 生产某个范围内浮点数的生成器:
def frange(start, stop, increment):
    x = start
    while x < stop:
        yield x
        x += increment

fr = frange(0.5,6.5,0.2)

next(fr)
0.5
next(fr)
0.7

读取linux 系统比较大的数据文件,进行范围查询时,可使用切片迭代器技术,来加速查询:

from itertools import islice #

f = open('/var/log/dmesg')
for line in islice(f, 100, 300):  # 截取100行到300行之间的内容
    print(line)
for line in islice(f, 500):  # 截取前500行之间的内容
    print(line)
for line in islice(f, 100, None):  # 截取100行到文件末尾之间的内容
    print(line)

可以让大家轻松写成可读性较高的python 点击这个连接可以查看itertools 模块来安排迭代器之间的交互关系。


8、何时使用迭代器

在处理大规模数据文件时或数据流(在线视频)时非常有用,迭代器可以让我们更加灵活地一次只处理一段数据,而不是必须一次性的将全部数都加载到内存当中。

函数不要直接返回列表,应该让他逐个生成列表里面的值,用生成器来实现比让函数把结果存储到列表里在返回更清晰一些。

生成器函数所返回的迭代器产生的一系列值,都是由函数体的下一条yield 表达式决定的。


总结:这一篇章,最重要的技术是迭代器,使用迭代器技术,才能性能更好,使用生成器才会让我们的代码更加简洁

Python中模块与包的运用

zhangtongle阅读(2115)

1、import介绍

它是用来导入工具包提供给我们可以快速构建我们的程序的功能函数。

> Correct:
import os
import sys

> Correct:
from subprocess import Popen, PIPE

导入应按以下顺序分组:

  1. 标准库导入。
  2. 相关第三方。
  3. 本地应用程序/库特定的导入,您应该在每组导入之间放置一个白行。
  4. 推荐绝对导入
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

5、相对导入(不推荐)python3 中已经废除

from . import sibling
from .sibling import example

2、导入模块的正确姿势

模块只是扩展名为.py 的文件,模块的名称就是文件的名称。模块当中可以有很多函数或类。

当我们创建模块名的时候,尽量要言简意赅。

不要这样:import create_women_object
应该这样:import girlfriend  或者from users import girlfriend
不要这样: import users.women.girlfriend、import USERS
应该这样: import users_girlfriend、import users

避免使用在文件名中使用点和特殊字符,导入代码的可读性也至关重要,毕竟万事开头难。开头的阅读新也至关重要。

不要这样:from user import *
         friend = get_girlFriend()

应该这样:from user import get_girlFriend
         friend = get_girlFriend()

最好是这样:import user
           user.get_girlFriend() # 可以明确标记get_girlFriend() 哪个模块下的。

当巧合出现: from user import get_girlFriend
            from person import get_girlFriend

            friend1 = get_girlFriend() # 就会发生冲突
            friend2 = get_girlFriend() # 就会发生冲突

冲突:如果两个包里有同名模块,或者两个模块里有同名的函数或类,那么后引入的那个会把先引入的覆盖掉。

解决冲突使用as起别名:  from user import get_girlFriend as u_gf
                      from person import get_girlFriend as p_gf

as 不仅可以给函数换名字,防止冲突,还能给整个模块换个名称,例如:import pandas as pd 

这是数据分析核心模块的pandas 的常规引入写法。如果pandas 还没有掌握,赶紧去查看同乐老师的[python自动化办公的10、11、16章的内容](https://edu.51cto.com/course/21337.html "python自动化办公的10、11、16章的内容")。

3、init.py 是干啥的、包干哈的?

自从Python3.3之后,不需要在目录中包含init.py文件来表明这个目录是一个Python 包。
3.3之前是需要的,Pycharm 在建立package的时候会默认新建一个,这个init尽管新版本不需要它来表示我是一个包了,但是它还有许多妙用。最好是不要删除,以便引起不必要的麻烦和异常。

包的含义简单解释:是用来管理模块的,就是一个文件夹下面可以创建很多模块。

来源于Python官方社区

包是一种用“点式模块名”构造 Python 模块命名空间的方法。例如,模块名 A.B 表示包 A 中名为 B 的子模块。导入包时,Python 搜索 sys.path 里的目录,查找包的子目录。

Python 只把含 init.py 文件的目录当成包。这样可以防止以 string 等通用名称命名的目录,无意中屏蔽出现在后方模块搜索路径中的有效模块。 最简情况下,init.py 只是一个空文件,但该文件也可以执行包的初始化代码,或设置 all 变量。

购物车模块:

利用init对购物车模块进行拆解

模块拆解的好处,就是好维护,功能单一职责,每个模块负责对应相关的功能职责。

利用init对拆解的模块逻辑式黏连在一起

但是我们再把拆开的模块,逻辑式的黏连在一起对外提供使用,就可以利用init.py

我们在modle_package包下的init.py文件中以from 的形式进行黏连在一起,以modle_package 作为对外服务的公共接口。来达到分久必合的原理。这种方式会让你用一个模块来处理项目的不同功能,这在大型项目和第三方库中特别有用。但是会给客户端要弄清楚这个功能函数在哪一个模块下,带来了一些负担,特别是喜欢用相对导入的小伙伴,利与弊自己权衡,我还是那句话,适合自己的才是最好的,PE8也不是都适合我们。

PE8代码规范:标准库代码应避免复杂的包布局并始终使用绝对导入。
在__init__文件中使用
from . import echo
from .. import formats
from ..filters import equalizer 

# 这种叫做相对导入,大多数情况下不建议这么做,这么做得唯一好处就是,可以省略冗长的导入,以及避免外部包的结构或者包名而改动。**

4、通过包构建稳定的API,使用all关键字

API 就是你写好的功能函数,以开源软件包的形式,提供给别人使用。以免新旧版本差异过大,你就得把他的功能稳定下来,为了做到这一点,就得隐藏软件包内的代码结构。不要让外部开发者依赖这套结构,

Python 允许我们通过all这个特殊属性,决定模块或包里面有哪些内容应该当做API公布给外界。all的值是一份列表,一般from foo import * 与 all 才是最佳拍档。

下面案例把相互关联的模块,以API方式共享

这种做法,可以很好的帮助我们打造明确而稳固的api.如果说你不对外提供,其实没有必要这么麻烦。

并且 from model_package import * 在内部用时,是禁止推荐使用的。


5、有用的扩展

有用扩展来源于python cookbook

读取位于包中的数据文件

mypackage/
    __init__.py
    somedata.dat
    spam.py

现在假设spam.py文件需要读取somedata.dat文件中的内容。你可以用以下代码来完成:

# spam.py
import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')

pkgutil是Python自带的用于包管理相关操作的库,pkgutil能根据包名找到包里面的数据文件,然后读取为bytes型的数据。如果数据文件内容是字符串,那么直接decode()以后就是正文内容了。

为什么pkgutil读取的数据文件是bytes型的内容而不直接是字符串类型?
这是因为并不是所有数据文件都是字符串,如果某些数据文件是二进制文件或者图片,那么以字符串方式打开就会导致报错。所以为了通用,pkgutil会以bytes型方式读入数据,这相当于open函数的“rb”读取方式。

使用pkgutil还有一个好处,就是只要知道包名就可以找到对应包下面的数据文件,数据文件并不一定要在当前包里面。!

下面案例截图来源于互联网:
https://www.cnblogs.com/du-jun/p/12192797.html


6、简单快捷导入多层级包下的某个模块

可以直接把多层级下某个模块,导入到系统环境变量,就像这样、

官方的解释
变量 sys.path 是字符串列表,用于确定解释器的模块搜索路径。该变量以环境变量 PYTHONPATH 提取的默认路径进行初始化,如未设置 PYTHONPATH,则使用内置的默认路径。可以用标准列表操作修改该变量:

import sys
sys.path.append('/ufs/guido/lib/python')

牛奶喝多了,有点醉了,深夜写写博客缓解一下,写了快一个多小时了,不写了,困了,睡觉!
明天继续更~

Python进阶编程必备技能-装饰器

zhangtongle阅读(2422)

1、装饰器的由来

想扩展自己的函数功能,又不想动自己原先的函数代码逻辑,那就得用装饰器,在其他语言里例如java 叫做面向切面编程,老师会给我们讲一堆面向切面底层实现的原理的过程中,我相信你已经丧失了对Java学习的兴趣,伤害永远是对比出来的,哈哈~。

在这里我会全面简单的介绍一下,装饰器有几种模式,不强调你使用哪种模式,按你的需求来。

2、初级自定义装饰器

func为函数名称,这里的思想是把函数作为参数传递给固定某一个函数,这种属于把函数转换为参数的思想。目的有很多种,比较实用的目的是让我们的各种功能行为更加符合规范.举例:比如我们之前有吃早饭,吃晚饭,吃中午饭函数,函数太多记不住,我们就定义了一个吃饭的函数,把吃晚饭、吃中午饭、吃早饭降维为参数进行传递和调用。

def say_Chinese():
    return "我是say_Chinese"

def say_English():
    return "我是say_English"

def say(func):
    return func()

say(say_Chinese)

3、中级自定义装饰器

初级装饰器其实已经满足了我们的需求,为什么要学中级装饰器呢,我们通过wrapFunction 来更改你传进来func 函数的前后的逻辑行为,say_middle 你可以定义多个wrapFunction1,2 来满足各种行为逻辑,并且根据判断条件return 返回对应的装饰器函数即可。

def say_middle(func):
    # 装饰函数
    def wrapFunction():
        print("说话之前,准备演讲稿")  #
        say_style = func()
        print("说话完毕,进行总结")

        return say_style

    return wrapFunction

@say_middle
def say_People():
    return "我是say_People"

# 因为say_People 头顶上装饰了say_middle
# 在调用say_People时候对say_People自动进行装饰。
# @say_middle 是最常用的。
say_People()

当我们想要知道当前执行的是哪个被装饰过得名称的时候。

print(say_People.__name__) # 打印的却是wrapFunction
# 为了解决优化这个问题,下面改进下我们的装饰器

4、改进装饰器

# 为不改变被装饰函数或类的性质,添加functools.wrap装饰器
from functools import wraps
def say_high(func):
    @wraps(func)
    def wrapFunction():
        print("说话之前,准备演讲稿")  
        say_style = func()
        print("说话完毕,进行总结")
        return say_style

    return wrapFunction

5、在类中创建装饰器

有的时候我们的装饰器更加独立化,类和模块都可以用来创建装饰器,Python对于创建装饰器没有限制,这完全取决于你。

为啥类能当装饰器,是因为Python提供了一个特殊的方法call 意味着类的实例可以当做函数来调用。

class Say_Super:

    def __call__(self, func):
        @wraps(func)
        def wrapFunction():
            print("说话之前,准备演讲稿")  #
            say_style = func()
            print("说话完毕,进行总结")
            return say_style

        return wrapFunction

@Say_Super()
def say_Code():
    return "我是say_Code"

say_Code()

6、带参数的装饰器

我们之前定义了都是不带参数的函数,其实不带参数的函数,say_Chinese、say_People的,都是没有灵魂的,因为这些函数,只能处理写成固定逻辑的功能,带参数就会让我们的功能更强,更灵活,因为有了值得判断和比较就会执行不同逻辑的代码。*args, **kwargs 一个是关键字参数,一个是命名关键字参数,目的是让函数可以灵活的传递各类型的参数。如果这块薄弱了,赶紧看看同乐老师录制的Python自动化办公的第十五章基础增强篇,进行基础增强,当基础增强了,才能更能快速掌握Python 的进阶编程所需要的内容。

定义一个装饰器把返回的字符串改成大写。

def to_uppercase(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        text = func(*args, **kwargs)
        if not isinstance(text, str):
            raise TypeError('你传的参数不是字符串,请重新输入')
        return text.upper()
    return wrapper

@to_uppercase
def say_Language(say_str):
    return say_str

language = say_Language('speak chinese')
print(language)  # 输出SPEAK CHINESE

7、在类的方法上使用装饰器

# 可以自定义重复请求的次数,和间隔的秒速
def retry_requests(retries=3, delay=5):
    def try_request(fun):
        @wraps(fun)
        def retry_decorators(*args, **kwargs):
            for _ in range(retries):
                res = fun(*args, **kwargs)
                print(res)
                time.sleep(delay)

        return retry_decorators

    return try_request

class ApiRequest:
    def __init__(self, url, headers):
        self.url = url
        self.headers = headers

    @retry_requests(retries=5, delay=1)
    def create_request(self):
        res = requests.get(url=self.url, headers=self.headers)

        return res

aq = ApiRequest('https://www.ztloo.com', headers=None)
aq.create_request()

8、解除装饰器函数

通过wrapped 关键字解除

@to_uppercase
def say_Language(say_str):
    return say_str

new_say_Language = say_Language.__wrapped__
language = new_say_Language('speak chinese')
print(language)  # 输出SPEAK CHINESE

当我们知道装饰器是咋回事的时候,其他的就需要积累,可以看看开源的模块中,的装饰器是如何管理,和实现的,让我们后续用最少的代码,实现功能最丰富的功能。

我是同乐,我为Python 自动化带盐!!!!!!!!!!!!!!!!!!

自动生成Python 文档-Sphinx

zhangtongle阅读(2390)

1、安装sphinx 模块

pip install Sphinx

2、创建doc文档项目路径

PS D:\rd\PyJob\tongle> sphinx-quickstart pydoc
Welcome to the Sphinx 4.2.0 quickstart utility.

> Separate source and build directories (y/n) [n]: y

The project name will occur in several places in the built documentation.
> Project name: ztloo
> Author name(s): 张同乐
> Project release []: 1.0
> Project language [en]: zh_CN

3、配置conf.py

import os
import sys
sys.path.insert(0, os.path.abspath('../../clean'))

添加扩展,来识别各种格式的注释

extensions = ['sphinx.ext.autodoc',
              'sphinx.ext.doctest',
              'sphinx.ext.intersphinx',
              'sphinx.ext.todo',
              'sphinx.ext.coverage',
              'sphinx.ext.mathjax',
              'sphinx.ext.napoleon']

4、生成RST文件

PS D:\rd\PyJob\tongle\pydoc> sphinx-apidoc -o ./source ../clean/
Creating file ./source\coding_clear.rst.
Creating file ./source\ztloo.rst.
Creating file ./source\modules.rst.

5、生成html

PS D:\rd\PyJob\tongle\pydoc> ./make html
Running Sphinx v4.2.0
loading translations [zh_CN]... done
making output directory... done

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715
error: Sorry,暂时内容不可复制!