PythonLibrary-博客中文翻译-五-

PythonLibrary 博客中文翻译(五)

原文:PythonLibrary Blog

协议:CC BY-NC-SA 4.0

Python 201:线程教程

原文:https://www.blog.pythonlibrary.org/2016/07/28/python-201-a-tutorial-on-threads/

在 Python 1.5.2 中首次引入了线程模块,作为低级线程模块的增强。线程模块使得处理线程变得更加容易,并且允许程序一次运行多个操作。

请注意,Python 中的线程最适合 I/O 操作,例如从互联网下载资源或读取计算机上的文件和目录。如果你需要做一些 CPU 密集型的事情,那么你会想看看 Python 的多重处理模块。原因是 Python 有全局解释器锁(GIL ),基本上让所有线程都在一个主线程中运行。因此,当您使用线程运行多个 CPU 密集型操作时,您可能会发现它实际上运行得更慢。所以我们将关注线程做得最好的地方:I/O 操作!


线程简介

线程可以让你运行一段长时间运行的代码,就像它是一个独立的程序一样。这有点像调用子进程,只不过你调用的是一个函数或类,而不是一个单独的程序。我总是觉得看一个具体的例子很有帮助。让我们来看看一些非常简单的东西:


import threading

def doubler(number):
    """
    A function that can be used by a thread
    """
    print(threading.currentThread().getName() + '\n')
    print(number * 2)
    print()

if __name__ == '__main__':
    for i in range(5):
        my_thread = threading.Thread(target=doubler, args=(i,))
        my_thread.start()

这里我们导入线程模块并创建一个名为 doubler 的常规函数。我们的函数取一个值,然后将它加倍。它还打印出调用该函数的线程的名称,并在末尾打印一个空行。然后在最后一段代码中,我们创建了五个线程,并依次启动每个线程。你会注意到,当我们实例化一个线程时,我们将它的目标设置为我们的 doubler 函数,并且我们还将一个参数传递给该函数。 args 参数看起来有点奇怪的原因是,我们需要将一个序列传递给 doubler 函数,它只需要一个参数,所以我们需要在末尾加一个逗号来创建一个序列 1。

注意,如果您想等待线程终止,您需要调用它的 join() 方法。

当您运行此代码时,您应该得到以下输出:


Thread-1

0

Thread-2

2

Thread-3

4

Thread-4

6

Thread-5

8

当然,您通常不希望将输出打印到 stdout。当你这样做的时候,这可能会变成一团乱麻。相反,你应该使用 Python 的日志模块。它是线程安全的,工作非常出色。让我们修改上面的例子来使用日志模块,并命名我们的线程,同时我们将它:


import logging
import threading

def get_logger():
    logger = logging.getLogger("threading_example")
    logger.setLevel(logging.DEBUG)

    fh = logging.FileHandler("threading.log")
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)

    logger.addHandler(fh)
    return logger

def doubler(number, logger):
    """
    A function that can be used by a thread
    """
    logger.debug('doubler function executing')
    result = number * 2
    logger.debug('doubler function ended with: {}'.format(
        result))

if __name__ == '__main__':
    logger = get_logger()
    thread_names = ['Mike', 'George', 'Wanda', 'Dingbat', 'Nina']
    for i in range(5):
        my_thread = threading.Thread(
            target=doubler, name=thread_names[i], args=(i,logger))
        my_thread.start()

这段代码中最大的变化是添加了 get_logger 函数。这段代码将创建一个设置为调试级别的记录器。它会将日志保存到当前工作目录(即脚本运行的位置),然后我们为记录的每一行设置格式。格式包括时间戳、线程名、日志级别和记录的消息。

在 doubler 函数中,我们将 print 语句改为 logging 语句。您会注意到,当我们创建线程时,我们将日志记录器传递给了 doubler 函数。我们这样做的原因是,如果您在每个线程中实例化日志记录对象,您最终会得到多个日志记录单例,并且您的日志中会有许多重复的行。

最后,我们通过创建一个名称列表来命名线程,然后使用 name 参数将每个线程设置为一个特定的名称。当您运行这段代码时,您应该得到一个包含以下内容的日志文件:


2016-07-24 20:39:50,055 - Mike - DEBUG - doubler function executing
2016-07-24 20:39:50,055 - Mike - DEBUG - doubler function ended with: 0
2016-07-24 20:39:50,055 - George - DEBUG - doubler function executing
2016-07-24 20:39:50,056 - George - DEBUG - doubler function ended with: 2
2016-07-24 20:39:50,056 - Wanda - DEBUG - doubler function executing
2016-07-24 20:39:50,056 - Wanda - DEBUG - doubler function ended with: 4
2016-07-24 20:39:50,056 - Dingbat - DEBUG - doubler function executing
2016-07-24 20:39:50,057 - Dingbat - DEBUG - doubler function ended with: 6
2016-07-24 20:39:50,057 - Nina - DEBUG - doubler function executing
2016-07-24 20:39:50,057 - Nina - DEBUG - doubler function ended with: 8

该输出是不言自明的,所以让我们继续。我想在这一部分再谈一个话题。即子类化线程。螺纹。让我们以最后一个例子为例,我们将创建自己的自定义子类,而不是直接调用 Thread。以下是更新后的代码:


import logging
import threading

class MyThread(threading.Thread):

    def __init__(self, number, logger):
        threading.Thread.__init__(self)
        self.number = number
        self.logger = logger

    def run(self):
        """
        Run the thread
        """
        logger.debug('Calling doubler')
        doubler(self.number, self.logger)

def get_logger():
    logger = logging.getLogger("threading_example")
    logger.setLevel(logging.DEBUG)

    fh = logging.FileHandler("threading_class.log")
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)

    logger.addHandler(fh)
    return logger

def doubler(number, logger):
    """
    A function that can be used by a thread
    """
    logger.debug('doubler function executing')
    result = number * 2
    logger.debug('doubler function ended with: {}'.format(
        result))

if __name__ == '__main__':
    logger = get_logger()
    thread_names = ['Mike', 'George', 'Wanda', 'Dingbat', 'Nina']
    for i in range(5):
        thread = MyThread(i, logger)
        thread.setName(thread_names[i])
        thread.start()

在这个例子中,我们只是子类化了线程。螺纹。我们像以前一样传入想要加倍的数字和日志对象。但是这一次,我们通过调用线程对象上的 setName 来设置线程的名称。我们仍然需要在每个线程上调用 start ,但是你会注意到我们不需要在子类中定义它。当你调用 start 时,它会通过调用 run 方法来运行你的线程。在我们的类中,我们调用 doubler 函数来进行处理。除了我添加了一行额外的输出之外,输出几乎是一样的。继续运行它,看看你会得到什么。


锁和同步

当您有多个线程时,您可能会发现自己需要考虑如何避免冲突。我这么说的意思是,你可能会有一个用例,其中不止一个线程需要同时访问相同的资源。如果你不考虑这些问题并相应地计划,那么你最终会遇到一些问题,这些问题总是发生在最坏的时候,通常发生在生产中。

解决方法是使用锁。Python 的线程模块提供了一个锁,可以由单个线程持有,也可以根本没有线程持有。如果一个线程试图获取一个已经锁定的资源的锁,那么这个线程基本上会暂停,直到锁被释放。让我们看一个相当典型的例子,一些代码没有任何锁定功能,但应该添加它:


import threading

total = 0

def update_total(amount):
    """
    Updates the total by the given amount
    """
    global total
    total += amount
    print (total)

if __name__ == '__main__':
    for i in range(10):
        my_thread = threading.Thread(
            target=update_total, args=(5,))
        my_thread.start()

让这个例子更加有趣的是添加一个长度可变的 time.sleep 调用。不管怎样,这里的问题是一个线程可能会调用 update_total ,在它完成更新之前,另一个线程可能会调用它并试图更新它。根据操作的顺序,该值可能只被添加一次。

让我们给函数添加一个锁。有两种方法可以做到这一点。第一种方法是使用 try/finally ,因为我们希望确保锁总是被释放。这里有一个例子:


import threading

total = 0
lock = threading.Lock()

def update_total(amount):
    """
    Updates the total by the given amount
    """
    global total
    lock.acquire()
    try:
        total += amount
    finally:
        lock.release()
    print (total)

if __name__ == '__main__':
    for i in range(10):
        my_thread = threading.Thread(
            target=update_total, args=(5,))
        my_thread.start()

在这里,我们只是在做其他事情之前获取锁。然后我们尝试更新总数,最后,我们释放锁并打印当前总数。实际上,我们可以使用 Python 的 with 语句来消除大量样板文件:


import threading

total = 0
lock = threading.Lock()

def update_total(amount):
    """
    Updates the total by the given amount
    """
    global total
    with lock:
        total += amount
    print (total)

if __name__ == '__main__':
    for i in range(10):
        my_thread = threading.Thread(
            target=update_total, args=(5,))
        my_thread.start()

如您所见,我们不再需要 try/finally ,因为由 with 语句提供的上下文管理器为我们完成了所有这些工作。

当然,你也会发现自己在需要多线程访问多个函数的地方编写代码。当您第一次开始编写并发代码时,您可能会这样做:


import threading

total = 0
lock = threading.Lock()

def do_something():
    lock.acquire()

    try:
        print('Lock acquired in the do_something function')
    finally:
        lock.release()
        print('Lock released in the do_something function')

    return "Done doing something"

def do_something_else():
    lock.acquire()

    try:
        print('Lock acquired in the do_something_else function')
    finally:
        lock.release()
        print('Lock released in the do_something_else function')

    return "Finished something else"

if __name__ == '__main__':
    result_one = do_something()
    result_two = do_something_else()

这在这种情况下工作正常,但是假设您有多个线程调用这两个函数。当一个线程在函数上运行时,另一个线程也可能在修改数据,结果会不正确。问题是,你可能甚至没有立即注意到结果是错误的。有什么解决办法?让我们试着弄清楚。

常见的第一个想法是在两个函数调用周围添加一个锁。让我们尝试修改上面的示例,如下所示:


import threading

total = 0
lock = threading.RLock()

def do_something():

    with lock:
        print('Lock acquired in the do_something function')
    print('Lock released in the do_something function')

    return "Done doing something"

def do_something_else():
    with lock:
        print('Lock acquired in the do_something_else function')
    print('Lock released in the do_something_else function')

    return "Finished something else"

def main():
    with lock:
        result_one = do_something()
        result_two = do_something_else()

    print (result_one)
    print (result_two)

if __name__ == '__main__':
    main()

当你真正去运行这段代码时,你会发现它只是挂起。原因是我们刚刚告诉线程模块获取锁。所以当我们调用第一个函数时,它发现锁已经被持有并阻塞。它将继续阻塞,直到锁被释放,这是永远不会发生的。

这里真正的解决方案是使用一个重入锁。Python 的线程模块通过 RLock 函数提供了一个。只需换线锁=穿线。Lock()lock =线程。RLock() 并尝试重新运行代码。您的代码现在应该可以工作了!

如果您想用实际的线程来尝试上面的代码,那么我们可以用下面的代码替换对 main 的调用:


if __name__ == '__main__':
    for i in range(10):
        my_thread = threading.Thread(
            target=main)
        my_thread.start()

这将在每个线程中运行 main 函数,该函数将依次调用其他两个函数。您最终也会得到 10 组输出。


线程模块有一个名为 Timer 的简洁类,您可以用它来表示在指定时间后应该发生的动作。他们实际上启动了自己的定制线程,并使用与普通线程相同的 start() 方法启动。您也可以使用取消方法来停止计时器。需要注意的是,你甚至可以在定时器开始前就取消它。

有一天,我遇到了一个用例,我需要与一个已经启动的子流程进行通信,但是我需要它超时。虽然有很多不同的方法来解决这个问题,但我最喜欢的解决方案是使用线程模块的 Timer 类。

对于这个例子,我们将看看如何使用 ping 命令。在 Linux 中,ping 命令将一直运行,直到您杀死它。所以 Timer 类在 Linux 领域变得特别方便。这里有一个例子:


import subprocess

from threading import Timer

kill = lambda process: process.kill()
cmd = ['ping', 'www.google.com']
ping = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

my_timer = Timer(5, kill, [ping])

try:
    my_timer.start()
    stdout, stderr = ping.communicate()
finally:
    my_timer.cancel()

print (str(stdout))

这里我们只是设置了一个 lambda,我们可以用它来终止进程。然后,我们开始 ping 作业并创建一个计时器对象。您会注意到第一个参数是以秒为单位的等待时间,然后是要调用的函数和要传递给该函数的参数。在这种情况下,我们的函数是一个 lambda,我们传递给它一个参数列表,这个列表碰巧只有一个元素。如果您运行这个代码,它应该运行大约 5 秒钟,然后打印出 ping 的结果。


其他线程组件

线程模块还包括对其他项目的支持。例如,您可以创建一个信号量,它是计算机科学中最古老的同步原语之一。基本上,一个信号量管理一个内部计数器,每当你对它调用获取时,这个计数器就会递减,当你调用释放时,就会递增。计数器被设计成不能低于零。所以如果你碰巧在它为零的时候调用了 acquire,那么它就会阻塞。

另一个有用的工具是事件。它将允许您使用信号在线程之间进行通信。在下一节中,我们将看一个使用事件的例子。

最后在 Python 3.2 中,添加了 Barrier 对象。Barrier 是一个原语,它基本上管理一个线程池,其中的线程必须相互等待。为了通过障碍,线程需要调用 wait() 方法,该方法将一直阻塞,直到所有线程都进行了调用。然后它将同时释放所有线程。


线程通信

在一些用例中,您会希望让线程相互通信。正如我们前面提到的,您可以使用 create aEvent来实现这个目的。但是更常见的方法是使用一个队列。对于我们的例子,我们将实际上使用两者!让我们看看这是什么样子:


import threading

from queue import Queue

def creator(data, q):
    """
    Creates data to be consumed and waits for the consumer
    to finish processing
    """
    print('Creating data and putting it on the queue')
    for item in data:
        evt = threading.Event()
        q.put((item, evt))

        print('Waiting for data to be doubled')
        evt.wait()

def my_consumer(q):
    """
    Consumes some data and works on it

    In this case, all it does is double the input
    """
    while True:
        data, evt = q.get()
        print('data found to be processed: {}'.format(data))
        processed = data * 2
        print(processed)
        evt.set()
        q.task_done()

if __name__ == '__main__':
    q = Queue()
    data = [5, 10, 13, -1]
    thread_one = threading.Thread(target=creator, args=(data, q))
    thread_two = threading.Thread(target=my_consumer, args=(q,))
    thread_one.start()
    thread_two.start()

    q.join()

让我们把它分解一下。首先,我们有一个 creator(也称为 producer)函数,用于创建我们想要处理(或消费)的数据。然后,我们使用另一个函数来处理我们称为 my_consumer 的数据。creator 函数将使用队列的 put 方法将数据放入队列,消费者将不断检查更多数据,并在数据可用时进行处理。队列处理所有锁的获取和释放,所以您不必这样做。

在这个例子中,我们创建了一个想要双精度的值的列表。然后我们创建两个线程,一个用于创建者/生产者,一个用于消费者。您会注意到,我们向每个线程传递一个队列对象,这是处理锁的神奇之处。队列将让第一个线程向第二个线程提供数据。当第一个将一些数据放入队列时,它还会传入一个事件,然后等待该事件完成。然后在消费者中,数据被处理,当它完成时,它调用事件的 set 方法,该方法告诉第一个线程第二个线程已经完成处理,它可以继续。

代码调用的最后一行是队列对象的 join 方法,它告诉队列等待线程完成。当第一个线程用完了要放入队列的项目时,它就结束了。


包扎

我们在这里讨论了很多材料。您已经了解了以下内容:

  • 线程基础
  • 锁定的工作原理
  • 什么是事件以及如何使用它们
  • 如何使用计时器
  • 使用队列/事件的线程间通信

现在你已经知道了线程是如何使用的,它们有什么用处,我希望你能在自己的代码中找到许多好的用法。


相关阅读

Python 201 -上下文管理器简介

原文:https://www.blog.pythonlibrary.org/2015/10/20/python-201-an-intro-to-context-managers/

几年前,Python 2.5 中出现了一个特殊的新关键字,称为“ with statement”。这个新关键字允许开发人员创建上下文管理器。但是等等!什么是上下文管理器?它们是方便的构造,允许你自动设置和拆除某些东西。例如,你可能想打开一个文件,在里面写一些东西,然后关闭它。这可能是上下文管理器的经典示例:


with open(path, 'w') as f_obj:
    f_obj.write(some_data)

回到 Python 2.4,您必须用老式的方法来做:


f_obj = open(path, 'w')
f_obj.write(some_data)
f_obj.close()

这在幕后的工作方式是通过使用 Python 的一些神奇方法: enterexit 。让我们试着创建我们自己的上下文管理器来演示这一切是如何工作的!

创建上下文管理器类

这里我们不重写 Python 的 open 方法,而是创建一个上下文管理器,它可以创建一个 SQLite 数据库连接,并在连接完成后关闭它。这里有一个简单的例子:


import sqlite3

########################################################################
class DataConn:
    """"""

    #----------------------------------------------------------------------
    def __init__(self, db_name):
        """Constructor"""
        self.db_name = db_name

    #----------------------------------------------------------------------
    def __enter__(self):
        """
        Open the database connection
        """
        self.conn = sqlite3.connect(self.db_name)
        return self.conn

    #----------------------------------------------------------------------
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Close the connection
        """
        self.conn.close()

#----------------------------------------------------------------------
if __name__ == '__main__':
    db = '/home/mdriscoll/test.db'
    with DataConn(db) as conn:
        cursor = conn.cursor()

在上面的代码中,我们创建了一个获取 SQLite 数据库文件路径的类。 enter 方法自动执行,创建并返回数据库连接对象。现在我们有了它,我们可以创建一个游标并写入数据库或查询它。当我们用语句退出时,会导致 exit 方法执行并关闭连接。

让我们尝试使用另一种方法创建一个上下文管理器。


使用 contextlib 创建上下文管理器

Python 2.5 不仅增加了带语句的,还增加了上下文库模块。这允许我们使用 contextlib 的 contextmanager 函数作为装饰器来创建上下文管理器。让我们试着创建一个打开和关闭文件的上下文管理器:


from contextlib import contextmanager

@contextmanager
def file_open(path):
    try:
        f_obj = open(path, 'w')
        yield f_obj
    except OSError:
        print "We had an error!"
    finally:
        print 'Closing file'
        f_obj.close()

#----------------------------------------------------------------------
if __name__ == '__main__':
    with file_open('/home/mdriscoll/test.txt') as fobj:
        fobj.write('Testing context managers')

这里我们只是从的 contextlib 中导入 contextmanager ,并用它来修饰我们的 file_open 函数。这允许我们使用 Python 的 with 语句调用 file_open 。在我们的函数中,我们打开文件,然后将它输出,以便调用函数可以使用它。一旦 with 语句结束,控制返回到 file_open 函数,并继续执行 yield 语句后面的代码。这导致执行 finally 语句,关闭文件。如果我们在处理文件时碰巧有一个 OSError ,它会被捕获,并且最终语句仍然会关闭文件处理程序。


包扎

上下文管理器非常有趣,并且总是很方便。例如,我在自动化测试中一直使用它们来打开和关闭对话框。现在,您应该能够使用 Python 的一些内置工具来创建自己的上下文管理器。开心快乐编码!


相关阅读

Python 201:分布式工具简介

原文:https://www.blog.pythonlibrary.org/2012/07/09/python-201-an-intro-to-distutils/

上次我们学习了如何创建模块和包。今天,我们将使用我们创建的包,并使用 Python 的 distutils 创建几种不同的方法来分发我们的代码。在本教程中,我们将学习以下内容:

  • 如何创建 setup.py 文件
  • 如何创建源分布
  • 如何创建 Windows 分发版

我们开始吧!

编写我们的第一个“setup.py”脚本

当您编写 setup.py 脚本时,需要注意它的位置。如果你把它放在你的包的文件夹中,当你试图构建一个发行版时,你会收到一个错误。如果您将 setup.py 文件放在有一堆其他文件夹的文件夹之外,您可能最终会添加一堆您不想添加的文件。因此,为了使这个超级简单,创建一个新文件夹,并将我们在上一篇文章中创建的“mymath”文件夹复制到您的新文件夹中。然后在 mymath 文件夹旁边创建 setup.py 文件。文件夹结构应该如下所示:


myNewFolder/
    mymath/
    setup.py

注:如果没有上一篇文章的代码,可以下载mymath . zip

现在我们需要把一些代码放到我们的新脚本中。让我们看看下面的例子:


from distutils.core import setup

setup(name = "mymath",
      version = "0.1",
      description = "A Simple Math Package",
      author = "Mike Driscoll",
      author_email = "mike@somedomain.com",
      url = "https://www.blog.pythonlibrary.org/2012/07/08/python-201-creating-modules-and-packages/",
      packages=["mymath"]
      )

嗯,这看起来很简单。如果我们想在 setup.py 文件中添加更多的包,我们只需将它们添加到列表中。还有一个 py_modules 列表,可以为单个模块添加。如果你深入研究文档,你会发现你也可以使用 disutils 的扩展类添加非 Python 文件。这是为了包含 C 文件或类似的文件,就像在 lxml 这样的第三方包中找到的一样。

Distutils:如何构建源代码和 Windows 发行版

现在我们已经定义并保存了安装脚本,我们可以创建一个源归档文件,我们可以用它来分发我们精彩的包。只需打开一个终端(或 Windows 上的命令行),将目录切换到您创建的新目录。然后执行以下命令:


python setup.py sdist

这应该会吐出这样的话:


C:\Users\mdriscoll\Documents\mymath-setup>python setup.py sdist
running sdist
warning: sdist: manifest template 'MANIFEST.in' does not exist (using default fi
le list)
writing manifest file 'MANIFEST'
creating mymath-0.1
creating mymath-0.1\mymath
copying files to mymath-0.1...
copying README.txt -> mymath-0.1
copying setup.py -> mymath-0.1
copying mymath\__init__.py -> mymath-0.1\mymath
copying mymath\add.py -> mymath-0.1\mymath
copying mymath\divide.py -> mymath-0.1\mymath
copying mymath\multiply.py -> mymath-0.1\mymath
copying mymath\subtract.py -> mymath-0.1\mymath
creating dist
creating 'dist\mymath-0.1.zip' and adding 'mymath-0.1' to it
adding 'mymath-0.1\PKG-INFO'
adding 'mymath-0.1\README.txt'
adding 'mymath-0.1\setup.py'
adding 'mymath-0.1\mymath\add.py'
adding 'mymath-0.1\mymath\divide.py'
adding 'mymath-0.1\mymath\multiply.py'
adding 'mymath-0.1\mymath\subtract.py'
adding 'mymath-0.1\mymath\__init__.py'
creating 'dist\mymath-0.1.zip' and adding 'mymath-0.1' to it
adding 'mymath-0.1\PKG-INFO'
adding 'mymath-0.1\README.txt'
adding 'mymath-0.1\setup.py'
adding 'mymath-0.1\mymath\add.py'
adding 'mymath-0.1\mymath\divide.py'
adding 'mymath-0.1\mymath\multiply.py'
adding 'mymath-0.1\mymath\subtract.py'
adding 'mymath-0.1\mymath\__init__.py'
removing 'mymath-0.1' (and everything under it)

这意味着 distutils 已经创建了一个名为 dist 的新文件夹,它现在有一个 zip 文件(mymath-0.1.zip ),其中包含您的软件包的所有文件。如果你在 Windows 上运行它,你会得到一个 zip 文件,而在*nix 上,你会得到一个 tarball。

现在,如果你想创建一个小的 Windows installer exe,你也可以这样做。只需在命令行上运行这个略有不同的命令:


python setup.py bdist_wininst

该命令将在您的 dist 文件夹中创建一个 build 目录(您可以忽略它)和一个名为“mymath-0.1.win32.exe”的文件,您可以在 Windows 上运行该文件来安装您的软件包。说到安装,我们可能应该尝试一下!

如何安装您的软件包

对于源文件,您需要解包/解压缩它,然后从终端/命令行窗口调用以下命令:


python setup.py install

如果一切正常,您将有一个新的软件包安装到您的系统。要使用 Windows exe,您只需双击它,您应该会看到以下向导出现:

注意,它显示了我们在 setup.py 脚本中创建的所有元数据。是不是很酷?无论如何,如果你运行安装程序,它也应该安装软件包。

注意: 以这种方式安装会将这个包添加到您的 Python 基础安装中。如果你不想这么做,那么你可以使用 virtualenv 作为你的测试平台。

包扎

至此,您应该能够使用 distutils 和 Python 创建并安装自己的包了。如果你想把你的包上传到 Python 包索引(PyPI),那么你应该阅读他们的教程。祝你好运,编码快乐!

进一步阅读

Python 201:生成器简介

原文:https://www.blog.pythonlibrary.org/2014/01/27/python-201-an-intro-to-generators/

关于发电机的话题之前已经讨论过无数次了。然而,这仍然是一个许多新程序员都有困难的话题,我大胆猜测,即使是有经验的用户也不会真正使用它们。

Python 生成器允许开发人员懒散地评估数据。这在你处理所谓的“大数据”时非常有帮助。它们的主要用途是产生价值,并以有效的方式产生价值。在本文中,我们将讨论如何使用生成器,并看看生成器表达式。希望到最后你能在自己的项目中自如地使用生成器。

生成器的典型用例是展示如何以一系列块或行的形式读取一个大文件。这个想法没有错,所以我们也把它用在第一个例子中。要创建一个生成器,我们需要做的就是使用 Python 的 yield 关键字。yield 语句将把一个函数变成一个迭代器。要将一个常规函数变成迭代器,你所要做的就是用一个产生语句替换返回语句。让我们来看一个例子:


#----------------------------------------------------------------------
def read_large_file(file_object):
    """
    Uses a generator to read a large file lazily
    """
    while True:
        data = file_object.readline()
        if not data:
            break
        yield data

#----------------------------------------------------------------------
def process_file(path):
    """"""
    try:
        with open(path) as file_handler:
            for line in read_large_file(file_handler):
                # process line
                print(line)
    except (IOError, OSError):
        print("Error opening / processing file")

#----------------------------------------------------------------------
if __name__ == "__main__":
    path = "TB_burden_countries_2014-01-23.csv"
    process_file(path)

为了让测试更容易,我去了世界卫生组织(世卫组织)的网站,下载了一个关于结核病的 CSV 文件。具体来说,我从这里抓取了“世卫组织结核病负担估计[csv 890kb]”文件。如果您已经有了一个大文件,请随意适当地编辑代码。总之,在这段代码中,我们创建了一个名为 read_large_file 的函数,通过让它返回数据,将它变成一个生成器。

魔术是这样工作的:我们为循环创建一个,它在我们的生成器函数上循环。对于每次迭代,generator 函数将产生一个包含一行数据的 generator 对象,For 循环将处理它。在这种情况下,“过程”只是将这一行打印到 stdout,但是您可以根据需要进行修改。在真实的程序中,您可能会将数据保存到数据库中,或者用数据创建 PDF 或其他报告。当生成器返回时,它会挂起函数的执行状态,以便保留局部变量。这允许我们在不丢失位置的情况下继续下一个循环。

无论如何,当生成器函数用完数据时,我们会中断它,这样循环就不会无限地继续下去。生成器允许我们一次只处理一个数据块,这样可以节省大量内存。

更新 2014/01/28 :我的一位读者指出,文件首先返回惰性迭代器,这是我认为他们做的事情。奇怪的是,每个人和他们的狗都推荐使用生成器来读取文件,但是仅仅迭代文件就足够了。因此,让我们重写上面的例子来利用这个概念:


#----------------------------------------------------------------------
def process_file_differently(path):
    """
    Process the file line by line using the file's returned iterator
    """
    try:
        with open(path) as file_handler:
            while True:
                print next(file_handler)
    except (IOError, OSError):
        print("Error opening / processing file")
    except StopIteration:
        pass

#----------------------------------------------------------------------
if __name__ == "__main__":
    path = "TB_burden_countries_2014-01-23.csv"
    process_file_differently(path)

在这段代码中,我们创建了一个无限循环,它将在文件处理程序对象上调用 Python 的 next 函数。这将导致 Python 逐行返回文件以供使用。当文件用完数据时,会引发 StopIteration 异常,所以我们要确保捕捉到它并忽略它。

生成器表达式

Python 有生成器表达式的概念。生成器表达式的语法非常类似于列表理解。让我们来看看两者的区别:


# list comprehension
lst = [ord(i) for i in "ABCDEFGHI"]

# equivalent generator expression
gen = list(ord(i) for i in "ABCDEFGHI")

这个例子是基于 Python 的 HOWTO 章节中关于生成器的一个例子,坦率地说,我觉得它有点迟钝。生成器表达式和列表理解之间的主要区别在于包含表达式的内容。对于列表理解,是方括号;对于生成器表达式,它是常规括号。让我们创建生成器表达式本身,而不把它变成列表:


gen = (ord(i) for i in "ABCDEFGHI")
while True:
    print gen.next()

如果您运行这段代码,您将看到它打印出字符串中每个成员的每个序数值,然后您将看到一个回溯,表明 StopIteration 已经发生。这意味着发电机本身已经耗尽(即它是空的)。到目前为止,我还没有在自己的工作中发现 generator 表达式的用途,但是我很想知道您用它来做什么。

包扎

现在你知道了发电机的用途和它最普遍的用途之一。您还了解了生成器表达式及其工作原理。我个人曾使用一个生成器来解析那些应该成为“大数据”的数据文件。你用这些做什么?

python 201:import lib 简介

原文:https://www.blog.pythonlibrary.org/2016/05/27/python-201-an-intro-to-importlib/

Python 提供了 importlib 包作为其标准模块库的一部分。其目的是为 Python 的导入语句(以及 import() 函数)提供实现。此外,importlib 使程序员能够创建他们自己的定制对象(又名导入器),可以在导入过程中使用。

小鬼呢?

还有一个叫做 imp 的模块,它为 Python 的imp语句背后的机制提供了一个接口。Python 3.4 中不赞成使用此模块。打算用 importlib 来代替它。

该模块相当复杂,因此我们将把本文的范围限制在以下主题上:

  • 动态导入
  • 检查一个模块是否可以导入
  • 从源文件本身导入

让我们从动态导入开始吧!


动态导入

importlib 模块支持导入作为字符串传递给它的模块的能力。因此,让我们创建几个我们可以使用的简单模块。我们将为两个模块提供相同的接口,但是让它们打印出自己的名字,这样我们就可以区分这两个模块。创建两个不同名称的模块,如 foo.pybar.py ,并在每个模块中添加以下代码:

def main():
    print(__name__)

现在我们只需要使用 importlib 来导入它们。让我们来看看实现这一点的一些代码。确保将这段代码放在与上面创建的两个模块相同的文件夹中。

# importer.py

import importlib

def dynamic_import(module):

    return importlib.import_module(module)

if __name__ == '__main__':
    module = dynamic_import('foo')
    module.main()

    module_two = dynamic_import('bar')
    module_two.main()

这里我们导入了方便的 importlib 模块,并创建了一个名为 dynamic_import 的非常简单的函数。这个函数所做的就是用我们传入的模块字符串调用 importlib 的 import_module 函数,并返回调用的结果。然后在我们底部的条件语句中,我们调用每个模块的 main 方法,它会忠实地打印出模块的名称。

您可能不会在自己的代码中经常这样做,但是偶尔您会发现自己想要导入一个模块,而此时您只有一个字符串形式的模块。importlib 模块给了我们这样做的能力。


模块导入检查

Python 有一种被称为 EAFP 的编码风格:请求原谅比请求许可更容易。这意味着,假设某些东西存在(比如字典中的一个键)并在我们出错时捕捉异常通常更容易。在我们上一章中你看到了这一点,我们试图导入一个模块,如果它不存在,我们就捕捉到了 ImportError 。如果我们想检查一个模块是否可以被导入,而不仅仅是猜测,那该怎么办?你可以用 importlib 来做!让我们来看看:

import importlib.util

def check_module(module_name):
    """
    Checks if module can be imported without actually
    importing it
    """
    module_spec = importlib.util.find_spec(module_name)
    if module_spec is None:
        print('Module: {} not found'.format(module_name))
        return None
    else:
        print('Module: {} can be imported!'.format(module_name))
        return module_spec

def import_module_from_spec(module_spec):
    """
    Import the module via the passed in module specification
    Returns the newly imported module
    """
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    return module

if __name__ == '__main__':
    module_spec = check_module('fake_module')
    module_spec = check_module('collections')
    if module_spec:
        module = import_module_from_spec(module_spec)
        print(dir(module))

这里我们导入了 importlib 的一个子模块,名为 utilcheck_module 代码是我们想看的第一个魔术。在其中,我们针对传入的模块字符串调用了 find_spec 函数。首先我们传入一个假名,然后我们传入一个 Python 模块的真名。如果您运行这段代码,您会看到当您传入一个没有安装的模块名时, find_spec 函数将返回 None ,我们的代码将打印出没有找到该模块。如果找到了,那么我们将返回模块规范。

我们可以获取模块规范,并使用它来实际导入模块。或者您可以将字符串传递给我们在上一节中了解到的 import_module 函数。但是我们已经讨论过了,所以让我们来学习如何使用模块规范。看看上面代码中的 import_module_from_spec 函数。接受 check_module 返回的模块规格。然后,我们将它传递给 importlib 的 module_from_spec 函数,该函数返回导入模块。Python 的文档建议在导入模块后执行它,所以这就是我们接下来用 exec_module 函数做的事情。最后,我们返回模块并对其运行 Python 的 dir 以确保它是我们期望的模块。


从源文件导入

importlib 的 util 子模块有另一个我想介绍的巧妙技巧。你可以使用 util 来导入一个模块,只需要它的名字和文件路径。下面是一个非常衍生的例子,但我认为它会让你明白这一点:

import importlib.util

def import_source(module_name):
    module_file_path = module_name.__file__
    module_name = module_name.__name__

    module_spec = importlib.util.spec_from_file_location(
        module_name, module_file_path)
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    print(dir(module))

    msg = 'The {module_name} module has the following methods:' \
        ' {methods}'
    print(msg.format(module_name=module_name, 
                     methods=dir(module)))

if __name__ == '__main__':
    import logging
    import_source(logging)

在上面的代码中,我们实际上导入了日志模块,并将其传递给我们的 import_source 函数。一旦到了那里,我们就获取模块的实际路径及其名称。然后我们调用将这些信息传递给 util 的 spec_from_file_location 函数,该函数将返回模块的规范。一旦我们有了这些,我们就可以使用在上一节中使用的相同的 importlib 机制来实际导入模块。


包扎

此时,您应该知道如何在自己的代码中使用 importlib 和 import 挂钩。这个模块的内容比本文所介绍的要多得多,所以如果您需要编写一个自定义导入器或加载器,那么您需要花一些时间阅读文档和源代码。


相关阅读

Python 201:迭代器和生成器介绍

原文:https://www.blog.pythonlibrary.org/2016/05/03/python-201-an-intro-to-iterators-and-generators/

自从开始用 Python 编程以来,你可能一直在使用迭代器和生成器,但你可能没有意识到这一点。在这篇文章中,我们将学习什么是迭代器和生成器。我们也将学习它们是如何被创建的,这样我们就可以在需要的时候创建我们自己的。

迭代程序

迭代器是一个允许你遍历容器的对象。Python 中的迭代器通过两种不同的方法实现: iternext 。您的容器需要 iter 方法来提供迭代支持。它将返回迭代器对象本身。但是如果您想要创建一个迭代器对象,那么您还需要定义 next ,这将返回容器中的下一个项目。

注意:在 Python 2 中,命名约定略有不同。你仍然需要 iter ,但是 next 被称为 next
为了让事情更加清楚,让我们回顾一下几个定义:

  • iterable -定义了 iter 方法的对象
  • iterator -同时定义了 iternext 的对象,其中 iter 将返回迭代器对象,而 next 将返回迭代中的下一个元素。

与大多数神奇的方法(带有双下划线的方法)一样,您不应该直接调用 iternext。相反,你可以使用一个进行循环或列表理解,Python 会自动为你调用这些方法。在某些情况下,您可能需要调用它们,但是您可以使用 Python 的内置函数来这样做: iternext

在我们继续之前,我想提一下序列。Python 3 有列表、元组、范围等几种序列类型。该列表是可迭代的,但不是迭代器,因为它不实现 next。这在下面的例子中很容易看出:


>>> my_list = [1, 2, 3]
>>> next(my_list)
Traceback (most recent call last):
  Python Shell, prompt 2, line 1
builtins.TypeError: 'list' object is not an iterator

在上面的例子中,当我们试图调用 list 的 next 方法时,我们收到了一个 TypeError ,并被告知 list 对象不是迭代器。


>>> iter(my_list)
 >>> list_iterator = iter(my_list)
>>> next(list_iterator)
1
>>> next(list_iterator)
2
>>> next(list_iterator)
3
>>> next(list_iterator)
Traceback (most recent call last):
  Python Shell, prompt 8, line 1
builtins.StopIteration: 

要将列表转换成迭代器,只需将其封装在对 Python 的 iter 方法的调用中。然后你可以调用的下一个,直到迭代器用完所有条目,并且的 StopIteration 被抛出。让我们试着把这个列表变成一个迭代器,并用一个循环对它进行迭代:


>>> for item in iter(my_list):
...     print(item)
... 
1
2
3

当使用循环迭代迭代器时,不需要调用 next,也不必担心会引发 StopIteration 异常。


创建你自己的迭代器

偶尔你会想要创建你自己的自定义迭代器。Python 让这变得非常容易。如前一节所述,您需要做的就是在您的类中实现 iternext 方法。让我们创建一个迭代器,它可以迭代一串字母:


class MyIterator:

    def __init__(self, letters):
        """
        Constructor
        """
        self.letters = letters
        self.position = 0

    def __iter__(self):
        """
        Returns itself as an iterator
        """
        return self

    def __next__(self):
        """
        Returns the next letter in the sequence or 
        raises StopIteration
        """
        if self.position >= len(self.letters):
            raise StopIteration
        letter = self.letters[self.position]
        self.position += 1
        return letter

if __name__ == '__main__':
    i = MyIterator('abcd')
    for item in i:
        print(item)

对于这个例子,我们的类中只需要三个方法。在我们的初始化中,我们传入字母字符串并创建一个类变量来引用它们。我们还初始化了一个位置变量,这样我们总是知道我们在字符串中的位置。iter 方法只返回它自己,这是它真正需要做的。next 方法是这个类中最重要的部分。在这里,我们根据字符串的长度检查位置,如果我们试图超过它的长度,就引发 StopIteration。否则,我们提取我们所在的字母,增加位置并返回字母。

让我们花点时间来创建一个无限迭代器。无限迭代器是可以永远迭代的迭代器。在调用这些函数时你需要小心,因为如果你不确定给它们加一个界限,它们会导致一个无限循环。


class Doubler:
    """
    An infinite iterator
    """

    def __init__(self):
        """
        Constructor
        """
        self.number = 0

    def __iter__(self):
        """
        Returns itself as an iterator
        """
        return self

    def __next__(self):
        """
        Doubles the number each time next is called
        and returns it. 
        """
        self.number += 1
        return self.number * self.number

if __name__ == '__main__':
    doubler = Doubler()
    count = 0

    for number in doubler:
        print(number)
        if count > 5:
            break
        count += 1         

在这段代码中,我们不向迭代器传递任何东西。我们只是实例化它。然后,为了确保我们不会陷入无限循环,我们在开始迭代自定义迭代器之前添加了一个计数器。最后,当计数器超过 5 时,我们开始迭代并中断。


发电机

一个普通的 Python 函数将总是返回一个值,无论它是一个列表、一个整数还是一些其他对象。但是,如果您希望能够调用一个函数并让它产生一系列值,该怎么办呢?这就是发电机的用武之地。生成器的工作原理是“保存”它最后停止的地方(或产出),并给调用函数一个值。因此,它不是将执行返回给调用者,而是将临时控制权交还给调用者。要做到这一点,生成器函数需要 Python 的 yield 语句。

附注:在其他语言中,生成器可能被称为协程。

让我们花点时间创建一个简单的生成器!


>>> def doubler_generator():
...     number = 2
...     while True:
...         yield number
...         number *= number
>>> doubler = doubler_generator()
>>> next(doubler)
2
>>> next(doubler)
4
>>> next(doubler)
16
>>> type(doubler)

这个特殊的生成器基本上会创建一个无限序列。你可以整天在它上面调用下一个,它将永远不会失去价值。因为可以在生成器上迭代,所以生成器被认为是迭代器的一种,但是没有人真正这样称呼它们。但是在幕后,生成器也定义了我们在上一节中看到的 next 方法,这就是为什么我们刚刚使用的 next 关键字有效。

让我们看看另一个例子,它只产生 3 项,而不是一个无限序列!


>>> def silly_generator():
...     yield "Python"
...     yield "Rocks"
...     yield "So do you!"
>>> gen = silly_generator()
>>> next(gen)
'Python'
>>> next(gen)
'Rocks'
>>> next(gen)
'So do you!'
>>> next(gen)
Traceback (most recent call last):
  Python Shell, prompt 21, line 1
builtins.StopIteration:

这里我们有一个生成器,它使用了 3 次 yield 语句。在每种情况下,它都会产生不同的字符串。你可以把收益率想象成一个发电机的收益语句。无论何时调用 yield,该函数都会停止并保存其状态。然后它输出值 out,这就是为什么在上面的例子中你会看到一些东西被输出到终端。如果我们的函数中有变量,这些变量也会被保存。

当你看到 StopIteration 的时候,你就知道你已经穷尽了迭代器。这意味着它用完了项目。这是所有迭代器的正常行为,正如你在迭代器部分看到的一样。

无论如何,当我们再次调用 next 时,生成器从它停止的地方开始,产生下一个值,或者我们完成函数,生成器停止。另一方面,如果你不再调用 next,那么这个状态最终会消失。

让我们重新实例化生成器,并尝试遍历它!


>>> gen = silly_generator()
>>> for item in gen:
...     print(item)
... 
Python
Rocks
So do you!

我们创建生成器的一个新实例的原因是,如果我们试图对它进行循环,将不会产生任何结果。这是因为我们已经遍历了生成器的特定实例中的所有值。因此,在本例中,我们创建了新的实例,对其进行循环,并打印出生成的值。循环的再次为我们处理 StopIteration 异常,并在生成器耗尽时退出循环。

生成器的最大好处之一是它可以迭代大型数据集,并一次返回一部分。当我们打开一个文件并逐行返回时,就会发生这种情况:


with open('/path/to/file.txt') as fobj:
    for line in fobj:
        # process the line

当我们以这种方式迭代文件对象时,Python 基本上将它变成了一个生成器。这允许我们处理太大而无法加载到内存中的文件。您会发现生成器对于您需要成块处理的任何大型数据集都很有用,或者当您需要生成一个大型数据集,否则它会填满您所有的计算机内存。


包扎

至此,你应该明白什么是迭代器以及如何使用迭代器。您还应该知道 iterable 和 iterator 之间的区别。最后,我们学习了什么是发电机,以及为什么你可能想要使用发电机。例如,生成器非常适合内存高效的数据处理。编码快乐!

python 201:ITER tools 简介

原文:https://www.blog.pythonlibrary.org/2016/04/20/python-201-an-intro-to-itertools/

Python 为创建自己的迭代器提供了一个很好的模块。我所指的模块是 itertools 。itertools 提供的工具速度快,内存效率高。您将能够利用这些构建块来创建您自己的专用迭代器,这些迭代器可用于高效的循环。在这一章中,我们将会看到每个构建模块的例子,这样到最后你就会明白如何在你自己的代码库中使用它们。

让我们从看一些无限迭代器开始吧!


无限迭代器

itertools 包附带了三个迭代器,可以无限迭代。这意味着,当你使用它们的时候,你需要明白你最终将需要脱离这些迭代器,否则你将会有一个无限循环。

例如,这对于生成数字或在未知长度的迭代上循环很有用。让我们开始了解这些有趣的项目吧!

计数(开始=0,步进=1)

count 迭代器将从您作为其 start 参数传入的数字开始返回均匀间隔的值。计数也接受一个参数。让我们看一个简单的例子:


>>> from itertools import count
>>> for i in count(10):
...     if i > 20: 
...         break
...     else:
...         print(i)
... 
10
11
12
13
14
15
16
17
18
19
20

这里我们从 itertools 导入计数,并为循环创建一个。我们添加了一个条件检查,如果迭代器超过 20,它将退出循环,否则它将打印出我们在迭代器中的位置。你会注意到输出从 10 开始,因为这是我们传递给 count 的初始值。

限制这个无限迭代器输出的另一种方法是使用 itertools 的另一个子模块,即 islice 。方法如下:


>>> from itertools import islice
>>> for i in islice(count(10), 5):
...     print(i)
... 
10
11
12
13
14

在这里,我们导入is ice并循环计数,从 10 开始,在 5 个项目后结束。您可能已经猜到,islice 的第二个参数是何时停止迭代。但这并不意味着“当我到达数字 5 时停止”。相反,它意味着“当我们达到五次迭代时停止”。

周期(可迭代)

itertools 的循环迭代器允许你创建一个迭代器,它将无限循环遍历一系列值。让我们给它传递一个 3 个字母的字符串,看看会发生什么:


>>> from itertools import cycle
>>> count = 0
>>> for item in cycle('XYZ'):
...     if count > 7:
...         break
...     print(item)
...     count += 1
... 
X
Y
Z
X
Y
Z
X
Y

这里我们为创建一个循环来循环三个字母:XYZ 的无限循环。当然,我们不想真的永远循环下去,所以我们添加了一个简单的计数器来打破循环。

您还可以使用 Python 的 next 内置来迭代您用 itertools 创建的迭代器:


>>> polys = ['triangle', 'square', 'pentagon', 'rectangle']
 >>> iterator = cycle(polys)
 >>> next(iterator)
 'triangle'
 >>> next(iterator)
 'square'
 >>> next(iterator)
 'pentagon'
 >>> next(iterator)
 'rectangle'
 >>> next(iterator)
 'triangle'
 >>> next(iterator)
 'square'

在上面的代码中,我们创建了一个简单的多边形列表,并将它们传递给周期。我们将新的迭代器保存到一个变量中,然后将该变量传递给下一个函数。每次我们调用 next,它都会返回迭代器中的下一个值。由于这个迭代器是无限的,我们可以整天调用 next,永远不会用完所有的项。

重复(对象[,次])

重复迭代器将一次又一次地返回一个对象,除非你设置它的乘以参数。它与周期非常相似,除了它不在一组值上重复循环。让我们看一个简单的例子:


>>> from itertools import repeat
>>> repeat(5, 5)
repeat(5, 5)
>>> iterator = repeat(5, 5)
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
Traceback (most recent call last):
  Python Shell, prompt 21, line 1
builtins.StopIteration:

这里我们导入 repeat 并告诉它重复数字 5 五次。然后我们在新的迭代器上调用 next 六次,看看它是否工作正常。当您运行这段代码时,您会看到 StopIteration 被抛出,因为我们已经用完了迭代器中的值。


终止的迭代器

用 itertools 创建的大多数迭代器都不是无限的。在这一节中,我们将学习 itertools 的有限迭代器。为了获得可读的输出,我们将使用 Python 内置的 list 类型。如果你不使用列表,那么你只会得到一个打印出来的 itertools 对象。

累加(iterable[,func])

accumulate 迭代器将返回两个参数函数的累加和或累加结果,您可以将它们传递给 accumulate 。accumulate 的默认值是加法,所以让我们快速尝试一下:


>> from itertools import accumulate
>>> list(accumulate(range(10)))
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

这里我们导入累加并传递给它一个范围为 0-9 的 10 个数字。它依次将它们相加,因此第一个是 0,第二个是 0+1,第三个是 1+2,依此类推。现在让我们导入操作符模块,并将其添加到组合中:


>>> import operator
>>> list(accumulate(range(1, 5), operator.mul))
[1, 2, 6, 24]

这里我们将数字 1-4 传递给我们的累积迭代器。我们也给它传递了一个函数: operator.mul 。这个函数接受要相乘的参数。因此,对于每次迭代,它都是乘法而不是加法(1x1=1,1x2=2,2x3=6,等等)。

accumulate 的文档显示了一些其他有趣的例子,如贷款的分期偿还或混乱的递归关系。你一定要看看这些例子,因为它们值得你花时间去做。

链(*iterables)

迭代器将接受一系列可迭代对象,并基本上将它们展平成一个长的可迭代对象。事实上,我最近正在帮助的一个项目需要它的帮助。基本上,我们有一个已经包含一些条目的列表和另外两个想要添加到原始列表中的列表,但是我们只想添加每个列表中的条目。最初我尝试这样做:


>>> my_list = ['foo', 'bar']
>>> numbers = list(range(5))
>>> cmd = ['ls', '/some/dir']
>>> my_list.extend(cmd, numbers)
>>> my_list
['foo', 'bar', ['ls', '/some/dir'], [0, 1, 2, 3, 4]]

嗯,这并不完全是我想要的方式。itertools 模块提供了一种更加优雅的方式,使用将这些列表合并成一个列表:


>>> from itertools import chain
>>> my_list = list(chain(['foo', 'bar'], cmd, numbers))
>>> my_list
['foo', 'bar', 'ls', '/some/dir', 0, 1, 2, 3, 4]

我的更精明的读者可能会注意到,实际上有另一种方法可以在不使用 itertools 的情况下完成同样的事情。您可以这样做来获得相同的效果:


>>> my_list = ['foo', 'bar']
>>> my_list += cmd + numbers
>>> my_list
['foo', 'bar', 'ls', '/some/dir', 0, 1, 2, 3, 4]

这两种方法当然都是有效的,在我了解链之前,我可能会走这条路,但我认为在这种特殊情况下,链是一种更优雅、更容易理解的解决方案。

chain.from_iterable(iterable)

您也可以使用的方法,从可迭代的调用。这种方法的工作原理与直接使用链略有不同。您必须传入嵌套列表,而不是传入一系列 iterables。让我们看看:


>>> from itertools import chain
>>> numbers = list(range(5))
>>> cmd = ['ls', '/some/dir']
>>> chain.from_iterable(cmd, numbers)
Traceback (most recent call last):
  Python Shell, prompt 66, line 1
builtins.TypeError: from_iterable() takes exactly one argument (2 given)
>>> list(chain.from_iterable([cmd, numbers]))
['ls', '/some/dir', 0, 1, 2, 3, 4]

这里我们像以前一样导入链。我们尝试传入两个列表,但最终得到了一个 TypeError !为了解决这个问题,我们稍微修改了一下我们的调用,将 cmd数字放在一个列表中,然后将这个嵌套列表传递给 from_iterable 。这是一个微妙的区别,但仍然易于使用!

压缩(数据,选择器)

compress 子模块对于过滤第一个 iterable 和第二个 iterable 非常有用。这是通过使第二个 iterable 成为一个布尔列表(或者等同于同一事物的 1 和 0)来实现的。它是这样工作的:


>>> from itertools import compress
>>> letters = 'ABCDEFG'
>>> bools = [True, False, True, True, False]
>>> list(compress(letters, bools))
['A', 'C', 'D']

在这个例子中,我们有一组七个字母和一个五个布尔的列表。然后我们将它们传递给压缩函数。compress 函数将遍历每个相应的 iterable,并对照第二个 iterable 检查第一个 iterable。如果第二个有匹配的 True,那么它将被保留。如果它是假的,那么这个项目将被删除。因此,如果你研究上面的例子,你会注意到我们在第一,第三和第四个位置有一个 True,分别对应于 A,C 和 d。

dropwhile(谓词,可迭代)

itertools 中有一个简洁的小迭代器叫做 dropwhile 只要过滤条件为真,这个有趣的小迭代器就会删除元素。因此,在谓词变为 False 之前,您不会看到这个迭代器的任何输出。这可能会使启动时间变得很长,因此需要注意这一点。

让我们看看 Python 文档中的一个例子:


>>> from itertools import dropwhile
>>> list(dropwhile(lambda x: x<5, [1,4,6,4,1]))
[6, 4, 1]

这里我们导入了 dropwhile ,然后我们给它传递了一个简单的 lambda 语句。如果 x 小于 5,该函数将返回 True。否则它将返回 False。dropwhile 函数将遍历列表并将每个元素传递给 lambda。如果 lambda 返回 True,那么该值将被删除。一旦我们到达数字 6,lambda 返回 False,我们保留数字 6 和它后面的所有值。

当我学习新东西时,我发现在 lambda 上使用常规函数很有用。因此,让我们颠倒一下,创建一个如果数字大于 5 则返回 True 的函数。


>>> from itertools import dropwhile
>>> def greater_than_five(x):
...     return x > 5 
... 
>>> list(dropwhile(greater_than_five, [6, 7, 8, 9, 1, 2, 3, 10]))
[1, 2, 3, 10]

这里我们在 Python 的解释器中创建了一个简单的函数。这个函数是我们的谓词或过滤器。如果我们传递给它的值为真,那么它们将被丢弃。一旦我们找到一个小于 5 的值,那么在这个值之后的所有值都将被保留,这在上面的例子中可以看到。

filterfalse(谓词,可迭代)

itertools 的 filterfalse 函数与 dropwhile 非常相似。然而,filterfalse 不会删除与 True 匹配的值,而只会返回那些计算结果为 false 的值。让我们使用上一节中的函数来说明:


>>> from itertools import filterfalse
>>> def greater_than_five(x):
...     return x > 5 
... 
>>> list(filterfalse(greater_than_five, [6, 7, 8, 9, 1, 2, 3, 10]))
[1, 2, 3]

这里我们传递 filterfalse 我们的函数和一个整数列表。如果整数小于 5,则保留该整数。否则就扔掉。你会注意到我们的结果只有 1,2 和 3。与 dropwhile 不同,filterfalse 将根据我们的谓词检查每个值。

groupby(iterable,key=None)

groupby 迭代器将从 iterable 中返回连续的键和组。如果没有例子,你很难理解这一点。所以我们来看一个吧!将以下代码放入您的解释器或保存在文件中:


from itertools import groupby

vehicles = [('Ford', 'Taurus'), ('Dodge', 'Durango'),
            ('Chevrolet', 'Cobalt'), ('Ford', 'F150'),
            ('Dodge', 'Charger'), ('Ford', 'GT')]

sorted_vehicles = sorted(vehicles)

for key, group in groupby(sorted_vehicles, lambda make: make[0]):
    for make, model in group:
        print('{model} is made by {make}'.format(model=model,
                                                 make=make))
	print ("**** END OF GROUP ***\n")

这里我们导入 groupby ,然后创建一个元组列表。然后,我们对数据进行排序,以便在输出时更有意义,它还让 groupby 正确地对项目进行分组。接下来,我们实际上是在 groupby 返回的迭代器上循环,这给了我们键和组。然后我们循环遍历这个组,并打印出其中的内容。如果您运行这段代码,您应该会看到类似这样的内容:


Cobalt is made by Chevrolet
**** END OF GROUP ***

Charger is made by Dodge
Durango is made by Dodge
**** END OF GROUP ***

F150 is made by Ford
GT is made by Ford
Taurus is made by Ford
**** END OF GROUP ***

只是为了好玩,试着修改一下代码,让你传入的是辆车而不是辆车。如果您这样做,您将很快了解为什么应该在通过 groupby 运行数据之前对其进行排序。

islice(可重复,开始,停止[,步进])

我们实际上在计数部分提到过 islice 。但在这里,我们将更深入地研究它。islice 是一个迭代器,它从 iterable 中返回选定的元素。这是一种不透明的说法。基本上,islice 所做的是通过索引获取 iterable(你迭代的对象)的一部分,并以迭代器的形式返回所选择的项。islice 实际上有两个实现。有 itertools.islice(iterable,stop) 还有更接近常规 Python 切片的 islice 版本: islice(iterable,start,stop[,step])

让我们看看第一个版本,看看它是如何工作的:


>>> from itertools import islice
>>> iterator = islice('123456', 4)
>>> next(iterator)
'1'
>>> next(iterator)
'2'
>>> next(iterator)
'3'
>>> next(iterator)
'4'
>>> next(iterator)
Traceback (most recent call last):
  Python Shell, prompt 15, line 1
builtins.StopIteration:

在上面的代码中,我们向 islice 传递了一个由六个字符组成的字符串,以及作为停止参数的数字 4。这意味着 islice 返回的迭代器将包含字符串的前 4 项。我们可以通过在迭代器上调用next四次来验证这一点,这就是我们上面所做的。Python 足够聪明,知道如果只有两个参数传递给 islice,那么第二个参数就是 stop 参数。

让我们试着给它三个参数,来证明你可以给它一个开始参数和一个停止参数。itertools 的 count 工具可以帮助我们说明这个概念:


>>> from itertools import islice
>>> from itertools import count
>>> for i in islice(count(), 3, 15):
...     print(i)
... 
3
4
5
6
7
8
9
10
11
12
13
14

在这里,我们只是调用 count,告诉它从数字 3 开始,到 15 时停止。这就像做切片一样,只不过你是对迭代器做切片,然后返回一个新的迭代器!

星图(函数,可迭代)

工具将创建一个迭代器,它可以使用提供的函数和 iterable 进行计算。正如文档中提到的,“map()和 starmap()之间的区别类似于函数(a,b)函数(c)* 。”

让我们看一个简单的例子:


>>> from itertools import starmap
>>> def add(a, b):
...     return a+b
... 
>>> for item in starmap(add, [(2,3), (4,5)]):
...     print(item)
... 
5
9

这里我们创建了一个简单的加法函数,它接受两个参数。接下来,我们为循环创建一个并调用 starmap ,将函数作为第一个参数,并为 iterable 创建一个元组列表。然后,starmap 函数会将每个元组元素传递到函数中,并返回结果的迭代器,我们会打印出来。

takewhile(谓词,可迭代)

takewhile 模块基本上与我们之前看到的 dropwhile 迭代器相反。takewhile 将创建一个迭代器,只要我们的谓词或过滤器为真,它就从 iterable 返回元素。让我们尝试一个简单的例子来看看它是如何工作的:


>>> from itertools import takewhile
>>> list(takewhile(lambda x: x<5, [1,4,6,4,1]))
[1, 4]

这里我们使用 lambda 函数和一个列表运行 takewhile。输出只是 iterable 的前两个整数。原因是 1 和 4 都小于 5,但 6 更大。因此,一旦 takewhile 看到 6,条件就变为 False,它将忽略 iterable 中的其余项。

三通(可迭代,n=2)

tee 工具将从单个可迭代对象中创建n个迭代器。这意味着你可以从一个 iterable 创建多个迭代器。让我们来看一些解释其工作原理的代码:


>>> from itertools import tee
>>> data = 'ABCDE'
>>> iter1, iter2 = tee(data)
>>> for item in iter1:
...     print(item)
... 
A
B
C
D
E
>>> for item in iter2:
...     print(item)
... 
A
B
C
D
E

这里我们创建一个 5 个字母的字符串,并将其传递给 tee 。因为 tee 默认为 2,所以我们使用多重赋值来获取从 tee 返回的两个迭代器。最后,我们遍历每个迭代器并打印出它们的内容。如你所见,它们的内容是一样的。

zip_longest(*iterables,fillvalue=None)

zip_longest迭代器可用于将两个可迭代对象压缩在一起。如果 iterables 的长度不一样,那么也可以传入一个fillvalue。让我们看一个基于这个函数的文档的愚蠢的例子:


>>> from itertools import zip_longest
>>> for item in zip_longest('ABCD', 'xy', fillvalue='BLANK'):
...     print (item)
... 
('A', 'x')
('B', 'y')
('C', 'BLANK')
('D', 'BLANK')

在这段代码中,我们导入 zip_longest,然后向它传递两个字符串以压缩在一起。您会注意到第一个字符串有 4 个字符长,而第二个只有 2 个字符长。我们还设置了一个填充值“空白”。当我们循环并打印出来时,你会看到我们得到了返回的元组。前两个元组分别是每个字符串的第一个和第二个字母的组合。最后两个插入了我们的填充值。

应该注意的是,如果传递给 zip_longest 的 iterable 可能是无限的,那么应该用 islice 之类的东西包装这个函数,以限制调用的次数。


组合生成器

itertools 库包含四个迭代器,可用于创建数据的组合和排列。我们将在这一节讨论这些有趣的迭代器。

组合(iterable,r)

如果您需要创建组合,Python 已经为您提供了 itertools.combinations 。组合可以让你从一个一定长度的 iterable 中创建一个迭代器。让我们来看看:


>>>from itertools import combinations
>>>list(combinations('WXYZ', 2))
[('W', 'X'), ('W', 'Y'), ('W', 'Z'), ('X', 'Y'), ('X', 'Z'), ('Y', 'Z')]

当您运行它时,您会注意到 combinations 返回元组。为了使这个输出更具可读性,让我们循环遍历迭代器,并将元组连接成一个字符串:


>>> for item in combinations('WXYZ', 2):
...     print(''.join(item))
... 
WX
WY
WZ
XY
XZ
YZ

现在更容易看到各种组合了。注意,combinations 函数的组合是按字典顺序排序的,所以如果 iterable 排序了,那么组合元组也会排序。同样值得注意的是,如果所有输入元素都是唯一的,组合不会在组合中产生重复值。

combinations with _ replacement(iterable,r)

带有迭代器的* * combinations _ with _ replacement * *与combinations非常相似。唯一的区别是,它实际上会创建元素重复的组合。让我们用上一节中的一个例子来说明:


>>> from itertools import combinations_with_replacement
>>> for item in combinations_with_replacement('WXYZ', 2):
...     print(''.join(item))
... 
WW
WX
WY
WZ
XX
XY
XZ
YY
YZ
ZZ

如您所见,我们现在有四个新的输出项:WW、XX、YY 和 ZZ。

产品(*iterables,repeat=1)

itertools 包有一个简洁的小函数,用于从一系列输入的可迭代对象中创建笛卡尔乘积。没错,那个功能就是**产品* *。让我们看看它是如何工作的!


>>> from itertools import product
>>> arrays = [(-1,1), (-3,3), (-5,5)]
>>> cp = list(product(*arrays))
>>> cp
[(-1, -3, -5),
 (-1, -3, 5),
 (-1, 3, -5),
 (-1, 3, 5),
 (1, -3, -5),
 (1, -3, 5),
 (1, 3, -5),
 (1, 3, 5)]

在这里,我们导入 product,然后建立一个元组列表,并将其分配给变量arrays。接下来,我们称之为具有这些阵列的产品。你会注意到我们用*数组来调用它。这将导致列表被“展开”或按顺序应用于产品功能。这意味着你要传入三个参数而不是一个。如果你愿意的话,试着在数组前面加上星号来调用它,看看会发生什么。

排列

itertools 的置换子模块将从您给定的 iterable 返回连续的 r 长度的元素置换。与 combinations 函数非常相似,排列是按字典排序顺序发出的。让我们来看看:


>>> from itertools import permutations
>>> for item in permutations('WXYZ', 2):
...     print(''.join(item))
... 
WX
WY
WZ
XW
XY
XZ
YW
YX
YZ
ZW
ZX
ZY

你会注意到输出比组合的输出要长很多。当您使用置换时,它将遍历字符串的所有置换,但如果输入元素是唯一的,它不会重复值。


包扎

itertools 是一套非常通用的工具,用于创建迭代器。您可以使用它们来创建您自己的迭代器,无论是单独使用还是相互组合使用。Python 文档中有很多很棒的例子,您可以研究这些例子来了解如何使用这个有价值的库。


相关阅读

  • 关于 itertools 的 Python 文档
  • itertools 简介
  • 看看 Python 的一些有用的工具 itertools
  • 本周 Python 模块: itertools

Python 201:模拟简介

原文:https://www.blog.pythonlibrary.org/2016/07/19/python-201-an-intro-to-mock/

从 Python 3.3 开始,unittest 模块现在包括一个模拟子模块。它将允许您用模拟对象替换正在测试的系统部分,并断言它们是如何被使用的。模拟对象用于模拟测试环境中不可用的系统资源。换句话说,你会发现有时候你想测试你的代码的一部分,而不测试它的其余部分,或者你需要测试一些代码,而不测试外部服务。

注意,如果您有 Python 3 之前的 Python 版本,您可以下载模拟库并获得相同的功能。

让我们考虑一下为什么您可能想要使用 mock。一个很好的例子是,如果你的应用程序绑定到某种第三方服务,如 Twitter 或脸书。如果您的应用程序的测试套件在每次运行时转发一堆项目或“喜欢”一堆帖子,那么这可能是不可取的行为,因为每次运行测试时都会这样做。另一个例子可能是,如果您设计了一个工具,使更新您的数据库表更容易。每次测试运行时,它都会对相同的记录进行一些更新,可能会清除有价值的数据。

您可以使用 unittest 的 mock 来代替做这些事情。它会让你嘲笑和根除那些副作用,所以你不必担心它们。代替与第三方资源的交互,您将针对与那些资源匹配的虚拟 API 运行您的测试。您最关心的是您的应用程序正在调用它应该调用的函数。您可能不太关心 API 本身是否实际执行。当然,有时候你会想做一个真正执行 API 的端到端测试,但是这些测试不需要模拟!


简单的例子

Python 模拟类可以模拟任何其他 Python 类。这允许你检查在你模仿的类上调用了什么方法,甚至传递了什么参数给它们。让我们先看几个简单的例子,演示如何使用模拟模块:

>>> from unittest.mock import Mock
>>> my_mock = Mock()
>>> my_mock.__str__ = Mock(return_value='Mocking')
>>> str(my_mock)
'Mocking'

在这个例子中,我们从 unittest.mock 模块中导入 Mock 类。然后我们创建一个模拟类的实例。最后,我们设置了模拟对象的 str 方法,这是一个神奇的方法,它控制着在对象上调用 Python 的 str 函数时会发生什么。在这种情况下,我们只返回字符串“Mocking”,这是我们最后实际执行 str()函数时所看到的。

模拟模块还支持五种断言。让我们来看看其中几个是如何运作的:

>>> from unittest.mock import Mock
>>> class TestClass():
...     pass
... 
>>> cls = TestClass()
>>> cls.method = Mock(return_value='mocking is fun')
>>> cls.method(1, 2, 3)
'mocking is fun'
>>> cls.method.assert_called_once_with(1, 2, 3)
>>> cls.method(1, 2, 3)
'mocking is fun'
>>> cls.method.assert_called_once_with(1, 2, 3)
Traceback (most recent call last):
  Python Shell, prompt 9, line 1
  File "/usr/local/lib/python3.5/unittest/mock.py", line 802, in assert_called_once_with
    raise AssertionError(msg)
builtins.AssertionError: Expected 'mock' to be called once. Called 2 times.
>>> cls.other_method = Mock(return_value='Something else')
>>> cls.other_method.assert_not_called()
>>>

首先,我们导入并创建一个空类。然后,我们创建该类的一个实例,并添加一个使用模拟类返回字符串的方法。然后我们调用带有三个整数参数的方法。您会注意到,这返回了我们之前设置为返回值的字符串。现在我们可以测试一个断言了!所以我们调用* * assert _ called _ once _ with * * assert,如果我们用相同的参数调用我们的方法两次或更多次,它将断言。我们第一次调用 assert 时,它通过了。所以我们用同样的方法再次调用这个方法,并第二次运行 assert,看看会发生什么。

如您所见,我们得到了一个 AssertionError 。为了完善这个例子,我们继续创建第二个方法,我们根本不调用它,然后断言它不是通过 assert_not_called assert 调用的。


副作用

您还可以通过 side_effect 参数创建模拟对象的副作用。副作用是在运行函数时发生的事情。例如,一些视频游戏已经整合到社交媒体中。当你获得一定数量的分数,赢得一个奖杯,完成一个级别或其他一些预定的目标,它会记录下来,并发布到 Twitter,脸书或任何它集成的地方。运行一个函数的另一个副作用是,它可能与你的用户界面紧密相连,导致不必要的重绘。

因为我们预先知道这些副作用,所以我们可以在代码中模拟它们。让我们看一个简单的例子:

from unittest.mock import Mock

def my_side_effect():
    print('Updating database!')

def main():
    mock = Mock(side_effect=my_side_effect)
    mock()

if __name__ == '__main__':
    main()

这里我们创建一个假装更新数据库的函数。然后在我们的 main 函数中,我们创建一个模拟对象,并给它一个副作用。最后,我们调用我们的模拟对象。如果这样做,您应该会看到一条关于数据库被更新的消息被打印到 stdout。

Python 文档还指出,如果您愿意,可以让副作用引发异常。一个相当常见的原因是,如果调用不正确,就会引发异常。一个例子可能是你没有传递足够的参数。您还可以创建一个 mock 来引发一个弃用警告。


自动筛选

模拟模块还支持自动规范的概念。autospec 允许您创建模拟对象,这些对象包含与您用模拟替换的对象相同的属性和方法。它们甚至会有与真实对象相同的调用签名!您可以使用 create_autospec 函数创建一个 autospec,或者通过将 autospec 参数传递给模拟库的 patch decorator 来创建一个 autospec,但是我们将把对 patch 的研究推迟到下一节。

现在,让我们看一个易于理解的 autospec 示例:

>>> from unittest.mock import create_autospec
>>> def add(a, b):
...     return a + b
... 
>>> mocked_func = create_autospec(add, return_value=10)
>>> mocked_func(1, 2)
10
>>> mocked_func(1, 2, 3)
Traceback (most recent call last):
  Python Shell, prompt 5, line 1
  File "", line 2, in add
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py", line 181, in checksig
    sig.bind(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py", line 2921, in bind
    return args[0]._bind(args[1:], kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py", line 2842, in _bind
    raise TypeError('too many positional arguments') from None
builtins.TypeError: too many positional arguments

在这个例子中,我们导入了 create_autospec 函数,然后创建了一个简单的加法函数。接下来,我们使用 create_autospec(),向它传递我们的 add 函数,并将其返回值设置为 10。只要你用两个参数传递这个新的模拟版本的 add,它将总是返回 10。但是,如果用不正确的参数数目调用它,将会收到一个异常。


补丁

mock 模块有一个叫做 patch 的简洁的小函数,可以用作函数装饰器、类装饰器甚至是上下文管理器。这将允许您轻松地在您想要测试的模块中创建模拟类或对象,因为它将被模拟替换。

让我们从创建一个阅读网页的简单函数开始。我们将称之为 webreader.py 。代码如下:

import urllib.request

def read_webpage(url):
    response = urllib.request.urlopen(url)
    return response.read()

这段代码非常简单明了。它所做的就是获取一个 URL,打开页面,读取 HTML 并返回它。现在,在我们的测试环境中,我们不想陷入从网站读取数据的困境,尤其是我们的应用程序碰巧是一个每天下载千兆字节数据的网络爬虫。相反,我们想创建一个 Python 的 urllib 的模拟版本,这样我们就可以调用上面的函数,而不用真正下载任何东西。

让我们创建一个名为 mock_webreader.py 的文件,并将其保存在与上面代码相同的位置。然后将以下代码放入其中:

import webreader

from unittest.mock import patch

@patch('urllib.request.urlopen')
def dummy_reader(mock_obj):
    result = webreader.read_webpage('https://www.google.com/')
    mock_obj.assert_called_with('https://www.google.com/')
    print(result)

if __name__ == '__main__':
    dummy_reader()

这里我们只是从模拟模块中导入我们之前创建的模块和补丁函数。然后我们创建一个装饰器来修补 urllib.request.urlopen 。在函数内部,我们用 Google 的 URL 调用 webreader 模块的read _ 网页函数并打印结果。如果您运行这段代码,您将看到我们的结果不是 HTML,而是 MagicMock 对象。这证明了补丁的威力。我们现在可以阻止下载数据,同时仍然正确调用原始函数。

文档指出,您可以像使用常规装饰器一样堆叠路径装饰器。因此,如果你有一个真正复杂的函数,访问数据库或写文件或几乎任何其他东西,你可以添加多个补丁,以防止副作用的发生。


包扎

模拟模块非常有用,也非常强大。也需要一些时间来学习如何正确有效地使用。Python 文档中有很多例子,尽管它们都是带有虚拟类的简单例子。我想你会发现这个模块对于创建健壮的测试很有用,它可以快速运行而不会产生意外的副作用。


相关阅读

Python 201 和 Python RegEx 包

原文:https://www.blog.pythonlibrary.org/2019/01/29/python-201-and-python-regex-bundle/

我最近有机会与《Python re(gex)的作者 Sundeep Agarwal 合作?用我的第二本书创建一个包, Python 201:中级 Python

Python 正则表达式一书涵盖了 Python 中的正则表达式。虽然只有 50 页,但它有很多例子,您可以使用这些例子来学习在自己的代码中使用正则表达式。可以去查一下 Github repo 看看书里是什么样的代码。

你可以在 Leanpub 上找到这个包。

Python 201 书籍大纲

原文:https://www.blog.pythonlibrary.org/2016/03/21/python-201-book-outline/

周末,我花了一些时间重新整理我最新的书的想法,这样我就有了这本书的四个特定部分。他们在这里:

第一部分-中间模块

  • 第 1 章 argparse 模块
  • 第 2 章-收集模块
  • 第 3 章-上下文库模块(上下文管理器)
  • 第 4 章 functools 模块(函数重载、缓存等)
  • 第 5 章-所有关于进口
  • 第 6 章 importlib 模块
  • 第 7 章 itertools 模块
  • 第 8 章-re 模块(Python 中正则表达式的介绍)
  • 第 9 章-输入模块(类型提示)

第二部分——零零碎碎

  • 第 10 章——生成器/迭代器
  • 第 11 章-映射、过滤、减少
  • 第 12 章-统一码
  • 第 13 章-基准测试
  • 第 14 章-加密
  • 第 15 章-连接到数据库

第三部分-网络

  • 第 16 章-网页抓取
  • 第 17 章-使用 web APIs
  • 第 18 章- ftplib
  • 第 19 章- urllib / httplib(客户端/服务器)

第四部分-测试

  • 第 20 章- Doctest
  • 第 21 章-单元测试
  • 第 22 章-嘲弄
  • 第 23 章- coverage.py

我想指出的是,这些只是绝对会包括在内的主题。我可以补充其他的。如果我达到了我的延伸目标,我还会添加其他人。如果你有兴趣提前接触这本书或者只是想支持这个博客,你可以在我的 Python 201 的 Kickstarter 上这样做!

Python 201 书籍写作更新:第 1 部分准备好了

原文:https://www.blog.pythonlibrary.org/2016/05/04/python-201-book-writing-update-part-1-is-ready/

我一直忙于我的第二本书,Python 201:中级 Python。这本书的第一部分有 10 章。我最近完成了这本书的最后一章。虽然我想对这本书的这一部分中的几个章节做一些调整,但我现在不去管它们,这样我就可以完成第二部分了。然后我将回到第 1 部分做一些更新。这也让早期采用者有时间阅读第一章,并向我发送有关打字错误或错误的消息。

对于那些没有在 Kickstarter 上看到这本书的人来说,前 10 章如下:

  • 第 1 章 argparse 模块
  • 第 2 章-收集模块
  • 第 3 章-上下文库模块(上下文管理器)
  • 第 4 章 functools 模块(函数重载、缓存等)
  • 第 5 章-所有关于进口
  • 第 6 章 importlib 模块
  • 第 7 章-迭代器和生成器
  • 第 8 章 itertools 模块
  • 第 9 章-re 模块(Python 中正则表达式的介绍)
  • 第 10 章-输入模块(类型提示)

到目前为止,在我的 Gumroad 版本中有 71 页,在 Leanpub 版本中有超过 80 页。Leanpub 的生成方式不同,这意味着他们使用不同的字体和字体大小,这就是为什么该版本有更多的页面。无论如何,这本书进展顺利,仍将于 2016 年 9 月发布!

Python 201:创建模块和包

原文:https://www.blog.pythonlibrary.org/2012/07/08/python-201-creating-modules-and-packages/

创建 Python 模块是大多数 Python 程序员每天都要做的事情。每当您保存一个新的 Python 脚本时,您就创建了一个新的模块。您可以将您的模块导入到其他模块中。包是模块的集合。从标准库中导入到脚本中的东西是模块或包。在本文中,我们将看看如何创建模块和包。我们将在软件包上花更多的时间,因为它们比模块更复杂。

如何创建 Python 模块

我们将从创建一个超级简单的模块开始。这个模块将为我们提供基本的算术和没有错误的处理。这是我们的第一个例子:


#----------------------------------------------------------------------
def add(x, y):
    """"""
    return x + y

#----------------------------------------------------------------------
def division(x, y):
    """"""
    return x / y

#----------------------------------------------------------------------
def multiply(x, y):
    """"""
    return x * y

#----------------------------------------------------------------------
def subtract(x, y):
    """"""
    return x - y

当然,这段代码有问题。如果您将两个整数传递给 division 方法,那么您将最终得到一个整数(在 Python 2.x 中),这可能不是您所期望的。也没有被零除或混合字符串和数字的错误检查。但这不是重点。关键是,如果您保存了这段代码,您就拥有了一个完全合格的模块。姑且称之为算术. py 。现在你能用一个模块做什么呢?您可以导入它并使用任何已定义的模块。我们可以稍加修改使其“可执行”。让我们双管齐下!

首先,我们将编写一个小脚本来导入我们的模块并运行其中的函数:


import arithmetic

print arithmetic.add(5, 8)
print arithmetic.subtract(10, 5)
print arithmetic.division(2, 7)
print arithmetic.multiply(12, 6)

现在让我们修改原始脚本,以便我们可以从命令行运行它。下面是一个蹩脚的方法:


#----------------------------------------------------------------------
def add(x, y):
    """"""
    return x + y

#----------------------------------------------------------------------
def division(x, y):
    """"""
    return x / y

#----------------------------------------------------------------------
def multiply(x, y):
    """"""
    return x * y

#----------------------------------------------------------------------
def subtract(x, y):
    """"""
    return x - y

#----------------------------------------------------------------------
if __name__ == "__main__":
    import sys
    print sys.argv
    v = sys.argv[1].lower()
    valOne = int(sys.argv[2])
    valTwo = int(sys.argv[3])
    if v == "a":
        print add(valOne, valTwo)
    elif v == "d":
        print division(valOne, valTwo)
    elif v == "m":
        print multiply(valOne, valTwo)
    elif v == "s":
        print subtract(valOne, valTwo)
    else:
        pass

完成这个脚本的正确方法是使用 Python 的optparse(2.7 之前)或 argparse (2.7 以上)模块。不过,你也可以把它当作一种练习。我们需要继续包装!

如何创建 Python 包

模块和包之间的主要区别在于,包是模块的集合,它有一个 init。py 文件。根据包的复杂程度,它可能有不止一个 init.py。让我们看一个简单的文件夹结构来使这一点更明显,然后我们将创建一些简单的代码来遵循该结构。


myMath/
    __init__.py
    adv/
        __init__.py
        sqrt.py
        fib.py
    add.py
    subtract.py
    multiply.py
    divide.py

现在我们只需要在我们自己的包中复制这个结构。让我们试一试!在如上所示的文件夹树中创建这些文件。对于加减乘除文件,您可以使用我们在前面的示例中创建的函数。对于另外两个,我们将使用下面的代码。

对于斐波那契数列,我们将使用来自 StackOverflow 的简单代码:


# fib.py
from math import sqrt

#----------------------------------------------------------------------
def fibonacci(n):
    """
    http://stackoverflow.com/questions/494594/how-to-write-the-fibonacci-sequence-in-python
    """
    return ((1+sqrt(5))**n-(1-sqrt(5))**n)/(2**n*sqrt(5))

对于 sqrt.py 文件,我们将使用以下代码:


# sqrt.py
import math

#----------------------------------------------------------------------
def squareroot(n):
    """"""
    return math.sqrt(n)

您可以保留这两个 init。py 文件是空白的,但是这样你就不得不编写类似于 mymath.add.add(x,y) 的代码,这有点糟糕,所以我们将下面的代码添加到 outer init。py 使使用我们的包更容易。


# outer __init__.py
from add import add
from divide import division
from multiply import multiply
from subtract import subtract
from adv.fib import fibonacci
from adv.sqrt import squareroot

现在,一旦我们在 Python 路径上有了模块,我们就应该能够使用它了。为此,您可以将该文件夹复制到 Python 的 site-packages 文件夹中。在 Windows 上,它位于以下位置:C:\ python 26 \ Lib \ site-packages。或者,您可以在测试代码中动态编辑路径。让我们看看这是怎么做到的:


import sys

sys.path.append('C:\Users\mdriscoll\Documents')

import mymath

print mymath.add(4,5)
print mymath.division(4, 2)
print mymath.multiply(10, 5)
print mymath.fibonacci(8)
print mymath.squareroot(48)

注意,我的路径不包括 mymath 文件夹。您希望追加包含新模块的父文件夹,而不是模块文件夹本身。如果你这样做,那么上面的代码应该工作。恭喜你!您刚刚创建了一个 Python 包!

进一步阅读

源代码

Python 201:修饰主函数

原文:https://www.blog.pythonlibrary.org/2012/05/31/python-201-decorating-the-main-function/

上周,我读了 Brett Cannon 的博客,他谈到了函数签名和修饰主函数。我没有完全听懂他讲的内容,但我认为这个概念非常有趣。下面的代码是基于 Cannon 先生提到的食谱的一个例子。我认为这说明了他在说什么,但基本上提供了一种消除标准的方法


if __name__ == "__main__":
   doSomething()

总之,这是代码。


#----------------------------------------------------------------------
def doSomething(name="Mike"):
    """"""
    print "Welcome to the program, " + name

#----------------------------------------------------------------------
def main(f):
    """"""
    if f.__module__ == '__main__':
        f()
    return f

main(doSomething)

这一点的好处是 main 函数可以在代码中的任何地方。如果你喜欢使用 decorators,那么你可以重写如下:


#----------------------------------------------------------------------
def main(f):
    """"""
    if f.__module__ == '__main__':
        f()
    return f

#----------------------------------------------------------------------
@main
def doSomething(name="Mike"):
    """"""
    print "Welcome to the program, " + name

请注意,在您可以修饰 doSomething 函数之前,必须先有 main 函数。我不确定我会如何或者是否会使用这种技巧,但是我想在某个时候尝试一下可能会很有趣。

Python 201:装饰者

原文:https://www.blog.pythonlibrary.org/2014/03/13/python-201-decorators/

Python decorators 真的很酷,但是一开始可能有点难以理解。Python 中的装饰器是一个接受另一个函数作为参数的函数。装饰者通常会修改或增强它接受的函数,并返回修改后的函数。这意味着当你调用一个修饰函数时,你将得到一个与基本定义相比可能有一点不同的函数,它可能有额外的特性。但是让我们倒回去一点。我们也许应该回顾一下装饰器的基本构件,即函数。


简单的功能

函数是一个代码块,以 Python 关键字 def 开头,后跟函数的实际名称。一个函数可以接受零个或多个参数、关键字参数或两者的混合。一个函数总是会返回一些东西。如果你不指定函数应该返回什么,它将返回 None 。下面是一个非常简单的函数,它只返回一个字符串:


#----------------------------------------------------------------------
def a_function():
    """A pretty useless function"""
    return "1+1"

#----------------------------------------------------------------------
if __name__ == "__main__":
    value = a_function()
    print(value)

我们调用函数并打印返回值。让我们创建另一个函数:


#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """
    def other_func():
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return other_func

该函数接受一个参数,并且该参数必须是函数或可调用的。事实上,它实际上只应该使用前面定义的函数来调用。你会注意到这个函数内部有一个嵌套函数,我们称之为 other_func 。它将接受传递给它的函数的结果,对它进行求值,并创建一个字符串来告诉我们它做了什么,然后返回这个字符串。让我们看看完整版的代码:


#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """

    def other_func():
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return other_func

#----------------------------------------------------------------------
def a_function():
    """A pretty useless function"""
    return "1+1"

#----------------------------------------------------------------------
if __name__ == "__main__":
    value = a_function()
    print(value)
    decorator = another_function(a_function)
    print decorator()

这就是装修工的工作方式。我们创建一个函数,然后将它传递给第二个函数。第二个函数是装饰函数。装饰器将修改或增强传递给它的函数,并返回修改内容。如果您运行此代码,您应该会看到以下内容作为 stdout 的输出:


1+1
The result of 1+1 is 2

让我们稍微修改一下代码,将 another_function 变成一个装饰器:


#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """

    def other_func():
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return other_func

#----------------------------------------------------------------------
@another_function
def a_function():
    """A pretty useless function"""
    return "1+1"

#----------------------------------------------------------------------
if __name__ == "__main__":
    value = a_function()
    print(value)

您会注意到,在 Python 中,decorator 以符号 @ 开始,后面是函数名,我们将用它来“装饰”我们的正则表达式。要应用装饰器,只需将它放在函数定义之前的行上。现在当我们调用 a_function 时,它将被修饰,我们将得到如下结果:


The result of 1+1 is 2

让我们创建一个真正有用的装饰器。


创建日志装饰器

有时你会想要创建一个函数正在做什么的日志。大多数时候,您可能会在函数本身中进行日志记录。有时,您可能希望在函数级别进行,以了解程序的流程,或者满足一些业务规则,如审计。这里有一个小装饰器,我们可以用它来记录任何函数的名称和它返回的内容:


import logging

#----------------------------------------------------------------------
def log(func):
    """
    Log what function is called
    """
    def wrap_log(*args, **kwargs):
        name = func.__name__
        logger = logging.getLogger(name)
        logger.setLevel(logging.INFO)

        # add file handler
        fh = logging.FileHandler("%s.log" % name)
        fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        formatter = logging.Formatter(fmt)
        fh.setFormatter(formatter)
        logger.addHandler(fh)

        logger.info("Running function: %s" % name)
        result = func(*args, **kwargs)
        logger.info("Result: %s" % result)
        return func
    return wrap_log

#----------------------------------------------------------------------
@log
def double_function(a):
    """
    Double the input parameter
    """
    return a*2

#----------------------------------------------------------------------
if __name__ == "__main__":
    value = double_function(2)

这个小脚本有一个接受函数作为唯一参数的 log 函数。它将创建一个 logger 对象和一个基于函数名的日志文件名。然后,日志函数将记录调用了什么函数以及函数返回了什么(如果有的话)。


内置装饰器

Python 附带了几个内置的装饰器。三大因素是:

  • @classmethod
  • @静态方法
  • @属性

Python 的标准库的各个部分也有 decorators。一个例子就是functools . wrapps。不过,我们将把我们的范围限制在以上三个方面。


@classmethod 和@staticmethod

我自己从来没有用过这些,所以我做了一些调查。调用 @classmethod decorator 时,可以使用类的实例,或者直接由类本身作为第一个参数。根据 Python 文档 : 它既可以在类上调用(比如 C.f()),也可以在实例上调用(比如 C())。f())。该实例被忽略,但其类除外。如果为派生类调用类方法,派生类对象将作为隐含的第一个参数传递。我在研究中发现,@classmethod 装饰器的主要用例是作为初始化的替代构造函数或帮助方法。

@staticmethod decorator 只是一个类内部的函数。无论有没有实例化该类,都可以调用它。一个典型的用例是当你有一个函数,你认为它和一个类有联系。这在很大程度上是一种风格选择。

看看这两个装饰器如何工作的代码示例可能会有所帮助:


########################################################################
class DecoratorTest(object):
    """
    Test regular method vs @classmethod vs @staticmethod
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        pass

    #----------------------------------------------------------------------
    def doubler(self, x):
        """"""
        print("running doubler")
        return x*2

    #----------------------------------------------------------------------
    @classmethod
    def class_tripler(klass, x):
        """"""
        print("running tripler: %s" % klass)
        return x*3

    #----------------------------------------------------------------------
    @staticmethod
    def static_quad(x):
        """"""
        print("running quad")
        return x*4

#----------------------------------------------------------------------
if __name__ == "__main__":
    decor = DecoratorTest()
    print(decor.doubler(5))
    print(decor.class_tripler(3))
    print(DecoratorTest.class_tripler(3))
    print(DecoratorTest.static_quad(2))
    print(decor.static_quad(3))

    print(decor.doubler)
    print(decor.class_tripler)
    print(decor.static_quad)

这个例子演示了你可以用同样的方式调用一个常规方法和两个修饰方法。您会注意到,可以从类或从类的实例中直接调用@classmethod 和@staticmethod 修饰函数。如果你试图用这个类(比如 DecoratorTest.doubler(2))调用一个常规函数,你会收到一个 TypeError 。您还会注意到,最后一条 print 语句显示 decor.static_quad 返回一个常规函数,而不是一个绑定方法。


Python 的属性

我今年已经写过一次关于 @property decorator 的文章,所以我将在这里转载那篇文章的一个微小变化。

Python 有一个被称为属性的简洁概念,它可以做几件有用的事情。我们将研究如何做到以下几点:

  • 将类方法转换为只读属性
  • 将设置器和获取器重新实现到属性中

使用属性的一个最简单的方法是将它用作方法的装饰。这允许您将类方法转换为类属性。当我需要对值进行某种组合时,我发现这很有用。其他人发现它对于编写他们希望作为方法访问的转换方法很有用。让我们看一个简单的例子:


########################################################################
class Person(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, first_name, last_name):
        """Constructor"""
        self.first_name = first_name
        self.last_name = last_name

    #----------------------------------------------------------------------
    @property
    def full_name(self):
        """
        Return the full name
        """
        return "%s %s" % (self.first_name, self.last_name)

在上面的代码中,我们创建了两个类属性: self.first_nameself.last_name 。接下来,我们创建一个 full_name 方法,它附带了一个 @property decorator。这允许我们在解释器会话中执行以下操作:


>>> person = Person("Mike", "Driscoll")
>>> person.full_name
'Mike Driscoll'
>>> person.first_name
'Mike'
>>> person.full_name = "Jackalope"
Traceback (most recent call last):
  File "", line 1, in <fragment>AttributeError: can't set attribute

如您所见,因为我们将方法转换成了属性,所以可以使用普通的点符号来访问它。但是,如果我们试图将属性设置为不同的值,我们将会引发一个 AttributeError 。更改全名属性的唯一方法是间接更改:


>>> person.first_name = "Dan"
>>> person.full_name
'Dan Driscoll'

这是一种限制,所以让我们看看另一个例子,我们可以创建一个属性,让允许我们设置它。

用 Python 属性替换 Setters 和 Getters

让我们假设我们有一些遗留代码,是某个不太了解 Python 的人写的。如果您和我一样,您以前已经见过这种代码:


from decimal import Decimal

########################################################################
class Fees(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self._fee = None

    #----------------------------------------------------------------------
    def get_fee(self):
        """
        Return the current fee
        """
        return self._fee

    #----------------------------------------------------------------------
    def set_fee(self, value):
        """
        Set the fee
        """
        if isinstance(value, str):
            self._fee = Decimal(value)
        elif isinstance(value, Decimal):
            self._fee = value

要使用这个类,我们必须使用如下定义的 setters 和 getters:


>>> f = Fees()
>>> f.set_fee("1")
>>> f.get_fee()
Decimal('1')

如果您想在不中断依赖这段代码的所有应用程序的情况下,将属性的普通点标记访问添加到这段代码中,您可以通过添加一个属性非常简单地更改它:


from decimal import Decimal

########################################################################
class Fees(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self._fee = None

    #----------------------------------------------------------------------
    def get_fee(self):
        """
        Return the current fee
        """
        return self._fee

    #----------------------------------------------------------------------
    def set_fee(self, value):
        """
        Set the fee
        """
        if isinstance(value, str):
            self._fee = Decimal(value)
        elif isinstance(value, Decimal):
            self._fee = value

    fee = property(get_fee, set_fee)

我们在这段代码的末尾添加了一行。现在我们可以这样做:


>>> f = Fees()
>>> f.set_fee("1")
>>> f.fee
Decimal('1')
>>> f.fee = "2"
>>> f.get_fee()
Decimal('2')

如您所见,当我们以这种方式使用属性时,它允许 fee 属性在不破坏遗留代码的情况下自己设置和获取值。让我们使用属性装饰器重写这段代码,看看我们是否能让它允许设置。


from decimal import Decimal

########################################################################
class Fees(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self._fee = None

    #----------------------------------------------------------------------
    @property
    def fee(self):
        """
        The fee property - the getter
        """
        return self._fee

    #----------------------------------------------------------------------
    @fee.setter
    def fee(self, value):
        """
        The setter of the fee property
        """
        if isinstance(value, str):
            self._fee = Decimal(value)
        elif isinstance(value, Decimal):
            self._fee = value

#----------------------------------------------------------------------
if __name__ == "__main__":
    f = Fees()

上面的代码演示了如何为费用属性创建一个“setter”。你可以通过用一个叫做 @fee.setter 的装饰器来装饰第二个方法,也叫做 fee 来做到这一点。当您执行以下操作时,将调用 setter:


>>> f = Fees()
>>> f.fee = "1"

如果你看看属性的签名,它有 fget、fset、fdel 和 doc 作为“参数”。如果您想捕捉针对属性的 del 命令,您可以使用 @fee.deleter 创建另一个使用相同名称的修饰方法来对应删除函数。


包扎

至此,您应该知道如何创建自己的装饰器,以及如何使用 Python 的一些内置装饰器。我们看了@classmethod、@property 和@staticmethod。我很想知道我的读者如何使用内置装饰器,以及他们如何使用自己的定制装饰器。


进一步阅读

Python 101/201 教育赠品

原文:https://www.blog.pythonlibrary.org/2016/10/04/python-201-educational-giveaway/

我认为青少年和大学生学习如何编程是非常重要的。科学、技术、工程和数学是如此重要的一组需要学习的主题,以至于我决定从今天开始向教师和教授赠送 Python 101Python 201:中级 Python 的副本,直到美国中部时间 2016 年 10 月 14 日晚上 11:59

**拥有有效教育电子邮件地址的学生也可以免费获得电子书,但他们没有资格获得平装书。

如何获得副本

请留下您的评论或通过我的联系表格联系我,告诉我您想要一份副本的原因。我确实需要一些证据来证明你是个教育者。如果您可以留下您的评论,或者使用官方电子邮件地址(如*)通过联系表格给我发电子邮件。edu 域名)或将我链接到一些其他证明(LinkedIn,你在学校网站上的个人资料等),这将是伟大的。

奖赏

  • 每个使用有效教育电子邮件或其他类型证明的人都将收到一本 Python 101 和 Python 201 的电子书。
  • 5 名幸运儿将获得一本平装版 Python 201:中级 Python +以上
  • 大奖将是一本平装本的 Python 101Python 201:中级 Python +电子书本

最后期限

在 2016 年 10 月 14 日美国中部时间晚上 11:59 之前,通过联系表格获得您的意见或发送电子邮件,您将被录取。我会检查参赛作品并联系获胜者。**

Python 201:如何按值对字典排序

原文:https://www.blog.pythonlibrary.org/2012/06/19/python-201-how-to-sort-a-dictionary-by-value/

有一天,有人问我是否有办法按值对字典进行排序。如果您经常使用 Python,那么您应该知道字典数据结构根据定义是一种未排序的映射类型。有些人会将 dict 定义为一个散列表。无论如何,我需要一种方法来根据嵌套字典中的值对嵌套字典(即字典中的字典)进行排序,这样我就可以按照指定的顺序遍历这些键。我们将花一些时间来看看我发现的一个实现。

在谷歌上搜索想法后,我在 stack overflow 上找到了一个答案,它完成了我想要的大部分工作。我不得不稍微修改它,让它使用我的嵌套字典值进行排序,但是这出奇的简单。在我们得到答案之前,我们应该快速地看一下数据结构。这是一个野兽的变体,但为了你的安全,去掉了隐私部位:


mydict = {'0d6f4012-16b4-4192-a854-fe9447b3f5cb': 
          {'CLAIMID': '123456789',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '365.64', 'EXPDATE': '20120831'}, 
          'fe614868-d0c0-4c62-ae02-7737dea82dba': 
          {'CLAIMID': '45689654', 
           'CLAIMDATE': '20120508', 
           'AMOUNT': '185.55', 'EXPDATE': '20120831'}, 
          'ca1aa579-a9e7-4ade-80a3-0de8af4bcb21': 
          {'CLAIMID': '98754651',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '93.00', 'EXPDATE': '20120831'},
          'ccb8641f-c1bd-45be-8f5e-e39b3be2e0e3': 
          {'CLAIMID': '789464321',
           'CLAIMDATE': '20120508', 'AMOUNT': '0.00',
           'EXPDATE': ''}, 
          'e1c445c2-5148-4a08-9b7e-ff5ed51c43ed': 
          {'CLAIMID': '897987945', 
           'CLAIMDATE': '20120508', 
           'AMOUNT': '62.66', 'EXPDATE': '20120831'}, 
          '77ad6dd4-5704-4060-9c38-6a93721ef98e': 
          {'CLAIMID': '23212315',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '41.05', 'EXPDATE': '20120831'}
          }

现在我们知道我们在对付什么了。让我们快速看一下我想出的稍加修改的答案:


sorted_keys = sorted(mydict.keys(), key=lambda y: (mydict[y]['CLAIMID']))

这是一个非常漂亮的一行程序,但我认为它有点令人困惑。以下是我对其工作原理的理解。 sorted 函数基于对列表(字典的键)进行排序,在本例中是一个匿名函数(lambda)。向匿名函数传递字典以及一个外部键和一个我们想要排序的内部键,在本例中是“CLAIMID”。一旦排序完毕,它将返回新的列表。就我个人而言,我发现 lambdas 有点令人困惑,所以我通常花一点时间将它们解构为一个命名函数,以便我能更好地理解它们。话不多说,下面是同一脚本的一个函数版本:


#----------------------------------------------------------------------
def func(key):
    """"""
    return mydict[key]['CLAIMID']

sorted_keys = sorted(mydict.keys(), key=func)

for key in sorted_keys:
    print mydict[key]['CLAIMID']

为了好玩,让我们编写一个脚本,它可以根据嵌套字典中的任意键对嵌套字典进行排序。


mydict = {'0d6f4012-16b4-4192-a854-fe9447b3f5cb': 
          {'CLAIMID': '123456789',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '365.64', 'EXPDATE': '20120831'}, 
          'fe614868-d0c0-4c62-ae02-7737dea82dba': 
          {'CLAIMID': '45689654', 
           'CLAIMDATE': '20120508', 
           'AMOUNT': '185.55', 'EXPDATE': '20120831'}, 
          'ca1aa579-a9e7-4ade-80a3-0de8af4bcb21': 
          {'CLAIMID': '98754651',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '93.00', 'EXPDATE': '20120831'},
          'ccb8641f-c1bd-45be-8f5e-e39b3be2e0e3': 
          {'CLAIMID': '789464321',
           'CLAIMDATE': '20120508', 'AMOUNT': '0.00',
           'EXPDATE': ''}, 
          'e1c445c2-5148-4a08-9b7e-ff5ed51c43ed': 
          {'CLAIMID': '897987945', 
           'CLAIMDATE': '20120508', 
           'AMOUNT': '62.66', 'EXPDATE': '20120831'}, 
          '77ad6dd4-5704-4060-9c38-6a93721ef98e': 
          {'CLAIMID': '23212315',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '41.05', 'EXPDATE': '20120831'}
          }

outer_keys = mydict.keys()
print "outer keys:"
for outer_key in outer_keys:
    print outer_key

print "*" * 40
inner_keys = mydict[outer_key].keys()

for key in inner_keys:
    sorted_keys = sorted(mydict.keys(), key=lambda y: (mydict[y][key]))
    print "sorted by: " + key
    print sorted_keys
    for outer_key in sorted_keys:
        print mydict[outer_key][key]
    print "*" * 40
    print

这段代码可以工作,但是它没有给出我期望的结果。试着运行这个,你会注意到输出有点奇怪。排序是在字符串上进行的,所以所有看起来像数字的值都像字符串一样排序。哎呀!大多数人希望数字像数字一样排序,所以我们需要快速地将类似数字的值转换成整数或浮点数。下面是代码的最终版本(是的,有点马虎):


mydict = {'0d6f4012-16b4-4192-a854-fe9447b3f5cb': 
          {'CLAIMID': '123456789',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '365.64', 'EXPDATE': '20120831'}, 
          'fe614868-d0c0-4c62-ae02-7737dea82dba': 
          {'CLAIMID': '45689654', 
           'CLAIMDATE': '20120508', 
           'AMOUNT': '185.55', 'EXPDATE': '20120831'}, 
          'ca1aa579-a9e7-4ade-80a3-0de8af4bcb21': 
          {'CLAIMID': '98754651',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '93.00', 'EXPDATE': '20120831'},
          'ccb8641f-c1bd-45be-8f5e-e39b3be2e0e3': 
          {'CLAIMID': '789464321',
           'CLAIMDATE': '20120508', 'AMOUNT': '0.00',
           'EXPDATE': ''}, 
          'e1c445c2-5148-4a08-9b7e-ff5ed51c43ed': 
          {'CLAIMID': '897987945', 
           'CLAIMDATE': '20120508', 
           'AMOUNT': '62.66', 'EXPDATE': '20120831'}, 
          '77ad6dd4-5704-4060-9c38-6a93721ef98e': 
          {'CLAIMID': '23212315',
           'CLAIMDATE': '20120508', 
           'AMOUNT': '41.05', 'EXPDATE': '20120831'}
          }

outer_keys = mydict.keys()
print "outer keys:"
for outer_key in outer_keys:
    print outer_key

print "*" * 40
inner_keys = mydict[outer_key].keys()

for outer_key in outer_keys:
    for inner_key in inner_keys:
        if mydict[outer_key][inner_key] == "":
            continue
        try:
            mydict[outer_key][inner_key] = int(mydict[outer_key][inner_key])
        except ValueError:
            mydict[outer_key][inner_key] = float(mydict[outer_key][inner_key])

for key in inner_keys:
    sorted_keys = sorted(mydict.keys(), key=lambda y: (mydict[y][key]))
    print "sorted by: " + key
    print sorted_keys
    for outer_key in sorted_keys:
        print mydict[outer_key][key]
    print "*" * 40
    print

所以现在我们用一种对人类感知更自然的方式对它进行了分类。现在还有一种方法可以做到这一点,那就是在将数据放入数据结构之前,按照我们想要的方式对数据进行排序。然而,只有当我们从 Python 2.7 开始使用来自集合模块的ordered direct时,这才会起作用。你可以在官方文件中读到。

现在你知道我对这个话题的了解了。我相信我的读者也会有其他的解决方案或方法。欢迎在评论中提及或链接到他们。

进一步阅读

Python 201:中级 Python 免费到周三!

原文:https://www.blog.pythonlibrary.org/2016/10/17/python-201-intermediate-python-free-until-wednesday/

我决定在 10 月 19 日周三之前免费赠送我的新书会很有趣。所以从今天开始,你可以通过 Gumroad 或者 Leanpub 免费获得 Python 201:中级 Python

如果您选择 Gumroad,那么您需要使用以下报价代码:2011 free。请注意,如果您想要接收图书的更新,您需要创建一个帐户并将图书添加到您的书库。

Python201_cover20160330_sm-237x300

Python 201 现在是一门在线课程

原文:https://www.blog.pythonlibrary.org/2017/03/08/python-201-is-now-an-online-course/

我的第二本书《Python 201:中级 Python》刚刚在教育学院作为在线课程发布。我也有 Python 101 在那里。Educative 是一个非常新的教育网站。这有点像代码学院,只是他们通常对所有课程的访问收费。

注意:这不是视频课程!

无论如何,他们很友好地给我提供了一张 50%的优惠券,让我与 Reddit 上的任何人分享。就是这里: au-py201-50注意:此优惠券代码仅在一周内有效。

如果你没有听说过我的第二本书,那么你会发现一个非常好的目录和一些关于教育的免费内容。你也可以在 Leanpub 上看到这本书的目录:https://leanpub.com/python201/

Python 201 正式出版了!

原文:https://www.blog.pythonlibrary.org/2016/09/06/python-201-is-officially-published/

我的第二本书 Python 201:中级 Python(ISBN:978-0-9960628-3-1)现在已经完成,正式出版。你可以在以下地点查阅:

Python201_cover20160330_sm-237x300

平装本将于本周晚些时候或本月在亚马逊和其他在线零售商上出售,这取决于 Lulu 需要多长时间才能将它推出。我还在为 iTunes 制作 iBook 版本,但目前还没有完成。

Python 201 是今天 Leanpub 上的特色书籍

原文:https://www.blog.pythonlibrary.org/2016/11/08/python-201-is-the-featured-book-on-leanpub-today/

Leanpub 今天在他们的主页上展示了我的第二本书:

py201_featured_leanpub

使用以下链接,你可以在明天之前以 50%的价格买到这本书:http://leanpub.com/python201/c/50percent

Python 201 涵盖了以下主题:

第一部分-中间模块

  • 第 1 章 argparse 模块
  • 第 2 章-收集模块
  • 第 3 章-上下文库模块(上下文管理器)
  • 第 4 章 functools 模块(函数重载、缓存等)
  • 第 5 章-所有关于进口
  • 第 6 章 importlib 模块
  • 第 7 章 itertools 模块
  • 第 8 章-re 模块(Python 中正则表达式的介绍)
  • 第 9 章-输入模块(类型提示)

第二部分——零零碎碎

  • 第 10 章——生成器/迭代器
  • 第 11 章-映射、过滤、减少
  • 第 12 章-统一码
  • 第 13 章-基准测试
  • 第 14 章-加密
  • 第 15 章-连接到数据库
  • 第 16 章-超级
  • 第 17 章-描述符(魔术方法)
  • 第 18 章-范围(本地、全局和新的非本地)

第三部分-网络

  • 第 19 章-网页抓取
  • 第 20 章-使用 web APIs
  • 第 21 章- ftplib
  • 第 22 章- urllib / httplib(客户端/服务器)

第四部分-测试

  • 第 23 章- Doctest
  • 第 24 章-单元测试
  • 第 25 章-嘲弄
  • 第 26 章- coverage.py

Python 201 Kickstarter 活动更新-新封面预览

原文:https://www.blog.pythonlibrary.org/2016/03/23/python-201-kickstarter-campaign-update-new-cover-preview/

我的新书《Python 201》的 Kickstarter 活动已经进行了一半,我的第一个挑战目标也已经过半。如果你知道有人想提高他们的 Python 技能,请务必在你选择的社交网站上发布该活动的链接。由于我的支持者的投入,我还增加了几个新的奖励等级。

你现在可以得到一件 T 恤衫+一本 Python 201 的平装本,或者你可以选择 EVERYTHING 级别的支持,这将得到 T 恤衫、一本 Python 201 和 101 的平装本、电子书和 Python 101 截屏。

最后,我联系了我的插画师,她给我发来了这本书封面的快速预览,我想分享一下:

py201_sample

请注意,这只是封面的粗略版本,但整个事情应该在下周的某个时候完成。

再次感谢您的支持!迈克

Python 201 Kickstarter 更新#3 -封面几乎完成!

原文:https://www.blog.pythonlibrary.org/2016/03/30/python-201-kickstarter-update-3-cover-almost-done/

我的艺术家今天给了我一个基本完成的封面,我想和大家分享一下:

Python 201 Cover

我们的活动还剩 8 天,我们几乎就要达到我们的延伸目标了!一定要告诉你的朋友,现在是支持这本有趣的书的最佳时机!你可以在这里承诺支持。

Python 201 Kickstarter 更新-末日即将来临!

原文:https://www.blog.pythonlibrary.org/2016/04/05/python-201-kickstarter-update-the-end-is-nigh/

我的第二本书《Python 201》的 Kickstarter 活动已经进入最后几天了。因为我们几乎达到了我们的延伸目标,所以我给这本书增加了新的章节。除了已经涵盖的内容,本书还将涵盖以下主题:

  • Python 的超级()
  • 描述符(魔术方法)
  • 范围(本地、全局和新的非本地)
  • 打字
  • 异步/等待/异步

这使得总章节数达到 27 章!如果你想投稿,可以点击这里:https://www . kickstarter . com/projects/34257246/python-201-intermediate-python

Python 201:列表理解

原文:https://www.blog.pythonlibrary.org/2012/07/28/python-201-list-comprehensions/

Python 中的列表理解非常方便。他们也可能有点难以理解你什么时候以及为什么要使用他们。列表理解往往比仅仅使用简单的作为循环更难阅读。我们将花一些时间来看看如何构建理解列表,并学习如何使用它们。到本文结束时,你应该有足够的能力自信地使用它们。

列表理解基本上是一行循环的,它产生一个 Python 列表数据结构。这里有一个简单的例子:


x = [i for i in range(5)]

这将返回一个包含整数 0-4 的列表。如果您需要快速创建列表,这将非常有用。例如,假设您正在解析一个文件并寻找某个特定的内容。你可以使用列表理解作为一种过滤器:


if [i for i in line if "SOME TERM" in i]:
    # do something

我曾使用类似的代码快速浏览文件,解析出文件的特定行或部分。当你将功能融入其中时,你可以开始做一些真正酷的事情。假设您想要对列表中的每个元素应用一个函数,例如当您需要将一串字符串转换为整数时:


>>> x = ['1', '2', '3', '4', '5']
>>> y = [int(i) for i in x]
>>> y
[1, 2, 3, 4, 5]

这种事情出现的频率比你想象的要高。我还不得不循环遍历一系列字符串并调用一个字符串方法,比如 strip on 它们,因为它们有各种各样的以空格结尾的前导:


myStrings = [s.strip() for s in myStringList]

也有需要创建嵌套列表理解的情况。这样做的一个原因是将多个列表合并成一个。这个例子来自 Python 文档:


>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

该文档还展示了其他几个关于嵌套列表理解的有趣例子。强烈推荐看一看!至此,您应该能够在自己的代码中使用列表理解,并很好地使用它们。只要发挥你的想象力,你就会发现很多你也可以利用它们的好地方。

进一步阅读

Python 201:命名元组

原文:https://www.blog.pythonlibrary.org/2016/03/15/python-201-namedtuple/

Python 的集合模块有专门的容器数据类型,可用于替换 Python 的通用容器。这里我们要关注的是 namedtuple ,它可以用来替换 Python 的 tuple。当然,正如您将很快看到的那样,namedtuple 并不是一个简单的替代品。我见过一些程序员像使用结构一样使用它。如果你还没有使用过包含 struct 的语言,那么这需要一点解释。结构基本上是一种复杂的数据类型,它将一系列变量组合在一个名称下。让我们看一个如何创建命名元组的示例,这样您就可以了解它们是如何工作的:


from collections import namedtuple

Parts = namedtuple('Parts', 'id_num desc cost amount')
auto_parts = Parts(id_num='1234', desc='Ford Engine',
                   cost=1200.00, amount=10)
print auto_parts.id_num

这里我们从集合模块导入 namedtuple。然后我们调用 namedtuple,它将返回 tuple 的一个新子类,但带有命名字段。所以基本上我们只是创建了一个新的元组类。你会注意到我们有一个奇怪的字符串作为第二个参数。这是我们要创建的以空格分隔的属性列表。

现在我们有了闪亮的新类,让我们创建它的一个实例!正如你在上面看到的,当我们创建 auto_parts 对象时,这是我们的下一步。现在我们可以使用点符号来访问 auto_parts 中的各个项目,因为它们现在是我们的 parts 类的属性。

使用 namedtuple 而不是常规 tuple 的好处之一是,您不再需要跟踪每个项目的索引,因为现在每个项目都是通过 class 属性命名和访问的。代码的区别如下:


>>> auto_parts = ('1234', 'Ford Engine', 1200.00, 10)
>>> auto_parts[2]  # access the cost
1200.0
>>> id_num, desc, cost, amount = auto_parts
>>> id_num
'1234'

在上面的代码中,我们创建了一个正则元组,并通过告诉 Python 我们想要的适当索引来访问车辆引擎的成本。或者,我们也可以使用多重赋值从元组中提取所有内容。就个人而言,我更喜欢 namedtuple 方法,因为它更容易理解,并且您可以使用 Python 的 dir() 方法来检查元组并找出其属性。试试看会发生什么!

有一天,我在寻找一种将 Python 字典转换成对象的方法,我发现了一些代码,它们做了类似这样的事情:


>>> from collections import namedtuple

>>> Parts = {'id_num':'1234', 'desc':'Ford Engine',
             'cost':1200.00, 'amount':10}
>>> parts = namedtuple('Parts', Parts.keys())(**Parts)
>>> parts
Parts(amount=10, cost=1200.0, id_num='1234', desc='Ford Engine')

这是一些奇怪的代码,所以让我们一次看一部分。我们导入的第一行和前面一样命名为 duple。接下来,我们创建一个零件字典。到目前为止,一切顺利。现在我们准备好了奇怪的部分。在这里,我们创建了一个名为 duple 的类,并将其命名为“Parts”。第二个参数是字典中的键列表。最后一段就是这段奇怪的代码: (Parts)** 。双星号意味着我们使用关键字参数调用我们的类,在本例中是我们的字典。我们可以把这条线分成两部分,让它看起来更清楚一些:


>>> parts = namedtuple('Parts', Parts.keys())
>>> parts
 >>> auto_parts = parts(**Parts)
>>> auto_parts
Parts(amount=10, cost=1200.0, id_num='1234', desc='Ford Engine') 

所以这里我们做和以前一样的事情,除了我们首先创建类,然后我们用我们的字典调用类来创建一个对象。我想提到的另一件事是,namedtuple 还接受一个冗长的参数和一个重命名的参数。verbose 参数是一个标志,如果您将它设置为 True,它将在构建之前打印出类定义。如果要从数据库或其他不受程序控制的系统中创建命名元组,rename 参数非常有用,因为它会自动为您重命名属性。


包扎

现在您已经知道如何使用 Python 方便的 namedtuple。我已经在我的代码库中找到了它的多种用途,我希望它对你的代码也有帮助。编码快乐!


相关阅读

Python 201 -有序直接

原文:https://www.blog.pythonlibrary.org/2016/03/24/python-201-ordereddict/

Python 的 collections 模块有另一个很棒的 dict 子类,叫做 OrderedDict。顾名思义,这个字典在添加键时跟踪它们的顺序。如果您创建一个常规字典,您会注意到它是一个无序的数据集合:


>>> d = {'banana': 3, 'apple':4, 'pear': 1, 'orange': 2}
>>> d
{'apple': 4, 'banana': 3, 'orange': 2, 'pear': 1}

每次打印出来,顺序可能都不一样。有时候,你需要按照特定的顺序遍历字典中的键。例如,我有一个用例,我需要对键进行排序,这样我就可以按顺序遍历它们。为此,您可以执行以下操作:


>>> keys = d.keys()
>>> keys
dict_keys(['apple', 'orange', 'banana', 'pear'])
>>> keys = sorted(keys)
['apple', 'banana', 'orange', 'pear']
>>> for key in keys:
...     print (key, d[key])
... 
apple 4
banana 3
orange 2
pear 1

让我们使用原始字典创建一个 OrderedDict 的实例,但是在创建过程中,我们将对字典的键进行排序:


>>> from collections import OrderedDict
>>> d = {'banana': 3, 'apple':4, 'pear': 1, 'orange': 2}
>>> new_d = OrderedDict(sorted(d.items()))
>>> new_d
OrderedDict([('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)])
>>> for key in new_d:
...     print (key, new_d[key])
... 
apple 4
banana 3
orange 2
pear 1

在这里,我们通过使用 Python 的 sorted 内置函数动态排序来创建 OrderedDict。sorted 函数接受字典的条目,这些条目是表示字典的键对的元组列表。它对它们进行排序,然后将它们传递给 OrderedDict,后者将保留它们的顺序。因此,当我们打印键和值时,它们是按照我们期望的顺序排列的。如果你要遍历一个常规的字典(不是一个排序的键列表),顺序会一直改变。

请注意,如果添加新的键,它们将被添加到 OrderedDict 的末尾,而不是被自动排序。

关于 OrderDicts,需要注意的另一点是,当您比较两个 OrderedDicts 时,它们不仅会测试条目是否相等,还会测试顺序是否正确。正规字典只看字典的内容,不关心它的顺序。

最后,OrderDicts 在 Python 3 中有两个新方法: popitemmove_to_end 。popitem 方法将返回并移除(key,item)对。move_to_end 方法会将现有键移动到 OrderedDict 的任意一端。如果 OrderedDict 的最后一个参数设置为 True(这是默认值),则该项将移动到右端,如果为 False,则该项将移动到开头。

有趣的是,OrderedDicts 使用 Python 的反向内置函数支持反向迭代:


>>> for key in reversed(new_d):
...     print (key, new_d[key])
... 
pear 1
orange 2
banana 3
apple 4

非常简洁,尽管你可能不会每天都需要这个功能。


包扎

此时,您应该准备好亲自尝试 OrderedDict 了。它是对您的工具箱的一个有用的补充,我希望您能在您的代码库中找到它的许多用途。


相关阅读

Python 201:属性

原文:https://www.blog.pythonlibrary.org/2014/01/20/python-201-properties/

Python 有一个被称为属性的简洁概念,它可以做几件有用的事情。在本文中,我们将探讨如何做到以下几点:

  • 将类方法转换为只读属性
  • 将设置器和获取器重新实现到属性中

在本文中,您将学习如何以几种不同的方式使用内置类属性。希望在文章结束时,您会看到它是多么有用。

入门指南

使用属性的一个最简单的方法是将它用作方法的装饰。这允许您将类方法转换为类属性。当我需要对值进行某种组合时,我发现这很有用。其他人发现它对于编写他们希望作为方法访问的转换方法很有用。让我们看一个简单的例子:

class Person(object):

    def __init__(self, first_name, last_name):
        """Constructor"""
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        """
        Return the full name
        """
        return "%s %s" % (self.first_name, self.last_name)

在上面的代码中,我们创建了两个类属性: self.first_nameself.last_name 。接下来,我们创建一个 full_name 方法,它附带了一个 @property decorator。这允许我们在解释器会话中执行以下操作:

>>> person = Person("Mike", "Driscoll")
>>> person.full_name
'Mike Driscoll'
>>> person.first_name
'Mike'
>>> person.full_name = "Jackalope"
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: can't set attribute

如您所见,因为我们将方法转换成了属性,所以可以使用普通的点符号来访问它。但是,如果我们试图将属性设置为不同的值,我们将会引发一个 AttributeError 。更改全名属性的唯一方法是间接更改:

>>> person.first_name = "Dan"
>>> person.full_name
'Dan Driscoll'

这是一种限制,所以让我们看看另一个例子,我们可以创建一个属性,让允许我们设置它。

用 Python 属性替换 Setters 和 Getters

让我们假设我们有一些遗留代码,是某个不太了解 Python 的人写的。如果你像我一样,你以前已经见过这种代码:

from decimal import Decimal

########################################################################
class Fees(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self._fee = None

    #----------------------------------------------------------------------
    def get_fee(self):
        """
        Return the current fee
        """
        return self._fee

    #----------------------------------------------------------------------
    def set_fee(self, value):
        """
        Set the fee
        """
        if isinstance(value, str):
            self._fee = Decimal(value)
        elif isinstance(value, Decimal):
            self._fee = value

要使用这个类,我们必须使用如下定义的 setters 和 getters:

>>> f = Fees()
>>> f.set_fee("1")
>>> f.get_fee()
Decimal('1')

如果您想在不中断依赖这段代码的所有应用程序的情况下,将属性的普通点标记访问添加到这段代码中,您可以通过添加一个属性非常简单地更改它:

from decimal import Decimal

########################################################################
class Fees(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self._fee = None

    #----------------------------------------------------------------------
    def get_fee(self):
        """
        Return the current fee
        """
        return self._fee

    #----------------------------------------------------------------------
    def set_fee(self, value):
        """
        Set the fee
        """
        if isinstance(value, str):
            self._fee = Decimal(value)
        elif isinstance(value, Decimal):
            self._fee = value

    fee = property(get_fee, set_fee)

我们在这段代码的末尾添加了一行。现在我们可以这样做:

>>> f = Fees()
>>> f.set_fee("1")
>>> f.fee
Decimal('1')
>>> f.fee = "2"
>>> f.get_fee()
Decimal('2')

如您所见,当我们以这种方式使用属性时,它允许 fee 属性在不破坏遗留代码的情况下自己设置和获取值。让我们使用属性装饰器重写这段代码,看看我们是否能让它允许设置。

from decimal import Decimal

########################################################################
class Fees(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self._fee = None

    #----------------------------------------------------------------------
    @property
    def fee(self):
        """
        The fee property - the getter
        """
        return self._fee

    #----------------------------------------------------------------------
    @fee.setter
    def fee(self, value):
        """
        The setter of the fee property
        """
        if isinstance(value, str):
            self._fee = Decimal(value)
        elif isinstance(value, Decimal):
            self._fee = value

#----------------------------------------------------------------------
if __name__ == "__main__":
    f = Fees()

上面的代码演示了如何为费用属性创建一个“setter”。你可以通过用一个叫做 @fee.setter 的装饰器来装饰第二个方法,也叫做 fee 来做到这一点。当您执行以下操作时,将调用 setter:

>>> f = Fees()
>>> f.fee = "1"

如果你看看属性的签名,它有 fget、fset、fdel 和 doc 作为“参数”。如果您想捕捉针对属性的 del 命令,您可以使用 @fee.deleter 创建另一个使用相同名称的修饰方法来对应删除函数。

包扎

现在您知道了如何在自己的类中使用 Python 属性。希望您能找到在自己的代码中使用它们的更有用的方法。

附加阅读

Python 201 将在两周后发布!

原文:https://www.blog.pythonlibrary.org/2016/08/23/python-201-releasing-in-2-weeks/

我的第二本书《Python 201:中级 Python》将于 2016 年 9 月 6 日,也就是从今天起两周后发行。我只是想提醒任何感兴趣的人,你可以在这里预订这本书的签名平装本,直到发行日。您还将收到一份以下数字格式的书:PDF、EPUB 和 MOBI (Kindle 格式)。

我的书也可以在 Gumroad 和 T2 Leanpub 网站上提前发布。

有关更多信息,请查看这些链接!

Python 201 -超级

原文:https://www.blog.pythonlibrary.org/2016/05/25/python-201-super/

过去我曾经简单地写过关于 super 的文章,但是我决定再写一些关于这个特殊 Python 函数的更有趣的东西。

早在 Python 2.2 中就引入了超级内置函数。超级函数将返回一个代理对象,该对象将方法调用委托给类型的父类或兄弟类。如果这一点还不清楚的话,它允许你做的是访问在类中被覆盖的继承方法。超级函数有两个用例。第一种是在单一继承中,super 可以用来引用父类,而不需要显式地命名它们。这可以使您的代码在将来更易于维护。这类似于你在其他编程语言中发现的行为,比如 Dylan 的next-method

第二个用例是在动态执行环境中,super 支持协作式多重继承。这实际上是一个非常独特的用例,可能只适用于 Python,因为它在只支持单一继承的语言和静态编译的语言中都找不到。

即使在核心开发者中,super 也有相当多的争议。原始文档很混乱,使用 super 也很棘手。有些人甚至将 super 标记为有害的,尽管那篇文章似乎更适用于 super 的 Python 2 实现,而不是 Python 3 版本。我们将从如何在 Python 2 和 3 中调用 super 开始这一章。然后我们将学习方法解析顺序。


Python 2 对 Python 3

让我们从一个常规的类定义开始。然后我们将使用 Python 2 添加 super,看看它是如何变化的。


class MyParentClass(object):
    def __init__(self):
        pass

class SubClass(MyParentClass):
    def __init__(self):
        MyParentClass.__init__(self)

这对于单一继承来说是一个非常标准的设置。我们有一个基类和子类。基类的另一个名字是父类,甚至是超类。无论如何,在子类中我们也需要初始化父类。Python 的核心开发者觉得把这种东西做得更抽象更便携会是个好主意,于是就加了超级函数。在 Python 2 中,子类如下所示:


class SubClass(MyParentClass):
    def __init__(self):
        super(SubClass, self).__init__()

Python 3 把这个简化了一点。让我们来看看:


class MyParentClass():
    def __init__(self):
        pass

class SubClass(MyParentClass):
    def __init__(self):
        super().__init__()

您将注意到的第一个变化是父类不再需要显式地基于对象基类。第二个变化是对超级的调用。我们不再需要向它传递任何东西,但是 super 会隐式地做正确的事情。大多数类实际上都传递了参数,所以让我们看看在这种情况下超级签名是如何变化的:


class MyParentClass():
    def __init__(self, x, y):
        pass

class SubClass(MyParentClass):
    def __init__(self, x, y):
        super().__init__(x, y)

这里我们只需要调用 super 的 init 方法并传递参数。还是很好很直接。


方法解析顺序(MRO)

方法解析顺序(MRO)只是派生该类的类型列表。所以如果你有一个类继承了另外两个类,你可能会认为 MRO 就是它自己和它继承的两个父类。然而,父类也继承了 Python 的基类:object。让我们来看一个例子,它将使这一点更加清楚:


class X:
    def __init__(self):
        print('X')
        super().__init__()

class Y:
    def __init__(self):
        print('Y')
        super().__init__()

class Z(X, Y):
    pass

z = Z()
print(Z.__mro__)

这里我们创建了 3 个类。前两个只是打印出类名,最后一个继承了前两个。然后我们实例化这个类,并打印出它的 MRO:


X
Y
(, <class>, <class>, <class>) 

如您所见,当您实例化它时,每个父类都打印出它的名称。然后我们得到方法解析顺序,是 ZXY 和 object。另一个很好的例子是,当您在基类中创建一个类变量,然后覆盖它时,会发生什么:


class Base:
    var = 5
    def __init__(self):
        pass

class X(Base):
    def __init__(self):
        print('X')
        super().__init__()

class Y(Base):
    var = 10
    def __init__(self):
        print('Y')
        super().__init__()

class Z(X, Y):
    pass

z = Z()
print(Z.__mro__)
print(super(Z, z).var)

所以在这个例子中,我们创建了一个基类,class 变量设置为 5。然后我们创建基类的子类:X 和 Y。Y 覆盖基类的 class 变量并将其设置为 10。最后,我们创建继承自 X 和 y 的类 Z。当我们在类 Z 上调用 super 时,哪个类变量会被打印出来?尝试运行此代码,您将获得以下结果:


X
Y
(, <class>, <class>, <class>, <class>)
10 

让我们稍微分析一下。类 Z 继承自 X 和 y。所以当我们问它它的 var 是什么时,MRO 会查看 X 是否被定义。它不在那里,所以它移动到 Y,Y 有它,所以这就是返回的内容。尝试向 X 添加一个类变量,您会看到它覆盖了 Y,因为它在方法解析顺序中是第一个。

有一个叫 Michele Simionato 的家伙创建了一个精彩的文档,详细描述了 Python 的方法解析顺序。你可以在这里查看:https://www.python.org/download/releases/2.3/mro/。这是一个很长的阅读,你可能需要重读几遍,但它很好地解释了 MRO。顺便说一句,你可能注意到这篇文章被标记为针对 Python 2.3,但它仍然适用于 Python 3,即使现在对 super 的调用有点不同。

Python 3 中对 super 方法进行了小幅更新。在 Python 3 中,super 可以计算出它是从哪个类调用的,以及包含它的方法的第一个参数。甚至在第一个参数不叫 self 的时候也可以!这是你在 Python 3 中调用 super() 时看到的。在 Python 2 中,您需要调用 super(ClassName,self) ,但是在 Python 3 中这被简化了。

因为这个事实,超级知道如何解释 MRO,并且它将这个信息存储在下面的魔法属性中: thisclassself_class 。让我们看一个例子:


class Base():
    def __init__(self):
        s = super()
        print(s.__thisclass__)
        print(s.__self_class__)
        s.__init__()

class SubClass(Base):
    pass

sub = SubClass()

在这里,我们创建一个基类,并将超级调用赋给一个变量,这样我们就可以找出那些神奇的属性包含了什么。然后我们把它们打印出来并初始化。最后,我们创建基类的一个子类,并实例化该子类。输出到 stdout 的结果是:


 <class>

这很酷,但是可能不太方便,除非你在做元类或混合类。


包扎

你会在互联网上看到很多有趣的超级例子。他们中的大部分人一开始会有点迷惑,但是你会发现他们,或者认为这真的很酷,或者想知道你为什么要这么做。就我个人而言,在我的大部分工作中,我并不需要 super,但它对向前兼容很有用。这意味着您希望您的代码在未来工作时尽可能少地进行更改。所以今天你可能从单一继承开始,但是一两年后,你可能会添加另一个基类。如果你正确地使用 super,那么这将是很容易添加的。我也看到了使用 super 进行依赖注入的论点,但是我没有看到后一种用例的任何好的、具体的例子。不过,这是一件值得记住的好事。

超级功能可能非常方便,也可能非常令人困惑,或者两者兼而有之。明智地使用它,它会很好地为你服务。


进一步阅读

python 201:func tools 模块(视频)

原文:https://www.blog.pythonlibrary.org/2021/03/23/python-201-the-functools-module-video/

我最近做了一个关于 Python 整洁的 functools 模块的演讲。该截屏涵盖了本模块的大部分内容:

  • 贮藏
  • total_ordering
  • partial
  • reduce
  • singledispatch
  • wraps

https://www.youtube.com/embed/gIVRaAb-XzA?feature=oembed

相关阅读

  • 鼠标 Vs Python - 部分功能
  • 真正的 Python: Python 的 reduce(): 从函数式到 Python 式
  • 鼠标与 Python——使用 singledispatch 的函数重载
  • 鼠标 Vs Python-Python–如何使用 functools.wraps

python 201——方便的默认字典

原文:https://www.blog.pythonlibrary.org/2016/03/23/python-201-the-handy-defaultdict/

collections 模块有另一个方便的工具叫做 defaultdict 。defaultdict 是 Python 的 dict 的子类,它接受 default_factory 作为其主要参数。default_factory 通常是 Python 类型,比如 int 或 list,但是也可以使用函数或 lambda。让我们首先创建一个常规的 Python 字典,它计算每个单词在一个句子中的使用次数:


sentence = "The red for jumped over the fence and ran to the zoo for food"
words = sentence.split(' ')

reg_dict = {}
for word in words:
    if word in reg_dict:
        reg_dict[word] += 1
    else:
        reg_dict[word] = 1

print(reg_dict)

如果运行此代码,您应该会看到类似于以下内容的输出:


{'The': 1,
 'and': 1,
 'fence': 1,
 'food': 1,
 'for': 2,
 'jumped': 1,
 'over': 1,
 'ran': 1,
 'red': 1,
 'the': 2,
 'to': 1,
 'zoo': 1}

现在让我们尝试用 defaultdict 做同样的事情!


from collections import defaultdict

sentence = "The red for jumped over the fence and ran to the zoo for food"
words = sentence.split(' ')

d = defaultdict(int)
for word in words:
    d[word] += 1

print(d)

您会马上注意到代码简单多了。defaultdict 会自动将零作为值赋给它还没有的任何键。我们增加一个,这样更有意义,如果这个词在句子中出现多次,它也会增加。


defaultdict(,
            {'The': 1,
             'and': 1,
             'fence': 1,
             'food': 1,
             'for': 2,
             'jumped': 1,
             'over': 1,
             'ran': 1,
             'red': 1,
             'the': 2,
             'to': 1,
             'zoo': 1}) 

现在让我们尝试使用 Python 列表类型作为我们的默认工厂。像以前一样,我们先从一本普通的字典开始。


my_list = [(1234, 100.23), (345, 10.45), (1234, 75.00),
           (345, 222.66), (678, 300.25), (1234, 35.67)]

reg_dict = {}
for acct_num, value in my_list:
    if acct_num in reg_dict:
        reg_dict[acct_num].append(value)
    else:
        reg_dict[acct_num] = [value]

print(reg_dict)

这个例子基于我几年前写的一些代码。基本上我是一行一行地读一个文件,需要获取账号和支付金额,并跟踪它们。然后在最后,我会总结每个帐户。我们在这里跳过求和部分。如果您运行此代码,您应该会得到类似如下的输出:


{345: [10.45, 222.66], 678: [300.25], 1234: [100.23, 75.0, 35.67]}

现在让我们使用 defaultdict 重新实现这段代码:


from collections import defaultdict

my_list = [(1234, 100.23), (345, 10.45), (1234, 75.00),
           (345, 222.66), (678, 300.25), (1234, 35.67)]

d = defaultdict(list)
for acct_num, value in my_list:
    d[acct_num].append(value)

print(d)

这又一次去掉了 if/else 条件逻辑,使代码更容易理解。下面是上面代码的输出:


defaultdict(,
            {345: [10.45, 222.66],
             678: [300.25],
             1234: [100.23, 75.0, 35.67]}) 

这是一些很酷的东西!让我们继续尝试使用 lambda 作为我们的默认工厂!


>>> from collections import defaultdict
>>> animal = defaultdict(lambda: "Monkey")
>>> animal['Sam'] = 'Tiger'
>>> print animal['Nick']
Monkey
>>> animal
defaultdict( at 0x7f32f26da8c0>, {'Nick': 'Monkey', 'Sam': 'Tiger'}) 

这里我们创建一个 defaultdict,它将把“Monkey”作为默认值分配给任何键。第一个键我们设置为“Tiger”,然后下一个键我们根本不设置。如果你打印第二个键,你会看到它被赋值为“Monkey”。如果您还没有注意到,只要您将 default_factory 设置为有意义的值,基本上不可能导致 KeyError 发生。文档中确实提到,如果您碰巧将 default_factory 设置为 None ,那么您将会收到一个 KeyError 。让我们看看它是如何工作的:


>>> from collections import defaultdict
>>> x = defaultdict(None)
>>> x['Mike']
Traceback (most recent call last):
  Python Shell, prompt 41, line 1
KeyError: 'Mike'

在这种情况下,我们只是创建了一个非常破的 defaultdict。它不能再为我们的键分配默认值,所以它抛出一个 KeyError。当然,由于它是 dict 的一个子类,我们只需将 key 设置为某个值就可以了。但是这有点违背了默认字典的目的。


包扎

现在,您已经知道如何使用 Python 集合模块中方便的 defaultdict 类型。除了刚才看到的赋值默认值之外,您还可以用它做更多的事情。我希望您能在自己的代码中找到一些有趣的用法。


相关阅读

Python 201:什么是描述符?

原文:https://www.blog.pythonlibrary.org/2016/06/10/python-201-what-are-descriptors/

描述符早在 2.2 版本中就被引入 Python 了。它们为开发人员提供了向对象添加托管属性的能力。创建描述符需要的方法有 getsetdelete 。如果您定义了这些方法中的任何一个,那么您就创建了一个描述符。

描述符背后的思想是从对象的字典中获取、设置或删除属性。当您访问一个类属性时,这将启动查找链。如果查找的值是一个定义了描述符方法的对象,那么描述符方法将被调用。

描述符增强了 Python 内部的许多魔力。它们使得属性、方法甚至超级函数工作。它们还用于实现 Python 2.2 中引入的新样式类。


描述符协议

创建描述符的协议非常简单。您只需定义以下一项或多项:

  • get(self,obj,type=None),返回值
  • set(self,obj,value),返回 None
  • delete(self,obj),返回 None

一旦您定义了至少一个,您就创建了一个描述符。如果您同时定义了 getset,您就创建了一个数据描述符。只定义了 get()的描述符称为非数据描述符,通常用于方法。描述符类型有这种区别的原因是,如果一个实例的字典碰巧有一个数据描述符,那么在查找过程中描述符将优先。如果实例的字典有一个条目与非数据描述符匹配,那么字典自己的条目将优先于描述符。

如果同时定义了 getset,也可以创建一个只读描述符,但是在调用 set 方法时会引发一个 AttributeError


调用描述符

调用描述符最常见的方法是在访问属性时自动调用描述符。典型的例子是 my_obj.attribute_name 。这将使你的对象在我的对象中查找属性名称。如果你的属性名恰好定义了 get(),那么属性名。get(my_obj) 将被调用。这完全取决于你的实例是一个对象还是一个类。

这背后的神奇之处在于被称为 getattribute 的神奇方法,它会把 my_obj.a 变成这个:类型(my_obj)。dict['a']。get(a,type(a)) 。你可以在这里阅读 Python 文档中关于实现的所有内容:【https://docs.python.org/3/howto/descriptor.html.

根据所述文档,关于调用描述符,有几点需要记住:

  • 描述符是通过 getattribute 方法的默认实现调用的
  • 如果您覆盖 getattribute,这将阻止描述符被自动调用
  • 对象。getattribute()并键入。getattribute()不要以同样的方式调用 get ()
  • 数据描述符总是会覆盖实例字典
  • 非数据描述符可以被实例字典覆盖。

关于所有这些如何工作的更多信息可以在 Python 的数据模型、Python 源代码和吉多·范·罗苏姆的文档“在 Python 中统一类型和类”中找到。


描述符示例

此时,您可能会对如何使用描述符感到困惑。当我学习一个新概念时,如果我有几个例子来演示它是如何工作的,我总是觉得很有帮助。所以在这一节中,我们将看一些例子,这样你将知道如何在你自己的代码中使用描述符!

让我们从编写一个非常简单的数据描述符开始,然后在一个类中使用它。这个例子基于 Python 文档中的一个例子:

class MyDescriptor():
    """
    A simple demo descriptor
    """
    def __init__(self, initial_value=None, name='my_var'):
        self.var_name = name
        self.value = initial_value

    def __get__(self, obj, objtype):
        print('Getting', self.var_name)
        return self.value

    def __set__(self, obj, value):
        msg = 'Setting {name} to {value}'
        print(msg.format(name=self.var_name, value=value))
        self.value = value

class MyClass():
    desc = MyDescriptor(initial_value='Mike', name='desc')
    normal = 10

if __name__ == '__main__':
    c = MyClass()
    print(c.desc)
    print(c.normal)
    c.desc = 100
    print(c.desc)

这里我们创建了一个类并定义了三个神奇的方法:

  • init -我们的构造函数,它接受一个值和变量的名称
  • get -打印当前变量名并返回值
  • set -打印出变量的名称和我们刚刚赋值的值,并自己设置值

然后我们创建一个类,它创建描述符的一个实例作为类属性,还创建一个普通的类属性。然后,我们通过创建一个普通类的实例并访问我们的类属性来运行一些“测试”。以下是输出:

Getting desc
Mike
10
Setting desc to 100
Getting desc
100

如你所见,当我们访问 c.desc 时,它打印出我们的“Getting”消息,我们打印出它返回的内容,即“Mike”。接下来,我们打印出常规类属性的值。最后,我们更改描述符变量的值,这导致我们的“设置”消息被打印出来。我们还要仔细检查当前值,以确保它确实被设置了,这就是为什么您会看到最后一条“获取”消息。

Python 使用描述符来构建属性、绑定/未绑定方法和类方法。如果您在 Python 的文档中查找 property 类,您会发现它非常接近描述符协议:

property(fget=None, fset=None, fdel=None, doc=None)

它清楚地显示了 property 类有一个 getter、setter 和一个 deleting 方法。

让我们看另一个例子,在这里我们使用描述符来进行验证:

from weakref import WeakKeyDictionary

class Drinker:
    def __init__(self):
        self.req_age = 21
        self.age = WeakKeyDictionary()

    def __get__(self, instance_obj, objtype):
        return self.age.get(instance_obj, self.req_age)

    def __set__(self, instance, new_age):
        if new_age < 21:
            msg = '{name} is too young to legally imbibe'
            raise Exception(msg.format(name=instance.name))
        self.age[instance] = new_age
        print('{name} can legally drink in the USA'.format(
            name=instance.name))

    def __delete__(self, instance):
        del self.age[instance]

class Person:
    drinker_age = Drinker()

    def __init__(self, name, age):
        self.name = name
        self.drinker_age = age

p = Person('Miguel', 30)
p = Person('Niki', 13)

我们再次创建一个描述符类。在这种情况下,我们使用 Python 的 weakref 库的 WeakKeyDictionary ,这是一个简洁的类,它创建了一个弱映射键的字典。这意味着当字典中没有对某个键的强引用时,该键及其值将被丢弃。我们在这个例子中使用它来防止我们的 Person 实例无限期地徘徊。

无论如何,我们最关心的描述符部分在我们的 set 方法中。在这里,我们检查实例的年龄参数是否大于 21,如果你想喝酒精饮料,这是你在美国需要达到的年龄。如果你的年龄较低,那么它将引发一个异常。否则它会打印出这个人的名字和一条信息。为了测试我们的描述符,我们创建了两个实例,一个大于 21 岁,另一个小于 21 岁。如果运行此代码,您应该会看到以下输出:

Miguel can legally drink in the USA
Traceback (most recent call last):
  File "desc_validator.py", line 32, in 
    p = Person('Niki', 13)
  File "desc_validator.py", line 28, in __init__
    self.drinker_age = age
  File "desc_validator.py", line 14, in __set__
    raise Exception(msg.format(name=instance.name))
Exception: Niki is too young to legally imbibe

这显然是按照预期的方式工作的,但它是如何工作的并不明显。这样做的原因是当我们设置 drinker_age 时,Python 注意到它是一个描述符。Python 知道 drinker_age 是一个描述符,因为我们在创建它作为一个类属性时就这样定义了它:

drinker_age = Drinker()

因此,当我们去设置它时,我们实际上调用了描述符的 set 方法,该方法传入了实例和我们试图设置的年龄。如果年龄小于 21 岁,那么我们用一个自定义消息引发一个异常。否则,我们会打印出一条消息,说明您已经足够大了。

回到这一切是如何工作的,如果我们试图打印出 drinker_age,Python 将执行 Person.drinker_age。get。因为 drinker_age 是一个描述符,所以它的 get 才是真正被调用的。如果你想设置饮酒者年龄,你可以这样做:

p.drinker_age = 32

Python 会调用 Person.drinker_age。set 由于该方法也在我们的描述符中实现,因此描述符方法是被调用的方法。一旦您跟踪几次代码执行,您将很快看到这一切是如何工作的。

要记住的主要事情是描述符链接到类而不是实例。


包装材料

描述符非常重要,因为它们出现在 Python 源代码的所有地方。如果你了解它们是如何工作的,它们也会对你非常有用。然而,它们的用例非常有限,您可能不会经常使用它们。希望本文能帮助您了解描述符的用处,以及您自己何时可能需要使用它。


相关阅读

Python 201 -什么是链图?

原文:https://www.blog.pythonlibrary.org/2016/03/29/python-201-what-is-a-chainmap/

一个 ChainMap 是来自集合模块的一个类,它提供了将多个映射链接在一起的能力,这样它们最终成为一个单一的单元。如果您查看文档,您会注意到它接受*映射,这意味着一个链图将接受任意数量的映射或字典,并将它们转换成一个您可以更新的单一视图。让我们来看一个例子,这样您就可以了解这是如何工作的:


>>> from collections import ChainMap
>>> car_parts = {'hood': 500, 'engine': 5000, 'front_door': 750}
>>> car_options = {'A/C': 1000, 'Turbo': 2500, 'rollbar': 300}
>>> car_accessories = {'cover': 100, 'hood_ornament': 150, 'seat_cover': 99}
>>> car_pricing = ChainMap(car_accessories, car_options, car_parts)
>>> car_pricing['hood']
500

这里,我们从集合模块中导入 ChainMap 。接下来,我们创建三个字典。然后,我们通过传入刚刚创建的三个字典来创建一个链图实例。最后,我们尝试访问链表中的一个键。当我们这样做的时候,链表将遍历每一个链表,以查看这个键是否存在并且有一个值。如果是这样,那么链表将返回它找到的第一个匹配那个键的值。

如果您想要设置默认值,这尤其有用。让我们假设我们想要创建一个有一些默认值的应用程序。应用程序还会知道操作系统的环境变量。如果有一个环境变量与我们在应用程序中默认的一个键相匹配,那么这个环境将覆盖我们的默认值。让我们进一步假设我们可以将参数传递给我们的应用程序。这些参数优先于环境和默认值。这是一个链图可以真正发光的地方。让我们看一个基于 Python 文档的简单例子:


import argparse
import os

from collections import ChainMap

def main():
    app_defaults = {'username':'admin', 'password':'admin'}

    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--username')
    parser.add_argument('-p', '--password')
    args = parser.parse_args()
    command_line_arguments = {key:value for key, value 
                              in vars(args).items() if value}

    chain = ChainMap(command_line_arguments, os.environ, 
                     app_defaults)
    print(chain['username'])

if __name__ == '__main__':
    main()
    os.environ['username'] = 'test'
    main()

让我们稍微分解一下。这里我们导入 Python 的 argparse 模块和 os 模块。我们还导入了链图。接下来我们有一个简单的函数,它有一些愚蠢的默认值。我见过一些流行的路由器使用这些默认设置。然后,我们设置我们的参数解析器,并告诉它如何处理某些命令行选项。您会注意到,argparse 没有提供获取其参数的字典对象的方法,所以我们使用字典理解来提取我们需要的内容。这里另一个很酷的部分是 Python 内置的变量的使用。如果你在没有参数的情况下调用它,var 的行为会像 Python 的内置局部变量一样。但是如果你确实传入了一个对象,那么 vars 就等同于 object 的 dict 属性。

换句话说,变量(参数)等于参数。dict 。最后,通过传入我们的命令行参数(如果有的话),然后传入环境变量,最后传入默认值来创建我们的链图。在代码的最后,我们尝试调用我们的函数,然后设置一个环境变量并再次调用它。试一试,你会看到它像预期的那样打印出管理测试。现在让我们尝试用命令行参数调用该脚本:


python chain_map.py -u mike

当我运行这个程序时,我得到了两次麦克的回复。这是因为我们的命令行参数覆盖了所有其他内容。我们设置环境并不重要,因为我们的链图会首先查看命令行参数。


包扎

现在你知道什么是链图,以及如何使用它。我发现它们非常有趣,我想我已经有了一两个用例,我希望很快就能实现。


相关阅读

Python 201:什么是超级?

原文:https://www.blog.pythonlibrary.org/2014/01/21/python-201-what-is-super/

Python 编程语言在 2.2 版本中添加了 super() 类型。出于某种原因,这仍然是一个很多初学者不理解的话题。我的一个读者最近问我关于它的问题,由于我并不真正使用它,我决定做一些研究,希望自己能理解它的用法,这样我就能解释什么是 super 以及为什么你会使用它。我们将花一些时间来看看不同的人对超级的定义,然后看一些例子来试图弄清楚这一点。

什么是超级?

下面是官方的 Python 文档超级 : 返回一个代理对象,该对象将方法调用委托给父类或兄弟类。这对于访问在类中被重写的继承方法很有用。搜索顺序与 getattr()使用的顺序相同,只是跳过了类型本身。

几年前,Cody Precord 发布了一本 wxPython 食谱,其中他一直使用 super。这在 wxPython 邮件列表上引起了几个星期对 super 的讨论。让我们来看看其中的一些线程

wxPython 的创建者 Robin Dunn 陈述如下:在大多数情况下没有任何区别。在有多重继承的情况下,super()有助于确保在继承树上移动时遵循正确的
方法解析顺序(MRO)。
然后他链接到斯塔克韦尔弗洛

在另一个主题中,Tim Roberts(一个非常有经验的 Python 程序员)说:

这是一条“捷径”,允许你访问一个派生类的基类,而不必知道或键入基类名。比如:


class This_is_a_very_long_class_name(object):
    def __init__(self):
        pass

class Derived(This_is_a_very_long_class_name):
    def __init__(self):
        super(Derived,self).__init__()   #1
        This_is_a_very_long_class_name.__init__(self)    #2

最后两行是同一事物的两种拼法。除了拼写之外,这还允许您更改基类,而不必遍历所有代码并替换基类名称。C++程序员经常在他们的派生类中使用 typedef 来实现这一点。

所以我从这两个陈述中得到的是,当你做多重继承时, super 是很重要的,但是除此之外,你是否使用它并不重要。如果你向下滚动第二个线程,你会看到 Robin Dunn 和 Tim Roberts 的几个例子,它们有助于说明使用 super 是有帮助的。让我们看看邓恩先生的例子。

注意:下面的代码使用 Python 2.x 语法进行 super。在 Python 3 中,不需要传递类名,甚至不需要传递 self!


class A(object):
    def foo(self):
        print 'A'

class B(A):
    def foo(self):
        print 'B'
        super(B, self).foo()

class C(A):
    def foo(self):
        print 'C'
        super(C, self).foo()

class D(B,C):
    def foo(self):
        print 'D'
        super(D, self).foo()

d = D()
d.foo() 

如果您运行这段代码,您将得到字母“DBCA”(每行一个字母)作为输出。正如 Robin Dunn 在帖子中指出的,“A”只被打印一次,即使从它派生出两个类。这是因为 Python 的新样式类中固有的方法解析顺序(MRO)。你可以通过在类 D 的 foo 函数中的超级调用下面添加 print D.mro 来签出 MRO(注意:这只在基类是从对象派生的情况下才有效)。这个栈溢出条目对此进行了更详细的解释。如果你想好好读一读关于 MRO 的主题,我会推荐 Python.org 上的以下摘要:http://www.python.org/download/releases/2.3/mro/

现在你可能认为这个例子不仅抽象,而且没什么用,你可能是对的。这就是为什么值得在网上做一些额外的挖掘来寻找其他的例子。幸运的是,我记得几年前在 super 上看过 Raymond Hettinger 的一篇文章。他是 Python 的核心开发人员,经常在 PyCon 上发言。总之,他的文章给出了几个使用 super 向子类添加新特性的真实例子。我强烈推荐看看他的文章,因为它对以一种适用的方式解释超级大有帮助。甚至 super()的 Python 文档也链接到了他的文章!

这里有一个关于这个话题的很好的列表:

Python 201 -什么是 deque?

原文:https://www.blog.pythonlibrary.org/2016/04/14/python-201-whats-a-deque/

根据 Python 文档, deques “是栈和队列的概括”。它们读作“deck”,是“双端队列”的缩写。它们是 Python 列表的替代容器。Deques 是线程安全的,支持从 deques 的任何一端进行内存有效的追加和弹出。列表针对快速固定长度操作进行了优化。您可以在 Python 文档中获得所有血淋淋的细节。一个双队列接受一个 maxlen 参数,该参数设置双队列的边界。否则,deque 将增长到任意大小。当一个有界的 deque 满了,任何添加的新项将导致相同数量的项从另一端弹出。

一般来说,如果你需要快速追加或者快速弹出,使用一个队列。如果你需要快速随机访问,使用列表。让我们花一些时间来看看如何创建和使用 deque。


>>> from collections import deque
>>> import string
>>> d = deque(string.ascii_lowercase)
>>> for letter in d:
...     print(letter)

这里,我们从集合模块中导入了 deque,还导入了字符串模块。为了实际创建一个 deque 的实例,我们需要向它传递一个 iterable。在本例中,我们传递给它 string.ascii_lowercase ,它返回字母表中所有小写字母的列表。最后,我们循环我们的 deque 并打印出每一项。现在让我们看看 deque 拥有的一些方法。


>>> d.append('bork')
>>> d
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 
       'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 
       'y', 'z', 'bork'])
>>> d.appendleft('test')
>>> d
deque(['test', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 
       'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 
       'v', 'w', 'x', 'y', 'z', 'bork'])
>>> d.rotate(1)
>>> d
deque(['bork', 'test', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 
       'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 
       't', 'u', 'v', 'w', 'x', 'y', 'z'])

让我们把它分解一下。首先,我们将一个字符串追加到队列的右端。然后,我们将另一个字符串追加到队列的左侧。最后,我们调用 deque 上的 rotate 并向其传递一个 1,这会使其向右旋转一次。换句话说,它导致一个项目从右端旋转到前端。你可以给它传递一个负数来使队列向左旋转。

让我们通过查看一个基于 Python 文档的示例来结束这一部分:


from collections import deque

def get_last(filename, n=5):
    """
    Returns the last n lines from the file
    """
    try:
        with open(filename) as f:
            return deque(f, n)
    except OSError:
        print("Error opening file: {}".format(filename))
        raise

这段代码的工作方式与 Linux 的 tail 程序非常相似。在这里,我们向脚本传递一个文件名以及我们希望返回的 n 行。dequee 绑定到我们作为 n 传入的任何数字,这意味着一旦 dequee 满了,当新的行被读入并添加到 dequee 时,旧的行从另一端弹出并被丢弃。我还将文件开头的with语句包装在一个简单的异常处理程序中,因为它很容易传入格式错误的路径。例如,这将捕获不存在的文件。

包扎

现在你知道 Python 的 deque 的基础了。这是集合模块中又一个方便的小工具。虽然我个人从来不需要这个特殊的集合,但它仍然是一个有用的结构供其他人使用。我希望您会在自己的代码中发现它的一些重要用途。

Python 201 写作更新:只剩 4 章了!

原文:https://www.blog.pythonlibrary.org/2016/07/21/python-201-writing-update-only-4-chapters-to-go/

本周早些时候,我完成了第四部分,这本书有 26 章,200 多页。我还计划了四章,然后对前几章做了一些更新。我的目标是在月底准备好这本书进行校对。然后,我将创建一个这本书的样本打印,并检查它的错误。

如果有人一直在阅读这本书,并发现任何错误,请让我知道。我将在八月中旬左右完成这几章,希望在此之前它们能尽可能的好。

非常感谢你的支持!迈克

附:如果你想购买这本书的早期版本,你可以在 GumroadLeanpub 购买

PyCon 2017 -第二天

原文:https://www.blog.pythonlibrary.org/2017/05/21/python-2017-second-day/

PyCon 2017 大会的第二天以与美国宇航局和皮克斯等公司的人共进早餐开始,随后是几次闪电谈话。我没有全部看完,但是它们很有趣。然后,他们转到了来自 Instagram 的郭美孜和丁辉当天的第一个主题演讲。我没有意识到他们使用 Django 和 Python 作为他们的核心技术。

他们讲述了如何从 Django 1.3 过渡到 1.8,以及如何从 Python 2 过渡到 3。这是一次非常有趣的演讲,深入探讨了他们如何在 Instagram 上使用 Python。看到 Python 能够扩展到几亿用户真是太棒了。如果我没记错的话,他们还提到,与 Python 2 相比,Python 3 节省了 30%的内存使用,同时提高了 12%的 CPU 使用。他们还提到,当他们进行转换时,他们通过使其与 Python 2 和 3 兼容,同时不断向他们的用户发布产品,在主分支中进行了转换。你可以在 Youtube 上看到这个视频:

https://www.youtube.com/watch?v=66XoCk79kjM

下一个主题演讲是由核工程师凯蒂·哈夫做的。虽然我个人并不觉得它像 Instagram one 一样有趣,但看到 Python 如何在如此多的科学社区和如此多的不同方式中被使用是很有趣的。如果你感兴趣,你可以看看这里的主题演讲:

https://www.youtube.com/watch?v=kaGS4YXwciQ

之后,我参加了今天的第一个讲座,是 PyCharm 团队的 Elizaveta Shashkova 的Python 3.6 调试:更好、更快、更强。她的演讲侧重于在 PEP 523 中引入 CPython 的新框架评估 API,以及它如何使调试更容易、更快,尽管需要更长的准备时间。这是视频:

https://www.youtube.com/watch?v=NdObDUbLjdg

接下来是 Dropbox 团队的朱卡·莱托萨洛和大卫·费希尔为 Python 开发的静态类型。他们讨论了如何使用 MyPy 通过现场代码演示引入静态类型,以及如何在 Dropbox 中使用它将类型添加到 700,000 行代码中。我认为这很吸引人,尽管我真的很喜欢 Python 的动态特性。我认为这是一个加强文档字符串的好方法,同时也使它们更具可读性。这是视频:

https://www.youtube.com/watch?v=7ZbwZgrXnwY

午饭后,我去了一个关于 Python 201 的开放空间房间,最终是关于人们在试图学习 Python 时面临的问题。这真的很有趣,让我对没有计算机科学背景的人所面临的问题有了新的认识。

我在 wxPython 上尝试了我自己的开放空间,但不知何故,房间被一群谈论无人机的人霸占了,据我所知,没有人出现来谈论 wxPython。令人失望,但无论如何。在我等待的时候,我开始做一个有趣的 wxPython 项目。

我参加的最后一个演讲是让·巴普蒂斯特·阿维亚特做的,题为在 2017 年写一个 C Python 扩展。他提到了用 Python 与 C/C++交互的几种不同方式,比如 ctypes、cffi、Cython 和 SWIG。他的选择是 ctypes。他有点难以理解,所以我强烈建议您亲自观看视频,看看您有什么想法:

https://www.youtube.com/watch?v=phe1s6p38gk

我的其他亮点只是在走廊或午餐时随机遇到的,在那里我遇到了其他使用 Python 的有趣的人。

Python 3.10 -带括号的上下文管理器

原文:https://www.blog.pythonlibrary.org/2021/09/08/python-3-10-parenthesized-context-managers/

Python 3.10 将于下个月发布,所以现在是时候开始谈论它将包含的新特性了。Python 的核心开发者最近宣布,他们正在添加带括号的上下文管理器,这是对 Python 3.9 和更早版本不支持跨行延续括号的错误的错误修复

您仍然可以有多个上下文管理器,但是它们不能在括号中。基本上,它使代码看起来有点笨拙。下面是一个使用 Python 3.8 的例子:

>>> with (open("test_file1.txt", "w") as test, 
          open("test_file2.txt", "w") as test2): 
        pass                                                                 

File "", line 1
    with (open("test_file1.txt", "w") as test,
                                      ^
SyntaxError: invalid syntax

有趣的是,尽管没有得到官方支持,这些代码在 Python 3.9 中似乎也能正常工作。如果你去读最初的错误报告,那里提到添加 PEG 解析器(PEP 617) 应该可以解决这个问题。

这有点让人想起 Python 字典是无序的,直到在 Python 3.6 中一个实现细节使它们有序,但直到 Python 3.8 才正式有序。

无论如何,Python 3.10 已经批准了这一更改,这使得以下所有示例代码都是有效的:

with (CtxManager() as example):
    ...

with (
    CtxManager1(),
    CtxManager2()
):
    ...

with (CtxManager1() as example,
      CtxManager2()):
    ...

with (CtxManager1(),
      CtxManager2() as example):
    ...

with (
    CtxManager1() as example1,
    CtxManager2() as example2
):
    ...

你可以在 Python 3.10 的新特性章节中了解更多关于这个新“特性”的信息。

Python 3.10 -简化了类型注释中的联合

原文:https://www.blog.pythonlibrary.org/2021/09/11/python-3-10-simplifies-unions-in-type-annotations/

Python 3.10 有几个新的类型特性。这里给出了它们的详细信息:

本教程的重点是谈论 PEP 604 ,这使得在向您的代码库添加类型注释(又名:类型提示)时编写联合类型更加容易。

工会老叶道

在 Python 3.10 之前,如果你想说一个变量或参数可以有多种不同的类型,你需要使用 Union :

from typing import Union

rate: Union[int, str] = 1

这是 Python 文档中的另一个例子:

from typing import Union

def square(number: Union[int, float]) -> Union[int, float]:
    return number ** 2

让我们看看 3.10 将如何解决这个问题!

新联盟

在 Python 3.10 中,你根本不再需要导入 Union 。所有细节都在 PEP 604 里。PEP 允许您用|操作符完全替换它。完成后,上面的代码如下所示:

def square(number: int | float) -> int | float:
    return number ** 2

您也可以使用新的语法来替换可选的,这也是您在类型提示中使用的,参数可以是None:

# Old syntax

from typing import Optional, Union

def f(param: Optional[int]) -> Union[float, str]:
   ...

# New syntax in 3.10

def f(param: int | None) -> float | str:
   ...

您甚至可以将此语法与 isinstance()issubclass() 一起使用:

>>> isinstance(1, int | str)
True

包扎

虽然这对于 Python 语言来说并不是一个令人惊奇的新特性,但是使用带有类型注释的|操作符使您的代码看起来更加整洁。你不需要进口那么多,这样看起来更好。

相关阅读

Python 3.4 今天发布!

原文:https://www.blog.pythonlibrary.org/2014/03/17/python-3-4-released-today/

Python 3.4 于今天(2014-03-17)发布,包含了许多优秀的东西。根据 Python 内部人士的说法,这些是主要的变化/增加:

  • PEP 428,一个" pathlib "模块,提供面向对象的文件系统路径
  • PEP 435,一个标准化的“枚举模块
  • PEP 436,一个构建增强,将帮助生成内置的自省信息
  • PEP 442 ,改进了对象终结的语义
  • PEP 443 ,向标准库中添加单调度通用函数
  • PEP 445 ,一个新的实现定制内存分配器的 C API
  • PEP 446,将文件描述符更改为在子进程中默认不被继承
  • PEP 450,新增“统计模块
  • PEP 451,为 Python 的模块导入系统标准化模块元数据
  • pip软件包管理器的捆绑安装程序 PEP 453
  • PEP 454,一个新的" tracemalloc "模块,用于跟踪 Python 内存分配
  • PEP 456 ,Python 字符串和二进制数据的新哈希算法
  • PEP 3154,一个新的和改进的酸洗对象协议
  • PEP 3156,一个新的" asyncio "模块,一个新的异步 I/O 框架

我迫不及待地想尝试这些新的模块,看看我能为我的锦囊妙计添加什么新的东西。立即前往下载您的副本!

Python 3.7 -词典现已订购

原文:https://www.blog.pythonlibrary.org/2018/02/27/python-3-7-dictionaries-now-ordered/

我的一位读者向我指出,Python 3.7 现在将默认拥有有序字典。你可以在 Python-Dev 列表上阅读“公告”。

Python 的字典一直是无序的,直到 Python 3.6,它才根据 Raymond Hettinger(Python 的核心开发人员之一)的提议变成有序的。

Python 3.6 的发行说明)说了以下内容:

这个新实现的保序方面被认为是一个实现细节,不应该依赖它(这在将来可能会改变,但是在改变语言规范以强制所有当前和将来的 Python 实现的保序语义之前,希望在几个版本的语言中有这个新的 dict 实现;这也有助于保持与旧版本语言的向后兼容性,其中随机迭代顺序仍然有效,例如 Python 3.5)。

现在,当 Python 3.7 发布时,dict 的有序实现将成为标准。我觉得这很棒。

python 3——asyncio 简介

原文:https://www.blog.pythonlibrary.org/2016/07/26/python-3-an-intro-to-asyncio/

在 Python 3.4 版本中, asyncio 模块作为临时包被添加到 Python 中。这意味着 asyncio 有可能接收到向后不兼容的更改,甚至可能在 Python 的未来版本中被删除。根据文档 asyncio " 提供了使用协程编写单线程并发代码、通过套接字和其他资源多路复用 I/O 访问、运行网络客户端和服务器以及其他相关原语的基础设施。本章并不打算涵盖 asyncio 的所有功能,但是您将学习如何使用该模块以及它为什么有用。

如果您在旧版本的 Python 中需要类似 asyncio 的东西,那么您可能想看看 Twisted 或 gevent。


定义

asyncio 模块提供了一个围绕事件循环的框架。事件循环基本上是等待某件事情发生,然后对事件进行操作。它负责处理诸如 I/O 和系统事件之类的事情。Asyncio 实际上有几个可用的循环实现。该模块将默认为对于它所运行的操作系统来说最有可能是最有效的模块;但是,如果您愿意,也可以显式选择事件循环。一个事件循环基本上就是说“当事件 A 发生时,用函数 B 反应”。

想象一个服务器,它在等待某人到来并请求一个资源,比如一个网页。如果网站不是很受欢迎,服务器会闲置很长时间。但是当它成功时,服务器需要做出反应。这种反应被称为事件处理。当用户加载网页时,服务器将检查并调用一个或多个事件处理程序。一旦这些事件处理程序完成,它们需要将控制权交还给事件循环。为了在 Python 中做到这一点,asyncio 使用了协程

协程是一个特殊的函数,它可以放弃对调用者的控制而不丢失它的状态。协程是消费者,也是生成器的扩展。与线程相比,它们的一大优势是执行时不会占用太多内存。请注意,当您调用一个协程函数时,它实际上并不执行。相反,它将返回一个协程对象,您可以将该对象传递给事件循环,以便立即或稍后执行它。

使用 asyncio 模块时,您可能会遇到的另一个术语是 future 。一个未来基本上是一个表示尚未完成的工作结果的对象。您的事件循环可以观察未来的对象,并等待它们完成。当一个未来结束时,它被设置为完成。Asyncio 还支持锁和信号量。

最后一条我要提的信息是任务。任务是协程的包装器和未来的子类。您甚至可以使用事件循环来安排任务。


异步和等待

Python 3.5 中添加了 asyncwait关键字,以定义一个原生协程,并使它们与基于生成器的协程相比成为一个独特的类型。如果你想深入了解 async 和 await,你可以看看 PEP 492。

在 Python 3.4 中,您将创建如下所示的协程:


# Python 3.4 coroutine example
import asyncio

@asyncio.coroutine
def my_coro():
    yield from func()

这个装饰器在 Python 3.5 中仍然有效,但是 types 模块收到了一个以协程函数形式的更新,它现在会告诉你你正在交互的是否是一个本地协程。从 Python 3.5 开始,您可以使用异步定义来从语法上定义一个协程函数。所以上面的函数看起来会像这样:


import asyncio

async def my_coro():
    await func()

当您以这种方式定义协程时,您不能在协程函数中使用 yield 。相反,它必须包含一个用于将值返回给调用者的返回等待语句。注意 await 关键字只能在异步定义函数中使用。

async / await 关键字可以被认为是用于异步编程的 API。asyncio 模块只是一个框架,恰好使用 async / await 进行异步编程。实际上有一个名为 curio 的项目证明了这个概念,因为它是一个事件循环的独立实现,在幕后使用了 async / await


一个糟糕的协同例子

虽然有大量的背景信息来了解所有这些是如何工作的肯定是有帮助的,但是有时您只是想看一些例子,这样您就可以对语法以及如何将这些东西放在一起有一个感觉。记住这一点,让我们从一个简单的例子开始!

你想完成的一个相当常见的任务是从某个地方下载一个文件,不管是内部资源还是互联网上的文件。通常你会想要下载多个文件。因此,让我们创建一对能够做到这一点的协程:


import asyncio
import os
import urllib.request

async def download_coroutine(url):
    """
    A coroutine to download the specified url
    """
    request = urllib.request.urlopen(url)
    filename = os.path.basename(url)

    with open(filename, 'wb') as file_handle:
        while True:
            chunk = request.read(1024)
            if not chunk:
                break
            file_handle.write(chunk)
    msg = 'Finished downloading {filename}'.format(filename=filename)
    return msg

async def main(urls):
    """
    Creates a group of coroutines and waits for them to finish
    """
    coroutines = [download_coroutine(url) for url in urls]
    completed, pending = await asyncio.wait(coroutines)
    for item in completed:
        print(item.result())

if __name__ == '__main__':
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]

    event_loop = asyncio.get_event_loop()
    try:
        event_loop.run_until_complete(main(urls))
    finally:
        event_loop.close()

在这段代码中,我们导入我们需要的模块,然后使用 async 语法创建我们的第一个协程。这个协程被称为download _ 协程,它使用 Python 的 urllib 来下载传递给它的任何 URL。完成后,它将返回一条这样的消息。

另一个协程是我们的主协程。它基本上接受一个或多个 URL 的列表,并将它们排队。我们使用 asyncio 的 wait 函数来等待协程完成。当然,要真正启动协同程序,需要将它们添加到事件循环中。我们在得到一个事件循环的最后这样做,然后调用它的 run_until_complete 方法。你会注意到我们将协程传递给了事件循环。这将开始运行主协程,主协程将第二个协程排队并让它运行。这就是所谓的链式协同程序。

这个例子的问题是,它实际上根本不是一个协程。原因是 download_coroutine 函数不是异步的。这里的问题是,urllib 不是异步的,而且,我也没有使用来自的等待产出。更好的方法是使用 aiohttp 包。接下来让我们来看看!


一个更好的协同例子

aiohttp 包是为创建异步 http 客户端和服务器而设计的。您可以像这样用 pip 安装它:


pip install aiohttp

安装完成后,让我们更新代码以使用 aiohttp,这样我们就可以下载文件了:


import aiohttp
import asyncio
import async_timeout
import os

async def download_coroutine(session, url):
    with async_timeout.timeout(10):
        async with session.get(url) as response:
            filename = os.path.basename(url)
            with open(filename, 'wb') as f_handle:
                while True:
                    chunk = await response.content.read(1024)
                    if not chunk:
                        break
                    f_handle.write(chunk)
            return await response.release()

async def main(loop):
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
        "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
        "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
        "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
        "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]

    async with aiohttp.ClientSession(loop=loop) as session:
        tasks = [download_coroutine(session, url) for url in urls]
        await asyncio.gather(*tasks)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(loop))

您会注意到我们在这里导入了几个新项目: aiohttpasync_timeout 。后者实际上是 aiohttp 的依赖项之一,允许我们创建超时上下文管理器。让我们从代码的底部开始,一步步向上。在底部的条件语句中,我们开始异步事件循环,并调用我们的 main 函数。在 main 函数中,我们创建了一个 ClientSession 对象,并将其传递给我们的 download 协程函数,用于我们想要下载的每个 URL。在 download_coroutine 中,我们创建了一个 async_timeout.timeout() 上下文管理器,它基本上创建了一个 X 秒的计时器。当秒数用完时,上下文管理器结束或超时。在这种情况下,超时时间为 10 秒。接下来,我们调用会话的 get() 方法,该方法为我们提供了一个响应对象。现在我们到了有点不可思议的部分。当您使用响应对象的内容属性时,它返回一个 aiohttp 的实例。StreamReader 允许我们下载任何大小的文件。当我们读取文件时,我们把它写到本地磁盘上。最后我们调用响应的 release() 方法,这将完成响应处理。

根据 aiohttp 的文档,因为响应对象是在上下文管理器中创建的,所以它在技术上隐式地调用 release()。但是在 Python 中,显式通常更好,文档中有一个注释,我们不应该依赖于正在消失的连接,所以我认为在这种情况下最好是释放它。

这里仍有一部分被阻塞,这是实际写入磁盘的代码部分。当我们写文件的时候,我们仍然在阻塞。还有另一个名为 aiofiles 的库,我们可以用它来尝试使文件写入也是异步的,但是我将把更新留给读者。


安排通话

您还可以使用 asyncio 事件循环调度对常规函数的调用。我们要看的第一个方法是 call_sooncall_soon 方法基本上会尽可能快地调用您的回调或事件处理程序。它作为一个 FIFO 队列工作,所以如果一些回调需要一段时间运行,那么其他的回调将被延迟,直到前面的回调完成。让我们看一个例子:


import asyncio
import functools

def event_handler(loop, stop=False):
    print('Event handler called')
    if stop:
        print('stopping the loop')
        loop.stop()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        loop.call_soon(functools.partial(event_handler, loop))
        print('starting event loop')
        loop.call_soon(functools.partial(event_handler, loop, stop=True))

        loop.run_forever()
    finally:
        print('closing event loop')
        loop.close() 

asyncio 的大多数函数不接受关键字,所以如果我们需要将关键字传递给事件处理程序,我们将需要 functools 模块。每当调用我们的常规函数时,它都会将一些文本输出到 stdout。如果您碰巧将它的停止参数设置为,它也会停止事件循环。

第一次调用它时,我们不停止循环。第二次调用时,我们停止了循环。我们想要停止循环的原因是我们已经告诉它 run_forever ,这将使事件循环进入无限循环。一旦循环停止,我们就可以关闭它。如果运行此代码,您应该会看到以下输出:


starting event loop
Event handler called
Event handler called
stopping the loop
closing event loop

有一个相关的函数叫做 call_soon_threadsafe 。顾名思义,它的工作方式与 call_soon 相同,但是它是线程安全的。

如果你真的想把一个呼叫延迟到将来的某个时间,你可以使用 call_later 功能。在这种情况下,我们可以将 call_soon 签名更改为:


loop.call_later(1, event_handler, loop)

这将延迟调用我们的事件处理程序一秒钟,然后它将调用它并将循环作为它的第一个参数传入。

如果你想在未来安排一个特定的时间,那么你需要获取循环的时间而不是计算机的时间。你可以这样做:


current_time = loop.time()

一旦你有了它,你就可以使用 call_at 函数并传递你想要它调用你的事件处理器的时间。假设我们想在五分钟后调用事件处理程序。你可以这样做:


loop.call_at(current_time + 300, event_handler, loop)

在本例中,我们使用获取的当前时间,并在其上附加 300 秒或 5 分钟。通过这样做,我们将调用事件处理程序的时间延迟了五分钟!相当整洁!


任务

任务是未来的子类,是协程的包装器。它们使您能够跟踪它们完成处理的时间。因为它们是未来的一种类型,所以其他协程可以等待一个任务,而你也可以在任务处理完成时获取它的结果。让我们看一个简单的例子:


import asyncio

async def my_task(seconds):
    """
    A task to do for a number of seconds
    """
    print('This task is taking {} seconds to complete'.format(
        seconds))
    await asyncio.sleep(seconds)
    return 'task finished'

if __name__ == '__main__':
    my_event_loop = asyncio.get_event_loop()
    try:
        print('task creation started')
        task_obj = my_event_loop.create_task(my_task(seconds=2))
        my_event_loop.run_until_complete(task_obj)
    finally:
        my_event_loop.close()

    print("The task's result was: {}".format(task_obj.result()))

这里我们创建一个异步函数,它接受函数运行所需的秒数。这模拟了一个长时间运行的过程。然后我们创建我们的事件循环,然后通过调用事件循环对象的 create_task 函数创建一个任务对象。 create_task 函数接受我们想要变成任务的函数。然后我们告诉事件循环运行,直到任务完成。在最后,我们得到任务的结果,因为它已经完成。

通过使用它们的 cancel 方法,任务也可以很容易地被取消。当你想结束一个任务的时候就调用它。如果一个任务在等待另一个操作时被取消,该任务将引发一个取消错误


包扎

至此,您应该已经了解了足够多的知识,可以开始自己使用 asyncio 库了。asyncio 库非常强大,允许你做很多非常酷和有趣的任务。Python 文档是开始学习 asyncio 库的好地方。

更新:这篇文章最近在这里被翻译成俄文


相关阅读

Python 3:加密介绍

原文:https://www.blog.pythonlibrary.org/2016/05/18/python-3-an-intro-to-encryption/

Python 3 的标准库中没有太多处理加密的内容。相反,你得到的是哈希库。我们将在本章中简要地看一下这些,但是主要的焦点将放在下面的第三方包上:PyCrypto 和 cryptography。我们将学习如何用这两个库来加密和解密字符串。


散列法

如果您需要安全散列或消息摘要算法,那么 Python 的标准库已经在 hashlib 模块中涵盖了您。它包括 FIPS 安全哈希算法 SHA1、SHA224、SHA256、SHA384 和 SHA512 以及 RSA 的 MD5 算法。Python 也支持 adler32 和 crc32 哈希函数,但这些都在 zlib 模块中。

哈希最常见的用途之一是存储密码的哈希而不是密码本身。当然,散列必须是一个好的散列或者它可以被解密。散列的另一个流行用例是散列一个文件,然后分别发送该文件及其散列。然后,接收文件的人可以对该文件运行散列,以查看它是否与发送的散列相匹配。如果是的话,那就意味着没有人在传输过程中更改过文件。

让我们试着创建一个 md5 散列:


>>> import hashlib
>>> md5 = hashlib.md5()
>>> md5.update('Python rocks!')
Traceback (most recent call last):
  File "", line 1, in <module>md5.update('Python rocks!')
TypeError: Unicode-objects must be encoded before hashing
>>> md5.update(b'Python rocks!')
>>> md5.digest()
b'\x14\x82\xec\x1b#d\xf6N}\x16*+[\x16\xf4w'

让我们花点时间来分解一下。首先,我们导入 hashlib ,然后创建一个 md5 散列对象的实例。接下来,我们将一些文本添加到 hash 对象中,得到一个回溯。事实证明,要使用 md5 散列,您必须向它传递一个字节字符串,而不是一个常规字符串。所以我们尝试了一下,然后调用它的 digest 方法来获取我们的散列。如果你喜欢十六进制摘要,我们也可以这样做:


>>> md5.hexdigest()
'1482ec1b2364f64e7d162a2b5b16f477'

实际上有一种创建散列的快捷方法,所以我们接下来在创建 sha512 散列时会看到:


>>> sha = hashlib.sha1(b'Hello Python').hexdigest()
>>> sha
'422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'

如您所见,我们可以创建我们的 hash 实例,同时调用它的 digest 方法。然后我们打印出散列来看看它是什么。我选择使用 sha1 散列,因为它有一个很好的短散列,更适合页面。但是它也不太安全,所以请随意尝试其他产品。


密钥派生

Python 对内置于标准库中的密钥派生的支持非常有限。事实上,hashlib 提供的唯一方法是 pbkdf2_hmac 方法,这是 PKCS#5 基于密码的密钥派生函数 2。它使用 HMAC 作为它的伪随机函数。您可以使用类似这样的东西来散列您的密码,因为它支持 salt 和迭代。例如,如果您要使用 SHA-256,您将需要至少 16 字节的 salt 和最少 100,000 次迭代。

顺便提一句,salt 只是一个随机数据,你可以把它作为额外的输入加入到你的 hash 中,使你的密码更难“解密”。基本上,它保护您的密码免受字典攻击和预先计算的彩虹表。

让我们看一个简单的例子:


>>> import binascii
>>> dk = hashlib.pbkdf2_hmac(hash_name='sha256',
        password=b'bad_password34', 
        salt=b'bad_salt', 
        iterations=100000)
>>> binascii.hexlify(dk)
b'6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02'

在这里,我们使用一个糟糕的 salt 在一个密码上创建一个 SHA256 散列,但是有 100,000 次迭代。当然,实际上并不建议使用 SHA 来创建密码的密钥。相反,你应该使用类似于 scrypt 的东西。另一个不错的选择是第三方包 bcrypt。它是专门为密码哈希而设计的。


PyCryptodome

PyCrypto 包可能是 Python 中最著名的第三方加密包。可悲的是 PyCrypto 的开发在 2012 年停止。其他人继续发布 PyCryto 的最新版本,所以如果你不介意使用第三方的二进制文件,你仍然可以获得 Python 3.5 的版本。比如我在 Github 上找到了一些 PyCrypto 的二进制 Python 3.5 轮子(https://Github . com/SF Bahr/py crypto-Wheels)。

幸运的是,这个项目有一个名为 PyCrytodome 的分支,它是 PyCrypto 的替代产品。要为 Linux 安装它,您可以使用以下 pip 命令:


pip install pycryptodome

Windows 有点不同:


pip install pycryptodomex

如果您遇到问题,可能是因为您没有安装正确的依赖项,或者您需要一个 Windows 编译器。查看 PyCryptodome 网站获取更多安装帮助或联系支持。

同样值得注意的是,PyCryptodome 在 PyCrypto 的上一个版本上有许多增强。这是非常值得你花时间去访问他们的主页,看看有什么新功能存在。

加密字符串

一旦你看完了他们的网站,我们可以继续看一些例子。对于我们的第一个技巧,我们将使用 DES 加密一个字符串:


>>> from Crypto.Cipher import DES
>>> key = 'abcdefgh'
>>> def pad(text):
        while len(text) % 8 != 0:
            text += ' '
        return text
>>> des = DES.new(key, DES.MODE_ECB)
>>> text = 'Python rocks!'
>>> padded_text = pad(text)
>>> encrypted_text = des.encrypt(text)
Traceback (most recent call last):
  File "", line 1, in <module>encrypted_text = des.encrypt(text)
  File "C:\Programs\Python\Python35-32\lib\site-packages\Crypto\Cipher\blockalgo.py", line 244, in encrypt
    return self._cipher.encrypt(plaintext)
ValueError: Input strings must be a multiple of 8 in length
>>> encrypted_text = des.encrypt(padded_text)
>>> encrypted_text
b'>\xfc\x1f\x16x\x87\xb2\x93\x0e\xfcH\x02\xd59VQ'

这段代码有点混乱,所以让我们花点时间来分解它。首先,应该注意 DES 加密的密钥大小是 8 个字节,这就是为什么我们将密钥变量设置为大小字母字符串。我们要加密的字符串长度必须是 8 的倍数,所以我们创建了一个名为 pad 的函数,它可以用空格填充任何字符串,直到它是 8 的倍数。接下来,我们创建一个 DES 实例和一些想要加密的文本。我们还创建了文本的填充版本。只是为了好玩,我们尝试加密字符串的原始未填充变量,这将引发一个值错误。在这里,我们了解到,我们毕竟需要填充字符串,所以我们把它传入。如你所见,我们现在有了一个加密的字符串!

当然,如果我们不知道如何解密我们的字符串,这个例子是不完整的:


>>> des.decrypt(encrypted_text)
b'Python rocks!   '

幸运的是,这很容易实现,因为我们所需要做的就是调用 des 对象上的decrypt方法来获取解密后的字节字符串。我们的下一个任务是学习如何使用 RSA 用 PyCrypto 加密和解密文件。但是首先我们需要创建一些 RSA 密钥!

创建一个 RSA 密钥

如果你想用 RSA 加密你的数据,那么你要么需要一个公开的/私有的 RSA 密钥对,要么你需要自己生成一个。对于这个例子,我们将只生成我们自己的。由于这很容易做到,我们将在 Python 的解释器中完成:


>>> from Crypto.PublicKey import RSA
>>> code = 'nooneknows'
>>> key = RSA.generate(2048)
>>> encrypted_key = key.exportKey(passphrase=code, pkcs=8, 
        protection="scryptAndAES128-CBC")
>>> with open('/path_to_private_key/my_private_rsa_key.bin', 'wb') as f:
        f.write(encrypted_key)
>>> with open('/path_to_public_key/my_rsa_public.pem', 'wb') as f:
        f.write(key.publickey().exportKey())

首先我们从 Crypto 导入 RSA 。公钥。然后我们创造一个愚蠢的密码。接下来,我们生成一个 2048 位的 RSA 密钥。现在我们来看看好东西。要生成私钥,我们需要调用 RSA key 实例的 exportKey 方法,并给它我们的密码,使用哪个 PKCS 标准以及使用哪个加密方案来保护我们的私钥。然后我们把文件写到磁盘上。

接下来,我们通过 RSA key 实例的 publickey 方法创建我们的公钥。我们在这段代码中使用了一个快捷方式,通过将对 exportKey 的调用与 publickey 方法调用链接起来,也将它写入磁盘。

加密文件

现在我们有了一个私钥和一个公钥,我们可以加密一些数据并将其写入文件。这是一个非常标准的例子:


from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP

with open('/path/to/encrypted_data.bin', 'wb') as out_file:
    recipient_key = RSA.import_key(
        open('/path_to_public_key/my_rsa_public.pem').read())
    session_key = get_random_bytes(16)

    cipher_rsa = PKCS1_OAEP.new(recipient_key)
    out_file.write(cipher_rsa.encrypt(session_key))

    cipher_aes = AES.new(session_key, AES.MODE_EAX)
    data = b'blah blah blah Python blah blah'
    ciphertext, tag = cipher_aes.encrypt_and_digest(data)

    out_file.write(cipher_aes.nonce)
    out_file.write(tag)
    out_file.write(ciphertext)

前三行包括我们从 PyCryptodome 的进口。接下来,我们打开一个要写入的文件。然后,我们将公钥导入到一个变量中,并创建一个 16 字节的会话密钥。对于本例,我们将使用混合加密方法,因此我们使用 PKCS#1 OAEP,这是最佳的非对称加密填充。这允许我们向文件中写入任意长度的数据。然后我们创建我们的 AES 密码,创建一些数据和加密数据。这将返回加密的文本和 MAC。最后,我们写出随机数、MAC(或标签)和加密文本。

顺便说一下,随机数是一个仅用于加密通信的任意数字。它们通常是随机数或伪随机数。对于 AES,它的长度必须至少为 16 个字节。请随意尝试在您最喜欢的文本编辑器中打开加密文件。你应该只看到胡言乱语。

现在让我们学习如何解密我们的数据:


from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP

code = 'nooneknows'

with open('/path/to/encrypted_data.bin', 'rb') as fobj:
    private_key = RSA.import_key(
        open('/path_to_private_key/my_rsa_key.pem').read(),
        passphrase=code)

    enc_session_key, nonce, tag, ciphertext = [ fobj.read(x) 
                                                for x in (private_key.size_in_bytes(), 
                                                16, 16, -1) ]

    cipher_rsa = PKCS1_OAEP.new(private_key)
    session_key = cipher_rsa.decrypt(enc_session_key)

    cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
    data = cipher_aes.decrypt_and_verify(ciphertext, tag)

print(data)

如果您遵循前面的例子,这段代码应该很容易解析。在本例中,我们以二进制模式打开加密文件进行读取。然后我们导入我们的私钥。请注意,当您导入私钥时,必须提供您的密码。否则你会得到一个错误。接下来我们读取我们的文件。您会注意到,我们首先读入私钥,然后读入随机数的 16 个字节,接下来的 16 个字节是标签,最后是文件的其余部分,也就是我们的数据。

然后我们需要解密我们的会话密钥,重新创建我们的 AES 密钥并解密数据。

您可以使用 PyCryptodome 做更多的事情。然而,我们需要继续前进,看看在 Python 中还能使用什么来满足我们的加密需求。


密码术包

密码术包旨在成为“人类的密码术”,就像请求库是“人类的 HTTP”。这个想法是,你将能够创建简单的安全易用的密码配方。如果需要的话,您可以使用低级加密原语,这需要您知道自己在做什么,否则您可能会创建一些不太安全的东西。

如果您使用的是 Python 3.5,可以用 pip 安装,如下所示:


pip install cryptography

您将会看到加密技术安装了一些依赖项。假设它们都成功完成,我们可以尝试加密一些文本。我们来试试 Fernet 模块。Fernet 模块实现了一个易于使用的身份验证方案,该方案使用对称加密算法,该算法可以保证在没有您定义的密钥的情况下,您用它加密的任何消息都不能被操纵或读取。Fernet 模块还支持通过multipernet进行密钥轮换。让我们看一个简单的例子:


>>> from cryptography.fernet import Fernet
>>> cipher_key = Fernet.generate_key()
>>> cipher_key
b'APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o='
>>> cipher = Fernet(cipher_key)
>>> text = b'My super secret message'
>>> encrypted_text = cipher.encrypt(text)
>>> encrypted_text
(b'gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f'
 b'6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=')
>>> decrypted_text = cipher.decrypt(encrypted_text)
>>> decrypted_text
b'My super secret message'

首先,我们需要导入 Fernet。接下来,我们生成一个密钥。我们打印出密钥,看看它看起来像什么。如你所见,这是一个随机的字节串。如果你愿意,你可以试着运行几次 generate_key 方法。结果总是不同的。接下来,我们使用我们的密钥创建我们的 Fernet 密码实例。

现在我们有了一个可以用来加密和解密信息的密码。下一步是创建一个值得加密的消息,然后使用 encrypt 方法加密它。我继续打印我们的加密文本,所以你可以看到,你不能再阅读文本。要解密我们的超级秘密消息,我们只需调用我们的密码上的解密,并传递加密文本给它。结果是我们得到了我们的消息的一个纯文本字节串。


包扎

这一章仅仅触及了 PyCryptodome 和加密软件包的皮毛。然而,它确实给了你一个关于用 Python 加密和解密字符串和文件的很好的概述。请务必阅读文档并开始试验,看看还能做些什么!


相关阅读

  • github 上 Python 3 的 PyCrypto Wheels
  • PyCryptodome 文档
  • Python 的加密服务
  • 加密软件包的网站

Python 3:枚举介绍

原文:https://www.blog.pythonlibrary.org/2018/03/20/python-3-an-intro-to-enumerations/

Python 在 3.4 版本的标准库中增加了 enum 模块。Python 文档描述了这样一个枚举:

枚举是绑定到唯一常数值的一组符号名称(成员)。在枚举中,成员可以通过标识进行比较,并且枚举本身可以被迭代。

让我们看看如何创建一个枚举对象:


>>> from enum import Enum
>>> class AnimalEnum(Enum):
        HORSE = 1
        COW = 2
        CHICKEN = 3
        DOG = 4
>>> print(AnimalEnum.CHICKEN)
AnimalEnum.CHICKEN
>>> print(repr(AnimalEnum.CHICKEN))

这里我们创建一个名为 AnimalEnum 的枚举类。在类内部,我们创建名为枚举成员的类属性,它们是常量。当您试图打印出一个枚举成员时,您将得到相同的字符串。但是如果您打印出枚举成员的 repr ,您将获得枚举成员及其值。

如果你试图修改一个枚举成员,Python 会抛出一个 AttributeError :


>>> AnimalEnum.CHICKEN = 5
Traceback (most recent call last):
  Python Shell, prompt 5, line 1
  File "C:\Users\mike\AppData\Local\Programs\PYTHON\PYTHON36-32\Lib\enum.py", line 361, in __setattr__
    raise AttributeError('Cannot reassign members.')
builtins.AttributeError: Cannot reassign members.

枚举成员具有一些属性,您可以使用这些属性来获取它们的名称和值:


>>> AnimalEnum.CHICKEN.name
'CHICKEN'
>>> AnimalEnum.CHICKEN.value
3

枚举也支持迭代。所以你可以做一些有趣的事情,比如:


>>> for animal in AnimalEnum:
        print('Name: {}  Value: {}'.format(animal, animal.value))

Name: AnimalEnum.HORSE  Value: 1
Name: AnimalEnum.COW  Value: 2
Name: AnimalEnum.CHICKEN  Value: 3
Name: AnimalEnum.DOG  Value: 4

Python 的枚举不允许创建同名的枚举成员:


>>> class Shapes(Enum):
...     CIRCLE = 1
...     SQUARE = 2
...     SQUARE = 3
... 
Traceback (most recent call last):
  Python Shell, prompt 13, line 1
  Python Shell, prompt 13, line 4
  File "C:\Users\mike\AppData\Local\Programs\PYTHON\PYTHON36-32\Lib\enum.py", line 92, in __setitem__
    raise TypeError('Attempted to reuse key: %r' % key)
builtins.TypeError: Attempted to reuse key: 'SQUARE'

如您所见,当您试图重用一个枚举成员名时,它将引发一个 TypeError

您也可以像这样创建一个枚举:


>>> AnimalEnum = Enum('Animal', 'HORSE COW CHICKEN DOG')
>>> AnimalEnum
 >>> AnimalEnum.CHICKEN
 <animal.chicken:></animal.chicken:> 

我个人认为这真的很棒!


枚举成员访问

有趣的是,有多种方法可以访问枚举成员。例如,如果您不知道哪个枚举是哪个,您可以直接调用该枚举并向其传递一个值:


>>> AnimalEnum(2)

如果您碰巧传入了一个无效值,那么 Python 会抛出一个 ValueError


>>> AnimalEnum(8)
Traceback (most recent call last):
  Python Shell, prompt 11, line 1
  File "C:\Users\mike\AppData\Local\Programs\PYTHON\PYTHON36-32\Lib\enum.py", line 291, in __call__
    return cls.__new__(cls, value)
  File "C:\Users\mike\AppData\Local\Programs\PYTHON\PYTHON36-32\Lib\enum.py", line 533, in __new__
    return cls._missing_(value)
  File "C:\Users\mike\AppData\Local\Programs\PYTHON\PYTHON36-32\Lib\enum.py", line 546, in _missing_
    raise ValueError("%r is not a valid %s" % (value, cls.__name__))
builtins.ValueError: 8 is not a valid AnimalEnum

您也可以通过名称访问枚举:


>>> AnimalEnum['CHICKEN']


枚举细节

enum 模块还有一些其他有趣的东西可以导入。例如,您可以为您的枚举创建自动值:


>>> from enum import auto, Enum
>>> class Shapes(Enum):
        CIRCLE = auto()
        SQUARE = auto()
        OVAL = auto() 
>>> Shapes.CIRCLE

您还可以导入一个方便的枚举装饰器来确保您的枚举成员是唯一的:


>>> @unique
    class Shapes(Enum):
        CIRCLE = 1
        SQUARE = 2
        TRIANGLE = 1

Traceback (most recent call last):
  Python Shell, prompt 18, line 2
  File "C:\Users\mike\AppData\Local\Programs\PYTHON\PYTHON36-32\Lib\enum.py", line 830, in unique
    (enumeration, alias_details))
builtins.ValueError: duplicate values found in : TRIANGLE -> CIRCLE 

这里我们创建一个枚举,它有两个成员试图映射到同一个值。因为我们添加了 @unique 装饰器,所以如果枚举成员中有任何重复值,就会引发 ValueError。如果您没有应用 @unique 装饰器,那么您可以拥有具有相同值的枚举成员。


包扎

虽然我不认为 enum 模块对 Python 来说是真正必要的,但它是一个很好的工具。该文档有更多的例子,并演示了其他类型的枚举,因此绝对值得一读。


进一步阅读

  • 关于枚举模块 的 Python 3 文档
  • 如何用 Python 表示一个枚举?

python 3:f 弦介绍

原文:https://www.blog.pythonlibrary.org/2018/03/13/python-3-an-intro-to-f-strings/

Python 3.6 增加了另一种进行字符串插值的方法,称为“f-strings”或格式化字符串文字( PEP 498 )。f 字符串背后的想法是使字符串插值更简单。要创建一个 f 字符串,你只需要在字符串前面加上字母“f”。字符串本身可以用与使用 str.format() 相同的方式进行格式化。换句话说,字符串中可以有用花括号括起来的替换字段。这里有一个简单的例子:


>>> name = 'Mike'
>>> f"Hello {name}"
'Hello Mike'

Python 文档中有一个有趣的例子,演示了如何嵌套替换字段。不过,我做了一点修改,让它更简单:


>>> total = 45.758
>>> width = 14
>>> precision = 4
>>> f"Your total is {total:{width}.{precision}}"
'Your total is          45.76'

这里我们创建了三个变量,第一个是浮点数,另外两个是整数。然后我们创建我们的 f-string,告诉它我们想把 total 变量放到我们的字符串中。但是你会注意到,在字符串的替换字段中,我们嵌套了宽度精度变量来格式化总数本身。在这种情况下,我们告诉 Python,我们希望 total 字段的宽度是 14 个字符,float 的精度是 4,所以结果是 45.76,您会注意到它是向上舍入的。

f 字符串还支持日期格式:


>>> import datetime
>>> today = datetime.datetime.today()
>>> f"{today:%B %d, %Y}"
'March 13, 2018'

我个人很喜欢 PEP 498 中给出的例子,它实际上展示了如何使用日期格式从日期中提取星期几:


>>> from datetime import datetime
>>> date = datetime(1992, 7, 4)
>>> f'{date} was on a {date:%A}'
'1992-07-04 00:00:00 was on a Saturday'

您也可以在 f 弦中重复使用相同的变量:


>>> spam = 'SPAM'
>>> f"Lovely {spam}! Wonderful {spam}!"
'Lovely SPAM! Wonderful SPAM!'

文档确实指出,在嵌套引号时,必须小心 f 字符串。比如,你显然不能做这样的事情:


>>> value = 123
>>> f"Your value is "{value}""

这是一个语法错误,就像使用常规字符串一样。您也不能在格式字符串中直接使用反斜杠:


>>> f"newline: {ord('\n')}"
Traceback (most recent call last):
  Python Shell, prompt 29, line 1
Syntax Error: f-string expression part cannot include a backslash: , line 1, pos 0 

文档指出,作为一种变通方法,您可以将反斜杠放入变量中:


>>> newline = ord('\n')
>>> f"newline: {newline}"
'newline: 10'

在这个例子中,我们将换行符转换成它的序数值。文档中提到的最后一点是,不能将 f 字符串用作 docstring。根据 Nick Coghlan 的说法,这是因为需要在编译时知道文档字符串,但是 f 字符串直到运行时才被解析。

包扎

此时,您应该有足够的信息开始在自己的代码中使用 f 字符串。这是对 Python 语言的一个有趣的补充,虽然不是绝对必要的,但我可以看到它在某些方面使字符串插值更简单。开心快乐编码!

进一步阅读

https://docs.python.org/3/reference/lexical_analysis.html#f-strings

Python 3 -类型提示介绍

原文:https://www.blog.pythonlibrary.org/2016/01/19/python-3-an-intro-to-type-hinting/

Python 3.5 增加了一个有趣的新库,叫做 typing 。这给 Python 增加了类型提示。类型提示就是声明你的函数参数具有某种类型。但是,类型提示没有绑定。这只是一个提示,所以没有什么可以阻止程序员传递他们不应该传递的东西。这毕竟是 Python。你可以在 PEP 484 中阅读类型提示规范,或者你可以在 PEP 483 中阅读其背后的理论。

让我们看一个简单的例子:


>>> def some_function(number: int, name: str) -> None:
    print("%s entered %s" % (name, number))

>>> some_function(13, 'Mike')
Mike entered 13

这意味着 some_function 需要两个参数,第一个是整数,第二个是字符串。还需要注意的是,我们已经暗示过这个函数不返回任何值。

让我们后退一点,用正常的方法写一个函数。然后我们将添加类型提示。在下面的例子中,我们有一个接受 list 和 name 的函数,在这个例子中是一个字符串。它所做的只是检查名字是否在列表中,并返回一个适当的布尔值。


def process_data(my_list, name):
    if name in my_list:
        return True
    else:
        return False

if __name__ == '__main__':
    my_list = ['Mike', 'Nick', 'Toby']
    print( process_data(my_list, 'Mike') )
    print( process_data(my_list, 'John') )

现在让我们给这个函数添加类型提示:


def process_data(my_list: list, name: str) -> bool:
    return name in my_list

if __name__ == '__main__':
    my_list = ['Mike', 'Nick', 'Toby']
    print( process_data(my_list, 'Mike') )
    print( process_data(my_list, 'John') )

在这段代码中,我们暗示第一个参数是一个列表,第二个参数是一个字符串,返回值是一个布尔值。

根据 PEP 484,“类型提示可以是内置类(包括标准库或第三方扩展模块中定义的类)、抽象基类、类型模块中可用的类型以及用户定义的类”。这意味着我们可以创建自己的类并添加一个提示。


class Fruit:
    def __init__(self, name, color):
        self.name = name
        self.color = color

def salad(fruit_one: Fruit, fruit_two: Fruit) -> list:
    print(fruit_one.name)
    print(fruit_two.name)
    return [fruit_one, fruit_two]

if __name__ == '__main__':
    f = Fruit('orange', 'orange')
    f2 = Fruit('apple', 'red')
    salad(f, f2)

这里我们创建了一个简单的类,然后创建了一个函数,该函数需要该类的两个实例并返回一个列表对象。另一个我认为有趣的话题是你可以创建一个别名。这里有一个超级简单的例子:


Animal = str

def zoo(animal: Animal, number: int) -> None:
    print("The zoo has %s %s" % (number, animal))

if __name__ == '__main__':
    zoo('Zebras', 10)

您可能已经猜到了,我们只是用变量动物作为字符串类型的别名。然后,我们使用动物别名向我们的函数添加了一个提示。


包扎

当我第一次听说类型暗示时,我很感兴趣。这是一个很好的概念,我肯定能看到它的用途。我想到的第一个用例就是自我记录你的代码。我工作过太多的代码库,在那里很难判断一个函数或类接受什么,虽然类型提示不强制任何东西,但它肯定会使一些模糊的代码变得清晰。如果一些 Python IDEs 添加了一个可选标志,可以检查代码的类型提示,并确保正确调用代码,那就太好了。

我强烈建议查看官方文档,因为那里有更多的信息。pep 也包含了很多好的细节。开心快乐编码!


相关阅读

Python 3 并发性 concurrent.futures 模块

原文:https://www.blog.pythonlibrary.org/2016/08/03/python-3-concurrency-the-concurrent-futures-module/

Python 3.2 中增加了 concurrent.futures 模块。根据 Python 文档,它为开发者提供了异步执行调用的高级接口。基本上是 concurrent.futures 是 Python 线程和多处理模块之上的一个抽象层,简化了它们的使用。然而,应该注意的是,虽然抽象层简化了这些模块的使用,但它也消除了它们的许多灵活性,所以如果您需要做一些定制,那么这可能不是您的最佳模块。

Concurrent.futures 包含一个名为 Executor 的抽象类。但是它不能直接使用,所以你需要使用它的两个子类之一: ThreadPoolExecutor 或者 ProcessPoolExecutor 。正如您可能已经猜到的,这两个子类分别映射到 Python 的线程和多处理 API。这两个子类都将提供一个池,您可以将线程或进程放入其中。

术语未来在计算机科学中有着特殊的含义。它指的是在使用并发编程技术时可以用于同步的构造。未来实际上是一种在进程或线程完成处理之前描述其结果的方式。我喜欢把它们看作一个悬而未决的结果。


创建池

当您使用 concurrent.futures 模块时,创建一个工人池是非常容易的。让我们从重写我的 asyncio 文章中的下载代码开始,这样它现在使用 concurrent.futures 模块。这是我的版本:


import os
import urllib.request

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed

def downloader(url):
    """
    Downloads the specified URL and saves it to disk
    """
    req = urllib.request.urlopen(url)
    filename = os.path.basename(url)
    ext = os.path.splitext(url)[1]
    if not ext:
        raise RuntimeError('URL does not contain an extension')

    with open(filename, 'wb') as file_handle:
        while True:
            chunk = req.read(1024)
            if not chunk:
                break
            file_handle.write(chunk)
    msg = 'Finished downloading {filename}'.format(filename=filename)
    return msg

def main(urls):
    """
    Create a thread pool and download specified urls
    """
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(downloader, url) for url in urls]
        for future in as_completed(futures):
            print(future.result())

if __name__ == '__main__':
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    main(urls)

首先,我们进口我们需要的东西。然后我们创建我们的下载器函数。我继续并稍微更新了它,这样它就可以检查 URL 的末尾是否有扩展名。如果没有,那么我们将引发一个运行时错误。接下来,我们创建一个 main 函数,在这里线程池被实例化。实际上,您可以将 Python 的 with 语句与 ThreadPoolExecutor 和 ProcessPoolExecutor 一起使用,这非常方便。

无论如何,我们设置我们的池,使它有五个工人。然后我们使用一个列表理解来创建一组期货(或工作),最后我们调用 as_complete 函数。这个方便的函数是一个迭代器,当它们完成时产生未来。当它们完成时,我们打印出结果,这是一个从我们的 downloader 函数返回的字符串。

如果我们使用的函数计算量非常大,那么我们可以很容易地将 ThreadPoolExecutor 替换为 ProcessPoolExecutor,并且只需要修改一行代码。

我们可以通过使用 concurrent.future 的 map 方法来稍微清理一下这段代码。让我们稍微重写一下我们的池代码来利用这一点:


import os
import urllib.request

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed

def downloader(url):
    """
    Downloads the specified URL and saves it to disk
    """
    req = urllib.request.urlopen(url)
    filename = os.path.basename(url)
    ext = os.path.splitext(url)[1]
    if not ext:
        raise RuntimeError('URL does not contain an extension')

    with open(filename, 'wb') as file_handle:
        while True:
            chunk = req.read(1024)
            if not chunk:
                break
            file_handle.write(chunk)
    msg = 'Finished downloading {filename}'.format(filename=filename)
    return msg

def main(urls):
    """
    Create a thread pool and download specified urls
    """
    with ThreadPoolExecutor(max_workers=5) as executor:
        return executor.map(downloader, urls, timeout=60)

if __name__ == '__main__':
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    results = main(urls)
    for result in results:
        print(result)

这里的主要区别在于 main 函数,它减少了两行代码。 map 方法就像 Python 的 map 一样,它接受一个函数和一个 iterable,然后为 iterable 中的每一项调用函数。您还可以为每个线程添加一个超时,这样如果其中一个线程挂起,它就会被停止。最后,从 Python 3.5 开始,他们增加了一个 chunksize 参数,当你有一个非常大的 iterable 时,这个参数可以在使用线程池时帮助提高性能。但是,如果您碰巧正在使用进程池,chunksize 将不会有任何影响。


僵局

concurrent.futures 模块的一个缺陷是,当与一个 Future 关联的调用者也在等待另一个 Future 的结果时,您可能会意外地创建死锁。这听起来有点令人困惑,所以让我们看一个例子:


from concurrent.futures import ThreadPoolExecutor

def wait_forever():
    """
    This function will wait forever if there's only one
    thread assigned to the pool
    """
    my_future = executor.submit(zip, [1, 2, 3], [4, 5, 6])
    result = my_future.result()
    print(result)

if __name__ == '__main__':
    executor = ThreadPoolExecutor(max_workers=1)
    executor.submit(wait_forever)

这里我们导入 ThreadPoolExecutor 类并创建它的一个实例。请注意,我们将其最大工作线程数设置为一个线程。然后我们提交我们的函数, wait_forever 。在我们的函数内部,我们向线程池提交另一个作业,该作业应该将两个列表压缩在一起,获取该操作的结果并将其打印出来。然而,我们刚刚制造了一个僵局!原因是我们有一个未来等待另一个未来结束。基本上,我们希望一个挂起的操作等待另一个不能很好工作的挂起的操作。

让我们稍微重写一下代码,让它工作起来:


from concurrent.futures import ThreadPoolExecutor

def wait_forever():
    """
    This function will wait forever if there's only one
    thread assigned to the pool
    """
    my_future = executor.submit(zip, [1, 2, 3], [4, 5, 6])

    return my_future

if __name__ == '__main__':
    executor = ThreadPoolExecutor(max_workers=3)
    fut = executor.submit(wait_forever)

    result = fut.result()
    print(list(result.result()))

在这种情况下,我们只是从函数返回内部未来,然后要求其结果。在我们返回的未来上调用 result 的结果实际上是另一个未来。如果我们在这个嵌套的 future 上调用 result 方法,我们会得到一个 zip 对象,所以为了找出实际的结果是什么,我们用 Python 的 list 函数包装 zip 并打印出来。


包扎

现在您有了另一个好用的并发工具。您可以根据需要轻松创建线程或进程池。如果您需要运行受网络或 I/O 限制的进程,您可以使用线程池类。如果您有一个计算量很大的任务,那么您会希望使用进程池类来代替。只是要小心不正确地调用期货,否则可能会出现死锁。


相关阅读

使用 singledispatch 的 Python 3 函数重载

原文:https://www.blog.pythonlibrary.org/2016/02/23/python-3-function-overloading-with-singledispatch/

Python 最近在 Python 3.4 中增加了对函数重载的部分支持。他们通过向名为 singledispatchfunctools 模块添加一个简洁的小装饰器来实现这一点。这个装饰器将把你的常规函数转换成一个单一的调度通用函数。但是请注意,singledispatch 只基于第一个参数的类型发生。让我们来看一个例子,看看这是如何工作的!


from functools import singledispatch

@singledispatch
def add(a, b):
    raise NotImplementedError('Unsupported type')

@add.register(int)
def _(a, b):
    print("First argument is of type ", type(a))
    print(a + b)

@add.register(str)
def _(a, b):
    print("First argument is of type ", type(a))
    print(a + b)

@add.register(list)
def _(a, b):
    print("First argument is of type ", type(a))
    print(a + b)

if __name__ == '__main__':
    add(1, 2)
    add('Python', 'Programming')
    add([1, 2, 3], [5, 6, 7])

这里我们从 functools 导入 singledispatch,并将其应用于一个简单的函数,我们称之为 add 。这个函数是我们的总括函数,只有当其他修饰函数都不处理传递的类型时才会被调用。你会注意到我们目前处理整数、字符串和列表作为第一个参数。如果我们用其他东西调用我们的 add 函数,比如字典,那么它将引发 NotImplementedError。

尝试自己运行代码。您应该会看到如下所示的输出:


First argument is of type  3
First argument is of type  <class>PythonProgramming
First argument is of type  <class>[1, 2, 3, 5, 6, 7]
Traceback (most recent call last):
  File "overloads.py", line 30, in <module>add({}, 1)
  File "/usr/local/lib/python3.5/functools.py", line 743, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File "overloads.py", line 5, in add
    raise NotImplementedError('Unsupported type')
NotImplementedError: Unsupported type 

如您所见,代码完全按照宣传的那样工作。它根据第一个参数的类型调用适当的函数。如果该类型未被处理,那么我们将引发 NotImplementedError。如果您想知道我们当前正在处理什么类型,您可以将下面这段代码添加到文件的末尾,最好是在引发错误的那一行之前:


print(add.registry.keys())

这将打印出类似这样的内容:


dict_keys([, <class>, <class>, <class>]) 

这告诉我们,我们可以处理字符串、整数、列表和对象(默认)。singledispatch 装饰器还支持装饰器堆栈。这允许我们创建一个可以处理多种类型的重载函数。让我们来看看:


from functools import singledispatch
from decimal import Decimal

@singledispatch
def add(a, b):
    raise NotImplementedError('Unsupported type')

@add.register(float)
@add.register(Decimal)
def _(a, b):
    print("First argument is of type ", type(a))
    print(a + b)

if __name__ == '__main__':
    add(1.23, 5.5)
    add(Decimal(100.5), Decimal(10.789))

这基本上告诉 Python,add 函数重载之一可以处理 float 和 decimal。十进制类型作为第一个参数。如果您运行这段代码,您应该会看到如下内容:


First argument is of type  6.73
First argument is of type  <class>111.2889999999999997015720510
dict_keys([<class>, <class>, <class>, <class> 

您可能已经注意到了这一点,但是由于所有这些函数的编写方式,您可以将 decorators 堆叠起来,将前面的示例和这个示例中的所有情况处理到一个重载函数中。然而,在正常的重载情况下,每个重载会调用不同的代码,而不是做相同的事情。


包扎

函数重载在其他语言中已经存在很长时间了。很高兴看到 Python 也添加了它。当然,其他一些语言允许多分派而不是单分派,这意味着它们查看不止一种参数类型。希望 Python 能在未来的版本中增加这个功能。还要注意,可以用 singledispatch 注册 lambdas 和预先存在的函数。有关完整的详细信息,请参见文档。


相关阅读

Python 3 -从 github 导入

原文:https://www.blog.pythonlibrary.org/2016/02/02/python-3-import-from-github/

前几天,我偶然发现了这个有趣的实验包,叫做 import_from_github_com 。这个包使用了 PEP 302 中提供的新的导入钩子,基本上允许你从 github 导入一个包。这个包实际上看起来要做的是安装这个包并把它添加到本地。反正你需要 Python 3.2 或者更高版本,git 和 pip 才能使用这个包。

安装完成后,您可以执行以下操作:


>>> from github_com.zzzeek import sqlalchemy
Collecting git+https://github.com/zzzeek/sqlalchemy
  Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build
Installing collected packages: SQLAlchemy
  Running setup.py install for SQLAlchemy ... done
Successfully installed SQLAlchemy-1.1.0b1.dev0
>>> locals()
{'__builtins__': , '__spec__': None,
 '__package__': None, '__doc__': None, '__name__': '__main__', 
'sqlalchemy': <module from="">,
 '__loader__': <class>}

软件包的 github 页面上没有提到的一个重要注意事项是,您需要以管理员身份运行 Python,否则它将无法安装其软件包。至少在 Xubuntu 上我是这样。总之,我认为这是一个整洁的小软件包,演示了一些可以添加到 Python 3 中的整洁的小导入挂钩。

Python 3 测试:单元测试简介

原文:https://www.blog.pythonlibrary.org/2016/07/07/python-3-testing-an-intro-to-unittest/

unittest 模块实际上是一个测试框架,最初是受 JUnit 的启发。它目前支持测试自动化、设置和关闭代码的共享、将测试聚集到集合中以及测试与报告框架的独立性。

单元测试框架支持以下概念:

  • 测试夹具——夹具是用来设置测试的,这样它就可以运行,并且在测试结束时也可以拆除。例如,您可能需要在测试运行之前创建一个临时数据库,并在测试完成后销毁它。
  • 测试用例——测试用例是你实际的测试。它通常会检查(或断言)特定的响应来自一组特定的输入。unittest 框架提供了一个名为TestCase的基类,您可以使用它来创建新的测试用例。
  • 测试套件——测试套件是测试用例、测试套件或两者的集合。
  • 测试运行者——运行者是控制或协调测试或套件运行的人。它还将向用户提供结果(例如,他们是通过还是失败)。跑步者可以使用图形用户界面,也可以是简单的文本界面。

简单的例子

我总是发现一两个代码示例是学习新事物如何工作的最快方法。所以让我们创建一个小模块,我们称之为 mymath.py 。然后将以下代码放入其中:


def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(numerator, denominator):
    return float(numerator) / denominator

这个模块定义了四个数学函数:加、减、乘、除。它们不做任何错误检查,它们实际上并不完全符合您的预期。例如,如果您要用两个字符串调用 add 函数,它会很高兴地将它们连接在一起并返回它们。但是为了便于说明,这个模块将创建一个测试用例。所以让我们实际上为 add 函数编写一个测试用例吧!我们将这个脚本命名为 test_mymath.py ,并保存在包含 mymath.py 的同一个文件夹中。


import mymath
import unittest

class TestAdd(unittest.TestCase):
    """
    Test the add function from the mymath library
    """

    def test_add_integers(self):
        """
        Test that the addition of two integers returns the correct total
        """
        result = mymath.add(1, 2)
        self.assertEqual(result, 3)

    def test_add_floats(self):
        """
        Test that the addition of two floats returns the correct result
        """
        result = mymath.add(10.5, 2)
        self.assertEqual(result, 12.5)

    def test_add_strings(self):
        """
        Test the addition of two strings returns the two string as one
        concatenated string
        """
        result = mymath.add('abc', 'def')
        self.assertEqual(result, 'abcdef')

if __name__ == '__main__':
    unittest.main()

让我们花一点时间来看看这段代码是如何工作的。首先,我们导入 mymath 模块和 Python 的 unittest 模块。然后我们子类化测试用例并添加三个测试,这转化为三个方法。第一个函数测试两个整数的相加;第二个函数测试两个浮点数的相加;最后一个函数将两个字符串连接在一起。最后我们在最后调用 unittest 的 main 方法。

您会注意到每个方法都以字母“test”开头。这个其实很重要!它告诉测试运行者哪些方法是它应该运行的测试。每个测试应该至少有一个断言来验证结果是否符合我们的预期。unittest 模块支持许多不同类型的断言。您可以测试异常、布尔条件和许多其他条件。

让我们尝试运行测试。打开终端,导航到包含 mymath 模块和测试模块的文件夹:


python test_mymath.py 

这将执行我们的测试,我们应该得到以下输出:


...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

你会注意到有三个时期。每个时期代表一个已经通过的测试。然后它告诉我们它运行了 3 次测试,花费的时间和结果:ok。这告诉我们所有的测试都成功通过了。

您可以通过传入 -v 标志使输出更加详细:


python test_mymath.py -v

这将导致以下输出被打印到 stdout:


test_add_floats (__main__.TestAdd) ... ok
test_add_integers (__main__.TestAdd) ... ok
test_add_strings (__main__.TestAdd) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

正如您所看到的,这向我们准确地显示了运行了哪些测试以及每个测试的结果。这也引导我们进入下一节,我们将学习一些可以在命令行上与 unittest 一起使用的命令。


命令行界面

unittest 模块附带了一些其他命令,您可能会发现这些命令很有用。要找出它们是什么,您可以直接运行 unittest 模块并传递给它 -h ,如下所示:


python -m unittest -h

这将导致以下输出被打印到 stdout。请注意,为了简洁起见,我已经删除了包含测试发现命令行选项的输出的一部分:


usage: python -m unittest [-h] [-v] [-q] [--locals] [-f] [-c] [-b]
                           [tests [tests ...]]

positional arguments:
  tests           a list of any number of test modules, classes and test
                  methods.

optional arguments:
  -h, --help      show this help message and exit
  -v, --verbose   Verbose output
  -q, --quiet     Quiet output
  --locals        Show local variables in tracebacks
  -f, --failfast  Stop on first fail or error
  -c, --catch     Catch ctrl-C and display results so far
  -b, --buffer    Buffer stdout and stderr during tests

Examples:
  python -m unittest test_module               - run tests from test_module
  python -m unittest module.TestClass          - run tests from module.TestClass
  python -m unittest module.Class.test_method  - run specified test method

现在我们有了一些想法,如果测试代码的底部没有对 unittest.main() 的调用,我们将如何调用测试代码。实际上,继续用不同的名称重新保存代码,比如删除最后两行的 test_mymath2.py 。然后运行以下命令:


python -m unittest test_mymath2.py

这应该会产生与之前相同的输出:


...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

在命令行上使用 unittest 模块最酷的一点是,我们可以在测试中使用它来调用特定的函数。这里有一个例子:


python -m unittest test_mymath2.TestAdd.test_add_integers

该命令将只运行运行测试,因此该命令的输出应该如下所示:


.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

或者,如果您在这个测试模块中有多个测试用例,那么您可以一次只调用一个测试用例,就像这样:


python -m unittest test_mymath2.TestAdd

这只是调用我们的 TestAdd 子类,并运行其中的所有测试方法。因此,结果应该与我们在第一个示例中运行的结果相同:


...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

这个练习的要点是,如果你在这个测试模块中有额外的测试用例,那么这个方法给你一个方法来运行单独的测试用例,而不是所有的测试用例。


创建更复杂的测试

大多数代码比我们的例子要复杂得多。因此,让我们创建一段依赖于现有数据库的代码。我们将创建一个简单的脚本,它可以创建带有一些初始数据的数据库(如果它不存在的话),以及一些允许我们查询、删除和更新行的函数。我们将这个脚本命名为 simple_db.py 。这是一个相当长的例子,所以请原谅我:


import sqlite3

def create_database():
    conn = sqlite3.connect("mydatabase.db")
    cursor = conn.cursor()

    # create a table
    cursor.execute("""CREATE TABLE albums
                          (title text, artist text, release_date text,
                           publisher text, media_type text)
                       """)
    # insert some data
    cursor.execute("INSERT INTO albums VALUES "
                   "('Glow', 'Andy Hunter', '7/24/2012',"
                   "'Xplore Records', 'MP3')")

    # save data to database
    conn.commit()

    # insert multiple records using the more secure "?" method
    albums = [('Exodus', 'Andy Hunter', '7/9/2002',
               'Sparrow Records', 'CD'),
              ('Until We Have Faces', 'Red', '2/1/2011',
               'Essential Records', 'CD'),
              ('The End is Where We Begin', 'Thousand Foot Krutch',
               '4/17/2012', 'TFKmusic', 'CD'),
              ('The Good Life', 'Trip Lee', '4/10/2012',
               'Reach Records', 'CD')]
    cursor.executemany("INSERT INTO albums VALUES (?,?,?,?,?)",
                       albums)
    conn.commit()

def delete_artist(artist):
    """
    Delete an artist from the database
    """
    conn = sqlite3.connect("mydatabase.db")
    cursor = conn.cursor()

    sql = """
    DELETE FROM albums
    WHERE artist = ?
    """
    cursor.execute(sql, [(artist)])
    conn.commit()
    cursor.close()
    conn.close()

def update_artist(artist, new_name):
    """
    Update the artist name
    """
    conn = sqlite3.connect("mydatabase.db")
    cursor = conn.cursor()

    sql = """
    UPDATE albums
    SET artist = ?
    WHERE artist = ?
    """
    cursor.execute(sql, (new_name, artist))
    conn.commit()
    cursor.close()
    conn.close()

def select_all_albums(artist):
    """
    Query the database for all the albums by a particular artist
    """
    conn = sqlite3.connect("mydatabase.db")
    cursor = conn.cursor()

    sql = "SELECT * FROM albums WHERE artist=?"
    cursor.execute(sql, [(artist)])
    result = cursor.fetchall()
    cursor.close()
    conn.close()
    return result

if __name__ == '__main__':
    import os
    if not os.path.exists("mydatabase.db"):
        create_database()

    delete_artist('Andy Hunter')
    update_artist('Red', 'Redder')
    print(select_all_albums('Thousand Foot Krutch'))

您可以稍微试验一下这段代码,看看它是如何工作的。一旦你适应了,我们就可以继续测试了。

现在有些人可能会说,为每个测试创建一个数据库并销毁它是相当大的开销。他们可能说得有道理。然而,为了测试某些功能,您有时需要做这类事情。此外,您通常不需要仅仅为了健全性检查而创建整个生产数据库。

无论如何,这再次是为了说明的目的。单元测试模块允许我们为这些类型的事情覆盖设置拆卸方法。因此,我们将创建一个创建数据库的 setUp 方法和一个在测试结束时删除数据库的 tearDown 方法。请注意,每次测试都会进行安装和拆卸。这可以防止测试以导致后续测试失败的方式更改数据库。

让我们来看看测试用例类的第一部分:


import os
import simple_db
import sqlite3
import unittest

class TestMusicDatabase(unittest.TestCase):
    """
    Test the music database
    """

    def setUp(self):
        """
        Setup a temporary database
        """
        conn = sqlite3.connect("mydatabase.db")
        cursor = conn.cursor()

        # create a table
        cursor.execute("""CREATE TABLE albums
                          (title text, artist text, release_date text,
                           publisher text, media_type text)
                       """)
        # insert some data
        cursor.execute("INSERT INTO albums VALUES "
                       "('Glow', 'Andy Hunter', '7/24/2012',"
                       "'Xplore Records', 'MP3')")

        # save data to database
        conn.commit()

        # insert multiple records using the more secure "?" method
        albums = [('Exodus', 'Andy Hunter', '7/9/2002',
                   'Sparrow Records', 'CD'),
                  ('Until We Have Faces', 'Red', '2/1/2011',
                   'Essential Records', 'CD'),
                  ('The End is Where We Begin', 'Thousand Foot Krutch',
                   '4/17/2012', 'TFKmusic', 'CD'),
                  ('The Good Life', 'Trip Lee', '4/10/2012',
                   'Reach Records', 'CD')]
        cursor.executemany("INSERT INTO albums VALUES (?,?,?,?,?)",
                           albums)
        conn.commit()

    def tearDown(self):
        """
        Delete the database
        """
        os.remove("mydatabase.db")

setUp 方法将创建我们的数据库,然后用一些数据填充它。拆卸方法将删除我们的数据库文件。如果您使用类似 MySQL 或 Microsoft SQL Server 的数据库,那么您可能会删除该表,但使用 sqlite,我们可以删除整个表。

现在让我们在代码中添加一些实际的测试。您可以将这些添加到上面的测试类的末尾:


def test_updating_artist(self):
    """
    Tests that we can successfully update an artist's name
    """
    simple_db.update_artist('Red', 'Redder')
    actual = simple_db.select_all_albums('Redder')
    expected = [('Until We Have Faces', 'Redder',
                 '2/1/2011', 'Essential Records', 'CD')]
    self.assertListEqual(expected, actual)

def test_artist_does_not_exist(self):
    """
    Test that an artist does not exist
    """
    result = simple_db.select_all_albums('Redder')
    self.assertFalse(result)

第一个测试将把其中一个艺术家的名字更新为字符串 Redder 。然后,我们进行查询,以确保新的艺术家姓名存在。下一个测试还将检查被称为“更红”的艺术家是否存在。这一次不应该,因为数据库在两次测试之间被删除并重新创建。让我们试着运行它,看看会发生什么:


python -m unittest test_db.py 

上面的命令应该会产生下面的输出,尽管您的运行时可能会有所不同:


..
----------------------------------------------------------------------
Ran 2 tests in 0.032s

OK

很酷,是吧?现在我们可以继续学习测试套件了!


创建测试套件

正如在开始提到的,一个测试套件仅仅是测试用例、测试套件或者两者的集合。大多数时候,当你调用 unittest.main() 时,它会做正确的事情,在执行之前为你收集所有模块的测试用例。但有时你会想成为掌控一切的人。在这种情况下,您可以使用测试套件类。这里有一个你可能如何使用它的例子:


import unittest

from test_mymath import TestAdd

def my_suite():
    suite = unittest.TestSuite()
    result = unittest.TestResult()
    suite.addTest(unittest.makeSuite(TestAdd))
    runner = unittest.TextTestRunner()
    print(runner.run(suite))

my_suite()

创建自己的套件是一个有点复杂的过程。首先你需要创建一个测试套件的实例和一个测试结果的实例。TestResult 类只保存测试的结果。接下来,我们在 suite 对象上调用 addTest 。这就是有点奇怪的地方。如果您只是传入 TestAdd ,那么它必须是 TestAdd 的一个实例,并且 TestAdd 还必须实现一个 runTest 方法。因为我们没有这样做,所以我们使用 unittest 的 makeSuite 函数将我们的 TestCase 类转换成一个套件。

最后一步是实际运行套件,这意味着如果我们想要良好的输出,我们需要一个 runner。因此,我们创建了一个 TextTestRunner 的实例,并让它运行我们的套件。如果您这样做,并打印出它返回的内容,您应该会在屏幕上看到类似这样的内容:


...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

另一种方法是调用 suite.run(result) 并打印出结果。然而,您得到的只是一个 TestResult 对象,看起来与上面最后一行输出非常相似。如果你想要更平常的输出,那么你将会想要使用一个转轮。


如何跳过测试

从 Python 3.1 开始,unittest 模块支持跳过测试。有一些跳过测试的用例:

  • 如果库的版本不支持您想要测试的内容,您可能想要跳过测试
  • 该测试依赖于运行它的操作系统
  • 或者你有一些跳过测试的其他标准

让我们改变我们的测试用例,这样就有几个测试将被跳过:


import mymath
import sys
import unittest

class TestAdd(unittest.TestCase):
    """
    Test the add function from the mymath module
    """

    def test_add_integers(self):
        """
        Test that the addition of two integers returns the correct total
        """
        result = mymath.add(1, 2)
        self.assertEqual(result, 3)

    def test_add_floats(self):
        """
        Test that the addition of two floats returns the correct result
        """
        result = mymath.add(10.5, 2)
        self.assertEqual(result, 12.5)

    @unittest.skip('Skip this test')
    def test_add_strings(self):
        """
        Test the addition of two strings returns the two string as one
        concatenated string
        """
        result = mymath.add('abc', 'def')
        self.assertEqual(result, 'abcdef')

    @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
    def test_adding_on_windows(self):
        result = mymath.add(1, 2)
        self.assertEqual(result, 3)

这里我们演示两种不同的跳过测试的方法: skipskipUnless 。你会注意到我们正在修饰需要跳过的功能。跳过装饰器可以用于以任何理由跳过任何测试。除非条件返回真,否则 skipUnless decorator 将跳过一个测试。因此,如果您在 Mac 或 Linux 上运行这个测试,它将被跳过。还有一个 skipIf decorator,如果条件为真,它将跳过一个测试。

您可以使用 verbose 标志运行这个脚本,看看它为什么跳过测试:


python -m unittest test_mymath.py -v

该命令将产生以下输出:


test_add_floats (test_mymath4.TestAdd) ... ok
test_add_integers (test_mymath4.TestAdd) ... ok
test_add_strings (test_mymath4.TestAdd) ... skipped 'Skip this test'
test_adding_on_windows (test_mymath4.TestAdd) ... skipped 'requires Windows'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=2)

他的输出告诉我们,我们试图运行四个测试,但是跳过了其中的两个。

还有一个expected failuredecorator,您可以将它添加到您知道会失败的测试中。我会把那个留给你自己去尝试。


与 doctest 集成

unittest 模块也可以与 Python 的 doctest 模块一起使用。如果您已经创建了许多包含文档测试的模块,那么您通常会希望能够系统地运行它们。这就是 unittest 的用武之地。从 Python 3.2 开始,unittest 模块支持测试发现。测试发现基本上允许 unittest 查看目录的内容,并根据文件名确定哪些目录可能包含测试。然后它通过导入它们来加载测试。

让我们创建一个新的空文件夹,并在其中创建一个名为 my_docs.py 的文件。它需要有以下代码:


def add(a, b):
    """
    Return the addition of the arguments: a + b

    >>> add(1, 2)
    3
    >>> add(-1, 10)
    9
    >>> add('a', 'b')
    'ab'
    >>> add(1, '2')
    Traceback (most recent call last):
      File "test.py", line 17, in add(1, '2')
      File "test.py", line 14, in add
        return a + b
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    """
    return a + b

def subtract(a, b):
    """
    Returns the result of subtracting b from a

    >>> subtract(2, 1)
    1
    >>> subtract(10, 10)
    0
    >>> subtract(7, 10)
    -3
    """
    return a - b 

现在我们需要在与这个模块相同的位置创建另一个模块,它将把我们的文档测试转换成单元测试。让我们称这个文件为 test_doctests.py. 将下面的代码放入其中:


import doctest
import my_docs
import unittest

def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite(my_docs))
    return tests

根据 doctest 模块的文档,测试发现在这里实际上需要函数名。我们在这里所做的是向 tests 对象添加一个套件,方式与我们之前所做的非常相似。在本例中,我们使用的是 doctest 的 DocTestSuite 类。如果您的测试需要,您可以给这个类一个 setUp 和 tearDown 方法作为参数。要运行此代码,您需要在新文件夹中执行以下命令:


python -m unittest discover

在我的机器上,我收到了以下输出:


..
----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK

您会注意到,当您使用 unittest 运行 doctest 时,每个 docstring 都被视为一个单独的测试。如果您直接用 doctest 运行 docstrings,那么您会注意到 doctest 会说还有更多测试。除此之外,它的工作和预期的差不多。


包扎

我们在这篇文章中讨论了很多。您学习了使用 unittest 模块的基础知识。然后我们继续学习如何从命令行使用 unittest。我们还发现了如何建立和拆除测试。您还发现了如何创建测试套件。最后,我们学习了如何将一系列文档测试转化为单元测试。请务必花些时间阅读这两个优秀库的文档,因为还有很多额外的功能没有在这里介绍。


相关阅读

Python 3 -解包一般化

原文:https://www.blog.pythonlibrary.org/2017/02/21/python-3-unpacking-generalizations/

Python 3.5 在 PEP 448 中增加了更多对解包泛化的支持。根据 PEP,它增加了* iterable 解包操作符和** dictionary 解包操作符的扩展用法,允许在更多位置、任意次数和在其他情况下解包。这意味着我们现在可以通过任意次数的解包来调用函数。我们来看一个 dict()的例子:


>>> my_dict = {'1':'one', '2':'two'}
>>> dict(**my_dict, w=6)
{'1': 'one', '2': 'two', 'w': 6}
>>> dict(**my_dict, w='three', **{'4':'four'})
{'1': 'one', '2': 'two', 'w': 'three', '4': 'four'}

有趣的是,如果键不是字符串,那么解包就不起作用:


>>> my_dict = {1:'one', 2:'two'}
>>> dict(**my_dict)
Traceback (most recent call last):
  File "", line 1, in <module>dict(**my_dict)
TypeError: keyword arguments must be strings

更新:我的一位读者很快指出这不起作用的原因是因为我试图解包成一个函数调用(即 dict())。如果我只使用 dict 语法进行解包,整数键就能很好地工作。我要说的是:


>>> {**{1: 'one', 2:'two'}, 3:'three'}
{1: 'one', 2: 'two', 3: 'three'}

dict 解包的另一个有趣的地方是后面的值总是会覆盖前面的值。PEP 中有一个很好的例子可以证明这一点:


>>> {**{'x': 2}, 'x': 1}
{'x': 1}

我觉得这很棒。您可以在 collections 模块中用 ChainMap 做同样的事情,但是这要简单得多。

然而,这种新的解包也适用于元组和列表。让我们尝试将一些不同类型的项目合并到一个列表中:


>>> my_tuple = (11, 12, 45)
>>> my_list = ['something', 'or', 'other']
>>> my_range = range(5)
>>> combo = [*my_tuple, *my_list, *my_range]
>>> combo
[11, 12, 45, 'something', 'or', 'other', 0, 1, 2, 3, 4]

在进行这种解包更改之前,您需要做这样的事情:


>>> combo = list(my_tuple) + list(my_list) + list(my_range)
[11, 12, 45, 'something', 'or', 'other', 0, 1, 2, 3, 4]

我认为新的语法实际上对于这种情况非常方便。实际上,我在 Python 2 中遇到过一两次这种情况,这种新的增强非常有用。


包扎

在 PEP 448 中有很多其他的例子,读起来很有趣,可以在 Python 的解释器中尝试。我强烈推荐你去看看,并尝试一下这个特性。每当我们最终迁移到 Python 3 时,我希望在我的新代码中开始使用这些特性。

Python 3:变量注释

原文:https://www.blog.pythonlibrary.org/2017/10/31/python-3-variable-annotations/

Python 在 3.6 版本中增加了称为变量注释的语法。变量注释基本上是类型提示的增强,是在 Python 3.5 中引入的。变量注释背后的完整解释在 PEP 526 中解释。在本文中,我们将快速回顾一下类型提示,然后介绍新的变量注释语法。

什么是类型暗示?

Python 中的类型提示基本上是声明函数和方法中的参数具有某种类型。Python 并不强制类型“提示”,但是您可以使用像 mypy 这样的工具来强制类型提示,就像 C++在运行时强制类型声明一样。让我们看一个没有添加类型提示的普通函数:


def add(a, b):
    return a + b

if __name__ == '__main__':
    add(2, 4)

这里我们创建了一个接受两个参数的 add() 函数。它将两个参数相加并返回结果。我们不知道的是我们需要传递给函数什么。我们可以向它传递整数、浮点数、链表或字符串,它很可能会工作。但是它会像我们预期的那样工作吗?让我们在代码中添加一些类型提示:


def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    print(add(2, 4))

这里我们改变了函数的定义,以利用类型提示。您会注意到,现在参数被标记了它们应该是什么类型:

  • 答:int
  • 乙:int

我们还暗示了返回值,也就是“-> int”是什么。这意味着我们期望一个整数作为返回值。如果您尝试用几个字符串或一个浮点数和一个整数调用 add()函数,您不会看到错误。正如我所说的,Python 只允许您提示参数的类型,但并不强制执行。

让我们将代码更新如下:


def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    print(add(5.0, 4))

如果你运行这个,你会看到它执行得很好。现在让我们使用 pip 安装 mypy:


pip install mypy

现在我们有了 mypy,我们可以用它来确定我们是否正确地使用了我们的函数。打开一个终端,导航到保存上述脚本的文件夹。然后执行以下命令:


mypy hints.py

当我运行这个命令时,我收到了以下输出:


hints.py:5: error: Argument 1 to "add" has incompatible type "float"; expected "int"

如你所见,mypy 在我们的代码中发现了一个问题。我们为第一个参数传入了一个 float,而不是 int。您可以在持续集成服务器上使用 mypy,该服务器可以在提交提交到您的分支之前检查您的代码是否存在这些问题,或者在提交代码之前在本地运行它。


变量注释

假设您不仅想注释函数参数,还想注释正则变量。在 Python 3.5 中,您不能使用与函数参数相同的语法来实现这一点,因为这会引发一个 SyntaxError 。相反,您可能需要使用注释,但是现在 3.6 已经发布了,我们可以使用新的语法了!让我们看一个例子:


from typing import List

def odd_numbers(numbers: List) -> List:
    odd: List[int] = []
    for number in numbers:
        if number % 2:
            odd.append(number)

    return odd

if __name__ == '__main__':
    numbers = list(range(10))
    print(odd_numbers(numbers))

在这里,他指定变量 odd 应该是一个整数列表。如果您对这个脚本运行 mypy,您将不会收到任何输出,因为我们做的一切都是正确的。让我们试着改变代码,添加一些整数以外的东西!


from typing import List

def odd_numbers(numbers: List) -> List:
    odd: List[int] = []
    for number in numbers:
        if number % 2:
            odd.append(number)

    odd.append('foo')

    return odd

if __name__ == '__main__':
    numbers = list(range(10))
    print(odd_numbers(numbers))

这里我们添加了一个新的行,将一个字符串追加到整数列表中。现在,如果我们对这个版本的代码运行 mypy,我们应该会看到以下内容:


hints2.py:9: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"

再次重申,在 Python 3.5 中,您可以进行变量注释,但是您必须将注释放在注释中:


# Python 3.6
odd: List[int] = []

# Python 3.5
odd = [] # type: List[int]

请注意,如果您将代码更改为使用变量注释语法的 Python 3.5 版本,mypy 仍然会正确标记错误。不过,您必须在井号后面指定“类型:”。如果你去掉它,它就不再是变量注释了。基本上,PEP 526 添加的所有内容都是为了使语法在整个语言中更加统一。


包扎

此时,无论您使用的是 Python 3.5 还是 3.6,您都应该有足够的信息开始在自己的代码中进行变量注释。我认为这是一个很好的概念,对于那些与更熟悉静态类型语言的人一起工作的程序员来说尤其有用。


相关阅读

Python:一个简单的逐步 SQLite 教程

原文:https://www.blog.pythonlibrary.org/2012/07/18/python-a-simple-step-by-step-sqlite-tutorial/

SQLite 是一个独立的、无服务器的、免配置的事务 SQL 数据库引擎。Python 在 2.5 版本中获得了 sqlite3 模块,这意味着您可以使用任何当前的 Python 创建 sqlite 数据库,而无需下载任何额外的依赖项。Mozilla 为其流行的 Firefox 浏览器使用 SQLite 数据库来存储书签和其他各种信息。在本文中,您将了解以下内容:

  • 如何创建 SQLite 数据库
  • 如何向表格中插入数据
  • 如何编辑数据
  • 如何删除数据
  • 基本 SQL 查询

这篇文章在功能上类似于本月早些时候出现在这个网站上的最近的 SQLAlchemy 教程。如果你想直观地检查你的数据库,你可以使用火狐的 SQLite 管理器插件,或者如果你喜欢命令行,你可以使用 SQLite 的命令行外壳

如何创建数据库并插入一些数据

在 SQLite 中创建一个数据库确实很容易,但是这个过程需要你懂一点 SQL。下面的代码将创建一个数据库来保存音乐专辑:


import sqlite3

conn = sqlite3.connect("mydatabase.db") # or use :memory: to put it in RAM

cursor = conn.cursor()

# create a table
cursor.execute("""CREATE TABLE albums
                  (title text, artist text, release_date text, 
                   publisher text, media_type text) 
               """)

首先,我们必须导入 sqlite3 库,并创建一个到数据库的连接。你可以给它一个文件路径,文件名或者使用特殊字符串“:memory:”在内存中创建数据库。在我们的例子中,我们在磁盘上一个名为 mydatabase.db 的文件中创建了它。接下来,我们创建一个 cursor 对象,它允许您与数据库交互并添加记录等。这里我们使用 SQL 语法创建一个名为相册的表格,有 5 个文本字段:标题、艺术家、发行日期、出版商和媒体类型。SQLite 只支持五种数据类型 : null、integer、real、text 和 blob。让我们在这段代码的基础上,将一些数据插入到我们的新表中!


# insert some data
cursor.execute("INSERT INTO albums VALUES ('Glow', 'Andy Hunter', '7/24/2012', 'Xplore Records', 'MP3')")

# save data to database
conn.commit()

# insert multiple records using the more secure "?" method
albums = [('Exodus', 'Andy Hunter', '7/9/2002', 'Sparrow Records', 'CD'),
          ('Until We Have Faces', 'Red', '2/1/2011', 'Essential Records', 'CD'),
          ('The End is Where We Begin', 'Thousand Foot Krutch', '4/17/2012', 'TFKmusic', 'CD'),
          ('The Good Life', 'Trip Lee', '4/10/2012', 'Reach Records', 'CD')]
cursor.executemany("INSERT INTO albums VALUES (?,?,?,?,?)", albums)
conn.commit()

这里我们使用 INSERT INTO SQL 命令将一条记录插入数据库。请注意,每一项都必须用单引号括起来。当您需要插入包含单引号的字符串时,这可能会变得复杂。无论如何,要将记录保存到数据库,我们必须提交它。下一段代码展示了如何使用光标的 executemany 方法一次添加多条记录。注意我们用的是问号(?)而不是字符串替换(%s)来插入值。使用字符串替换是不安全的,也不应该使用,因为它会允许 SQL 注入攻击发生。问号方法要好得多,使用 SQLAlchemy 甚至更好,因为它为您完成了所有的转义,这样您就不必为将嵌入的单引号转换成 SQLite 可以接受的内容而烦恼了。

更新和删除记录

能够更新数据库记录是保持数据准确的关键。如果你不能更新,那么你的数据将很快变得过时和无用。有时您也需要从数据中删除行。我们将在本节中讨论这两个主题。首先,我们来做一个更新!


import sqlite3

conn = sqlite3.connect("mydatabase.db")
cursor = conn.cursor()

sql = """
UPDATE albums 
SET artist = 'John Doe' 
WHERE artist = 'Andy Hunter'
"""
cursor.execute(sql)
conn.commit()

这里我们使用 SQL 的 UPDATE 命令来更新相册表。您可以使用 SET 来更改一个字段,因此在本例中,我们将艺术家字段设置为“安迪·亨特”的任何记录中的艺术家字段更改为“John Doe”。那不是很容易吗?请注意,如果您不提交更改,那么您的更改将不会写出到数据库中。删除命令几乎一样简单。我们去看看!


import sqlite3

conn = sqlite3.connect("mydatabase.db")
cursor = conn.cursor()

sql = """
DELETE FROM albums
WHERE artist = 'John Doe'
"""
cursor.execute(sql)
conn.commit()

删除甚至比更新更容易。SQL 只有 2 行!在这种情况下,我们所要做的就是使用 WHERE 子句告诉 SQLite 从(相册)中删除哪个表,删除哪些记录。因此,它会在艺术家字段中查找任何包含“John Doe”的记录,然后将其删除。

基本 SQLite 查询

SQLite 中的查询与其他数据库(如 MySQL 或 Postgres)中的查询非常相似。您只需使用普通的 SQL 语法来运行查询,然后让游标对象执行 SQL。这里有几个例子:


import sqlite3

conn = sqlite3.connect("mydatabase.db")
#conn.row_factory = sqlite3.Row
cursor = conn.cursor()

sql = "SELECT * FROM albums WHERE artist=?"
cursor.execute(sql, [("Red")])
print cursor.fetchall()  # or use fetchone()

print "\nHere's a listing of all the records in the table:\n"
for row in cursor.execute("SELECT rowid, * FROM albums ORDER BY artist"):
    print row

print "\nResults from a LIKE query:\n"
sql = """
SELECT * FROM albums 
WHERE title LIKE 'The%'"""
cursor.execute(sql)
print cursor.fetchall()

我们执行的第一个查询是一个 **SELECT *** ,这意味着我们希望选择所有与我们传入的艺术家姓名匹配的记录,在本例中是“Red”。接下来,我们执行 SQL 并使用 fetchall()返回所有结果。还可以使用 fetchone()来获取第一个结果。你还会注意到有一个注释掉的部分与一个神秘的 row_factory 有关。如果您取消对该行的注释,结果将作为行对象返回,这有点像 Python 字典,让您可以像字典一样访问该行的字段。但是,不能对行对象进行项目分配。

第二个查询与第一个非常相似,但是它返回数据库中的每条记录,并按艺术家姓名以升序对结果进行排序。这也演示了我们如何对结果进行循环。最后一个查询显示了如何使用 SQL 的 LIKE 命令来搜索部分短语。在这种情况下,我们在整个表格中搜索以“the”开头的标题。百分号(%)是通配符。

包扎

现在您知道了如何使用 Python 创建 SQLite 数据库。您还可以创建、更新和删除记录,以及对数据库进行查询。走出去,开始你自己的整洁的数据库和/或在评论中分享你的经验!

进一步阅读

  • sqlite3 库的官方文档
  • Zetcode 的 SQLite 教程
  • 一个简单的 SqlAlchemy 0.7 / 0.8 教程

源代码

Python:关于装饰者的一切

原文:https://www.blog.pythonlibrary.org/2017/07/18/python-all-about-decorators/

第一次遇到装饰者时,他们可能会有点困惑,调试起来也有点棘手。但是它们是向函数和类添加功能的一个好方法。装饰者也被称为“高阶函数”。这意味着它们可以接受一个或多个函数作为参数,并返回一个函数作为结果。换句话说,装饰者将接受他们正在装饰的函数并扩展它的行为,而实际上并不修改函数本身的功能。

自 2.2 版以来,Python 中有两个装饰器,即 classmethod()staticmethod() 。然后将 PEP 318 放在一起,并添加修饰语法,使 Python 2.4 中的修饰函数和方法成为可能。类装饰器在 PEP 3129 中被提议包含在 Python 2.6 中。它们似乎在 Python 2.7 中工作,但是 PEP 指出它们直到 Python 3 才被接受,所以我不确定那里发生了什么。

让我们从讨论一般的函数开始,以便有一个工作的基础。


卑微的功能

Python 和许多其他编程语言中的函数只是可重用代码的集合。一些程序员会采用一种几乎类似 bash 的方法,将他们所有的代码写在一个文件中,根本没有函数。代码只是从上到下运行。这可能会导致大量的复制粘贴式代码。当你看到两段代码在做同样的事情时,它们几乎总是可以放入一个函数中。这将使更新你的代码更容易,因为你只有一个地方来更新它们。

这是一个基本功能:


def doubler(number):
    return number * 2

这个函数接受一个参数,数字。然后将它乘以 2 并返回结果。您可以像这样调用该函数:


>>> doubler(5)
10

如您所见,结果将是 10。


函数也是对象

在 Python 中,很多作者将函数描述为“一级对象”。当他们这样说的时候,他们的意思是一个函数可以被传递并作为其他函数的参数使用,就像你处理一个普通的数据类型一样,比如一个整数或字符串。让我们看几个例子,这样我们就可以习惯这个想法:


>>> def doubler(number):
       return number * 2
>>> print(doubler)
 >>> print(doubler(10))
20
>>> doubler.__name__
'doubler'
>>> doubler.__doc__
None
>>> def doubler(number):
        """Doubles the number passed to it"""
        return number * 2 
>>> doubler.__doc__
'Doubles the number passed to it'
>>> dir(doubler)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name'] 

正如您所看到的,您可以创建一个函数,然后将它传递给 Python 的 print() 函数或任何其他函数。您还会注意到,一旦定义了一个函数,它就会自动拥有我们可以访问的属性。例如,在上面的例子中,我们访问了最初为空的 func_doc 。该属性保存函数的 docstring 的内容。因为我们没有 docstring,所以它不返回任何内容。所以我们重新定义了函数来添加一个 docstring,并再次访问 func_doc 来查看 docstring。我们还可以通过 func_name 属性来获取函数的名称。请随意查看上面最后一个示例中显示的一些其他属性..


我们的第一个装潢师

创建一个装饰器实际上很容易。如前所述,创建装饰器所需要做的就是创建一个接受另一个函数作为参数的函数。让我们来看看:


>>> def doubler(number):
        """Doubles the number passed to it"""
        return number * 2
>>> def info(func):
        def wrapper(*args):
            print('Function name: ' + func.__name__)
            print('Function docstring: ' + str(func.__doc__))
            return func(*args)
        return wrapper 
>>> my_decorator = info(doubler)
>>> print(my_decorator(2))
Function name: doubler
Function docstring: Doubles the number passed to it
4

您会注意到,在装饰函数 info() 中,嵌套了一个名为 wrapper() 的函数。您可以随意调用嵌套函数。包装器函数接受用装饰器包装的函数的参数(也可以是关键字参数)。在本例中,我们打印出包装函数的名称和 docstring,如果它存在的话。然后我们返回函数,用它的参数调用它。最后,我们返回包装函数。

为了使用装饰器,我们创建一个装饰器对象:


>>> my_decorator = info(doubler)

然后为了调用装饰器,我们像调用普通函数一样调用它: my_decorator(2)

然而,这不是调用装饰器的常用方法。Python 有一个专门的语法!


使用装饰语法

Python 允许使用以下语法调用装饰器: @info 。让我们更新前面的例子,使用正确的修饰语法:


def info(func):
    def wrapper(*args):
        print('Function name: ' + func.__name__)
        print('Function docstring: ' + str(func.__doc__))
        return func(*args)
    return wrapper

@info
def doubler(number):
    """Doubles the number passed to it"""
    return number * 2

print(doubler(4))

现在可以调用 doubler() 本身,而不是调用 decorator 对象。函数定义上面的 @info 告诉 Python 自动包装(或修饰)函数,并在调用函数时调用装饰器。


堆叠装饰者

你也可以堆叠或者链接装饰器。这意味着你可以在一个函数上同时使用多个装饰器!让我们来看一个愚蠢的例子:


def bold(func):
    def wrapper():
        return "" + func() + ""
    return wrapper

def italic(func):
    def wrapper():
        return "*" + func() + "*"
    return wrapper

@bold
@italic
def formatted_text():
    return 'Python rocks!'

print(formatted_text())

bold() decorator 将使用标准的粗体 HTML 标签来包装文本,而 italic() decorator 做同样的事情,但是使用斜体 HTML 标签。你应该试着颠倒一下装饰者的顺序,看看会有什么样的效果。在继续之前尝试一下。

现在你已经完成了,你会注意到你的 Python 首先运行离函数最近的装饰器,然后沿着链向上。所以在上面的代码版本中,文本将首先用斜体显示,然后用粗体标记显示。如果你交换它们,就会发生相反的情况。


向装饰者添加参数

向 decorators 添加参数与您想象的有所不同。你不能只做类似于 @my_decorator(3,' Python') 的事情,因为 decorator 希望将函数本身作为它的参数...还是可以?


def info(arg1, arg2):
    print('Decorator arg1 = ' + str(arg1))
    print('Decorator arg2 = ' + str(arg2))

    def the_real_decorator(function):

        def wrapper(*args, **kwargs):
            print('Function {} args: {} kwargs: {}'.format(
                function.__name__, str(args), str(kwargs)))
            return function(*args, **kwargs)

        return wrapper

    return the_real_decorator

@info(3, 'Python')
def doubler(number):
    return number * 2

print(doubler(5))

如你所见,我们有一个嵌套在函数中的函数!这是如何工作的?函数的参数似乎没有被定义。让我们移除装饰器,按照之前创建装饰器对象时的方式进行操作:


def info(arg1, arg2):
    print('Decorator arg1 = ' + str(arg1))
    print('Decorator arg2 = ' + str(arg2))

    def the_real_decorator(function):

        def wrapper(*args, **kwargs):
            print('Function {} args: {} kwargs: {}'.format(
                function.__name__, str(args), str(kwargs)))
            return function(*args, **kwargs)

        return wrapper

    return the_real_decorator

def doubler(number):
    return number * 2

decorator = info(3, 'Python')(doubler)
print(decorator(5))

这段代码相当于前面的代码。当您调用 info(3,' Python') 时,它返回实际的装饰函数,然后我们通过向它传递函数 doubler 来调用它。这给了我们装饰对象本身,然后我们可以用原始函数的参数调用它。不过,我们可以进一步细分:


def info(arg1, arg2):
    print('Decorator arg1 = ' + str(arg1))
    print('Decorator arg2 = ' + str(arg2))

    def the_real_decorator(function):

        def wrapper(*args, **kwargs):
            print('Function {} args: {} kwargs: {}'.format(
                function.__name__, str(args), str(kwargs)))
            return function(*args, **kwargs)

        return wrapper

    return the_real_decorator

def doubler(number):
    return number * 2

decorator_function = info(3, 'Python')
print(decorator_function)

actual_decorator = decorator_function(doubler)
print(actual_decorator)

# Call the decorated function
print(actual_decorator(5))

这里我们展示了我们首先获得装饰函数对象。然后我们得到 decorator 对象,它是 info() 中的第一个嵌套函数,即 the_real_decorator() 。这是您希望传递正在被修饰的函数的地方。现在我们有了修饰函数,所以最后一行是调用修饰函数。

我还发现了一个巧妙的技巧,你可以用 Python 的 functools 模块来做,这将使创建带参数的装饰器变得更短:


from functools import partial

def info(func, arg1, arg2):
    print('Decorator arg1 = ' + str(arg1))
    print('Decorator arg2 = ' + str(arg2))

    def wrapper(*args, **kwargs):
        print('Function {} args: {} kwargs: {}'.format(
            function.__name__, str(args), str(kwargs)))
        return function(*args, **kwargs)

    return wrapper

decorator_with_arguments = partial(info, arg1=3, arg2='Py')

@decorator_with_arguments
def doubler(number):
    return number * 2

print(doubler(5))

在这种情况下,您可以创建一个分部函数,它接受您要传递给装饰器的参数。这允许您将要修饰的函数和修饰器的参数传递给同一个函数。这实际上非常类似于如何使用 functools.partial 向 wxPython 或 Tkinter 中的事件处理程序传递额外的参数。


班级装饰者

当你查找术语“类装饰者”时,你会发现各种各样的文章。有些人谈论使用类来创建装饰者。其他人谈论用一个函数装饰一个类。让我们从创建一个可以用作装饰器的类开始:


class decorator_with_arguments:

    def __init__(self, arg1, arg2):
        print('in __init__')
        self.arg1 = arg1
        self.arg2 = arg2
        print('Decorator args: {}, {}'.format(arg1, arg2))

    def __call__(self, f):
        print('in __call__')
        def wrapped(*args, **kwargs):
            print('in wrapped()')
            return f(*args, **kwargs)
        return wrapped

@decorator_with_arguments(3, 'Python')
def doubler(number):
    return number * 2

print(doubler(5))

这里我们有一个简单的类,它接受两个参数。我们覆盖了 call() 方法,该方法允许我们将正在装饰的函数传递给类。然后在我们的 call()方法中,我们只是打印出我们在代码中的位置并返回函数。这与上一节中的示例的工作方式非常相似。我个人喜欢这种方法,因为我们没有在另一个函数中嵌套两层的函数,尽管有些人可能会认为部分示例也解决了这个问题。

总之,你通常会发现的类装饰器的另一个用例是一种元编程。假设我们有下面的类:


class MyActualClass:
    def __init__(self):
        print('in MyActualClass __init__()')

    def quad(self, value):
        return value * 4

obj = MyActualClass()
print(obj.quad(4))

这很简单,对吧?现在,假设我们想在不修改类已有功能的情况下向类中添加特殊功能。例如,这可能是由于向后兼容的原因或一些其他业务需求,我们不能更改的代码。相反,我们可以修饰它来扩展它的功能。下面是我们如何添加一个新方法,例如:


def decorator(cls):
    class Wrapper(cls):
        def doubler(self, value):
            return value * 2
    return Wrapper

@decorator
class MyActualClass:
    def __init__(self):
        print('in MyActualClass __init__()')

    def quad(self, value):
        return value * 4

obj = MyActualClass()
print(obj.quad(4))
print(obj.doubler(5)

这里我们创建了一个装饰函数,它内部有一个类。这个类将使用传递给它的类作为它的父类。换句话说,我们正在创建一个子类。这允许我们添加新的方法。在这种情况下,我们添加 doubler()方法。现在,当您创建修饰的 MyActualClass() 类的实例时,您将实际上以包装器()子类版本结束。如果你打印 obj 变量,你实际上可以看到这一点。


包扎

Python 语言本身内置了很多修饰功能。有@property、@classproperty 和@staticmethod 可以直接使用。然后是 functoolscontextlib 模块,它们提供了许多方便的装饰器。例如,您可以使用functools . wrapps修复装饰器混淆,或者通过context lib . context manager将任何函数设为上下文管理器。

许多开发人员使用 decorator 通过创建日志记录 decorator、捕捉异常、增加安全性等等来增强他们的代码。它们值得花时间去学习,因为它们可以让你的代码更具可扩展性,甚至更具可读性。装饰者也提倡代码重用。尽快给他们一个尝试的机会!


相关阅读

Python:坏猴子打补丁的一个例子

原文:https://www.blog.pythonlibrary.org/2016/02/26/python-an-example-of-bad-monkey-patching/

今天我的一个同事来找我,让我解释他们发现的一些奇怪的 Python 代码。它处理了 cmake,但是因为它是内部代码,所以我不能在这里展示。相反,我写了一些有同样问题的东西,所以你可以看到我认为不好的,或者至少是非常愚蠢的代码:


class Config:

    def Run(self):
        print('Program Usage: blah blah blah')

        print(self.product)

        self.asset_tag = 'test'
        print(self.asset_tag)

        total = self.sub_total_a + self.sub_total_b

当我第一次看到它时,我被它如何使用未初始化的属性所震惊,我想知道这是如何工作的。然后我意识到他们一定在做某种猴子补丁。Monkey patching 是您编写代码在运行时动态修改类或模块的地方。所以我进一步查看了脚本,找到了一些代码,它们创建了该类的一个实例,并做了类似这样的事情:


def test_config():
    cfg = Config()
    cfg.product = 'laptop'
    cfg.asset_tag = '12345ABCD'
    cfg.sub_total_a = 10.23
    cfg.sub_total_b = 112.63
    cfg.Run()

if __name__ == '__main__':
    test_config()

所以基本上每当你创建一个类的实例时,你都需要修补它,这样在你调用 Run 方法之前属性就存在了。

虽然我认为 Python 可以做这种事情很酷,但对于不熟悉 Python 的人来说,这是非常令人困惑的,尤其是当代码像这样缺乏文档记录的时候。如您所见,没有注释或文档字符串,所以需要花点时间来弄清楚发生了什么。幸运的是,所有代码都在同一个文件中。否则这可能会变得非常棘手。

我个人认为这是糟糕编码的一个很好的例子。如果是我写的,我会创建一个 init 方法,并在那里初始化所有这些属性。那么就不会对这个类的工作方式产生混淆。我也非常相信写好的文档串和有用的注释。

无论如何,我希望你觉得这很有趣。我还认为我的读者应该意识到你将在野外看到的一些奇怪的代码片段。

Python:缓存介绍

原文:https://www.blog.pythonlibrary.org/2016/02/25/python-an-intro-to-caching/

缓存是一种存储有限数据量的方式,以便将来可以更快地检索对所述数据的请求。在本文中,我们将看一个简单的例子,它为我们的缓存使用了一个字典。然后我们将继续使用 Python 标准库的 functools 模块来创建缓存。让我们首先创建一个将构造我们的缓存字典的类,然后我们将根据需要扩展它。代码如下:


########################################################################
class MyCache:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.cache = {}
        self.max_cache_size = 10

这个类的例子没有什么特别的地方。我们只是创建一个简单的类,并设置两个类变量或属性, cachemax_cache_size 。缓存只是一个空字典,而另一个是不言自明的。让我们充实这段代码,让它真正做点什么:


import datetime
import random

########################################################################
class MyCache:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.cache = {}
        self.max_cache_size = 10

    #----------------------------------------------------------------------
    def __contains__(self, key):
        """
        Returns True or False depending on whether or not the key is in the 
        cache
        """
        return key in self.cache

    #----------------------------------------------------------------------
    def update(self, key, value):
        """
        Update the cache dictionary and optionally remove the oldest item
        """
        if key not in self.cache and len(self.cache) >= self.max_cache_size:
            self.remove_oldest()

        self.cache[key] = {'date_accessed': datetime.datetime.now(),
                           'value': value}

    #----------------------------------------------------------------------
    def remove_oldest(self):
        """
        Remove the entry that has the oldest accessed date
        """
        oldest_entry = None
        for key in self.cache:
            if oldest_entry is None:
                oldest_entry = key
            elif self.cache[key]['date_accessed'] < self.cache[oldest_entry][
                'date_accessed']:
                oldest_entry = key
        self.cache.pop(oldest_entry)

    #----------------------------------------------------------------------
    @property
    def size(self):
        """
        Return the size of the cache
        """
        return len(self.cache)

这里我们导入了日期时间随机模块,然后我们看到了我们之前创建的类。这一次,我们添加了一些方法。其中一种方法是一种叫做_ _ 包含 __ 的魔法方法。我有点滥用它,但基本思想是,它将允许我们检查类实例,看看它是否包含我们正在寻找的键。 update 方法将使用新的键/值对更新我们的缓存字典。如果达到或超过最大缓存值,它还会删除最旧的条目。 remove_oldest 方法实际上删除了字典中最老的条目,在这种情况下,这意味着具有最老访问日期的条目。最后,我们有一个名为 size 的属性,它返回我们缓存的大小。

如果您添加以下代码,我们可以测试缓存是否按预期工作:


if __name__ == '__main__':
    # Test the cache
    keys = ['test', 'red', 'fox', 'fence', 'junk',
            'other', 'alpha', 'bravo', 'cal', 'devo',
            'ele']
    s = 'abcdefghijklmnop'
    cache = MyCache()
    for i, key in enumerate(keys):
        if key in cache:
            continue
        else:
            value = ''.join([random.choice(s) for i in range(20)])
            cache.update(key, value)
        print("#%s iterations, #%s cached entries" % (i+1, cache.size))
    print

在这个例子中,我们设置了一组预定义的键,并对它们进行循环。如果键不存在,我们就把它们添加到缓存中。缺少的部分是更新访问日期的方法,但是我将把它留给读者作为练习。如果您运行这段代码,您会注意到当缓存填满时,它开始适当地删除旧的条目。

现在让我们继续,看看使用 Python 内置的 functools 模块创建缓存的另一种方式!


使用 functools.lru_cache

functools 模块提供了一个方便的装饰器,叫做 lru_cache 。注意是在 3.2 中添加的。根据文档,它将“用一个记忆化的可调用函数来包装一个函数,这个函数可以保存最大大小的最近调用”。让我们根据文档中的例子编写一个快速函数,它将抓取各种网页。在这种情况下,我们将从 Python 文档站点获取页面。


import urllib.error
import urllib.request

from functools import lru_cache

@lru_cache(maxsize=24)
def get_webpage(module):
    """
    Gets the specified Python module web page
    """    
    webpage = "https://docs.python.org/3/library/{}.html".format(module)
    try:
        with urllib.request.urlopen(webpage) as request:
            return request.read()
    except urllib.error.HTTPError:
        return None

if __name__ == '__main__':
    modules = ['functools', 'collections', 'os', 'sys']
    for module in modules:
        page = get_webpage(module)
        if page:
            print("{} module page found".format(module))

在上面的代码中,我们用 lru_cache 来装饰我们的get _ 网页函数,并将其最大大小设置为 24 个调用。然后,我们设置一个网页字符串变量,并传入我们希望函数获取的模块。我发现,如果你在 Python 解释器中运行它,效果最好,比如 IDLE。这就要求你对函数运行几次循环。您将很快看到,它第一次运行代码时,输出相对较慢。但是如果您在同一个会话中再次运行它,您会看到它会立即打印出来,这表明 lru_cache 已经正确地缓存了调用。在您自己的解释器实例中尝试一下,亲自看看结果。

还有一个类型的参数,我们可以将它传递给装饰器。它是一个布尔值,告诉装饰器如果 typed 设置为 True,就分别缓存不同类型的参数。


包扎

现在,您已经对使用 Python 编写自己的缓存有所了解。如果您正在进行大量昂贵的 I/O 调用,或者如果您想要缓存诸如登录凭证之类的东西,这是一个有趣的工具,非常有用。玩得开心!

Python:正则表达式简介

原文:https://www.blog.pythonlibrary.org/2016/06/08/python-an-intro-to-regular-expressions/

正则表达式基本上是一种微小的语言,你可以在 Python 和许多其他编程语言中使用它。您经常会听到正则表达式被称为“regex”、“regexp”或只是“RE”。有些语言,比如 Perl 和 Ruby,实际上直接在语言本身中支持正则表达式语法。Python 只通过需要导入的库来支持它们。正则表达式的主要用途是匹配字符串。使用正则表达式创建字符串匹配规则,然后将其应用于字符串,以查看是否有匹配。

正则表达式“language”实际上很小,所以您不能用它来满足所有的字符串匹配需求。除此之外,虽然有一些任务可以使用正则表达式,但它可能会变得非常复杂,以至于难以调试。在这种情况下,你应该使用 Python。应该注意的是,Python 本身就是一种优秀的文本解析语言,可以用于正则表达式中的任何操作。然而,这样做可能需要更多的代码,并且比正则表达式慢,因为正则表达式是用 c 编译和执行的。


匹配的字符

当你想匹配一个字符串中的一个字符时,在大多数情况下你可以只使用那个字符或者那个子字符串。因此,如果我们想要匹配“狗”,那么我们将使用字母 dog 。当然,有一些字符是为正则表达式保留的。这些被称为元字符。以下是 Python 正则表达式实现支持的元字符的完整列表:


. ^ $ * + ? { } [ ] \ | ( )

让我们花点时间看看其中一些是如何工作的。您将遇到的最常见的元字符对之一是方括号:[和]。有用于创建一个“字符类”,这是一组你想匹配的字符。你可以这样单独列出角色:【XYZ】。这将匹配大括号之间列出的任何字符。也可以用破折号来表示要匹配的一系列字符:【a-g】。在这个例子中,我们将匹配字母 a 到 g 中的任何一个。

但是,要真正进行搜索,我们需要添加一个要查找的开始字符和一个结束字符。为了使这更容易,我们可以使用允许重复的星号。*不是匹配*,而是告诉正则表达式前一个字符可能匹配零次或多次。

看一个简单的例子总是有帮助的:


'a[b-f]*f

这个正则表达式模式意味着我们将寻找字母 a ,我们类中的零个或多个字母,[b-f]并且它需要以一个 f 结尾。让我们尝试在 Python 中使用这个表达式:


>>> import re
>>> text = 'abcdfghijk'
>>> parser = re.search('a[b-f]*f', text)
<_sre.sre_match object="" span="(0," match="abcdf">
>>> parser.group()
'abcdf'

基本上这个表达式会查看我们传递给它的整个字符串,在这个例子中是 abcdfghijk 。它会在一开始的比赛中找到一只和一只。然后,因为它有一个末尾带星号的字符类,所以它实际上会读入字符串的其余部分,看看是否匹配。如果不匹配,它会一次回溯一个字符,试图找到匹配。

当我们调用 re 模块的搜索函数时,所有这些神奇的事情就发生了。如果我们找不到匹配,那么返回 None 。否则,我们会得到一个匹配的对象,你可以在上面看到。要真正看到匹配的样子,需要调用方法。

还有另一个类似于*的重复元字符。就是 + ,会匹配一次或多次。这与匹配0 次或更多次的*有细微的区别。 + 要求它所寻找的字符至少出现一次。

最后两个重复元字符的工作方式略有不同。有个问号,?,它将匹配一次或零次。它将前面的字符标记为可选。一个简单的例子是“co-?op”。这将与“coop”和“co-op”匹配。

最后一个重复的元字符是{a,b},其中 a 和 b 是十进制整数。这意味着必须至少有 a 次重复,最多有 b 次重复。您可能想尝试这样的方法:


xb{1,4}z

这是一个相当愚蠢的例子,但它说的是我们将匹配像 xbzxbbzxbbzxbbz这样的东西,而不是 xz ,因为它没有“b”。

我们要学习的下一个元字符是 ^ 。这个字符将允许我们匹配没有在我们的类中列出的字符。换句话说,它将补充我们的课程。只有当我们真正把放入我们的类中时,这才会起作用。如果是在课堂之外,那么我们将尝试与进行真正的比赛。一个很好的例子是这样的:[^a].这将匹配除字母“a”以外的任何字符。

^ 也用作锚,因为它通常用于字符串开头的匹配。弦的末端有对应的锚,是 $

我们已经花了很多时间介绍正则表达式的各种概念。在接下来的几节中,我们将深入研究一些更真实的代码示例!


使用搜索的模式匹配

让我们花点时间来学习一些模式匹配的基础知识。当使用 Python 在字符串中寻找模式时,可以像本章前面的例子一样使用 search 函数。方法如下:


import re

text = "The ants go marching one by one"

strings = ['the', 'one']

for string in strings:
    match = re.search(string, text)
    if match:
        print('Found "{}" in "{}"'.format(string, text))
        text_pos = match.span()
        print(text[match.start():match.end()])
    else:
        print('Did not find "{}"'.format(string))

对于这个例子,我们导入了 re 模块并创建了一个简单的字符串。然后我们创建一个两个字符串的列表,我们将在主字符串中搜索。接下来,我们循环遍历我们计划搜索的字符串,并实际运行对它们的搜索。如果有匹配的,我们就打印出来。否则我们告诉用户没有找到字符串。

在这个例子中,还有几个其他的函数值得解释。你会注意到我们称为跨度。这给了我们匹配的字符串的开始和结束位置。如果您打印出我们分配给 span 的 text_pos ,您将得到这样一个元组:(21,24)。或者,你可以调用一些匹配方法,这就是我们接下来要做的。我们用 startend 来抓取比赛的开始和结束位置,这也应该是我们从 span 得到的两个数字。


转义码

在 Python 中,还可以使用特殊的转义码来搜索一些序列。这里有一个简短的列表,对每个问题都有简要的解释:


\d
    Matches digit
\D
    Matches non-digit
\s
    Matches whitespace
\S
    Matches non-whitespace
\w
    Matches alphanumeric
\W
    Matches non-alphanumeric

您可以在字符类中使用这些转义码,比如: [\d] 。这将允许我们找到任何数字,相当于【0-9】。我强烈推荐你自己尝试一下其他的。


收集

re 模块允许你“编译”你经常搜索的表达式。这将基本上允许你把你的表达式变成一个 SRE 模式对象。然后,您可以在搜索功能中使用该对象。让我们使用前面的代码,并将其修改为使用 compile:


import re

text = "The ants go marching one by one"

strings = ['the', 'one']

for string in strings:
    regex = re.compile(string)
    match = re.search(regex, text)
    if match:
        print('Found "{}" in "{}"'.format(string, text))
        text_pos = match.span()
        print(text[match.start():match.end()])
    else:
        print('Did not find "{}"'.format(string))

您会注意到,这里我们通过对列表中的每个字符串调用 compile 来创建模式对象,并将结果赋给变量 regex 。然后,我们将这个正则表达式传递给我们的搜索函数。代码的其余部分是相同的。使用编译的主要原因是保存它,以便稍后在您的代码中重用。然而,compile 也有一些标志,可以用来启用各种特殊的功能。接下来我们将对此进行研究。

特别注意:当你编译模式时,它们会被自动缓存,所以如果你没有在代码中使用很多正则表达式,那么你可能不需要将编译后的对象保存到变量中。


编译标志

Python 3 中包含了 7 个编译标志,可以改变编译模式的行为。让我们来看看它们各自的功能,然后我们来看看如何使用编译标志。

关于。答/答美国信息交换标准代码

ASCII 标志告诉 Python,当与以下转义码结合使用时,只根据 ASCII 进行匹配,而不使用完全 Unicode 匹配:\w,\b,\B,\d,\D,\s 和\S。U / re。UNICODE 标志也是为了向后兼容;然而,这些标志是多余的,因为 Python 3 在缺省情况下已经在 Unicode 中匹配了。

关于。调试

这将显示关于已编译表达式的调试信息。

关于。我/ re。IGNORECASE

如果您想要执行不区分大小写的匹配,那么这就是适合您的标志。如果您的表达式是 [a-z]``并且您用这个标志编译它,您的模式也将匹配大写字母!这也适用于 Unicode,并且不受当前区域设置的影响。

关于。L / re。现场

使转义码:\w,\W,\b,\d,\s 和\S 取决于当前的区域设置。然而,文档说你不应该依赖这个标志,因为本地机制本身是非常不可靠的。相反,只需使用 Unicode 匹配。文档继续指出,这个标志实际上只对字节模式有意义。

关于。M / re。多线

当您使用这个标志时,您是在告诉 Python 让 ^ 模式字符在字符串的开头和每一行的开头都匹配。它还告诉 Python$应该在字符串的末尾和每一行的末尾匹配,这与它们的缺省值有微妙的不同。有关其他信息,请参见文档。

关于。S / re。DOTALL

这面有趣的旗帜将会成为。(句点)元字符匹配任何字符。如果没有标志,它将匹配除换行符之外的任何内容。

关于。X / re。冗长的

如果您发现您的正则表达式难以阅读,那么这个标志可能正是您所需要的。它将允许可视化地分离正则表达式的逻辑部分,甚至添加注释!模式中的空白将被忽略,除非是在字符类中,或者空白前面有未转义的反斜杠。

使用编译标志

让我们花点时间看看一个使用详细编译标志的简单例子!一个很好的例子是用一个常见的电子邮件查找正则表达式,如 r'[\w\。-]+@[\w-]+'并使用 VERBOSE 标志添加一些注释。让我们来看看:


re.compile('''
           [\w\.-]+      # the user name
           @
           [\w\.-]+'     # the domain
           ''',
           re.VERBOSE)

让我们继续学习如何找到多个匹配。


查找多个实例

到目前为止,我们所看到的是如何在一个字符串中找到第一个匹配。但是如果一个字符串中有多个匹配呢?让我们回顾一下如何找到单个匹配:


>>> import re
>>> silly_string = "the cat in the hat"
>>> pattern = "the"
>>> match = re.search(pattern, text)
>>> match.group()
'the'

现在您可以看到单词“the”有两个实例,但我们只找到一个。有两种方法可以找到这两者。我们要看的第一个是 findall 函数:


>>> import re
>>> silly_string = "the cat in the hat"
>>> pattern = "the"
>>> re.findall(pattern, silly_string)
['the', 'the']

函数会搜索你传递给它的字符串,并将每个匹配添加到一个列表中。一旦搜索完你的字符串,它将返回一个匹配列表。寻找多个匹配的另一种方法是使用 finditer 函数。


import re

silly_string = "the cat in the hat"
pattern = "the"

for match in re.finditer(pattern, silly_string):
    s = "Found '{group}' at {begin}:{end}".format(
        group=match.group(), begin=match.start(),
        end=match.end())
    print(s)

正如您可能已经猜到的,finditer 方法返回匹配实例的迭代器,而不是从 findall 获得的字符串。所以我们需要对结果进行一些格式化,然后才能打印出来。试一下这段代码,看看它是如何工作的。


反斜杠很复杂

在 Python 的正则表达式中,反斜杠有点复杂。原因是正则表达式使用反斜杠来表示特殊形式或允许搜索一个特殊字符,而不是调用它,例如当我们想要搜索美元符号:$时。如果我们不反斜杠,我们只是创建一个锚。这个问题的出现是因为 Python 在文字字符串中使用反斜杠字符做同样的事情。假设您想要搜索这样的字符串(去掉引号):“\python”。

要在正则表达式中进行搜索,您需要对反斜杠进行转义,但是因为 Python 也使用反斜杠,所以反斜杠也必须进行转义,所以您将得到以下搜索模式:“\\python”。幸运的是,Python 通过在字符串前加上字母“r”来支持原始字符串。因此,我们可以通过执行以下操作来提高可读性:r"\python "。

所以如果你需要用反斜杠搜索某些东西,一定要使用原始字符串,否则你可能会得到一些意想不到的结果!


包扎

本文仅仅触及了正则表达式的皮毛。事实上,模块本身有更多的内容。有整本书都是关于正则表达式的,但是这应该给了你入门的基础。您将需要搜索示例和阅读文档,当您使用正则表达式时可能需要多次,但是当您需要时它们是一个方便的工具。

Python 和 Microsoft Office -使用 PyWin32

原文:https://www.blog.pythonlibrary.org/2010/07/16/python-and-microsoft-office-using-pywin32/

大多数典型用户都用过微软 Office。虽然 Office 可能是技术支持的克星,但我们仍然必须应对它。Python 可以用来编写 Office 脚本(也称为自动化),让我们或我们的用户更容易使用。这可能不像录制宏那样容易,但也很接近了。在本文中,您将学习如何使用 PyWin32 模块访问一些 Office 程序,并使用 Python 操作它们。有些论坛说,你需要在 Microsoft Word(和 Excel)上运行 PythonWin 的 makepy 实用程序,然后才能访问 Office 应用程序。我不认为我需要这样做来使它工作(至少,在 2007 版本中没有)。然而,PythonWin 附带了 PyWin32,所以如果您遇到麻烦,可以尝试一下。

Python 和 Microsoft Excel

如果你寻找过使用 Python 和 Office 的例子,你通常会发现最常被黑的组件是 Excel。事实上,有几个专门为读写 Excel 文件而创建的非 PyWin32 模块。它们分别被称为 xlrd 和 xlwt。但这是另一篇文章的主题。在这里,我们将看到如何使用 PyWin32 接口来处理 Excel。请注意,以下脚本仅适用于 Windows。xlrd 和 xlwt 的一个优点是可以在任何平台上使用。

我们来看一个简单的例子,好吗?


import time
import win32com.client as win32

#----------------------------------------------------------------------
def excel():
    """"""
    xl = win32.gencache.EnsureDispatch('Excel.Application')
    ss = xl.Workbooks.Add()
    sh = ss.ActiveSheet

    xl.Visible = True
    time.sleep(1)

    sh.Cells(1,1).Value = 'Hacking Excel with Python Demo'

    time.sleep(1)
    for i in range(2,8):
        sh.Cells(i,1).Value = 'Line %i' % i
        time.sleep(1)

    ss.Close(False)
    xl.Application.Quit()

if __name__ == "__main__":
    excel()

上面的例子和你通常在网上找到的很相似。它实际上是基于我在 Wesley Chun 的优秀著作核心 Python 编程中看到的一个例子。让我们花些时间来解开代码。为了访问 Excel,我们导入 win32com.client ,然后调用它的 gencache。EnsureDispatch ,传入我们想要打开的应用程序名称。在这种情况下,要传递的字符串是“Excel。应用”。所做的就是在后台打开 Excel。此时,用户甚至不会知道 Excel 已经打开,除非他们运行了任务管理器。下一行是通过调用 Excel 实例的“Workbooks ”,向 Excel 添加一个新工作簿。Add()"方法。这将返回一个 sheets 对象(我想)。为了获得 ActiveSheet,我们调用 ss。活动表。最后,我们通过将该属性设置为 True 来使 Excel 程序本身可见。

要设置特定单元格的值,可以这样调用: sh。单元格(行,列)。Value = "某值"。请注意,我们的实例不是从零开始的,实际上会将值放在正确的行/列组合中。如果我们想提取一个值,我们只需去掉等号。如果我们想要分子式呢?为了解决这个问题,我在 Excel 中记录了一个宏,并执行了一个仅粘贴公式的选择性粘贴命令。使用生成的代码,我发现要获得 Python 中的公式,只需这样做:


formula = sh.Cells(row, col).Formula

如果您需要更改您所在的工作表,该怎么办?录制宏也向我展示了如何完成这一壮举。这是来自 Excel 的 VBA 代码:

Sub Macro1() ' ' Macro1 Macro ' Sheets("Sheet2").Select End Sub

从这段代码中,我得出结论,我需要调用我的 sheets 对象的“Sheets”方法,在做了一点小改动之后,我通过执行以下操作让它工作了:


sheet2 = ss.Sheets("Sheet2")

现在我们有了工作簿中第二个工作表的句柄。如果您想要编辑或检索值,只需在前面使用的相同方法前面加上您调用的 sheet2 实例(即 sheet2。单元格(1,1)。值)。原始程序的最后两行将关闭工作表,然后退出整个 Excel 实例。

您可能会想,到目前为止,我所展示的只是如何创建一个新文档。如果你想打开一个现有的文件呢?在代码的开头做这样的事情:


xl = win32.gencache.EnsureDispatch('Excel.Application')
ss = xl.Workbooks.Open(filename)

现在你知道了!现在,您已经了解了使用 Excel 的 COM 对象模型用 Python 入侵 Excel 的基本知识。如果您需要了解更多,我建议尝试录制一个宏,然后将结果翻译成 Python。注意:我找不到一个可以保存电子表格的例子...有几个例子声称他们的工作,但他们没有为我。

Python 和 Microsoft Word

使用 Python 访问 Microsoft Word 遵循我们用于 Excel 的相同语法。让我们快速了解一下如何访问 Word。


from time import sleep
import win32com.client as win32

RANGE = range(3, 8)

def word():
    word = win32.gencache.EnsureDispatch('Word.Application')
    doc = word.Documents.Add()
    word.Visible = True
    sleep(1)

    rng = doc.Range(0,0)
    rng.InsertAfter('Hacking Word with Python\r\n\r\n')
    sleep(1)
    for i in RANGE:
        rng.InsertAfter('Line %d\r\n' % i)
        sleep(1)
    rng.InsertAfter("\r\nPython rules!\r\n")

    doc.Close(False)
    word.Application.Quit()

if __name__ == '__main__':
    word()

这个特殊的例子也是基于春的书。然而,网上有很多其他的例子看起来也几乎和这个一模一样。现在让我们解开这段代码。为了获得 Microsoft Word 应用程序的句柄,我们调用win32 . gen cache . ensure dispatch(' Word。应用');然后我们通过调用 word 实例的文档来添加一个新文档。添加()。如果您想向用户展示您在做什么,可以将 Word 的 visibility 设置为 True。

如果您想在文档中添加文本,那么您需要告诉 Word 您想将文本添加到哪里。这就是 Range 方法的用武之地。虽然您看不到它,但有一种“网格”告诉 Word 如何在屏幕上布局文本。因此,如果我们想在文档的最顶端插入文本,我们告诉它从(0,0)开始。要在 Word 中添加新的一行,我们需要将“\r\n”追加到字符串的末尾。如果你不知道不同平台上的行尾的恼人之处,你应该花些时间在 Google 上学习一下,这样你就不会被奇怪的 bug 咬了!

代码的其余部分是不言自明的,将留给读者去解释。我们现在将继续打开和保存文档:


# Based on examples from http://code.activestate.com/recipes/279003/
word.Documents.Open(doc)
word.ActiveDocument.SaveAs("c:\\a.txt", FileFormat=win32com.client.constants.wdFormatTextLineBreaks)

这里我们展示了如何打开一个现有的 Word 文档并将其保存为文本。我还没有完全测试这个,所以你的里程可能会有所不同。如果您想阅读文档中的文本,您可以执行以下操作:


docText = word.Documents[0].Content

关于 Word 文档的 Python 黑客课程到此结束。由于我在微软 Word 和 Python 上找到的许多信息都是陈旧的,而且似乎有一半时间都不起作用,所以我不会添加到糟糕信息的混乱中。希望这能让你开始自己的文字处理之旅。

进一步阅读

Python 和 2010 年谷歌代码之夏

原文:https://www.blog.pythonlibrary.org/2010/10/18/python-and-the-2010-google-summer-of-code/

一个多月来,我一直在写一篇关于参加 2010 年谷歌代码之夏的各种 Python 项目的文章。有很多项目和人需要联系,我想说的是,在我联系的项目和人中,只有 50-60%的人回复了。所以,我要用我现在得到的,如果你想让我突出你的项目,那么你可以联系我,我会这样做。应该注意的是,我也写了一篇类似的文章,也将发布在 Python 软件基金会的博客上。

Python 软件基金会今年通过招募导师和支持来自社区的项目来支持许多 T2 谷歌代码之夏项目。随着夏天接近尾声,PSF 认为让你知道事情的结果是个好主意,所以我联系了一些参与者,询问他们的经历。

来自 pip 项目的 Carl Meyer 告诉我们,“今年夏天的主要目标是为 pip 建立一个持续的集成服务器,加速测试并使它们在没有网络接入的情况下运行,以及将 pip 移植到 Python 3。第一个和最后一个完全完成,中间的大部分完成。雨果·洛佩斯·塔瓦雷斯(Hugo Lopes Tavares)也关闭了一些杂项门票。”

卡尔还告诉我们,他很高兴成为这个项目的导师。GSoC 结束后,Hugo 加入了 Globo.com,他认为他在 GSoC 的经历帮助他获得了工作。

rpy2

rpy2 项目的劳伦特·戈蒂埃也喜欢当导师。他的项目是让 rpy2 在 C 级上与 Python 3 兼容,并用 rpy2 做一些定制的 R 图形设备。他们能够完成他们的项目。

IPython

费尔南多·佩雷斯是奥马尔·安德烈斯·萨帕塔·梅萨和杰拉尔多·古铁雷斯完成的 IPython 工作的导师。他们从事独立但相关的项目,处理 IPython 内核托管的多进程模型和一些使用 ZeroMQ 消息库的客户端软件。

你可以在 http://github.com/omazapa/ipython/的和 http://github.com/muzgash/ipython/tree/ipythonqt-km查看代码

交响乐

sympy 的人们全力以赴,在网上发布了他们所有的 GSoC 信息。他们的项目非常详细,而且技术性很强。勇敢点,无论如何都要读完它们!

PyGame

朱利安·哈布洛克,PyGame 项目的学生,在 T2 的博客上发布了他的作品。他和导师马库斯·冯·阿彭一起为 pygame 和 pygame2 开发了一个新的绘图模块。朱利安认为这个项目很有趣,他学会了如何组织更大的项目,并鼓励其他有时间和动力的学生明年加入 GSoC。

告诉我们你的项目

让我知道你的项目在这个夏天做了什么!谷歌代码之夏网站`列出了很多 Python 相关的项目,但是网上的详细程度并不一致。如果您想让我知道您的项目完成了什么,请发送电子邮件至 mike at pythonlibrary dot org。

了解更多信息

有关更多信息,请参见 Python GSOC wiki 页面

Python 自动化库(视频)

原文:https://www.blog.pythonlibrary.org/2022/05/25/python-automation-libraries-video/

在这个方便的视频中,了解一些可以在 Python 中使用的自动化包。视频下方还提供了链接。

https://www.youtube.com/embed/5-wG0MpiJLQ?feature=oembed

Web 自动化包

GUI 自动化包

Excel 自动化

了解更多 Python

Python:当今糟糕的代码(2013 年 10 月 30 日版)

原文:https://www.blog.pythonlibrary.org/2013/10/30/python-bad-code-of-the-day-oct-30th-2013-ed/

我们都会时不时地写出糟糕的代码。当我在解释器或调试器中试验时,我倾向于故意这样做。我说不出我为什么要这样做,除了看看我能在一行代码里放些什么很有趣。总之,本周我需要解析一个文件,如下所示:


00000       TM        YT                                                TSA1112223  0000000000000000000000000020131007154712PXXXX_Name_test_test_section.txt
data line 1
data line 2

你可以在这里下载如果你不想滚动上面的片段。无论如何,我们的目标是解析出文件头中的文件名部分,即上面的“section”。文件名本身就是XXXX _ 名字 _ 测试 _ 测试 _ 章节. txt 。文件名的这一部分用于标识数据库中的某些内容。所以在 Python 解释器中,我想出了下面这个方法:


header.split("|")[0][125:].split("_")[-1].split(".")[0]

让我们给它更多的背景。上面的代码片段适合这样一个函数:


#----------------------------------------------------------------------
def getSection(path):
    """"""
    with open(path) as inPath:
        lines = inPath.readlines()

    header = lines.pop(0)
    section = header.split("|")[0][125:].split("_")[-1].split(".")[0]

那是相当难看的代码,需要一个相当长的注释来解释我在做什么。并不是说我一开始就打算用它,但是写起来很有趣。总之,我最后把它分解成一些代码,大概如下:


#----------------------------------------------------------------------
def getSection(path):
    """"""
    with open(path) as inPath:
        lines = inPath.readlines()

    header = lines.pop(0)
    filename = header[125:256].strip()
    fname = os.path.splitext(filename)[0]
    section = fname.split("_")[-1]

    print section
    print header.split("|")[0][125:].split("_")[-1].split(".")[0]

#----------------------------------------------------------------------
if __name__ == "__main__":
    path = "test.txt"
    getSection(path)

我在上面的例子中留下了“坏的”代码,以表明结果是相同的。是的,它仍然有点难看,但是它更容易理解,代码是自文档化的。

你最近写了什么时髦的代码?

Python Black Friday / Cyber Monday Sales 2021

原文:https://www.blog.pythonlibrary.org/2021/11/24/python-black-friday-cyber-monday-sales-2021/

Lots of companies do sales on Black Friday, sometimes for the entire week. I am going to link to Black Friday sales for Python-related materials, including my own, in this article.

I am having a sale where you can get $10 off if you use the following coupon: black21. This coupon works on any of my Python books on Gumroad

This code will work on my newest book, Automating Excel with Python. It will also work on older titles like Pillow: Image Processing with Python and Creating GUI Applications with wxPython among others.

All My Python Books

Other Python Sales

There are other indie authors and regular publishers that are having sales. Here are a few:

My friend, Sundeep, is also having some Python book sales:

  1. Practice Python Projects (https://learnbyexample.gumroad.com/l/py_projects/blackfriday…) ebook is free (normal price $10)

  2. Learn by example Python bundle (https://learnbyexample.gumroad.com/l/python-bundle/blackfriday…) is $2 (normal price $12) - includes Python re(gex)?, 100 Page Python Intro and Practice Python Projects

  3. All Books Bundle (https://learnbyexample.gumroad.com/l/all-books/blackfriday…) is $5 (normal price $22) - includes all three Python books mentioned in the bundle above

These sales from Sundeep are good until the end of November

Trey Hunner is keeping track of all the Python sales too on his site, so check it out too so that you don't miss anything

蟒蛇皮黑色星期五/网络星期一销售 2022

原文:https://www.blog.pythonlibrary.org/2022/11/22/python-black-friday-cyber-monday-sales-2022/

又到了一年中可以买到 Python 书籍和课程的时候了!

您可以使用以下优惠券代码在 Gumroad 上的任何一本我自己出版的书上减去$ 10:black 2022

这段代码也适用于我最新的未完成的书Python 问答书。在 Python 测试手册中,您可以学到许多巧妙的技巧和诀窍。目前,它在有超过 40 个问答,但是在【2023 年 3 月发布时将会有 100 多个。立即获取早期副本!

All My Python Books

教我 Python 黑色星期五特卖

教我 Python 也在进行黑色星期五大甩卖。结账时使用 black22 即可获得八折优惠

Black Friday Sale for Teach Me Python

Trey Hunner Python 销售

在特雷·亨纳的“T2 蟒蛇大餐”上,你每年可以节省 108 美元。特雷还在他的网站上跟踪其他与 Python 相关的节日销售!

鲁文·勒纳的 Python 类

鲁文·勒纳的内容太惊艳了!你可以在本周以 40%的价格获得 Reuven 的各种 Python 课程。使用此链接访问优惠券代码为 BF2022 的 Reuven 商店。

其他 Python 销售

Django 销售公司

亚当·约翰逊整理了一份伟大的 Django 相关的黑色星期五和网络星期一销售

Python 博客评论:一口大小的 Python 技巧

原文:https://www.blog.pythonlibrary.org/2013/11/14/python-blog-review-bite-sized-python-tips/

我最近被要求评论一个 Python 博客,这是我以前从未真正做过的事情。这个博客的名字叫“一口大小的蟒蛇技巧”,你可以在这里找到它:http://freepythontips.wordpress.com/。它是由一个叫 Yasoob 的人经营的。当我第一次浏览该网站时,我看到了许多与我自己相似的文章。然而,我认为这只是巧合。总之这家伙写了很多文章。我喜欢他的网站简洁明了。这些文章篇幅很大,涵盖了各种各样的话题。我认为对于初学者和中级程序员来说,仅仅是为了收集想法,这是值得遵循的。我建议你自己去看看,你可以决定是否要把它添加到博客列表中来阅读。

出售 Python 书籍

原文:https://www.blog.pythonlibrary.org/2012/03/20/python-books-for-sale/

我最近得到了一份新工作,这份工作需要我跨越几个州,所以我决定卖掉几本我在过去几年里买的 Python 编程书籍。出于某种原因,易贝的搜索在我写这篇文章的时候被破坏了,所以我将只列出我在这里卖的东西和页面的直接链接。如果你想在市场上购买一些新的或二手的 Python 书籍,那么这是一个购买的好时机。我要以 99 美分开始所有的图书拍卖。

  • 巨蟒之力!Matt Telles 的综合指南
  • 里克·科普兰的《基础 Sqlalchemy 》(不错的参考,虽然有点过时)
  • CherryPy Essentials:Sylvain Hellegouarch 的《快速 Python Web 应用程序开发》(注:我没有读过这本书,所以不知道它好不好)

  • Django 实用项目第二版。作者詹姆斯·贝内特。我也在卖蹩脚的第一版
  • Mike Ohlson de Fine 的 Python 2.6 图形烹饪书(注:我实际上为这本书做了技术审查,并且在书中有我的名字!事实上,我也写了一个关于它的预览。)
  • David M. Beazley 的《Python 基本参考》
  • Abe Fettig 的《扭曲的网络编程要点》
  • 专业 Python 框架:用 Django 和 TurboGears 进行 Web 2.0 编程
  • Python 3 中的编程:马克·萨默菲尔德的 Python 语言完全介绍
  • Ayman Hourieh 的 Django 1.0 网站开发

我还打算在那里卖一些其他东西,比如一个安卓平板电脑,一个汽车立体声接收器,可能还有我的一些 DVD 收藏。这个链接应该列出我卖的所有东西,但是到目前为止我只能让它列出两个项目。

Python 手册项目预览

原文:https://www.blog.pythonlibrary.org/2013/04/16/python-brochure-project-preview/

我去年听说过 Python 小册子项目,但当时的印象是它是为企业服务的,因为看起来你必须购买它们。它看起来真的很好,但因为我不是一家公司,不能大量购买它们,我没有想太多。然而,Python 软件基金会现在发布了小册子的预览图,值得一看。小册子是为了 Python 的营销,主要面向非技术人员(比如经理)。如果你一直在努力让 Python 在你的工作场所被采用,这本小册子可能会有所帮助,因为它展示了 Python 如何在从工业和科学到教育和政府的各个领域的业务中使用。

你可以从他们的网站下载看看。我觉得这很巧妙。你甚至可以提交你自己的案例研究,或者帮助赞助这本小册子。去看看,把消息传出去!

Python:更改 Microsoft Office 用户姓名缩写

原文:https://www.blog.pythonlibrary.org/2010/10/27/python-changing-microsoft-office-user-initials/

几个月前,在工作中,我们收到一份报告,称一个文件被锁定。出现的对话框显示了一个已经不再为我们工作的用户的姓名首字母。因此,我们发现了一个可能在 Office 中突然出现的令人讨厌的 bug。基本上,在第一次运行相应的应用程序时,Word 或 Excel 会要求用户输入他们的姓名和首字母,无论以后谁登录该机器,它都会保留这些数据。当我们得到这类错误消息时,这会导致一些严重的混乱。无论如何,让我们快速地看一下如何完成这项工作。

我们将使用 Python 的 _winreg 模块进行这次攻击。你可以看到下面说的黑客:


from _winreg import *

key = CreateKey(HKEY_CURRENT_USER,
                r'Software\Microsoft\Office\11.0\Common\UserInfo')
res = QueryValueEx(key, "UserInitials")
print repr (res) 

username = u"mldr\0"
SetValueEx(key, "UserInitials", 0, REG_BINARY, username)
CloseKey(key)

这里我们使用了 CreateKey 方法,以防这个键还不存在。如果密钥确实存在,那么 CreateKey 将会打开它。脚本的前半部分用于检查键中是否有正确的值。最后三行用我的姓名首字母覆盖了该值。我不记得为什么我必须制作一个 unicode 字符串,但是 PyWin32 上的人告诉我这是实现它的方法。我可以告诉你,我从来没有能够得到一个简单的字符串工作。一旦设置了值,我们就通过关闭键来进行清理。

就是这样!简单,嗯?玩 Python 开心点!

Python 代码 Kata: Fizzbuzz

原文:https://www.blog.pythonlibrary.org/2019/09/18/python-code-kata-fizzbuzz/

代码形是计算机程序员练习编码的一种有趣方式。他们也经常被用来学习如何在编写代码时实现测试驱动开发(TDD)。其中一个流行的编程招式叫做 FizzBuzz 。这也是计算机程序员普遍的面试问题。

FizzBuzz 背后的概念如下:

  • 写一个程序打印数字 1-100,每一行一个
  • 对于每个是 3 的倍数的数字,打印“Fizz ”,而不是数字
  • 对于每个是 5 的倍数的数字,打印“Buzz”而不是数字
  • 对于每个都是 3 和 5 的倍数的数字,打印“FizzBuzz”而不是数字

现在你知道你需要写什么了,你可以开始了!


创建工作空间

第一步是在您的机器上创建一个工作空间或项目文件夹。例如,你可以创建一个 katas 文件夹,里面有一个 fizzbuzz

下一步是安装源代码控制程序。最流行的一个是 Git,但是您也可以使用像 Mercurial 这样的东西。出于本教程的目的,您将使用 Git。可以从 Git 网站获取。

如果你是 Windows 用户,现在打开一个终端或者运行 cmd.exe。然后在终端中导航到您的 fizzbuzz 文件夹。你可以使用 cd 命令来实现。进入文件夹后,运行以下命令:

git init

这将把 fizzbuzz 文件夹初始化为 Git 存储库。你在 fizzbuzz 文件夹中添加的任何文件或文件夹现在都可以添加到 Git 并进行版本控制。


嘶嘶测试

为了简单起见,您可以在 fizzbuzz 文件夹中创建您的测试文件。许多人会将他们的测试保存在名为测试测试的子文件夹中,并告诉他们的测试运行人员将顶层文件夹添加到 sys.path 中,以便测试可以导入它。

注意:如果你需要温习如何使用 Python 的 unittest 库,那么你可能会发现 Python 3 测试:unittest 简介很有帮助。

继续在你的 fizzbuzz 文件夹中创建一个名为 test_fizzbuzz.py 的文件。

现在,在 Python 文件中输入以下内容:


import fizzbuzz
import unittest

class TestFizzBuzz(unittest.TestCase):

    def test_multiple_of_three(self):
       self.assertEqual(fizzbuzz.process(6), 'Fizz')

if __name__ == '__main__':
    unittest.main()

Python 附带了内置的 unittest 库。要使用它,你需要做的就是导入它并子类化 unittest。测试用例。然后,您可以创建一系列函数来表示您想要运行的测试。

请注意,您还导入了 fizzbuzz 模块。您还没有创建那个模块,所以当您运行这个测试代码时,您将收到一个 ModuleNotFoundError 。除了导入之外,您甚至不需要添加任何代码就可以创建这个文件,并且测试会失败。但是为了完整起见,您继续断言 fizzbuzz.process(6) 返回正确的字符串。

修复方法是创建一个空的 fizzbuzz.py 文件。这只会修复 ModuleNotFoundError ,但是它将允许您运行测试并查看其输出。

您可以通过以下方式运行测试:

python test_fizzbuzz.py

输出将如下所示:

`ERROR: test_multiple_of_three (main.TestFizzBuzz)

Traceback (most recent call last):
File "/Users/michael/Dropbox/code/fizzbuzz/test_fizzbuzz.py", line 7, in test_multiple_of_three
self.assertEqual(fizzbuzz.process(6), 'Fizz')
AttributeError: module 'fizzbuzz' has no attribute 'process'`

在 0.001 秒内完成一项测试

失败(错误=1)

所以这告诉你你的 fizzbuzz 模块缺少一个叫做进程的属性。

您可以通过在您的 fizzbuzz.py 文件中添加一个 process() 函数来解决这个问题:


def process(number):
    if number % 3 == 0:
        return 'Fizz'

该函数接受一个数字,并使用模数运算符将该数字除以 3,并检查是否有余数。如果没有余数,那么你知道这个数可以被 3 整除,所以你可以返回字符串“Fizz”。

现在,当您运行测试时,输出应该如下所示:

`.

Ran 1 test in 0.000s`

好的

上面第一行中的句点表示您运行了一个测试,它通过了。

让我们快速回到这里。当测试失败时,它被认为处于“红色”状态。当测试通过时,这是一个“绿色”状态。这指的是红色/绿色/重构的测试驱动开发(TDD)咒语。大多数开发人员会通过创建一个失败的测试(红色)来开始一个新项目。然后他们会编写代码使测试通过,通常是以最简单的方式(绿色)。

当您的测试为绿色时,这是提交您的测试和代码变更的好时机。这允许您拥有一段可以回滚的工作代码。现在,您可以编写一个新的测试或重构代码来使它变得更好,而不用担心您会丢失您的工作,因为现在您有一个简单的方法来回滚到代码的前一个版本。

要提交代码,可以执行以下操作:

git add fizzbuzz.py test_fizzbuzz.py git commit -m "First commit"

第一个命令将添加两个新文件。不需要提交 。pyc 文件,只是 Python 文件。有一个方便的文件叫做。gitignore* 您可以添加到您的 Git 存储库中,您可以使用它来排除某些文件类型或文件夹,如* . pyc。Github 有一些默认的 gitignore 文件,用于各种语言,如果您想查看示例,您可以获得这些文件。

第二个命令是如何将代码提交到本地存储库。“-m”表示消息,后面是关于您正在提交的更改的描述性消息。如果你也想把你的修改保存到 Github(这对于备份来说很好),你应该看看这篇文章。

现在我们准备编写另一个测试了!


嗡嗡声测试

你可以写的第二个测试可以是 5 的倍数。要添加一个新的测试,您可以在 TestFizzBuzz 类中创建另一个方法:


import fizzbuzz
import unittest

class TestFizzBuzz(unittest.TestCase):

    def test_multiple_of_three(self):
        self.assertEqual(fizzbuzz.process(6), 'Fizz')

    def test_multiple_of_five(self):
        self.assertEqual(fizzbuzz.process(20), 'Buzz')

if __name__ == '__main__':
    unittest.main()

这一次,您希望使用只能被 5 整除的数字。当您调用 fizzbuzz.process() 时,应该会得到“buzz”的返回。但是,当您运行测试时,您将收到以下内容:

`F.

FAIL: test_multiple_of_five (main.TestFizzBuzz)

Traceback (most recent call last):
File "test_fizzbuzz.py", line 10, in test_multiple_of_five
self.assertEqual(fizzbuzz.process(20), 'Buzz')
AssertionError: None != 'Buzz'`

在 0.000 秒内运行 2 次测试

失败(失败次数=1)

哎呀!现在,您的代码使用模数运算符来检查除以 3 后的余数。如果数字 20 有余数,这个语句就不会运行。函数的默认返回值是 None ,所以这就是为什么你会得到上面的失败。

继续将 process() 函数更新如下:


def process(number):
    if number % 3 == 0:
        return 'Fizz'
    elif number % 5 == 0:
        return 'Buzz'

现在你可以用 3 和 5 来检查余数。当您这次运行测试时,输出应该如下所示:

`..

Ran 2 tests in 0.000s`

好的

耶!您的测试通过,现在是绿色的!这意味着您可以将这些更改提交到您的 Git 存储库中。

现在你已经准备好为 FizzBuzz 添加一个测试了!


嘶嘶声测试

你能写的下一个测试将是当你想要得到“FizzBuzz”的时候。你可能还记得,只要这个数能被 3 和 5 整除,你就会得到 FizzBuzz。继续添加第三个测试:


import fizzbuzz
import unittest

class TestFizzBuzz(unittest.TestCase):

    def test_multiple_of_three(self):
        self.assertEqual(fizzbuzz.process(6), 'Fizz')

    def test_multiple_of_five(self):
        self.assertEqual(fizzbuzz.process(20), 'Buzz')

    def test_fizzbuzz(self):
        self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')

if __name__ == '__main__':
    unittest.main()

对于这个测试, test_fizzbuzz ,您要求您的程序处理数字 15。这应该还不能正常工作,但是继续运行测试代码来检查:

`F..

FAIL: test_fizzbuzz (main.TestFizzBuzz)

Traceback (most recent call last):
File "test_fizzbuzz.py", line 13, in test_fizzbuzz
self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
AssertionError: 'Fizz' != 'FizzBuzz'`

在 0.000 秒内运行了 3 次测试

失败(失败次数=1)

进行了三次测试,只有一次失败。你现在回到红色。这次的错误是‘嘶嘶’!= 'FizzBuzz' 而不是拿 None 和 FizzBuzz 比较。原因是你的代码检查 15 是否能被 3 整除,所以它返回“Fizz”。

因为这不是您想要发生的,所以您将需要更新您的代码,在检查 3:


def process(number):
    if number % 3 == 0 and number % 5 == 0:
        return 'FizzBuzz'
    elif number % 3 == 0:
        return 'Fizz'
    elif number % 5 == 0:
        return 'Buzz'

在这里,首先对 3 和 5 进行整除检查。然后像以前一样检查另外两个。

现在,如果您运行您的测试,您应该得到以下输出:

`...

Ran 3 tests in 0.000s`

好的

到目前为止一切顺利。然而,你没有返回不能被 3 或 5 整除的数字的代码。该进行另一项测试了!


最终测试

您的代码需要做的最后一件事是,当该数除以 3 和 5 后还有余数时,返回该数。让我们用几种不同的方法来测试它:


import fizzbuzz
import unittest

class TestFizzBuzz(unittest.TestCase):

    def test_multiple_of_three(self):
        self.assertEqual(fizzbuzz.process(6), 'Fizz')

    def test_multiple_of_five(self):
        self.assertEqual(fizzbuzz.process(20), 'Buzz')

    def test_fizzbuzz(self):
        self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')

    def test_regular_numbers(self):
        self.assertEqual(fizzbuzz.process(2), 2)
        self.assertEqual(fizzbuzz.process(98), 98)

if __name__ == '__main__':
    unittest.main()

对于这个测试,您将使用 test_regular_numbers() 测试来测试正常数字 2 和 98。这些数字在被 3 或 5 除时总会有余数,所以它们应该被返回。

当您现在运行测试时,您应该得到类似这样的结果:

`...F

FAIL: test_regular_numbers (main.TestFizzBuzz)

Traceback (most recent call last):
File "test_fizzbuzz.py", line 16, in test_regular_numbers
self.assertEqual(fizzbuzz.process(2), 2)
AssertionError: None != 2`

在 0.000 秒内运行了 4 次测试

失败(失败次数=1)

这一次,您又回到了将 None 与 number 进行比较,这就是您可能怀疑的输出。

继续更新进程()函数,如下所示:


def process(number):
    if number % 3 == 0 and number % 5 == 0:
        return 'FizzBuzz'
    elif number % 3 == 0:
        return 'Fizz'
    elif number % 5 == 0:
        return 'Buzz'
    else:
        return number

那很容易!此时您需要做的就是添加一个返回数字的 else 语句。

现在,当您运行测试时,它们应该都通过了:

`....

Ran 4 tests in 0.000s`

好的

干得好!现在你的代码工作了。您可以通过将以下内容添加到您的 fizzbuzz.py 模块来验证它是否适用于 1-100 的所有数字:


if __name__ == '__main__':
    for i in range(1, 101):
        print(process(i))

现在,当您自己使用 python fizzbuzz.py 运行 fizzbuzz 时,您应该会看到本教程开头指定的适当输出。

这是提交您的代码并将其推送到云的好时机。


包扎

现在你知道了使用测试驱动开发来驱动你解决编码问题的基础。Python 的 unittest 模块拥有比这篇简短教程中所涵盖的更多类型的断言和功能。您还可以修改本教程以使用 pytest,这是另一个流行的第三方 Python 包,您可以用它来代替 Python 自己的 unittest 模块。

进行这些测试的好处是,现在您可以重构代码,并通过运行测试来验证您没有破坏任何东西。这也允许您在不破坏现有功能的情况下更容易地添加新功能。只要确保在添加更多特性时添加更多测试。


相关阅读

Python 来到德州仪器和卡西欧图形计算器

原文:https://www.blog.pythonlibrary.org/2021/08/20/python-comes-to-texas-instruments-and-casio-graphing-calculators/

德州仪器宣布,他们正在添加一款名为 TI-84 加 CE Python 的新图形计算器。TI-84 Plus 使用 CircuitPython ,而不是大多数开发者都知道的标准 Python。

CircuitPython 是由 AdaFruit 为嵌入式设备开发的,比如计算器。你可以在 AdaFruit 的网站上阅读更多关于 CircuitPython 被添加到计算器中的信息。

卡西欧也有一个图形计算器,其中包括一个名为 fx-CG50 PRIZM 计算器的 Python 版本。它使用了 MicroPython ,这也是 CircuitPython 所基于的。

希望惠普(HP)能看到曙光,并在他们的一个或多个图形计算器中加入 Python 支持。时间会证明一切。

Python 并发性:队列的一个例子

原文:https://www.blog.pythonlibrary.org/2012/08/01/python-concurrency-an-example-of-a-queue/

Python 内置了很多很酷的并发工具,比如线程、队列、信号量和多处理。在本文中,我们将花一些时间学习如何使用队列。如果您直接使用队列,那么它可以用于先进先出或后进后出的类似堆栈的实现。如果你想看实际操作,请看本文末尾的 Hellman 文章。我们将混合线程并创建一个简单的文件下载器脚本来演示队列在我们需要并发的情况下是如何工作的。

创建下载应用程序

这段代码大致基于 Hellman 的文章和 IBM 的文章,因为它们都展示了如何以各种方式下载 URL。这个实现实际上是下载文件。在我们的例子中,我们将使用美国国税局的税务表格。让我们假设我们是一个小企业主,我们需要为我们的员工下载一堆这样的表格。下面是一些符合我们需求的代码:


import os
import Queue
import threading
import urllib2

########################################################################
class Downloader(threading.Thread):
    """Threaded File Downloader"""

    #----------------------------------------------------------------------
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    #----------------------------------------------------------------------
    def run(self):
        while True:
            # gets the url from the queue
            url = self.queue.get()

            # download the file
            self.download_file(url)

            # send a signal to the queue that the job is done
            self.queue.task_done()

    #----------------------------------------------------------------------
    def download_file(self, url):
        """"""
        handle = urllib2.urlopen(url)
        fname = os.path.basename(url)
        with open(fname, "wb") as f:
            while True:
                chunk = handle.read(1024)
                if not chunk: break
                f.write(chunk)

#----------------------------------------------------------------------
def main(urls):
    """
    Run the program
    """
    queue = Queue.Queue()

    # create a thread pool and give them a queue
    for i in range(5):
        t = Downloader(queue)
        t.setDaemon(True)
        t.start()

    # give the queue some data
    for url in urls:
        queue.put(url)

    # wait for the queue to finish
    queue.join()

if __name__ == "__main__":
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    main(urls)

让我们把它分解一下。首先,我们需要看一下 main 函数定义,看看这一切是如何流动的。这里我们看到它接受一个 URL 列表。然后,main 函数创建一个队列实例,并将其传递给 5 个守护线程。守护进程线程和非守护进程线程的主要区别在于,你必须跟踪非守护进程线程并自己关闭它们,而对于守护进程线程,你基本上只需设置它们并忘记它们,当你的应用程序关闭时,它们也会关闭。接下来,我们用传入的 URL 加载队列(使用它的 put 方法)。最后,我们通过 join 方法告诉队列等待线程进行处理。在下载类中,我们有一行“self.queue.get()”,它会一直阻塞,直到队列有东西要返回。这意味着线程只是无所事事地等待拾取某些东西。这也意味着线程要从队列中“获取”某些东西,它必须调用队列的“get”方法。因此,当我们在队列中添加或放置项目时,线程池将拾取或“获取”项目并处理它们。这也被称为“德清”。一旦处理完队列中的所有项目,脚本就结束并退出。在我的机器上,它可以在不到一秒的时间内下载完所有 5 个文档。

进一步阅读

Python 并发性:线程介绍

原文:https://www.blog.pythonlibrary.org/2014/02/24/python-concurrency-an-intro-to-threads/

Python 有许多不同的并发结构,比如线程、队列和多处理。线程模块曾经是实现并发的主要方式。几年前,多处理模块被添加到 Python 标准库套件中。不过,本文将主要关注线程模块。


入门指南

我们将从一个简单的例子开始,这个例子演示了线程是如何工作的。我们将子类化 Thread 类,并将其名称输出到 stdout。让我们开始编码吧!


import random
import time

from threading import Thread

########################################################################
class MyThread(Thread):
    """
    A threading example
    """

    #----------------------------------------------------------------------
    def __init__(self, name):
        """Initialize the thread"""
        Thread.__init__(self)
        self.name = name
        self.start()

    #----------------------------------------------------------------------
    def run(self):
        """Run the thread"""
        amount = random.randint(3, 15)
        time.sleep(amount)
        msg = "%s has finished!" % self.name
        print(msg)

#----------------------------------------------------------------------
def create_threads():
    """
    Create a group of threads
    """
    for i in range(5):
        name = "Thread #%s" % (i+1)
        my_thread = MyThread(name=name)

if __name__ == "__main__":
    create_threads()

在上面的代码中,我们导入 Python 的随机模块,时间模块,并从线程模块导入线程类。接下来,我们子类化 Thread 并覆盖它的 init 方法,以接受一个我们标记为“name”的参数。要启动一个线程,你必须调用它的 start() 方法,所以我们在 init 结束时这样做。当你启动一个线程时,它会自动调用它的 run 方法。我们覆盖了它的 run 方法,让它选择一个随机的睡眠时间。这里的 random.randint 示例将使 Python 从 3-15 中随机选择一个数字。然后我们让线程休眠我们随机选择的秒数来模拟它实际做的事情。最后,我们打印出线程的名称,让用户知道线程已经完成。

create_threads 函数将创建 5 个线程,给每个线程一个唯一的名字。如果您运行这段代码,您应该会看到类似这样的内容:


Thread #2 has finished!
Thread #1 has finished!
Thread #3 has finished!
Thread #4 has finished!
Thread #5 has finished!

输出的顺序每次都会不同。尝试运行该代码几次,以查看顺序的变化。


编写线程下载程序

除了作为解释线程如何工作的工具之外,前面的例子没有什么用处。所以在这个例子中,我们将创建一个可以从互联网下载文件的线程类。美国国税局有大量的 PDF 表单,供其公民用于纳税。我们将使用这个免费资源进行演示。代码如下:


# Use this version for Python 2
import os
import urllib2

from threading import Thread

########################################################################
class DownloadThread(Thread):
    """
    A threading example that can download a file
    """

    #----------------------------------------------------------------------
    def __init__(self, url, name):
        """Initialize the thread"""
        Thread.__init__(self)
        self.name = name
        self.url = url

    #----------------------------------------------------------------------
    def run(self):
        """Run the thread"""
        handle = urllib2.urlopen(self.url)
        fname = os.path.basename(self.url)
        with open(fname, "wb") as f_handler:
            while True:
                chunk = handle.read(1024)
                if not chunk:
                    break
                f_handler.write(chunk)
        msg = "%s has finished downloading %s!" % (self.name,
                                                   self.url)
        print(msg)

#----------------------------------------------------------------------
def main(urls):
    """
    Run the program
    """
    for item, url in enumerate(urls):
        name = "Thread %s" % (item+1)
        thread = DownloadThread(url, name)
        thread.start()

if __name__ == "__main__":
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    main(urls)

这基本上是对第一个脚本的完全重写。在这个示例中,我们导入了 os 和 urllib2 模块以及线程模块。我们将使用 urllib2 在 thread 类中进行实际的下载。os 模块用于提取我们正在下载的文件的名称,因此我们可以使用它在我们的机器上创建一个同名的文件。在 DownloadThread 类中,我们设置了 init 来接受线程的 url 和名称。在 run 方法中,我们打开 url,提取文件名,然后使用该文件名在磁盘上命名/创建文件。然后,我们使用一个 while 循环一次下载一千字节的文件,并将其写入磁盘。文件保存完成后,我们打印出线程的名称和下载完成的 url。

更新:

Python 3 的代码版本略有不同。你必须导入 urllib 并使用 urllib.request.urlopen 而不是 urllib2.urlopen 。下面是 Python 3 版本:


# Use this version for Python 3
import os
import urllib.request

from threading import Thread

########################################################################
class DownloadThread(Thread):
    """
    A threading example that can download a file
    """

    #----------------------------------------------------------------------
    def __init__(self, url, name):
        """Initialize the thread"""
        Thread.__init__(self)
        self.name = name
        self.url = url

    #----------------------------------------------------------------------
    def run(self):
        """Run the thread"""
        handle = urllib.request.urlopen(self.url)
        fname = os.path.basename(self.url)
        with open(fname, "wb") as f_handler:
            while True:
                chunk = handle.read(1024)
                if not chunk:
                    break
                f_handler.write(chunk)
        msg = "%s has finished downloading %s!" % (self.name,
                                                   self.url)
        print(msg)

#----------------------------------------------------------------------
def main(urls):
    """
    Run the program
    """
    for item, url in enumerate(urls):
        name = "Thread %s" % (item+1)
        thread = DownloadThread(url, name)
        thread.start()

if __name__ == "__main__":
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    main(urls)


包扎

现在你知道了如何在理论和实践中使用线程。当你创建一个用户界面并希望保持界面可用时,线程尤其有用。如果没有线程,当您下载大文件或对数据库进行大查询时,用户界面会变得没有响应,并且看起来会挂起。为了防止这种情况发生,你需要在线程中执行长时间运行的过程,然后在完成后将信息反馈给你的接口。


相关阅读

Python 并发性:从队列移植到多处理

原文:https://www.blog.pythonlibrary.org/2012/08/03/python-concurrency-porting-from-a-queue-to-multiprocessing/

本周早些时候,我写了一篇关于 Python 队列的简单的帖子,并演示了如何将它们与线程池一起使用,以从美国国税局的网站下载一组 pdf。今天,我决定尝试将代码“移植”到 Python 的多处理模块上。正如我的一位读者所指出的,由于 Python 中的全局解释器锁(GIL ), Python 的队列和线程只能在一个内核上运行。多处理模块(以及 Stackless 和其他几个项目)可以在多核和 GIL 上运行(如果你好奇,请参见文档)。不管怎样,我们开始吧。

创建多处理下载应用程序

从队列切换到使用多处理模块非常简单。为了方便起见,我们还将使用请求库而不是 urllib 来下载文件。让我们看看代码:

import multiprocessing
import os
import requests

########################################################################
class MultiProcDownloader(object):
    """
    Downloads urls with Python's multiprocessing module
    """

    #----------------------------------------------------------------------
    def __init__(self, urls):
        """ Initialize class with list of urls """
        self.urls = urls

    #----------------------------------------------------------------------
    def run(self):
        """
        Download the urls and waits for the processes to finish
        """
        jobs = []
        for url in self.urls:
            process = multiprocessing.Process(target=self.worker, args=(url,))
            jobs.append(process)
            process.start()
        for job in jobs:
            job.join()

    #----------------------------------------------------------------------
    def worker(self, url):
        """
        The target method that the process uses tp download the specified url
        """
        fname = os.path.basename(url)
        msg = "Starting download of %s" % fname
        print msg, multiprocessing.current_process().name
        r = requests.get(url)
        with open(fname, "wb") as f:
            f.write(r.content)

#----------------------------------------------------------------------
if __name__ == "__main__":
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    downloader = MultiProcDownloader(urls)
    downloader.run()

您应该将类似这样的内容输出到 stdout:

Starting download of f1040a.pdf Process-2 Starting download of f1040.pdf Process-1 Starting download of f1040es.pdf Process-4 Starting download of f1040sb.pdf Process-5 Starting download of f1040ez.pdf Process-3

让我们把这段代码分解一下。很快,你会注意到你没有像使用 threading.Thread 那样子类化多处理模块,相反,我们只是创建了一个只接受 URL 列表的泛型类。在我们实例化该类之后,我们调用它的 run 方法,该方法将遍历 URL 并为每个 URL 创建一个进程。它还会将每个进程添加到一个作业列表中。我们这样做的原因是因为我们想要调用每个进程的 join 方法,正如您所料,该方法会等待进程完成。如果您愿意,您可以向 join 方法传递一个数字,这个数字基本上是一个超时值,它将导致 join 返回进程是否实际完成。如果不这样做,那么 join 将无限期阻塞。

`如果一个进程挂起或者你厌倦了等待它,你可以调用它的 terminate 方法来杀死它。根据文档,在多处理模块中有一个队列类,你可以以与普通队列相似的方式使用它,因为它几乎是原始队列的克隆。如果你想更深入地挖掘这个很酷的模块的所有可能性,我建议你看看下面的一些链接。

额外资源

Python:将数字转换成单词

原文:https://www.blog.pythonlibrary.org/2010/10/21/python-converting-numbers-to-words/

我在工作中的第一个自我强加的项目是重新创建一个令人讨厌的应用程序,它是一个弗兰肯斯坦怪物:一个带有 VBA 图形用户界面的微软 Access 文件。在很大程度上,该应用程序甚至没有数据库。无论如何,应用程序的一部分允许用户键入支票的金额,VBA 代码会神奇地将这些数字翻译成你通常在支票上写的文本。例如,假设我开了一张 1234.56 美元的支票。它会将其转换为“一千二百三十四美元五十六美分”。我需要用 Python 做同样的事情!

我花了相当多的时间试图想出正确的单词公式输入到谷歌,这将返回我所需要的。不幸的是,经过大量的劳动和疯狂的打字,我一无所获。我发现了一些在皮斯顿没有人做过的事情!!!或者那天我的 Google-fu 很糟糕,但我想是前者。

VBA 密码有一条线索,我已经记不起来了。我想是 VBA 图书馆的名字起了作用。总之,不管是什么原因,我找到了下面的代码(注意:下面的链接不再有效):


#!/usr/bin/env python
'''Convert number to English words
$./num2eng.py 1411893848129211
one quadrillion, four hundred and eleven trillion, eight hundred and ninety 
three billion, eight hundred and forty eight million, one hundred and twenty 
nine thousand, two hundred and eleven
$

Algorithm from http://mini.net/tcl/591
'''

# modified to exclude the "and" between hundreds and tens - mld

__author__ = 'Miki Tebeka '
__version__ = '$Revision: 7281 $'

# $Source$

import math

# Tokens from 1000 and up
_PRONOUNCE = [ 
    'vigintillion',
    'novemdecillion',
    'octodecillion',
    'septendecillion',
    'sexdecillion',
    'quindecillion',
    'quattuordecillion',
    'tredecillion',
    'duodecillion',
    'undecillion',
    'decillion',
    'nonillion',
    'octillion',
    'septillion',
    'sextillion',
    'quintillion',
    'quadrillion',
    'trillion',
    'billion',
    'million ',
    'thousand ',
    ''
]

# Tokens up to 90
_SMALL = {
    '0' : '',
    '1' : 'one',
    '2' : 'two',
    '3' : 'three',
    '4' : 'four',
    '5' : 'five',
    '6' : 'six',
    '7' : 'seven',
    '8' : 'eight',
    '9' : 'nine',
    '10' : 'ten',
    '11' : 'eleven',
    '12' : 'twelve',
    '13' : 'thirteen',
    '14' : 'fourteen',
    '15' : 'fifteen',
    '16' : 'sixteen',
    '17' : 'seventeen',
    '18' : 'eighteen',
    '19' : 'nineteen',
    '20' : 'twenty',
    '30' : 'thirty',
    '40' : 'forty',
    '50' : 'fifty',
    '60' : 'sixty',
    '70' : 'seventy',
    '80' : 'eighty',
    '90' : 'ninety'
}

def get_num(num):
    '''Get token <= 90, return '' if not matched'''
    return _SMALL.get(num, '')

def triplets(l):
    '''Split list to triplets. Pad last one with '' if needed'''
    res = []
    for i in range(int(math.ceil(len(l) / 3.0))):
        sect = l[i * 3 : (i + 1) * 3]
        if len(sect) < 3: # Pad last section
            sect += [''] * (3 - len(sect))
        res.append(sect)
    return res

def norm_num(num):
    """Normelize number (remove 0's prefix). Return number and string"""
    n = int(num)
    return n, str(n)

def small2eng(num):
    '''English representation of a number <= 999'''
    n, num = norm_num(num)
    hundred = ''
    ten = ''
    if len(num) == 3: # Got hundreds
        hundred = get_num(num[0]) + ' hundred'
        num = num[1:]
        n, num = norm_num(num)
    if (n > 20) and (n != (n / 10 * 10)): # Got ones
        tens = get_num(num[0] + '0')
        ones = get_num(num[1])
        ten = tens + ' ' + ones
    else:
        ten = get_num(num)
    if hundred and ten:
        return hundred + ' ' + ten
        #return hundred + ' and ' + ten
    else: # One of the below is empty
        return hundred + ten

#FIXME: Currently num2eng(1012) -> 'one thousand, twelve'
# do we want to add last 'and'?
def num2eng(num):
    '''English representation of a number'''
    num = str(long(num)) # Convert to string, throw if bad number
    if (len(num) / 3 >= len(_PRONOUNCE)): # Sanity check
        raise ValueError('Number too big')

    if num == '0': # Zero is a special case
        return 'zero'

    # Create reversed list
    x = list(num)
    x.reverse()
    pron = [] # Result accumolator
    ct = len(_PRONOUNCE) - 1 # Current index
    for a, b, c in triplets(x): # Work on triplets
        p = small2eng(c + b + a)
        if p:
            pron.append(p + ' ' + _PRONOUNCE[ct])
        ct -= 1
    # Create result
    pron.reverse()
    return ', '.join(pron)

if __name__ == '__main__':
    from sys import argv, exit
    from os.path import basename
    if len(argv) < 2:
        print 'usage: %s NUMBER[s]' % basename(argv[0])
        exit(1)
    for n in argv[1:]:
        try:
            print num2eng(n)
        except ValueError, e:
            print 'Error: %s' % e 

正如代码中的注释所指出的,我稍微修改了代码,以匹配 VBA 代码所做的。除此之外,和我发现的一模一样。我不会解释这一块,因为它是一种有趣的发现自己。希望你喜欢!

Python:用 Faker 创建假数据

原文:https://www.blog.pythonlibrary.org/2014/06/18/python-create-fake-data-with-faker/

偶尔,我会遇到需要虚拟数据来测试代码的情况。如果您需要对一个新的数据库或表进行测试,您将经常遇到对虚拟数据的需求。最近偶然发现一个有趣的包,叫做 Faker 。Faker 的唯一目的是创建半随机的假数据。Faker 可以创建假的名字,地址,浏览器用户代理,域名,段落等等。在本文中,我们将花一些时间展示 Faker 的一些功能。


入门指南

首先,你需要安装 Faker。如果你有皮普(为什么你不会?),你需要做的就是这个:


pip install fake-factory

现在您已经安装了这个包,我们可以开始使用它了!


制造假数据

用 Faker 创建假数据真的很好做。我们来看几个例子。我们将从几个创造假名字的例子开始:


from faker import Factory

#----------------------------------------------------------------------
def create_names(fake):
    """"""
    for i in range(10):
        print fake.name()

if __name__ == "__main__":
    fake = Factory.create()
    create_names(fake)

如果您运行上面的代码,您将看到 10 个不同的名字被打印到 stdout。这是我运行它时得到的结果:


Mrs. Terese Walter MD
Jess Mayert
Ms. Katerina Fisher PhD
Mrs. Senora Purdy PhD
Gretchen Tromp
Winnie Goodwin
Yuridia McGlynn MD
Betty Kub
Nolen Koelpin
Adilene Jerde

你可能会收到一些不同的东西。每次我运行这个脚本,结果都不一样。大多数情况下,我不希望名字有前缀或后缀,所以我创建了另一个只产生名和姓的脚本:


from faker import Factory

#----------------------------------------------------------------------
def create_names2(fake):
    """"""
    for i in range(10):
        name = "%s %s" % (fake.first_name(),
                          fake.last_name())
        print name

if __name__ == "__main__":
    fake = Factory.create()
    create_names2(fake)

如果您运行第二个脚本,您看到的姓名不应该包含前缀(如女士、先生等)或后缀(如 PhD、Jr .等)。让我们来看看我们可以用这个包生成的一些其他类型的假数据。


创造其他虚假的东西

现在,我们将花一些时间了解 Faker 可以生成的其他一些假数据。下面这段代码将创建六个假数据。让我们来看看:


from faker import Factory

#----------------------------------------------------------------------
def create_fake_stuff(fake):
    """"""
    stuff = ["email", "bs", "address",
             "city", "state",
             "paragraph"]
    for item in stuff:
        print "%s = %s" % (item, getattr(fake, item)())

if __name__ == "__main__":
    fake = Factory.create()
    create_fake_stuff(fake)

这里我们使用 Python 内置的 getattr 函数来调用 Faker 的一些方法。当我运行这个脚本时,我收到了以下输出:


email = pacocha.aria@kris.com
bs = reinvent collaborative systems
address = 57188 Leuschke Mission
Lake Jaceystad, KY 46291
city = West Luvinialand
state = Oregon
paragraph = Possimus nostrum exercitationem harum eum in. Dicta aut officiis qui deserunt voluptas ullam ut. Laborum molestias voluptatem consequatur laboriosam. Omnis est cumque culpa quo illum.

那不是很有趣吗?


包扎

Faker 包还有很多其他的方法,这里没有介绍。你应该看看他们完整的文档,看看你还能用这个包做些什么。只需做一点工作,您就可以使用这个包轻松地填充数据库或报告。

Python:用 lxml.objectify 创建 XML

原文:https://www.blog.pythonlibrary.org/2014/03/26/python-creating-xml-with-lxml-objectify/

lxml.objectify 子包对于解析和创建 xml 非常方便。在本文中,我们将展示如何使用 lxml 包创建 XML。我们将从一些简单的 XML 开始,然后尝试复制它。我们开始吧!

在过去的篇文章中,我使用了以下愚蠢的 XML 示例进行演示:


 <appointment><begin>1181251680</begin>
        <uid>040000008200E000</uid>
        <alarmtime>1181572063</alarmtime>
        <state><location><duration>1800</duration>
        <subject>Bring pizza home</subject></location></state></appointment> 
    <appointment><begin>1234360800</begin>
        <duration>1800</duration>
        <subject>Check MS Office website for updates</subject>
        <location><uid>604f4792-eb89-478b-a14f-dd34d3cc6c21-1234360800</uid>
        <state>dismissed</state></location></appointment> 

让我们看看如何使用 lxml.objectify 来重新创建这个 xml:


from lxml import etree, objectify

#----------------------------------------------------------------------
def create_appt(data):
    """
    Create an appointment XML element
    """
    appt = objectify.Element("appointment")
    appt.begin = data["begin"]
    appt.uid = data["uid"]
    appt.alarmTime = data["alarmTime"]
    appt.state = data["state"]
    appt.location = data["location"]
    appt.duration = data["duration"]
    appt.subject = data["subject"]
    return appt

#----------------------------------------------------------------------
def create_xml():
    """
    Create an XML file
    """

    xml = '''

    '''

    root = objectify.fromstring(xml)
    root.set("reminder", "15")

    appt = create_appt({"begin":1181251680,
                        "uid":"040000008200E000",
                        "alarmTime":1181572063,
                        "state":"",
                        "location":"",
                        "duration":1800,
                        "subject":"Bring pizza home"}
                       )
    root.append(appt)

    uid = "604f4792-eb89-478b-a14f-dd34d3cc6c21-1234360800"
    appt = create_appt({"begin":1234360800,
                        "uid":uid,
                        "alarmTime":1181572063,
                        "state":"dismissed",
                        "location":"",
                        "duration":1800,
                        "subject":"Check MS Office website for updates"}
                       )
    root.append(appt)

    # remove lxml annotation
    objectify.deannotate(root)
    etree.cleanup_namespaces(root)

    # create the xml string
    obj_xml = etree.tostring(root,
                             pretty_print=True,
                             xml_declaration=True)

    try:
        with open("example.xml", "wb") as xml_writer:
            xml_writer.write(obj_xml)
    except IOError:
        pass

#----------------------------------------------------------------------
if __name__ == "__main__":
    create_xml()

让我们把它分解一下。我们将从 create_xml 函数开始。在其中,我们使用 objectify 模块的 fromstring 函数创建了一个 XML 根对象。根对象将包含 zAppointment 作为它的标签。我们设置根的提醒属性,然后使用字典作为参数调用我们的 create_appt 函数。在 create_appt 函数中,我们创建了一个元素的实例(从技术上讲,它是一个 ObjectifiedElement ),我们将它分配给了我们的 appt 变量。这里我们使用点符号来创建这个元素的标签。最后,我们返回 appt 元素并将其附加到我们的根对象中。我们对第二个约会实例重复这个过程。

create_xml 函数的下一部分将删除 lxml 注释。如果您不这样做,您的 XML 将看起来像下面这样:


 <appointment py:pytype="TREE"><begin py:pytype="int">1181251680</begin>
        <uid py:pytype="str">040000008200E000</uid>
        <alarmtime py:pytype="int">1181572063</alarmtime>
        <state py:pytype="str"><location py:pytype="str"><duration py:pytype="int">1800</duration>
        <subject py:pytype="str">Bring pizza home</subject></location></state></appointment> <appointment py:pytype="TREE"><begin py:pytype="int">1234360800</begin>
        <uid py:pytype="str">604f4792-eb89-478b-a14f-dd34d3cc6c21-1234360800</uid>
        <alarmtime py:pytype="int">1181572063</alarmtime>
        <state py:pytype="str">dismissed</state>
        <location py:pytype="str"><duration py:pytype="int">1800</duration>
        <subject py:pytype="str">Check MS Office website for updates</subject></location></appointment> 

为了删除所有不需要注释,我们调用以下两个函数:


objectify.deannotate(root)
etree.cleanup_namespaces(root)

难题的最后一部分是让 lxml 自己生成 xml。这里我们使用 lxml 的 etree 模块来完成这项艰巨的工作:


obj_xml = etree.tostring(root, pretty_print=True)

tostring 函数将返回一个漂亮的 XML 字符串,如果您将 pretty_print 设置为 True,它通常也会以漂亮的格式返回 XML。

Python 3 的 2020 年 11 月更新

在 Python 3 中,上一节中的代码没有将“修饰过的”XML 输出到文件中。要使它正常工作,你必须经历更多的困难。这里是一个更新版本的代码。在 Mac OS 上的 Python 3.9 中测试:


from lxml import etree, objectify
from io import BytesIO

def create_appt(data):
    """
    Create an appointment XML element
    """
    appt = objectify.Element("appointment")
    appt.begin = data["begin"]
    appt.uid = data["uid"]
    appt.alarmTime = data["alarmTime"]
    appt.state = data["state"]
    appt.location = data["location"]
    appt.duration = data["duration"]
    appt.subject = data["subject"]
    return appt

def create_xml():
    """
    Create an XML file
    """

    xml = '''

    '''

    root = objectify.fromstring(xml)
    root.set("reminder", "15")

    appt = create_appt({"begin":1181251680,
                        "uid":"040000008200E000",
                        "alarmTime":1181572063,
                        "state":"",
                        "location":"",
                        "duration":1800,
                        "subject":"Bring pizza home"}
                       )
    root.append(appt)

    uid = "604f4792-eb89-478b-a14f-dd34d3cc6c21-1234360800"
    appt = create_appt({"begin":1234360800,
                        "uid":uid,
                        "alarmTime":1181572063,
                        "state":"dismissed",
                        "location":"",
                        "duration":1800,
                        "subject":"Check MS Office website for updates"}
                       )
    root.append(appt)

    # remove lxml annotation
    objectify.deannotate(root)
    etree.cleanup_namespaces(root)

    # create the xml string
    parser = etree.XMLParser(remove_blank_text=True)
    file_obj = BytesIO(etree.tostring(root))
    tree = etree.parse(file_obj, parser)

    try:
        with open("example.xml", "wb") as xml_writer:
            tree.write(xml_writer, pretty_print=True)
    except IOError:
        pass

if __name__ == "__main__":
    create_xml()

这是基于在 StackOverflow 上找到的解决方案。

您需要在文件的顶部添加一个新的导入来获得字节数。然后在代码的结尾,你需要修改你的代码,看起来像这样:


# create the xml string
parser = etree.XMLParser(remove_blank_text=True)
file_obj = BytesIO(etree.tostring(root))
tree = etree.parse(file_obj, parser)

try:
    with open("example.xml", "wb") as xml_writer:
        tree.write(xml_writer, pretty_print=True)
except IOError:
    pass

这将添加一个新的 XML 解析器对象,从根目录中删除空白文本。这将发生在你把根变成一个字节串,而这个字节串本身又用 BytesIO 变成一个类似文件的对象之后。试一试,您应该得到一个包含适当缩进的 XML 代码的文件。


包扎

现在您知道了如何使用 lxml 的 objectify 模块来创建 xml。这是一个非常方便的界面,并且在很大程度上相当 Pythonic 化。


相关阅读

Python:在 zip 中分发数据支持文件

原文:https://www.blog.pythonlibrary.org/2013/07/11/python-distributing-data-support-files-in-a-zip/

前几天,我听说当我们将一些脚本转移到我们的批处理场中时,我们不能转移子文件夹。因此,如果我有一个如下所示的目录结构,它需要扁平化:

Top --> data --> font --> images

我需要把我所有的支持文件放在主目录中,而不是为我的字体、图片等设置子文件夹。我认为这是相当蹩脚的,因为我喜欢保持我的目录有组织。如果你的代码遵循模型-视图-控制器(MVC)模型,那么你也会发现这很烦人。所以我思考了一下这个问题,意识到 Python 可以通过使用 zipfile 库或者通过 zipimport 的一些魔法来访问 zip 存档中的文件。现在我可以使用 zipfile,但是如果我将 Python 文件放在子目录中,或者如果我只想将它们放在 zip 存档的顶部,我希望能够导入它们,所以我决定走这条神奇的路线。

现在你不应该直接使用 zipimport。它实际上是 Python 导入机制的一部分,默认情况下是启用的。所以我们不会在我们的代码中使用它,但是我想你应该知道它在幕后做一些事情。无论如何,只是为了额外的背景,我创建了许多使用各种标志和字体的自定义 pdf。因此,我通常将这些文件保存在单独的目录中,以保持有序。我还使用了大量的配置文件,因为一些客户希望以这种方式完成一些事情。因此,我将向您展示一个非常简单的代码片段,您可以用它来导入 Python 文件和提取配置文件。对于后者,我们将使用 Python 方便的 pkgutil 库。

代码如下:


import os
import pkgutil
import StringIO
import sys
from configobj import ConfigObj

base = os.path.dirname(os.path.abspath( __file__ ))
zpath = os.path.join(base, "test.zip")
sys.path.append(zpath)

import hello

#----------------------------------------------------------------------
def getCfgFromZip():
    """
    Extract the config file from the zip file
    """
    cfg_data = pkgutil.get_data("config", "config.ini")
    print cfg_data
    fileLikeObj = StringIO.StringIO(cfg_data)
    cfg = ConfigObj(fileLikeObj)
    cfg_dict = cfg.dict()
    print cfg_dict

if __name__ == "__main__":
    getCfgFromZip()

现在,我们还有一个名为 test.zip 的 zip 存档,它包含以下内容:

  • hello.py
  • 名为 config 的文件夹,包含两个文件:config.ini 和 init。巴拉圭

如您所见,Python 知道它可以导入 hello 模块,因为我们通过 sys.path.append 将归档文件添加到导入路径中。hello 模块所做的就是向 stdout 输出一条消息。为了提取配置文件,我们使用 pkgutil.get_data(folder_name,file_name)。这将返回一个字符串。因为我们希望将配置加载到 ConfigObj 中,所以我们需要将该字符串转换成类似文件的对象,因此我们使用 StringIO 来实现这个目的。你可以对图像做同样的事情。pkgutil 将返回一个二进制字节字符串,然后您可以将该字符串传递给 reportlab 或 Python 图像库进行进一步处理。我添加了打印语句,以便您可以看到原始数据的样子以及 ConfigObj 的输出。

这就是全部了。我认为这很方便,我希望你在自己的工作中会发现它很有用。

进一步阅读

下载源代码

来自 Packt 的 Python 电子书销售

原文:https://www.blog.pythonlibrary.org/2013/12/20/python-ebook-sale-packt/

Packt Publishing 今天联系了我,告诉我他们所有的电子书都有一个5 美元的销售。他们有很多 Python 书籍,所以我想我应该让我的读者知道这个提议。我以前看过他们的一些书,所以你可以随意看看,以帮助你决定是否想要他们的书:

请注意,我没有审查所有的 Packt 的 Python 书籍。他们拥有的比我看过的数字要多得多。

An Intro to Python Editors

原文:https://www.blog.pythonlibrary.org/2021/09/26/python-editors/

The Python programming language comes with its own built-in Integrated Development Environment (IDE) called IDLE. The name, IDLE, supposedly came from the actor, Eric Idle, who was a part of the Monty Python troupe, which is what Python itself is named after.

IDLE comes with Python on Windows and some Linux variants. You may need to install IDLE separately on your particular flavor of Linux or on Mac if you plan to use the Python that came with the operating system. You should check out the Python website for full instructions on how to do so as each operating system is different.

Here are some of the reasons that Integrated Development Environments are useful:

  • They provide syntax highlighting which helps prevent coding errors
  • Autocomplete of variable names and built-in names
  • Breakpoints and debugging.

On that last point, breakpoints tell the debugger where to pause execution. Debugging is the process of going through your code step-by-step to figure out how it works or to fix an issue with your code.

IDLE itself has other attributes that are useful, such as access to Python documentation, easy access to the source code via the Class Browser, and much more. However, IDLE is not the only way to code in Python. There are many useful IDEs out there. You can also use a text editor if you prefer. Notepad, SublimeText, Vim, etc., are examples of text editors. Text editors do not have all the features that a full-fledged IDE has, but tend to have the advantage of being simpler to use.

Here is a shortlist of IDEs that you can use to program in Python:

  • PyCharm
  • Wing Python IDE
  • VS Code (also called Visual Studio Code)
  • Spyder
  • Eclipse with PyDev

PyCharm and WingIDE both have free and paid versions of their programs. The paid versions have many more features, but if you are just starting out, their free offerings are quite nice. VS Code and Spyder are free. VS Code can also be used for coding in many other languages. Note that to use VS Code effectively with Python, you will need to install a Python extension. You can also use the PyDev plugin for Eclipse to program in Python.

Other popular editors for Python include SublimeText, vim, emacs, and even Notepad++. These editors may not be 100% up-to-date on the syntax of the language, but you can use them for multiple programming languages.

But let's back up a bit and talk about Python's basic console, also known as the REPL, which stands for Read Evaluate Print Loop.

What About the REPL?

REPL or READ, EVAL, PRINT, LOOP is basically Python's interpreter. Python allows you to type code into an interpreter which will run your code live and let you learn about the language. You can access the interpreter, or REPL, by running Python in your terminal (if you are on Mac or Linux) or command console (if you are on Windows).

On Windows, you can go to the Start menu and search for cmd or "Command Prompt" to open the console or terminal:

The Windows Command Prompt

Once you have the terminal open you can try typing python. You should see something like this:

REPL in Windows

If this doesn't work and you get an "Unrecognized Command" or some other error, then Python may not be installed or configured correctly. On Windows, you may need to add Python to your system's path or you can just type out the full path to Python in your command console. For example, if you installed Python in C:\Python\Python38, then you can run it using cmd.exe like you did above, but instead of typing python, you would type C:\Python\Python38\python.

If you need to get help in the REPL, you can type help():

Running help() in the REPL

You can type live Python code into the REPL and it will be immediately evaluated, which means the code will run as soon as you press enter.

Here's how you would print out "Hello World" and add some numbers in the REPL:

Printing in the REPL

Python comes with its own code editor called IDLE. Let's learn about that next!

Getting Started with IDLE

IDLE is a good place to start learning Python. Once you have it installed, you can start it up and the initial screen will look like this:

The IDLE Shell

This is a REPL. You can enter code here and it will be evaluated as soon as you press the Return or Enter key.

If you want to actually write a full program, then you will need to open up the editor view by going to File --> New.

You should now have the following dialog on your screen:

New Code Editor in IDLE

You can enter your code here and save it.

Running Your Code

Let's write a small bit of code in our code editor and then run it. Enter the following code and then save the file by going to File --> Save.

print('Hello World')

To run this code in IDLE, go to the Run menu and choose the first option labeled Run Module:

Running Code in IDLE

When you do this, IDLE will switch to the Shell and show you the output of your program, if there is any:

Output when code is run in IDLE

You can also use the Run menu's Check Module option to check your code for syntax errors.

Accessing Help / Documentation

Sometimes you need help. Fortunately IDLE has some built-in help about itself and the Python language, too! You can access help about IDLE by going to the Help menu and choosing IDLE Help:

IDLE help

If you'd rather look up how something works in the Python language, then go to the Help menu and choose Python Docs or press F1 on your keyboard:

Python documentation in IDLE

This will show you Python's official documentation. Depending on your O/S this may load local help files, or start a browser to show the official on-line help documents.

Restarting the Shell

Let's go back to the Shell screen of IDLE rather than the editor. It has several other functions that are worth going over. The first is that you can restart the shell.

Restarting the shell is useful when you need to start over with a clean slate but don't want to close and reopen the program. To restart the shell, go to the Shell menu and choose Restart Shell:

Restarting IDLE

If you haven't restarted the shell before, then your screen will look like this:

IDLE after restarting

This tells you that your shell has restarted.

Module Browser

IDLE comes with a handy tool called the Module Browser. This tool can be found in the File menu.

When you open it, you will see the following:

Open Module dialog

Modules in Python are code that the Python core development team has created for you. You can use the Module Browser to browse the source code of Python itself.

Try entering the following into the dialog above: os. Then press OK.

You should now see the following:

Module browser

This allows you to browse the source code for os.py. You can double-click anything in the Module Browser and it will jump to the beginning of where that code is defined in IDLE's code editor.

Path Browser

Another useful tool that you can use in IDLE is the Path Browser. The Path Browser allows you to see where Python is installed and also what paths Python uses to import modules from. You will learn more about importing and modules later on in this book.

You can open it by going to File and then Path Browser:

Path Browser

The Path Browser is a good way to diagnose issues with importing modules. It can show you that you might not have Python configured correctly. Or it might show you that you have installed a 3rd party module in the wrong location.

Getting Started with PyCharm Community Edition

PyCharm is a commercial Python IDE from a company called JetBrains. They have a professional version, which costs money, and a community edition, which is free. PyCharm is one of the most popular choices for creating and editing Python programs.

PyCharm Professional has tons of features and is a great debugger. However, if you are a beginner, you may find all the functionality in this software to be a bit overwhelming.

To get a copy of PyCharm Community Edition, you can go to the following website:

https://www.jetbrains.com/pycharm/

The Community Edition does not have all the features that PyCharm Professional has. But that is okay when you are new to Python. If you would like to try PyCharm, go ahead and download and install the software.

When you run PyCharm it may ask you to import settings. You can ignore that or import settings if you have used PyCharm previously and already have some.

Next, you will probably need to accept their privacy policy / EULA. Depending on the operating system, you may also get asked what theme to apply. The default is Darkula on Windows.

At this point you should see the following Welcome banner:

PyCharm Initial Screen

PyCharm prefers that you work in a project rather than opening a simple file. Projects are typically collections of related files or scripts. You can set up a new project here or open a pre-existing one.

Once you have gone through that process, your screen should look like this:

PyCharm Editor without any files open

Creating a Python Script

To create a new Python script in PyCharm, you can go to File and choose New. Then pick Python File from the choices presented:

PyCharm new dialog

Give the file a name, such as hello.py. Now PyCharm should look like this:

Hello world file in PyCharm

Running Code in PyCharm

Let's add some code to your file:

print('Hello PyCharm')

To run your code, go to the Run menu and choose Run. PyCharm might ask you to set up a debug configuration before running it. You can save the defaults and continue.

You should now see the following at the bottom of PyCharm:

Running code in PyCharm

PyCharm Features

PyCharm has tons of features. In fact, it has so many that you could write an entire book on them. For the purposes of this book, you should know that PyCharm will give you suggestions about your code based on PEP8, which is Python's code style guide. You will learn more about that in the next chapter. It will also highlight many other things about your code.

You can usually hover over any code that looks weird to you and a tooltip will appear that will explain the issue or warning.

The debugger that ships with PyCharm is useful for figuring out why your code doesn't work. You can use it to walk through your code line-by-line.

PyCharm's documentation is quite good, so if you get stuck, check their documentation.

Getting Started with Wing Personal

Wingware's Python IDE is written in Python and PyQt. It is my personal favorite IDE for Python. You can get it in Professional (paid), Personal (free) or 101 (really stripped-down version, but also free). Their website explains the differences between the 3 versions.

You can get Wingware here:

https://wingware.com/

After you have downloaded and installed the software, go ahead and run it. You will need to accept the License Agreement to load up the IDE.

Once it is fully loaded, you will see something like this:

Wing Personal Initial Screen

Running Code in Wingware

Let's create some code in Wing. You can open a new file by going to the File menu and choosing New:

Creating a new file in Wing

Now enter the following code:

print('Hello Wingware')

Save the code to disk by going to File and then Save.

To run this code, you can go to the Debug menu, press F5 or click the green "play" button in the toolbar. You will see a debug message dialog:

Running code in Wing

Hit OK and the code will run. You will see the output in the Debug I/O tab if there is any.

Note that Wing does not require you to create a project to run a single Python file. You can create projects if you want to though.

Wing Features

Wing has an incredible debugger. However, you cannot use it to its full extent in the free versions of the software. But there is a Source Assistant tab in the Personal edition that is very useful. It will show you information about the functions / modules that you have loaded as you use them. This makes learning new modules much easier.

Wing will also show you various issues with your code while you type, although PyCharm seems to do more in this area than Wing does.

Both products have plugins and you can write your own for both IDEs as well.

Getting Started with Visual Studio Code

Visual Studio Code, or VS Code for short, is a general-purpose programming editor. Unlike PyCharm and WingIDE, it is designed to work with lots of languages. PyCharm and WingIDE will let you write in other languages too, but their primary focus is on Python.

VS Code is made by Microsoft and it is free. You can download it here:

https://code.visualstudio.com/

Once you have it downloaded and installed, you will need to install support for Python from the VS Code marketplace.

If you open up VS Code, the screen will look something like this:

Initial VS Code screen

Under Customize you can see there is an option for installing Python. If that isn't there, you can click on the Extensions button that is on the left and search for Python there:

Installing Python in VS Code

Go ahead and install the Python extension so that VS Code will recognize Python correctly.

Running Code in VS Code

Open a folder in the File Explorer tab and then you can right-click in there to create a new file. Alternatively, you can go to the File menu and choose New File and do it that way.

Once that is done, you can enter the following code and save it:

print('Hello VS Code')

Then right-click anywhere in the editor and select the Run Python File in Terminal selection. This will cause your code to run and you will see the following:

Running Python code in VS Code

Note: I didn't have the PSReadline module installed when I ran this code which is why you see the error in the console above.

VS Code Features

VS Code can run all kinds of different languages. However, for the purposes of Python, Microsoft has a team of Python developers that are constantly improving this IDE and adding new features. There are tons of extensions that you can install to enhance the editor's functionality.

One of the coolest extensions that you can install is Live Share, which lets you do real-time collaboration between developers. It basically shares your coding session with others. Since this IDE is the newest of the bunch and its feature set is changing a lot, you will need to research it on your own time.

Wrapping Up

There are lots of Python code editors to choose from. IDLE is nice in that it comes with Python and is written in Python, so you can actually learn a lot just by looking at its source code. PyCharm and VS Code are very popular right now. Wing IDE used to be more popular than it is today, but I think it is still really great. All of these tools are good, but you should give them a try to see which one works the best for you.

Want to learn more Python basics? Then check out the following tutorials:

Python:在 Windows 中查找提交费用值

原文:https://www.blog.pythonlibrary.org/2010/03/05/python-finding-the-commit-charge-values-in-windows/

本周,我的任务是设法找出我们虚拟工作站上的峰值提交值。原因是我们试图节省资金,并想知道我们是否分配了太多的内存。我们不需要总提交费用或限制提交费用值,但是由于我在研究中已经知道如何获得这些值,所以我也将展示如何获得这些值。

当我第一次开始搜索这个主题时,我尝试了诸如“python 峰值提交值”及其变体这样的搜索术语。这让我一无所获,所以我用“wmi”替换了“oython ”,并在 MSDN 上找到了Win32 _ performatteddata _ perfs _ Memory类。我以为这就是了,但它只给了我提交费用限额和总提交费用。下面是我如何使用蒂姆·戈登的 WMI 模块得到这些值的:


import wmi

c = wmi.WMI()

for item in c.Win32_PerfFormattedData_PerfOS_Memory():
    commitChargeLimit = int(item.CommitLimit) / 1024

for item in c.Win32_PerfFormattedData_PerfOS_Memory():
    commitChargeTotal = int(item.CommittedBytes) / 1024

print "Commit Charge Limit: ", commitChargeLimit
print "Commit Charge Total: ", commitChargeTotal

这是很好的东西,显示了在 MSDN 获取文档并将其翻译成可用的 Python 语言是多么容易。不幸的是,它并没有给我所需要的信息。我的下一站是 PyWin32 邮件列表,Mark Hammond 在那里告诉我 win32pdh 和 win32pdhutil 模块。这些公开了性能计数器,但是我也找不到使用它来获取这些信息的方法。幸运的是,我在 sysinternals 论坛上找到了一个旧帖子,给了我一个线索。它是这样说的:

据我所知,获得这一细节的唯一方法是从 SYSTEM_PERFORMANCE_INFORMATION 结构的 uMmPeakCommitLimit 成员中获取,当使用 SystemPerformanceInformation 类型调用它时,会将该成员传递给 NtQuerySystemInformation。

我问哈蒙德先生这是否意味着我需要使用 ctypes ,因为 NtQuerySystemInformation 类没有被 PyWin32 公开,他说“可能”。ctypes 模块非常低级,除了从 ActiveState 复制脚本时,我没有用过它。这是一个非常方便的模块,在 2.5 版本中被添加到了标准库中。据我所知,它是由托马斯·海勒创作的。

反正 ctypes 有自己的邮件列表,所以我决定去那里试试。我收到了两个回复,其中一个是那个人本人(海勒)。他给了我一个剧本,开始看起来不太管用,但是和他反复讨论之后,他把我弄明白了。结果如下:


from ctypes import *

SystemBasicInformation = 0
SystemPerformanceInformation = 2

class SYSTEM_BASIC_INFORMATION(Structure):
    _fields_ = [("Reserved1", c_long * 10),
                ("NumberOfProcessors", c_byte),
                ("bUnknown2", c_byte),
                ("bUnknown3", c_short)
                ]

class SYSTEM_PERFORMANCE_INFORMATION(Structure):
    _fields_ = [("IdleTime", c_int64),
                ("ReadTransferCount", c_int64),
                ("WriteTransferCount", c_int64),
                ("OtherTransferCount", c_int64),
                ("ReadOperationCount", c_ulong),
                ("WriteOperationCount", c_ulong),
                ("OtherOperationCount", c_ulong),
                ("AvailablePages", c_ulong),
                ("TotalCommittedPages", c_ulong),
                ("TotalCommitLimit", c_ulong),
                ("PeakCommitment", c_ulong),
                ("PageFaults", c_ulong),
                ("WriteCopyFaults", c_ulong),
                ("TransitionFaults", c_ulong),
                ("Reserved1", c_ulong),
                ("DemandZeroFaults", c_ulong),
                ("PagesRead", c_ulong),
                ("PageReadIos", c_ulong),
                ("Reserved2", c_ulong * 2),
                ("PagefilePagesWritten", c_ulong),
                ("PagefilePageWriteIos", c_ulong),
                ("MappedFilePagesWritten", c_ulong),
                ("MappedFilePageWriteIos", c_ulong),
                ("PagedPoolUsage", c_ulong),
                ("NonPagedPoolUsage", c_ulong),
                ("PagedPoolAllocs", c_ulong),
                ("PagedPoolFrees", c_ulong),
                ("NonPagedPoolAllocs", c_ulong),
                ("NonPagedPoolFrees", c_ulong),
                ("TotalFreeSystemPtes", c_ulong),
                ("SystemCodePage", c_ulong),
                ("TotalSystemDriverPages", c_ulong),
                ("TotalSystemCodePages", c_ulong),
                ("SmallNonPagedLookasideListAllocateHits", c_ulong),
                ("SmallPagedLookasideListAllocateHits", c_ulong),
                ("Reserved3", c_ulong),
                ("MmSystemCachePage", c_ulong),
                ("PagedPoolPage", c_ulong),
                ("SystemDriverPage", c_ulong),
                ("FastReadNoWait", c_ulong),
                ("FastReadWait", c_ulong),
                ("FastReadResourceMiss", c_ulong),
                ("FastReadNotPossible", c_ulong),
                ("FastMdlReadNoWait", c_ulong),
                ("FastMdlReadWait", c_ulong),
                ("FastMdlReadResourceMiss", c_ulong),
                ("FastMdlReadNotPossible", c_ulong),
                ("MapDataNoWait", c_ulong),
                ("MapDataWait", c_ulong),
                ("MapDataNoWaitMiss", c_ulong),
                ("MapDataWaitMiss", c_ulong),
                ("PinMappedDataCount", c_ulong),
                ("PinReadNoWait", c_ulong),
                ("PinReadWait", c_ulong),
                ("PinReadNoWaitMiss", c_ulong),
                ("PinReadWaitMiss", c_ulong),
                ("CopyReadNoWait", c_ulong),
                ("CopyReadWait", c_ulong),
                ("CopyReadNoWaitMiss", c_ulong),
                ("CopyReadWaitMiss", c_ulong),
                ("MdlReadNoWait", c_ulong),
                ("MdlReadWait", c_ulong),
                ("MdlReadNoWaitMiss", c_ulong),
                ("MdlReadWaitMiss", c_ulong),
                ("ReadAheadIos", c_ulong),
                ("LazyWriteIos", c_ulong),
                ("LazyWritePages", c_ulong),
                ("DataFlushes", c_ulong),
                ("DataPages", c_ulong),
                ("ContextSwitches", c_ulong),
                ("FirstLevelTbFills", c_ulong),
                ("SecondLevelTbFills", c_ulong),
                ("SystemCalls", c_ulong)]

sbi = SYSTEM_BASIC_INFORMATION()
retlen = c_ulong()

res = windll.ntdll.NtQuerySystemInformation(SystemBasicInformation,
                                            byref(sbi),
                                            sizeof(sbi),
                                            byref(retlen))
print res, retlen
print sbi.NumberOfProcessors

spi = SYSTEM_PERFORMANCE_INFORMATION()
retlen = c_ulong()

res = windll.ntdll.NtQuerySystemInformation(SystemPerformanceInformation,
                                            byref(spi),
                                            sizeof(spi),
                                            byref(retlen))
print res, retlen
print "Peak commit: ",
print spi.PeakCommitment * 4096 / 1024

我真的不明白这里发生的一切,但我很高兴它起作用了。嗯,我应该说它在 Windows XP Professional 上工作,32 位,Python 2.5。我在 64 位的 Windows 7 上也尝试过,当脚本运行时,它返回“0L”。我猜 64 位操作系统需要稍微不同的脚本,但是因为我们所有的工作站目前都使用 32 位,所以这一点并不重要。Python 社区再一次帮助了我,向我展示了他们有多棒!

面向程序员的 Python 电子书赠品

原文:https://www.blog.pythonlibrary.org/2019/03/26/python-for-programmers-ebook-giveaway/

更新:这本书的所有副本现已被认领!感谢您的检查!

Pearson 最近联系我,介绍他们的新 Python 书籍程序员 Python:大数据和人工智能案例研究

我有 5 本电子书要赠送。你需要做的就是在推特上发布这篇文章,给我贴上标签 @driscollis ,然后在推特上直接给我发一条带有链接的消息,或者通过这个博客上的联系表格把链接发给我。

以下是他们网站上关于这本书的更多信息:

面向具有任何高级语言背景的开发人员编写的《面向程序员的 Python 和数据科学入门》深入探讨了 Python 语言和 Python APIs,将 Deitels 的签名实时代码方法应用于编程教学。Paul Deitel 和 Harvey M. Deitel 博士在经过全面测试的程序环境中介绍概念,包括语法阴影、代码突出显示、逐行代码遍历和程序输出。它们包含数百个完整的 Python 程序,近 20,000 行经过验证的 Python 代码,以及数百个帮助您构建健壮应用程序的技巧。您将从使用早期的类和对象方法介绍 Python 开始,然后快速转向更高级的主题。

在整个过程中,您将享受到 Deitels 对面向对象编程的经典处理。完成后,您将拥有构建工业级 Python 应用程序所需的一切。

Kickstarter 上的真正的 Python Web 开发书籍

原文:https://www.blog.pythonlibrary.org/2013/02/19/python-for-web-development-book-on-kickstarter/

上周,有人联系我,说在 Kickstarter 上有一本听起来很酷的书,书名是《面向 Web 开发的真实 Python》,作者是 Michael Herman。我不得不承认,我不熟悉赫尔曼先生,也不熟悉最初就这本书联系我的那个人,但因为我喜欢阅读 Python 书籍,而且这本书听起来很有趣,所以我想我也应该让我的读者了解一下。如果你愿意,你可以自己支持这个项目。他最近在书中添加了一些关于 Flask 的教程,因此您可以了解一些关于两个 Python web 框架的知识!

Python -如何使用 functools.wraps

原文:https://www.blog.pythonlibrary.org/2016/02/17/python-functools-wraps/

今天我想谈谈一个鲜为人知的工具。它叫做 包裹 ,是 functools 模块的一部分。您可以使用包装作为修饰器来修复文档字符串和被修饰函数的名称。为什么这很重要?起初,这听起来像是一个奇怪的边缘案例,但是如果你正在编写一个 API 或者其他人会使用的任何代码,那么这可能是很重要的。原因是当你使用 Python 的内省来理解别人的代码时,修饰过的函数会返回错误的信息。让我们来看一个简单的例子,我称之为decrument . py:


# decorum.py

#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """

    def wrapper():
        """
        A wrapping function
        """
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return wrapper

#----------------------------------------------------------------------
@another_function
def a_function():
    """A pretty useless function"""
    return "1+1"

#----------------------------------------------------------------------
if __name__ == "__main__":
    print(a_function.__name__)
    print(a_function.__doc__)

在这段代码中,我们用的另一个 _ 函数来修饰名为 a_function 的函数。您可以使用函数的 namedoc 属性打印出 a_function 的名称和 docstring。如果您运行这个示例,您将得到以下输出:


wrapper

        A wrapping function

这是不对的!如果你在 IDLE 或者解释器中运行这个程序,它会变得更加明显,变得非常混乱,非常快。


>>> import decorum
>>> help(decorum)
Help on module decorum:

NAME
    decorum - #----------------------------------------------------------------------

FILE
    /home/mike/decorum.py

FUNCTIONS
    a_function = wrapper()
        A wrapping function

    another_function(func)
        A function that accepts another function

>>> help(decorum.a_function)
Help on function other_func in module decorum:

wrapper()
    A wrapping function

基本上,这里发生的是装饰器将被装饰的函数的名称和 docstring 改为它自己的。


快来救援!

我们如何解决这个小问题?Python 开发者在 functools.wraps 中给了我们解决方案!让我们来看看:


from functools import wraps

#----------------------------------------------------------------------
def another_function(func):
    """
    A function that accepts another function
    """

    @wraps(func)
    def wrapper():
        """
        A wrapping function
        """
        val = "The result of %s is %s" % (func(),
                                          eval(func())
                                          )
        return val
    return wrapper

#----------------------------------------------------------------------
@another_function
def a_function():
    """A pretty useless function"""
    return "1+1"

#----------------------------------------------------------------------
if __name__ == "__main__":
    #a_function()
    print(a_function.__name__)
    print(a_function.__doc__)

这里我们从 functools 模块导入包装器,并将其用作 another_function 内部嵌套包装器函数的装饰器。如果这次运行它,输出将会发生变化:


a_function
A pretty useless function

现在我们又有了正确的名称和 docstring。如果你进入你的 Python 解释器,帮助功能现在也可以正常工作了。我将跳过把它的输出放在这里,留给你去尝试。


包扎

wraps decorator 很像一匹只会一招的小马,但是当你需要它的时候,它非常方便。如果您碰巧注意到您的函数没有给您正确的名称或 docstring,那么您现在知道如何非常容易地修复它了。祝您编码愉快!

Python 获得 DARPA 资助的大数据项目:Blaze

原文:https://www.blog.pythonlibrary.org/2013/02/14/python-gets-funded-by-darpa-for-big-data-project-blaze/

我第一次听说 Blaze 是在 2012 年 12 月,是从 NumPy 最初的开发者的博客上。几天前,信息周刊宣布 DARPA 正在资助这个项目,总计 300 万美元,以获得一些为 Python 编写的大数据库。将会有两个新项目,Blaze 和 Bokeh。Blaze 将是 NumPy 和 SciPy 的扩展,并使这些库对大数据友好。散景项目将用于大数据可视化。

Blaze 将是开源的。你可以在这里阅读更多相关信息。我无法找到一个具体的链接到散景项目。

Python GUI 框架(视频)

原文:https://www.blog.pythonlibrary.org/2021/03/02/python-gui-frameworks-video/

在本教程中,我将讨论一些 Python 最流行的 GUI 框架。你将学习图形用户界面的基础知识。然后您将学习如何使用 wxPython 创建一个简单的图像查看器。最后,您将看到如何使用 PySimpleGUI 重写图像查看器。

https://www.youtube.com/embed/Feb79MiFcOg?feature=oembed

相关阅读

Python:如何创建异常日志装饰器

原文:https://www.blog.pythonlibrary.org/2016/06/09/python-how-to-create-an-exception-logging-decorator/

前几天,我决定创建一个装饰器来捕捉异常并记录它们。我在 Github 上找到了一个相当复杂的例子,我用它来思考如何完成这项任务,并得出了以下结论:


# exception_decor.py

import functools
import logging

def create_logger():
    """
    Creates a logging object and returns it
    """
    logger = logging.getLogger("example_logger")
    logger.setLevel(logging.INFO)

    # create the logging file handler
    fh = logging.FileHandler("/path/to/test.log")

    fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)

    # add handler to logger object
    logger.addHandler(fh)
    return logger

def exception(function):
    """
    A decorator that wraps the passed in function and logs 
    exceptions should one occur
    """
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        logger = create_logger()
        try:
            return function(*args, **kwargs)
        except:
            # log the exception
            err = "There was an exception in  "
            err += function.__name__
            logger.exception(err)

            # re-raise the exception
            raise
    return wrapper

在这段代码中,我们有两个函数。第一个创建一个日志对象并返回它。第二个函数是我们的装饰函数。这里,我们将传入的函数包装在一个 try/except 中,并使用我们的记录器记录发生的任何异常。您会注意到,我还记录了发生异常的函数名。

现在我们只需要测试一下这个装饰器。为此,您可以创建一个新的 Python 脚本,并向其中添加以下代码。确保将它保存在与上面代码相同的位置。


from exception_decor import exception

@exception
def zero_divide():
    1 / 0

if __name__ == '__main__':
    zero_divide()

当您从命令行运行此代码时,您应该得到一个包含以下内容的日志文件:


2016-06-09 08:26:50,874 - example_logger - ERROR - There was an exception in  zero_divide
Traceback (most recent call last):
  File "/home/mike/exception_decor.py", line 29, in wrapper
    return function(*args, **kwargs)
  File "/home/mike/test_exceptions.py", line 5, in zero_divide
    1 / 0
ZeroDivisionError: integer division or modulo by zero

我认为这是一个方便的代码,我希望你会发现它也很有用!

UPDATE :一位敏锐的读者指出,将这个脚本一般化是一个好主意,这样您就可以向装饰者传递一个 logger 对象。让我们来看看它是如何工作的!

将一个记录器传递给我们的装饰者

首先,让我们将我们的日志代码分离到它自己的模块中。姑且称之为 exception_logger.py 。下面是放入该文件的代码:


# exception_logger.py

import logging

def create_logger():
    """
    Creates a logging object and returns it
    """
    logger = logging.getLogger("example_logger")
    logger.setLevel(logging.INFO)

    # create the logging file handler
    fh = logging.FileHandler(r"/path/to/test.log")

    fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)

    # add handler to logger object
    logger.addHandler(fh)
    return logger

logger = create_logger()

接下来,我们需要修改装饰器代码,这样我们就可以接受一个日志记录器作为参数。务必将其保存为 exception_decor.py


# exception_decor.py

import functools

def exception(logger):
    """
    A decorator that wraps the passed in function and logs 
    exceptions should one occur

    @param logger: The logging object
    """

    def decorator(func):

        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except:
                # log the exception
                err = "There was an exception in  "
                err += func.__name__
                logger.exception(err)

            # re-raise the exception
            raise
        return wrapper
    return decorator

你会注意到这里有多层嵌套函数。请务必仔细研究,以了解发生了什么。最后我们需要修改我们的测试脚本:


from exception_decor import exception
from exception_logger import logger

@exception(logger)
def zero_divide():
    1 / 0

if __name__ == '__main__':
    zero_divide()

这里我们导入了我们的装饰器和记录器。然后我们装饰我们的函数,并把 logger 对象传递给 decorator。如果运行这段代码,您应该会看到与第一个示例中生成的文件相同的文件。玩得开心!

Python:如何创建旋转日志

原文:https://www.blog.pythonlibrary.org/2014/02/11/python-how-to-create-rotating-logs/

Python 的日志模块有很多选项。在本文中,我们将研究日志模块创建循环日志的能力。Python 支持两种类型的旋转日志:

  • 基于大小旋转日志(旋转文件处理器
  • 根据某个时间间隔( TimedRotatingFileHandler )轮换日志

让我们花一些时间来了解这两种类型的记录器是如何实现和使用的。


旋转文件处理程序

日志模块中的 RotatingFileHandler 类允许开发人员创建一个日志处理程序对象,使他们能够根据日志的大小来旋转日志。您可以使用 maxBytes 参数来告诉它何时旋转日志。这意味着当日志达到一定的字节数时,它会被“翻转”。当文件大小即将超出时,会出现这种情况。处理程序将关闭该文件,并自动打开一个新文件。如果您为 backupCount 参数传入一个数字,那么它会将“. 1”、“. 2”等附加到日志文件的末尾。让我们看一个简单的例子:


import logging
import time

from logging.handlers import RotatingFileHandler

#----------------------------------------------------------------------
def create_rotating_log(path):
    """
    Creates a rotating log
    """
    logger = logging.getLogger("Rotating Log")
    logger.setLevel(logging.INFO)

    # add a rotating handler
    handler = RotatingFileHandler(path, maxBytes=20,
                                  backupCount=5)
    logger.addHandler(handler)

    for i in range(10):
        logger.info("This is test log line %s" % i)
        time.sleep(1.5)

#----------------------------------------------------------------------
if __name__ == "__main__":
    log_file = "test.log"
    create_rotating_log(log_file)

这段代码基于 Python 日志记录指南中的一个例子。这里我们创建一个日志级别为 INFO 的循环日志。然后,我们设置处理程序,每当日志文件的长度为 20 个字节时就旋转日志。是的,这是一个低得荒谬的数字,但它使演示发生了什么更容易。接下来,我们创建一个循环,它将在日志文件中创建 10 行,在每次调用 log 之间有一个休眠。如果您运行这段代码,您应该得到六个文件:原始的 test.log 和 5 个备份日志。

现在让我们看看如何使用一个 TimedRotatingFileHandler


TimedRotatingFileHandler

TimedRotatingFileHandler 允许开发人员根据过去的时间创建一个循环日志。您可以将其设置为在以下时间条件下旋转日志:

  • 分钟(米)
  • 小时
  • 日(d)
  • w0-w6(工作日,0 =星期一)
  • 午夜

要设置其中一个条件,只需在第二个参数 when 中传递它。您还需要设置间隔参数。让我们来看一个例子:


import logging
import time

from logging.handlers import TimedRotatingFileHandler

#----------------------------------------------------------------------
def create_timed_rotating_log(path):
    """"""
    logger = logging.getLogger("Rotating Log")
    logger.setLevel(logging.INFO)

    handler = TimedRotatingFileHandler(path,
                                       when="m",
                                       interval=1,
                                       backupCount=5)
    logger.addHandler(handler)

    for i in range(6):
        logger.info("This is a test!")
        time.sleep(75)

#----------------------------------------------------------------------
if __name__ == "__main__":
    log_file = "timed_test.log"
    create_timed_rotating_log(log_file)

此示例将每分钟轮换一次日志,备份计数为 5。更实际的轮换可能是在小时上,所以您应该将间隔设置为 60,或者将时间设置为“h”。当这段代码运行时,它也将创建 6 个文件,但是它将使用 strftime 格式 %Y-%m-%d_%H-%M-%S 附加一个时间戳,而不是将整数附加到日志文件名上。


包扎

现在你知道如何使用 Python 强大的旋转日志了。希望你能把它集成到你自己的应用程序或未来的程序中。


相关阅读

Python:如何判断 Windows 空闲了多长时间

原文:https://www.blog.pythonlibrary.org/2010/05/05/python-how-to-tell-how-long-windows-has-been-idle/

有一天,我收到了一个请求,要求创建一个脚本,可以告诉 Windows XP 机器空闲了多长时间,并在空闲了一定时间后提醒用户。我在谷歌上做了一点研究,找到了几个方法来完成这个壮举。我唯一能够使用的是 ctypes 示例,所以事不宜迟,让我们来看看吧!

以下 ctypes 相关代码摘自 stackoverflow 论坛:


from ctypes import Structure, windll, c_uint, sizeof, byref

# http://stackoverflow.com/questions/911856/detecting-idle-time-in-python
class LASTINPUTINFO(Structure):
    _fields_ = [
        ('cbSize', c_uint),
        ('dwTime', c_uint),
    ]

def get_idle_duration():
    lastInputInfo = LASTINPUTINFO()
    lastInputInfo.cbSize = sizeof(lastInputInfo)
    windll.user32.GetLastInputInfo(byref(lastInputInfo))
    millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
    return millis / 1000.0

如果你不理解上面的代码,请在 ctypes 邮件列表上寻求帮助。我也不是完全理解。我明白了基本要点,但仅此而已。下面是我用来让上面的代码发挥作用的片段:


while 1:
    GetLastInputInfo = int(get_idle_duration())
    print GetLastInputInfo
    if GetLastInputInfo == 480:
        # if GetLastInputInfo is 8 minutes, play a sound
        sound = r"c:\windows\media\notify.wav"
        winsound.PlaySound(sound, winsound.SND_FILENAME)
    if GetLastInputInfo == 560:
        # if GetLastInputInfo is 9 minutes, play a more annoying sound
        sound = r"c:\windows\media\ringout.wav"
        winsound.PlaySound(sound, winsound.SND_FILENAME)
        winsound.PlaySound(sound, winsound.SND_FILENAME)
        winsound.PlaySound(sound, winsound.SND_FILENAME)

    time.sleep(1)

在我的代码中,我检查机器是否空闲了 8 分钟和 9 分钟。根据空闲时间的长短,代码使用 Python winsound 模块播放特定的 wav 文件。在我们店里,有些机器闲置 10 分钟就会自动上锁。我们的用户不太喜欢这样,所以他们要求我们在机器即将锁定时发出声音警告他们。这就是这个脚本要完成的任务。希望你能更好地利用这些知识。

python——如何判断函数是否被调用

原文:https://www.blog.pythonlibrary.org/2017/03/10/python-how-to-tell-if-a-function-has-been-called/

去年,我遇到了一种情况,我需要知道一个函数是否被调用了。基本上,我们试图防止关闭一个扭曲的事件循环两次或启动其中两次。总之,在我的研究中,我无意中发现了一个有趣的帖子,展示了几个这样做的方法。

第一种利用了 Python 中的一切都是对象的事实,包括函数本身。让我们看一个简单的例子:


def self_aware_function(a, b):
    self_aware_function.has_been_called = True
    return a + b

if __name__ == '__main__':
    self_aware_function.has_been_called = False

    for i in range(2):
        if self_aware_function.has_been_called:
            print('function already called')
        else:
            print('function not called')

        self_aware_function(1, 2)

在这个例子中,我们在被命名为的函数上创建了一个属性。当函数被调用时,我们将其设置为 True。当你启动你的程序时,你会想要初始化这个属性为 False,就像上面我们做的那样。然后我们用一个 for 循环循环两次。第一次通过它将检查函数是否被调用。既然没有,你会看到它落到 else 语句。现在我们调用了函数,第二次通过循环执行 if 语句的第一部分。

StackOverflow 的那篇文章还提到了一种使用装饰器跟踪函数调用的好方法。下面是我写的一个例子:


import functools

def calltracker(func):
    @functools.wraps(func)
    def wrapper(*args):
        wrapper.has_been_called = True
        return func(*args)
    wrapper.has_been_called = False
    return wrapper

@calltracker
def doubler(number):
    return number * 2

if __name__ == '__main__':
    if not doubler.has_been_called:
        print("You haven't called this function yet")
        doubler(2)

    if doubler.has_been_called:
        print('doubler has been called!')

在这个例子中,我导入了 functools 并创建了一个装饰器,我称之为 calltracker 。在这个函数中,我们设置了与上一个例子相同的属性,但是在这个例子中,我们将它附加到我们的包装器(即装饰器)上。然后我们修饰一个函数,并尝试一下我们的代码。第一个 if 语句检查函数是否已经被调用。它没有,所以我们继续调用它。然后,我们确认在第二个 if 语句中调用了该函数。

包扎

虽然这些东西在运行时确实有用,但是您也可以使用 Python 的 trace 模块来跟踪代码的执行,从而做类似的事情。这类事情也是通过覆盖工具来完成的。你也会在 Python 模拟对象中发现这种功能,因为模拟可以告诉你它何时被调用。

无论如何,希望你会发现这个练习和我一样有趣。虽然我已经知道 Python 中的一切都是对象,但我还没有想过使用这种功能给函数添加属性。

Python 图像处理 Kickstarter 下周上线!

原文:https://www.blog.pythonlibrary.org/2020/12/29/python-image-processing-kickstarter-coming-next-week/

我将在 1 月 4 日星期一**发布一个新的 Kickstarter 来帮助发布我的第九本书, Pillow:用 Python 处理图像。**

在这本书里,你将学习如何用 Python 编辑照片。你会发现如何提取元数据,裁剪,应用过滤器,调整大小等等!

Pillow: Image Processing with Python Kickstarter

以下是高级目录:

Python 访谈书发布!

原文:https://www.blog.pythonlibrary.org/2018/03/01/python-interviews-book-released/

我的 Python 访谈书现在正式发布了!在这篇文章中,您会发现 20 篇来自不同领域的 Python 专家的访谈。

我还有一个来自 Packt 的特殊代码,它将为多达 1000 名读者提供电子书 40%的折扣。结账时只需套用以下代码: PIMD40 。此码有效期至 2018 年 3 月 16 日。

注:该书目前仅在 Packt 发售,但将于 2018 年 3 月 9 日在亚马逊和其他零售点发售。

Packt 有向开源项目捐款的历史,并希望从这本书中向 Python 软件基金会捐款。因此,Packt 在 3 月份卖出的每一本书,他们都会通过自己的电子商务网站和亚马逊的折扣代码向 PSF 捐赠。亚马逊的折扣代码是 30PYTHON (这个代码可能要到 3 月 9 日才能生效)

Python 访谈摘录:塞巴斯蒂安·拉什卡

原文:https://www.blog.pythonlibrary.org/2018/03/05/python-interviews-excerpt-sebastian-raschka/

以下是摘自我的书 Python 访谈

塞巴斯蒂安·拉什卡2017 年获得密歇根州立大学定量生物学和生物化学及分子生物学博士学位。Sebastian 是 Python 机器学习的畅销书作者,该书获得了 2016 年 ACM 最佳计算奖。

Driscoll : Python 是目前人工智能和机器学习中使用的语言之一。你能解释是什么使它如此受欢迎吗?

拉什卡:我觉得主要有两个原因,这两个原因很有关系。第一个原因是 Python 超级容易阅读和学习。

我认为,大多数从事机器学习和人工智能工作的人都希望专注于以最便捷的方式尝试他们的想法。重点是研究和应用,编程只是让你达到目的的工具。一门编程语言学起来越容易,对于更多数学和统计导向的人来说,入门门槛就越低。

Python 还具有超强的可读性,这有助于与机器学习和人工智能的现状保持同步,例如在阅读算法和思想的代码实现时。尝试人工智能和机器学习的新想法通常需要实现相对复杂的算法,语言越透明,就越容易调试。

第二个主要原因是,虽然 Python 本身是一种简单的语言,但我们在它的基础上有许多很棒的库,使我们的工作更容易。没有人愿意花时间从零开始重新实现基本算法(除非是在研究机器学习和人工智能的背景下)。现有的大量 Python 库有助于我们专注于比重新发明轮子更令人兴奋的事情。

顺便说一下,Python 也是一种优秀的包装语言,可以与更高效的 C/C++实现和 CUDA/cuDNN 一起工作,这就是为什么现有的机器学习和深度学习库在 Python 中运行得非常高效。这对于从事机器学习和 AI 领域的工作也是超级重要的。

总而言之,我想说 Python 是一种伟大的语言,它让研究人员和从业者专注于机器学习和人工智能,并且比其他语言更少分心。

Driscoll :那么 Python 只是在正确的时间成为正确的工具,还是有其他原因让它在人工智能和机器学习中变得如此重要?

拉什卡:我认为这是一个先有鸡还是先有蛋的问题。

要解开它,我会说 Python 使用方便,这导致了它的广泛采用。社区在科学计算的背景下开发了许多有用的包。许多机器学习和人工智能开发人员更喜欢 Python 作为科学计算的通用编程语言,他们已经在它的基础上开发了库,如 Theano,MXNet,TensorFlow 和 PyTorch。

有趣的是,我一直活跃在机器学习和深度学习社区,有一件事我经常听到:“Torch 库很棒,但它是用 Lua 编写的,我不想花时间学习另一种语言。”请注意,我们现在有 PyTorch。

里看剩下的采访。使用以下代码从 Packt 网站购买可以获得 40 折:pimd 40。此码有效期至2018 年 3 月 16 日

根据 IEEE Spectrum,Python 在 2017 年排名第一

原文:https://www.blog.pythonlibrary.org/2017/07/23/python-is-1-in-2017-according-to-ieee-spectrum/

看到哪些语言被认为是前十名总是很有趣的。今年, IEEE Spectrum 将 Python 命名为 Web 和企业类别的第一语言。在 Reddit 的一些 Python 社区认为语言的评分是有缺陷的,因为 Javascript 在 web 编程中处于 R 以下。这也让我犹豫了。坦白地说,当谈到 web 编程时,我真的看不出有什么比 Javascript 更好。

不管怎样,通读这篇文章还是很有意思的。

相关文章

2010 年 12 月 23 日的 Python 链接

原文:https://www.blog.pythonlibrary.org/2010/12/23/python-links-for-12-23-2010/

我认为就有趣的 Python /技术新闻项目创建一个每周或每两周一次的系列可能会很有趣(并且自我激励)。我知道这很没创意,但我喜欢创建一个常规“专栏”的想法,我希望它能帮助我保持写作状态。Ned Batchelder 也经常做这种事情,所以也可以随时查看他的。我会附上一些评论,这样你就知道你得到了什么,我对这个话题有什么看法。

  • 蓝鲷是新的 Zope 3。是的,这是旧闻,但我一直忘记新名字。
  • 有人会说这也是旧闻:金字塔是新的。让我想起了皮纳斯和姜戈。也许我这样做是为了帮助我跟踪不断变化的 Python web 框架!
  • PyPy 1.4 出来了!我从未用过 PyPy,但我喜欢这个概念。不幸的是,这个版本没有 Windows 版本。从好的方面来看,崔泰已经写了一篇关于如何建造一个的文章。
  • 不知道如何在 Windows 64 位机器上编译 PyCrypto?看这篇文章,增长见识!
  • 你最喜欢的蛇诞生了它的最新版本:Python 3.2 beta 2。阅读新内容或者下载并开始报告你发现的任何错误
  • 我刚刚发现了这个:一个交互式的基于浏览器的 Python 控制台,由 T4 的迈克尔·福德用 Silverlight / IronPython 构建。注意:这在 Firefox - YMMV 中崩溃了
  • Python GUI 工具包更新: PySide 1.0.0 Beta 2(诺基亚的 PyQt 开源版本)PyGUI 2.3.3
  • 关于这个话题的 Python 证书线程由史蒂夫霍尔登回答问题。

如果你有一些 Python 新闻,你认为我应该把它们放在接下来的文章中,请给我留言或留下评论。

本周 Python 链接(06/03/2011)

原文:https://www.blog.pythonlibrary.org/2011/06/03/python-links-for-the-week-06032011/

我已经有一段时间没有做过任何有趣的 Python 链接了。我退出了一段时间,因为这些东西需要一段时间才能组合起来,似乎很少有人感兴趣,但我会再试一次。本周有一篇关于 Python 和机器人的非常有趣的文章。Jesse Noller 有一篇有趣的文章,他也收集了各种 Python 故事(我也为我的故事收集了他的几个精选,因为他是对的:它们很有趣!)

如果你认为我错过了最近在 Python 世界发生的一些很棒的事情,请在评论中告诉我,也许下次我会添加它。感谢您的支持!

本周 Python 链接:2011 年 7 月 22 日

原文:https://www.blog.pythonlibrary.org/2011/07/22/python-links-for-the-week-7222011/

又一周过去了,我们有一套新的文章供您在本周末查阅。你可以学习新的东西,尝试模块,并富有成效。或者你可以去派对。不要忘记今年世界各地所有不同的 PyCons。有本地的,也有国际会议。

就是下周末!

这星期就这些了。如果你发现任何你认为我下次应该强调的很酷的东西,请告诉我。

本周 Python 链接:2011 年 7 月 15 日

原文:https://www.blog.pythonlibrary.org/2011/07/15/python-links-of-the-week-07152011/

最近我有点懒于把这些放出来,但我已经决定试着更经常地做这件事。本周涵盖了许多不同的话题,包括史蒂夫·霍尔登的一篇有争议的文章!您还可以了解 Python 会议、Sage 和元编程的奇特地点。如果您是 Python 的新手,那么您会对主题的多样性感到惊讶。这些甚至还没有开始触及 Python 世界中正在发生的事情的表面,但是它们可能会激起你的兴趣。尽情享受吧!

  • 曾经想了解一点 Python 中的元编程吗?现在你的机会来了!
  • 不认识贤者?现在有一本初学者指南,下面是约翰·库克的一篇评论。
  • 史蒂夫·霍尔登谈论关于某个 Python 模块命名的幼稚行为
  • PyCon 澳洲赛程已经公布!现在你也可以了解 Python,并在袋鼠的自然栖息地看到它们。我称之为双赢!
  • Python 非洲之旅有了新进展
  • 关于最新的 Python 新闻,请查看 Twitter
  • IronPython 2.7 的 NWS gi 2.1现已发布

Python 日志记录:如何记录到多个位置

原文:https://www.blog.pythonlibrary.org/2013/07/18/python-logging-how-to-log-to-multiple-locations/

今天我决定弄清楚如何让 Python 同时记录到文件和控制台。大多数时候,我只是想记录一个文件,但偶尔我也想在控制台上看到一些东西,以帮助调试。我在 Python 文档中找到了这个古老的例子,并最终用它模拟了下面的脚本:


import logging

#----------------------------------------------------------------------
def log(path, multipleLocs=False):
    """
    Log to multiple locations if multipleLocs is True
    """
    fmt_str = '%(asctime)s - %(name)s - %(message)s'
    formatter = logging.Formatter(fmt_str)

    logging.basicConfig(filename=path, level=logging.INFO,
                        format=fmt_str)

    if multipleLocs:
        console = logging.StreamHandler()
        console.setLevel(logging.INFO)
        console.setFormatter(formatter)

        logging.getLogger("").addHandler(console)

    logging.info("This is an informational message")
    try:
        1 / 0
    except ZeroDivisionError:
        logging.exception("You can't do that!")

    logging.critical("THIS IS A SHOW STOPPER!!!")

if __name__ == "__main__":
    log("sample.log") # log only to file
    log("sample2.log", multipleLocs=True) # log to file AND console!

正如您所看到的,当您将 True 传递给第二个参数时,该脚本将创建一个 StreamHandler ()的实例,然后您可以通过以下调用配置该实例并将其添加到当前记录器中:


logging.getLogger("").addHandler(console)

这在 Linux 上工作得很好,但是在 Windows 7 上没有创建 sample2.log ,所以我必须修改 if 语句如下:


if multipleLocs:
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(formatter)

    fhandler = logging.FileHandler(path)
    fhandler.setFormatter(formatter)

    logging.getLogger("").addHandler(console)
    logging.getLogger("").addHandler(fhandler)

现在,我应该注意到这导致了一个相当奇怪的错误,因为 Python 以某种方式跟踪我在调用我的日志函数时写入的文件名,因此当我告诉它写入 sample2.log 时,它会写入 sample 2 . log 和原始的 sample.log。下面是一个正确工作的更新示例:


import logging
import os

#----------------------------------------------------------------------
def log(path, multipleLocs=False):
    """
    Log to multiple locations if multipleLocs is True
    """
    fname = os.path.splitext(path)[0]
    logger = logging.getLogger("Test_logger_%s" % fname)
    logger.setLevel(logging.INFO)
    fh = logging.FileHandler(path)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(message)s')
    fh.setFormatter(formatter)
    logger.addHandler(fh)

    if multipleLocs:
        console = logging.StreamHandler()
        console.setLevel(logging.INFO)
        console.setFormatter(formatter)
        logger.addHandler(console)

    logger.info("This is an informational message")
    try:
        1 / 0
    except ZeroDivisionError:
        logger.exception("You can't do that!")

    logger.critical("THIS IS A SHOW STOPPER!!!")

if __name__ == "__main__":
    log("sample.log") # log only to file
    log("sample2.log", multipleLocs=True) # log to file AND console!

您会注意到,这一次我们将日志记录器的名称基于日志的文件名。日志模块非常灵活,玩起来很有趣。我希望你和我一样对此感兴趣。

使用 lggr 的 Python 日志记录

原文:https://www.blog.pythonlibrary.org/2012/08/27/python-logging-with-lggr/

有些人抱怨我的上一篇日志文章,怀疑它是否有必要,因为 docs 和 Doug Hellman 已经写了关于这个主题的文章。我有时想知道为什么我也写这些话题,但通常当我写的时候,我会有很多读者。在这种情况下,我在一周内就那一篇文章获得了近 10,000 次点击,所以我想肯定还有空间让我写这样的主题。我收到了一些关于替代日志库的评论。其中之一是彼得·唐斯的《lggr》。我们将很快地看一下这个项目,看看它是如何发展的。文档在这一点上非常肤浅,但是让我们看看我们能做些什么。

入门指南

如您所料,您需要从 Github 下载或签出该项目。一旦有了它,您就可以使用通常的命令来安装它:

python setup.py install

当然,你也可以在下载后使用 easy_install 或 pip 来安装它,我认为甚至有一种方法可以直接使用 pip 和 github URL。

编写简单的 Lggr

现在让我们深入研究一些代码,看看这个包是如何工作的。这里有一个非常简单的例子:


import lggr

log = lggr.Lggr()

# add handler to write to stdout
log.add(log.ERROR, lggr.Printer())

# add handler to write to file
f = open("sample.log", "a")
log.add(log.INFO, lggr.Printer(f))

log.info("This is an informational message")

try:
    print (1/0)
except ZeroDivisionError:
    log.error("ERROR: You can't divide by zero!")
log.close()

这段代码将创建两个处理程序。一个将错误消息输出到 stdout,另一个将信息性消息输出到文件。然后我们给每个人写一条信息。遗憾的是,lggr 似乎不提供 Python 的标准日志模块在记录错误消息时提供的回溯信息。在日志模块中,它实际上将是一个完整的回溯。另一方面,lggr 的代码非常简洁,易于理解。lggr 包还提供了以下记录器(或者作者喜欢称之为协程):

  • StderrPrinter -写入 stderr
  • SocketWriter(主机,端口)-写入网络套接字
  • Emailer(收件人)-发送电子邮件
  • GMailer(收件人、gmail _ 用户名、Gmail _ 密码、subject= "可选")也发送电子邮件,但它是从 Gmail 发送的

如果你在公司网络上,Emailer 可能无法工作,因为那个端口可能被阻塞了。至少,当我尝试的时候,这似乎是我的问题。你可能更幸运,这取决于你的公司屏蔽了多少。无论如何,lggr 包还提供了二十几个可以记录的日志变量,比如 threadname、codecontext、stack_info、filename 等等。我个人可以看到这将是非常方便的。

包扎

我没有看到任何关于旋转文件处理程序的东西,但除此之外,这个项目似乎是一个非常容易使用的日志包。如果您正在寻找比 Python 的日志模块学习曲线稍低的东西,那么这个包可能就是您想要的。

Python 恶意软件可能会入侵您附近的计算机

原文:https://www.blog.pythonlibrary.org/2020/08/04/python-malware-may-be-coming-to-a-computer-near-you/

Cyborg Security 最近报道称,使用 Python 编程语言编写的恶意软件开始出现。传统上,大多数恶意软件都是用编译语言编写的,如 C 或 C++。

原因很简单。编译语言让攻击者创建更小、更难检测的可执行文件。然而,Python 的流行和易用性使得它对恶意软件作者更有吸引力。Python 对于恶意软件的最大问题是,它往往比用 C 或 C++编写的恶意软件使用更多的 RAM 和 CPU。

当然,随着个人电脑像现在这样强大,这不再是一个问题。尤其是当你想到有如此多的应用程序是用电子语言编写的。你的网络浏览器现在是一个巨大的资源猪!

正如 Cyborg Security 网站所指出的,您可以使用 PyInstaller 或 py2exe 来创建 Python 代码的可执行文件。这篇文章没有提到的是,有人还需要对该软件进行数字签名,才能让它在 Windows 10 上运行。这篇文章提到的一件让我感兴趣的事情是,你可以使用 Nuitka 将你的 Python 代码转换成 C 语言,最终你会得到一个比使用 PyInstaller 或 py2exe 小得多的可执行文件。

Python 恶意软件的具体例子包括 2015 年和 2016 年针对民主党全国委员会使用的 SeaDuke 。他们还提到了 PWOBot,这是一种类似的 Python 恶意软件,可以进行按键记录以及下载和执行其他应用程序。

趋势科技覆盖了基于 Python 的勒索软件 PyLocky。它可以使用 3DES 加密文件。

提到的最后一款恶意软件是 PoetRAT,这是一款特洛伊木马,今年曾被用于攻击阿塞拜疆政府和能源部门。

查看全文。这真的很有趣,涵盖了更多关于这个话题的内容。

Python 连续 5 年在 CodeEval 上最受欢迎

原文:https://www.blog.pythonlibrary.org/2016/03/14/python-most-popular-for-5-years-straight/

我注意到 CodeEval 发布了一个帖子,说 Python 是他们连续 5 年最受欢迎的编程语言!不可否认的是,他们的 Python 使用率下降了 14%,而其他人则有所增加,但 Python 仍然设法保持领先地位。

Python 最受欢迎的大学教学语言?

原文:https://www.blog.pythonlibrary.org/2014/07/08/python-most-popular-university-teaching-language/

昨天,Philip Guo 在 ACM 博客上声称 Python 现在是美国顶尖大学最受欢迎的入门教学语言。我是通过行星 Python 重新发布瓦苏德夫·拉姆关于这个主题的博客帖子偶然发现这个信息的。

如果这是真的,那么我认为这真的很酷。Python 易于学习,并且能够胜任大多数编程任务。我已经在各种环境中使用它超过 8 年了,Python 几乎总能帮我搞定。无论如何,看看这篇文章,并随时留下一些反馈。

Python 被 CodeEval 评为最受欢迎的编码语言

原文:https://www.blog.pythonlibrary.org/2015/02/09/python-named-most-popular-coding-language-by-codeeval/

今天, CodeEval 宣布 Python 连续第四年蝉联第一名!CodeEval 是一个有趣的网站,在这里你可以用流行的编程语言进行编码挑战。它会给你的表现打分,你可以在排名中看到自己。反正他们关于自己支持的编程语言的统计还是挺有意思的。看到 Python 做得这么好总是很有趣。

相关文章

Python on Windows:如何设置以帮助核心开发

原文:https://www.blog.pythonlibrary.org/2012/05/21/python-on-windows-how-to-get-set-up-to-help-with-core-development/

我正在阅读 Hynek Schlawack 关于成为 Python 核心开发人员的精彩文章,并决定弄清楚在我的机器上进行设置有多难,以便我可以准备好自己进行核心开发,如果我有幸成为团队的一员的话。因为我在 Windows 上运行的时间最长,所以我只想谈谈我是如何为这个操作系统做准备的。我一直在考虑尝试帮助核心开发一段时间,所以现在是最好的时机。让我们看看设置过程有多简单或多难!

你需要什么

作为一名 Windows 上的 Python 开发者,你需要一个 Mercurial 客户端来下载 Python,更新和创建补丁。您可以使用命令行工具,也可以使用 TortoiseHg,这是一个 shell GUI。正确配置后,您可以执行

hg clone http://hg.python.org/cpython

或者使用 Tortoise 来检查存储库。这将使您获得最新的 Python 3.x 版本。如果你想帮助发布一个维护版本,那么你需要阅读文档。你需要的最后一个主要工具是编译器,最新的 Python 需要的工具是 Microsoft Visual Studio 2010。幸运的是,你不需要购买整个东西。事实上,你只需获得 Visual C++ 2010 的速成版,就万事大吉了。注意,这个工具并不是轻量级的,最终会占用超过 1gb 的磁盘空间。可悲,但却是事实。

在 Windows 7 上编译 Python

你可能已经猜到了,我们将在 Windows 7 上编译 Python。我也有 Windows XP,但这个操作系统现在几乎已经死了,所以我不打算讨论它。我怀疑这有什么不同。不管怎样,根据开发指南文档,你需要进入你刚刚在你的机器上创建的 repo 并进入 PCBuild 文件夹。然后找到 pcbuild.sln 文件,并在新的 Visual Studio C++应用程序中运行它。你可能会看到一个关于 Express 版本不支持解决方案的警告,但是请忽略它。一旦项目被加载,进入调试菜单并选择构建解决方案。奇怪的是,官方文件说进入构建菜单,而不是调试菜单,但是我的副本没有构建菜单可供选择。

当我运行构建时,最终得到了以下结果:

========== Build: 20 succeeded, 8 failed, 0 up-to-date, 3 skipped ==========

查看日志,看起来它无法找到 sqlite3 和 tcl 的头文件,并且它的 bzip 库有一些问题。它还抱怨我没有安装 ActivePerl / openssh。然而,它仍然编译 Python,我在我的 PCBuild 文件夹中有一个新的 python_d.exe 文件,我可以运行它。我通过双击它来运行它,但是你也可以使用 F5 在 Visual Studio 中运行它,或者转到调试菜单并点击开始调试

我想在这一点上,我已经准备好弄清楚如何创建一个补丁。如果我这样做了,我可能会尝试修补那些混乱的文档,这样人们就不会花时间去寻找一个不存在的菜单。然后我会写一篇关于如何提交补丁和使用 Python 的问题跟踪系统的文章。

相关阅读

Python 是 2016 年将出现的八种语言之一

原文:https://www.blog.pythonlibrary.org/2016/01/06/python-one-of-eight-languages-to-have-on-resume-in-2016/

我最近看到了 BusinessInsider 的一个帖子,声称 Python 是 2016 年你简历上的八大语言之一。Python 排在第六位。然而,这篇文章引用了一篇关于 CodingDojo 的文章作为其信息来源,该文章谈到了“2015 年最受欢迎的 8 种编程语言”。如果你看这两篇文章,你会发现 BusinessInsider 基本上只是重写了 CodingDojo 的文章,然后将其读者重定向到各种 Udemy 课程,这是他们的附属课程。

我还想指出,BusinessInsider 称 Python 为“像 JavaScript 一样的脚本语言”,这完全不是 CodingDojo 所描述的那样。Python 不仅仅是一种脚本语言。我使用 Python 已经将近 10 年了,在此期间,我使用 Python 的大部分工作都不是编写脚本。

不管怎样,亲爱的读者,我想你可能会对整件事感兴趣。我个人认为,如果你还没有学习 Python,你应该学习一下,但是我很有偏见。

Python 打包索引删除了 3,653 个恶意库

原文:https://www.blog.pythonlibrary.org/2021/03/03/python-packaging-index-removes-3653-malicious-libraries/

Python 打包索引(PyPI)再次受到恶意库的攻击。事实上超过 3500 人。你可以在 The RegisterSonatype 博客上了解更多信息。PyPI 的管理员很快删除了这些库,并将人们安装它们的风险降至最低。

从积极的一面来看,这些图书馆似乎大多向东京的 IP 发出良性 GET 请求。他们还设法淹没了国家预防机制的包装网站。

我见过的唯一一个被报道的特定恶意包是 CuPy 的变种,这是一个 Python 包,使用 Nvidia 的并行计算平台 NumPy。

虽然这可能是试图警告开发者他们供应链中的弱点,但过去在 PyPI 上已经发生了几起其他T2 域名仿冒事件,这些事件更加阴险。

和往常一样,在使用 pip 时,请确保您了解您要安装的内容。您有责任确保您下载并安装了正确的软件包。

Python:使用轮子打包

原文:https://www.blog.pythonlibrary.org/2014/01/10/python-packaging-wheel/

Python 的第一个主流包是。鸡蛋文件。现在城里有一种新的形式叫做轮子(*。whl)。一个“被设计成以非常接近于磁盘格式的方式包含 PEP 376 兼容安装的所有文件”。在这篇文章中,我们将学习如何创建一个轮子,然后在一个虚拟环境中安装我们的轮子。

入门指南

你需要画中画来创建轮子。要学习如何安装 pip,我强烈推荐阅读 pip 的安装页面。如果您已经有 pip,那么您可能需要将其升级到最新版本。方法如下:在控制台窗口中,键入以下内容:


pip install --upgrade pip

一旦你完成了,我们就可以开始学习如何制作轮子了!

创建轮子

首先,您需要安装转轮套件:


pip install wheel

那很容易!接下来,我们将使用 unidecode 包来创建我们的第一个轮子,因为在撰写本文时它还没有制作出来,我自己在几个项目中使用过这个包。


pip wheel --wheel-dir=my_wheels Unidecode

现在你应该在一个名为 my_wheels 的文件夹中有一个名为Unidecode-0 . 04 . 14-py26-none-any . whl的轮子。让我们学习如何安装我们的新车轮!

安装一个巨蟒轮

让我们创建一个 virtualenv 来进行测试。你可以在这里阅读更多关于虚拟 T2 的信息。安装后,运行以下命令:


virtualenv test

这将为我们创建一个虚拟沙盒,其中包括 pip。在继续之前,确保从脚本文件夹中运行激活来启用虚拟菜单。现在 virtualenv 不包括 wheel,所以您必须重新安装 wheel:


pip install wheel

安装完成后,我们可以使用以下命令安装我们的轮子:


pip install --use-wheel --no-index --find-links=path/to/my_wheels Unidecode

要测试这是否可行,请从 virtualenv 中的 Scripts 文件夹运行 Python,并尝试导入 unidecode。如果它导入,那么你成功地安装了你的车轮!

注:我原来装了一个旧版本的 virtualenv,相当麻烦。一定要升级你的,否则你将不得不做很多徒劳的事情来让它工作。

。whl 文件类似于。它基本上是一个。伪装的 zip 文件。如果您将扩展名从重命名为。whl 到*。你可以在闲暇时打开你选择的 zip 应用程序,检查里面的文件和文件夹。

包扎

现在你应该准备好创建你自己的轮子了。它们看起来是为您的项目创建一个快速安装的本地依赖库的好方法。您可以创建几个不同的 wheel 存储库,以方便地在不同的版本集之间进行切换,用于测试目的。当与 virtualenv 结合使用时,您可以非常容易地看到新版本的依赖项如何影响您的项目,而无需多次下载它们。

附加说明

Python:用 lxml 解析 XML

原文:https://www.blog.pythonlibrary.org/2010/11/20/python-parsing-xml-with-lxml/

上次,我们看了 Python 的一个内置 XML 解析器。在本文中,我们将看看有趣的第三方包,来自 codespeak 的 lxml。它使用了 ElementTree API 等。lxml 包支持 XPath 和 XSLT,包括一个用于 SAX 的 API 和一个与 C/Pyrex 模块兼容的 C 级 API。我们只是用它做一些简单的事情。

无论如何,对于本文,我们将使用 minidom 解析示例中的例子,看看如何用 lxml 解析这些例子。下面是一个 XML 示例,它来自一个为跟踪约会而编写的程序:


 <appointment><begin>1181251680</begin>
        <uid>040000008200E000</uid>
        <alarmtime>1181572063</alarmtime>
        <state><location><duration>1800</duration>
        <subject>Bring pizza home</subject></location></state></appointment> 
    <appointment><begin>1234360800</begin>
        <duration>1800</duration>
        <subject>Check MS Office website for updates</subject>
        <location><uid>604f4792-eb89-478b-a14f-dd34d3cc6c21-1234360800</uid>
        <state>dismissed</state></location></appointment> 

上面的 XML 显示了两个约会。从纪元开始的开始时间以秒为单位;uid 是基于开始时间和一个密钥(我认为)的哈希生成的;报警时间是自该时期以来的秒数,但应该小于开始时间;而状态就是任命有没有被打盹儿,有没有被辞退。其余的就不言自明了。现在让我们看看如何解析它。


from lxml import etree
from StringIO import StringIO

#----------------------------------------------------------------------
def parseXML(xmlFile):
    """
    Parse the xml
    """
    f = open(xmlFile)
    xml = f.read()
    f.close()

    tree = etree.parse(StringIO(xml))
    context = etree.iterparse(StringIO(xml))
    for action, elem in context:
        if not elem.text:
            text = "None"
        else:
            text = elem.text
        print elem.tag + " => " + text   

if __name__ == "__main__":
    parseXML("example.xml")

首先,我们导入所需的模块,即来自 lxml 包的 etree 模块和来自内置 StringIO 模块的 StringIO 函数。我们的 parseXML 函数接受一个参数:所讨论的 XML 文件的路径。我们打开文件,阅读并关闭它。现在有趣的部分来了!我们使用 etree 的 parse 函数来解析从 StringIO 模块返回的 XML 代码。出于我不完全理解的原因,parse 函数需要一个类似文件的对象。

无论如何,接下来我们迭代上下文(即 lxml.etree.iterparse 对象)并提取标记元素。我们添加条件语句 if 来用单词“None”替换空字段,以使输出更加清晰。仅此而已。

解析图书示例

这个例子的结果有点蹩脚。大多数情况下,您希望保存提取的数据并对其进行处理,而不仅仅是将其输出到 stdout。因此,对于我们的下一个例子,我们将创建一个数据结构来包含结果。这个例子的数据结构将是一个字典列表。我们将在这里使用 MSDN 图书的例子:


 <book id="bk101"><author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications 
      with XML.</description></book> 
   <book id="bk102"><author>Ralls, Kim</author>
      <title>Midnight Rain</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-12-16</publish_date>
      <description>A former architect battles corporate zombies, 
      an evil sorceress, and her own childhood to become queen 
      of the world.</description></book> 
   <book id="bk103"><author>Corets, Eva</author>
      <title>Maeve Ascendant</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-11-17</publish_date>
      <description>After the collapse of a nanotechnology 
      society in England, the young survivors lay the 
      foundation for a new society.</description></book> 
   <book id="bk104"><author>Corets, Eva</author>
      <title>Oberon's Legacy</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2001-03-10</publish_date>
      <description>In post-apocalypse England, the mysterious 
      agent known only as Oberon helps to create a new life 
      for the inhabitants of London. Sequel to Maeve 
      Ascendant.</description></book> 
   <book id="bk105"><author>Corets, Eva</author>
      <title>The Sundered Grail</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2001-09-10</publish_date>
      <description>The two daughters of Maeve, half-sisters, 
      battle one another for control of England. Sequel to 
      Oberon's Legacy.</description></book> 
   <book id="bk106"><author>Randall, Cynthia</author>
      <title>Lover Birds</title>
      <genre>Romance</genre>
      <price>4.95</price>
      <publish_date>2000-09-02</publish_date>
      <description>When Carla meets Paul at an ornithology 
      conference, tempers fly as feathers get ruffled.</description></book> 
   <book id="bk107"><author>Thurman, Paula</author>
      <title>Splish Splash</title>
      <genre>Romance</genre>
      <price>4.95</price>
      <publish_date>2000-11-02</publish_date>
      <description>A deep sea diver finds true love twenty 
      thousand leagues beneath the sea.</description></book> 
   <book id="bk108"><author>Knorr, Stefan</author>
      <title>Creepy Crawlies</title>
      <genre>Horror</genre>
      <price>4.95</price>
      <publish_date>2000-12-06</publish_date>
      <description>An anthology of horror stories about roaches,
      centipedes, scorpions  and other insects.</description></book> 
   <book id="bk109"><author>Kress, Peter</author>
      <title>Paradox Lost</title>
      <genre>Science Fiction</genre>
      <price>6.95</price>
      <publish_date>2000-11-02</publish_date>
      <description>After an inadvertant trip through a Heisenberg
      Uncertainty Device, James Salway discovers the problems 
      of being quantum.</description></book> 
   <book id="bk110"><author>O'Brien, Tim</author>
      <title>Microsoft .NET: The Programming Bible</title>
      <genre>Computer</genre>
      <price>36.95</price>
      <publish_date>2000-12-09</publish_date>
      <description>Microsoft's .NET initiative is explored in 
      detail in this deep programmer's reference.</description></book> 
   <book id="bk111"><author>O'Brien, Tim</author>
      <title>MSXML3: A Comprehensive Guide</title>
      <genre>Computer</genre>
      <price>36.95</price>
      <publish_date>2000-12-01</publish_date>
      <description>The Microsoft MSXML3 parser is covered in 
      detail, with attention to XML DOM interfaces, XSLT processing, 
      SAX and more.</description></book> 
   <book id="bk112"><author>Galos, Mike</author>
      <title>Visual Studio 7: A Comprehensive Guide</title>
      <genre>Computer</genre>
      <price>49.95</price>
      <publish_date>2001-04-16</publish_date>
      <description>Microsoft Visual Studio 7 is explored in depth,
      looking at how Visual Basic, Visual C++, C#, and ASP+ are 
      integrated into a comprehensive development 
      environment.</description></book> 

现在让我们解析它,并把它放到我们的数据结构中!


from lxml import etree
from StringIO import StringIO

#----------------------------------------------------------------------
def parseBookXML(xmlFile):

    f = open(xmlFile)
    xml = f.read()
    f.close()

    tree = etree.parse(StringIO(xml))
    print tree.docinfo.doctype
    context = etree.iterparse(StringIO(xml))
    book_dict = {}
    books = []
    for action, elem in context:
        if not elem.text:
            text = "None"
        else:
            text = elem.text
        print elem.tag + " => " + text
        book_dict[elem.tag] = text
        if elem.tag == "book":
            books.append(book_dict)
            book_dict = {}
    return books

if __name__ == "__main__":
    parseBookXML("example2.xml")

这个例子与上一个非常相似,所以我们只关注这里的不同之处。在开始迭代上下文之前,我们创建了一个空的 dictionary 对象和一个空的 list。然后在循环内部,我们像这样创建字典:


book_dict[elem.tag] = text

文本为 elem.text 或“无”。最后,如果标签碰巧是“book ”,那么我们在一本书的末尾,需要将字典添加到我们的列表中,并为下一本书重置字典。如你所见,这正是我们所做的。更现实的例子是将提取的数据放入 Book 类。我以前用 json 提要做过后者。

重构代码

正如我警惕的读者所指出的,我写了一些相当糟糕的代码。所以我对代码进行了一些清理,希望这样会好一点:


from lxml import etree

#----------------------------------------------------------------------
def parseBookXML(xmlFile):
    """"""

    context = etree.iterparse(xmlFile)
    book_dict = {}
    books = []
    for action, elem in context:
        if not elem.text:
            text = "None"
        else:
            text = elem.text
        print elem.tag + " => " + text
        book_dict[elem.tag] = text
        if elem.tag == "book":
            books.append(book_dict)
            book_dict = {}
    return books

if __name__ == "__main__":
    parseBookXML("example.xml")

如您所见,我们完全放弃了 StringIO 模块,将所有文件 I/O 内容放在 lxml 方法调用中。其余都一样。很酷吧?像往常一样,巨蟒摇滚!

包扎

你从这篇文章中学到什么了吗?我当然希望如此。Python 在其标准库内外都有很多很酷的解析库。一定要检查它们,看看哪一个最适合你的编程方式。

进一步阅读

  • lxml 官方网站
  • 一篇关于 lxml 的 IBM 文章
  • StringIO 文档

Python:用 minidom 解析 XML

原文:https://www.blog.pythonlibrary.org/2010/11/12/python-parsing-xml-with-minidom/

如果你是一个长期读者,你可能记得我在 2006 年开始编程 Python。在一年左右的时间里,我的雇主决定从 Microsoft Exchange 迁移到开源的 Zimbra 客户端。Zimbra 是一个不错的客户端,但它缺少一种好的方式来提醒用户他们有一个约会,所以我必须创建一种方法来查询 Zimbra 的信息并显示一个对话框。但是,所有这些晦涩难懂的东西与 XML 有什么关系呢?嗯,我认为使用 XML 是跟踪哪些约会被添加、删除、暂停或其他什么的好方法。结果证明我错了,但这不是这个故事的重点。

在本文中,我们将看到我第一次尝试用 Python 解析 XML。如果您对这个主题做一点研究,您很快就会发现 Python 在它的 xml 模块中内置了一个 XML 解析器。我最终使用了那个模块的 minidom 子组件...至少一开始是这样。最终我改用 lxml,它使用 ElementTree,但这超出了本文的范围。让我们快速看一下我想到的一些难看的 XML:


 <appointment><begin>1181251680</begin>        
        <uid>040000008200E000</uid>
        <alarmtime>1181572063</alarmtime>
        <state><location><duration>1800</duration>
        <subject>Bring pizza home</subject></location></state></appointment> 

现在我们知道我需要解析什么了。让我们看看在 Python 中使用 minidom 解析类似内容的典型方式。


import xml.dom.minidom
import urllib2

class ApptParser(object):

    def __init__(self, url, flag='url'):
        self.list = []
        self.appt_list = []        
        self.flag = flag
        self.rem_value = 0
        xml = self.getXml(url) 
        print "xml"
        print xml
        self.handleXml(xml)

    def getXml(self, url):
        try:
            print url
            f = urllib2.urlopen(url)
        except:
            f = url
        #print f
        doc = xml.dom.minidom.parse(f)
        node = doc.documentElement        
        if node.nodeType == xml.dom.Node.ELEMENT_NODE:
            print 'Element name: %s' % node.nodeName
            for (name, value) in node.attributes.items():
                #print '    Attr -- Name: %s  Value: %s' % (name, value)
                if name == 'reminder':
                    self.rem_value = value                    

        return node

    def handleXml(self, xml):
        rem = xml.getElementsByTagName('zAppointments')        
        appointments = xml.getElementsByTagName("appointment")
        self.handleAppts(appointments)

    def getElement(self, element):
        return self.getText(element.childNodes)

    def handleAppts(self, appts):
        for appt in appts:
            self.handleAppt(appt)
            self.list = []

    def handleAppt(self, appt):
        begin     = self.getElement(appt.getElementsByTagName("begin")[0])
        duration  = self.getElement(appt.getElementsByTagName("duration")[0])
        subject   = self.getElement(appt.getElementsByTagName("subject")[0])
        location  = self.getElement(appt.getElementsByTagName("location")[0])
        uid       = self.getElement(appt.getElementsByTagName("uid")[0])

        self.list.append(begin)
        self.list.append(duration)
        self.list.append(subject)
        self.list.append(location)
        self.list.append(uid)
        if self.flag == 'file':

            try:
                state     = self.getElement(appt.getElementsByTagName("state")[0])
                self.list.append(state)
                alarm     = self.getElement(appt.getElementsByTagName("alarmTime")[0])
                self.list.append(alarm)
            except Exception, e:
                print e

        self.appt_list.append(self.list)        

    def getText(self, nodelist):
        rc = ""
        for node in nodelist:
            if node.nodeType == node.TEXT_NODE:
                rc = rc + node.data
        return rc

如果我没记错的话,这段代码是基于 Python 文档中的一个例子(或者是深入 Python 中的一个章节)。我还是不喜欢这个代码。您在 ApptParser 类中看到的 url 参数可以是 url,也可以是文件。我有一个来自 Zimbra 的 XML 提要,我会定期检查它的变化,并将其与我下载的 XML 的最后一个副本进行比较。如果有新的东西,我会把修改添加到下载的副本中。无论如何,让我们稍微解开这个代码。

getXml 中,我们使用一个异常处理程序来尝试打开 url。如果它碰巧引发了一个错误,那么我们假设这个 url 实际上是一个文件路径。接下来,我们使用 minidom 的解析方法来解析 XML。然后我们从 XML 中取出一个节点。我们将忽略条件句,因为它对这个讨论不重要(它与我的程序有关)。最后,我们返回节点对象。

从技术上讲,节点是 XML,我们将它传递给 handleXml 。为了获取 XML 中所有的约会实例,我们这样做: xml.getElementsByTagName("约会")。然后,我们将该信息传递给处理设备方法。是的,到处都在传递各种价值观。试图跟踪它并在以后调试它让我发疯。总之,handle appt方法所做的就是循环每个约会,并调用 handleAppt 方法从中提取一些附加信息,将数据添加到一个列表,并将该列表添加到另一个列表。我的想法是以一个包含所有与我的约会相关的数据的列表结束。

你会注意到 handleAppt 方法调用了 getElement 方法,后者调用了 getText 方法。我不知道原作者为什么要这样做。我会直接调用 getText 方法,跳过 getElement 方法。亲爱的读者,我想这对你是一种锻炼。

现在您已经了解了使用 minidom 进行解析的基础知识。我个人不喜欢这种方法,所以我决定尝试用 minidom 提出一种更简洁的解析 XML 的方法。

让 minidom 更容易关注

我不会说我的代码有什么好的,但是我会说我认为我想出了一些更容易理解的东西。我肯定有些人会认为代码不够灵活,但是没关系。下面是我们将要解析的一个新的 XML 示例(在 MSDN 上找到的):


 <book id="bk101"><author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications 
      with XML.</description></book> 
   <book id="bk102"><author>Ralls, Kim</author>
      <title>Midnight Rain</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-12-16</publish_date>
      <description>A former architect battles corporate zombies, 
      an evil sorceress, and her own childhood to become queen 
      of the world.</description></book> 
   <book id="bk103"><author>Corets, Eva</author>
      <title>Maeve Ascendant</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-11-17</publish_date>
      <description>After the collapse of a nanotechnology 
      society in England, the young survivors lay the 
      foundation for a new society.</description></book> 
   <book id="bk104"><author>Corets, Eva</author>
      <title>Oberon's Legacy</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2001-03-10</publish_date>
      <description>In post-apocalypse England, the mysterious 
      agent known only as Oberon helps to create a new life 
      for the inhabitants of London. Sequel to Maeve 
      Ascendant.</description></book> 
   <book id="bk105"><author>Corets, Eva</author>
      <title>The Sundered Grail</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2001-09-10</publish_date>
      <description>The two daughters of Maeve, half-sisters, 
      battle one another for control of England. Sequel to 
      Oberon's Legacy.</description></book> 
   <book id="bk106"><author>Randall, Cynthia</author>
      <title>Lover Birds</title>
      <genre>Romance</genre>
      <price>4.95</price>
      <publish_date>2000-09-02</publish_date>
      <description>When Carla meets Paul at an ornithology 
      conference, tempers fly as feathers get ruffled.</description></book> 
   <book id="bk107"><author>Thurman, Paula</author>
      <title>Splish Splash</title>
      <genre>Romance</genre>
      <price>4.95</price>
      <publish_date>2000-11-02</publish_date>
      <description>A deep sea diver finds true love twenty 
      thousand leagues beneath the sea.</description></book> 
   <book id="bk108"><author>Knorr, Stefan</author>
      <title>Creepy Crawlies</title>
      <genre>Horror</genre>
      <price>4.95</price>
      <publish_date>2000-12-06</publish_date>
      <description>An anthology of horror stories about roaches,
      centipedes, scorpions  and other insects.</description></book> 
   <book id="bk109"><author>Kress, Peter</author>
      <title>Paradox Lost</title>
      <genre>Science Fiction</genre>
      <price>6.95</price>
      <publish_date>2000-11-02</publish_date>
      <description>After an inadvertant trip through a Heisenberg
      Uncertainty Device, James Salway discovers the problems 
      of being quantum.</description></book> 
   <book id="bk110"><author>O'Brien, Tim</author>
      <title>Microsoft .NET: The Programming Bible</title>
      <genre>Computer</genre>
      <price>36.95</price>
      <publish_date>2000-12-09</publish_date>
      <description>Microsoft's .NET initiative is explored in 
      detail in this deep programmer's reference.</description></book> 
   <book id="bk111"><author>O'Brien, Tim</author>
      <title>MSXML3: A Comprehensive Guide</title>
      <genre>Computer</genre>
      <price>36.95</price>
      <publish_date>2000-12-01</publish_date>
      <description>The Microsoft MSXML3 parser is covered in 
      detail, with attention to XML DOM interfaces, XSLT processing, 
      SAX and more.</description></book> 
   <book id="bk112"><author>Galos, Mike</author>
      <title>Visual Studio 7: A Comprehensive Guide</title>
      <genre>Computer</genre>
      <price>49.95</price>
      <publish_date>2001-04-16</publish_date>
      <description>Microsoft Visual Studio 7 is explored in depth,
      looking at how Visual Basic, Visual C++, C#, and ASP+ are 
      integrated into a comprehensive development 
      environment.</description></book> 

对于这个例子,我们只需要解析 XML,提取书名并打印到 stdout。你准备好了吗?开始了。


import xml.dom.minidom as minidom

#----------------------------------------------------------------------
def getTitles(xml):
    """
    Print out all titles found in xml
    """
    doc = minidom.parse(xml)
    node = doc.documentElement
    books = doc.getElementsByTagName("book")

    titles = []
    for book in books:
        titleObj = book.getElementsByTagName("title")[0]
        titles.append(titleObj)

    for title in titles:
        nodes = title.childNodes
        for node in nodes:
            if node.nodeType == node.TEXT_NODE:
                print node.data

if __name__ == "__main__":
    document = 'example.xml'
    getTitles(document)

这段代码只是一个接受一个参数(XML 文件)的简短函数。我们导入 minidom 模块,并给它相同的名称,以便更容易引用。然后我们解析 XML。函数中的前两行与前一个例子非常相似。我们使用 getElementsByTagName 获取我们想要的 XML 部分,然后迭代结果并从中提取书名。这实际上提取了标题对象,所以我们也需要对其进行迭代并提取纯文本,这就是第二个嵌套的 for 循环的目的。

就是这样。再也没有了。

包扎

好吧,我希望这篇漫无边际的文章能够教会您一些关于使用 Python 内置的 XML 解析器解析 XML 的知识。在以后的文章中,我们将会看到更多的 XML 解析。如果您有自己喜欢的方法或模块,请随意给我指出来,我会看一看。

附加阅读

Python Partials

原文:https://www.blog.pythonlibrary.org/2016/02/11/python-partials/

Python 附带了一个有趣的模块,叫做 functools 。它的一个类是分部类。您可以使用它创建一个新函数,部分应用您传递给它的参数和关键字。您可以使用 partial 来“冻结”函数的一部分参数和/或关键字,从而生成一个新对象。另一种说法是,partial 用一些默认值创建了一个新函数。我们来看一个例子!


>>> from functools import partial
>>> def add(x, y):
...     return x + y
... 
>>> p_add = partial(add, 2)
>>> p_add(4)
6

这里,我们创建一个简单的加法函数,返回其参数 x 和 y 相加的结果。接下来,我们创建一个新的 callable,方法是创建一个 partial 实例,并将我们的函数和该函数的一个参数传递给它。换句话说,我们基本上将我们的 add 函数的 x 参数默认为数字 2。最后,我们调用新的可调用函数, p_add ,参数为数字 4,结果为 6,因为 2 + 4 = 6。

片段的一个方便的用例是将参数传递给回调。让我们用 wxPython 来看看:


import wx

from functools import partial 

########################################################################
class MainFrame(wx.Frame):
    """
    This app shows a group of buttons
    """

    #----------------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """Constructor"""
        super(MainFrame, self).__init__(parent=None, title='Partial')
        panel = wx.Panel(self)

        sizer = wx.BoxSizer(wx.VERTICAL)
        btn_labels = ['one', 'two', 'three']
        for label in btn_labels:
            btn = wx.Button(panel, label=label)
            btn.Bind(wx.EVT_BUTTON, partial(self.onButton, label=label))
            sizer.Add(btn, 0, wx.ALL, 5)

        panel.SetSizer(sizer)
        self.Show()

    #----------------------------------------------------------------------
    def onButton(self, event, label):
        """
        Event handler called when a button is pressed
        """
        print 'You pressed: ', label

if __name__ == '__main__':
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()

这里我们使用 partial 调用带有额外参数的 onButton 事件处理程序,该参数恰好是按钮的标签。这对你来说可能没什么用,但是如果你经常做 GUI 编程,你会看到很多人问如何做这种事情。当然,你也可以使用 lambda 来传递参数给回调函数。

我们在工作中使用的一个用例是我们的自动化测试框架。我们用 Python 测试了一个 UI,我们希望能够传递一个函数来关闭某些对话框。基本上你可以传递一个函数和对话框的名字来关闭对话框,但是在这个过程中的某个时刻需要调用这个函数才能正常工作。由于我不能展示这些代码,这里有一个传递部分函数的基本例子:


from functools import partial

#----------------------------------------------------------------------
def add(x, y):
    """"""
    return x + y

#----------------------------------------------------------------------
def multiply(x, y):
    """"""
    return x * y

#----------------------------------------------------------------------
def run(func):
    """"""
    print func()

#----------------------------------------------------------------------
def main():
    """"""
    a1 = partial(add, 1, 2)
    m1 = partial(multiply, 5, 8)
    run(a1)
    run(m1)

if __name__ == "__main__":
    main()

这里,我们在主函数中创建了几个部分函数。接下来,我们将这些片段传递给我们的 run 函数,调用它,然后打印出被调用函数的结果。


包扎

至此,您应该知道如何使用 functools partial 来创建自己的“冻结”可调用程序。偏音有许多用途,但它们并不总是显而易见的。我建议您开始尝试使用它们,您可能会看到自己代码的用途。玩得开心!


相关阅读

Python:使用 pdfdocument 创建 PDF

原文:https://www.blog.pythonlibrary.org/2013/10/17/python-pdf-creation-with-pdfdocument/

我使用 Reportlab 用 Python 做了很多 PDF 报告的创建。偶尔我也会加入 PyPDF。所以我一直在寻找其他成熟的 Python PDF 工具。PDFDocument 并不完全成熟,但是有点意思。PDFDocument 项目实际上是 Reportlab 的包装器。可以在 github 上获取。我发现这个项目很容易使用,但相当有限。让我们花几分钟来看看它是如何工作的。

深入 pdf 文档

学习新事物的最好方法通常是去尝试。这条规则的唯一例外是,如果它是易碎的并且很贵。那你可能想看看手册。既然开源软件不是这样的,那我们就试一试吧!

注意:要运行这段代码,您需要下载并将其安装到您的本地 Python 安装或 virtualenv


from pdfdocument.document import PDFDocument

#----------------------------------------------------------------------
def createPDF(path):
    """
    Create a simple PDF
    """
    pdf = PDFDocument(path)
    pdf.init_report()
    addr = {'first_name':"John", 'last_name':"Hanes",
            'address':"123 Ding Dong Lane", 
            'zip_code':"75002", 'city':"Dakota"}
    pdf.address(addr)
    pdf.p("This is a paragraph!")
    pdf.generate()

if __name__ == "__main__":
    createPDF("test.pdf")

如果一切顺利,您应该最终得到一个看起来像下面这样的 PDF 文档:

pdfdocument_example

你可以在这里下载 PDF 文件:test.pdf

您可能会注意到,出于某种原因,PDFDocument 将邮政编码放在城市的前面。您还会注意到,您不能添加州值,因此邮寄该文档可能会有困难。奇怪的是,所有这些都被硬编码到包中。在 github 上的最新版本(截止到 2013 年 10 月 17 日)在 address 方法里可以看到,接近下课了。我必须创建许多需要额外地址行的报告,这也不支持。仅供参考:如果你用 Python 报告做了大量的邮件工作,这个项目可能不适合你。

根据文档,您可以使用 PDFDocument 通过 PDF 模板创建信函和报告。用法如下所示:


pdf.init_report()
# Or:
pdf.init_letter()

还支持在顶部包含红十字和水印的“机密”报告。还有一些特殊的换行使样式更改更容易,例如设置粗体、heading1、small 等字体属性。最后,有一些 helper 方法可以让 Django 集成更容易。

包扎

虽然我不觉得这个项目很有用,但你也许可以在自己的工作或爱好项目中找到一些用处。或者,您可能会发现下面相关文章中的某个项目对您有用。

相关文章

Python PDF 系列 metaPDF 简介

原文:https://www.blog.pythonlibrary.org/2012/07/21/python-pdf-series-an-intro-to-metapdf/

在研究 Python 的 PDF 库时,我偶然发现了另一个名为 metaPDF 的小项目。根据其网站,metaPDF 是一个轻量级 Python 库,针对元数据提取和插入进行了优化,它是优秀 pyPdf 库的快速包装器。它的工作原理是在解析 xref 表之前快速搜索 PDF 的最后 2048 个字节,与直接逐行解析表相比,性能提高了 50-60%。我不确定这有多有用,但是让我们试一试,看看 metaPDF 能做什么。

获取和使用 metaPDF

metaPDF 的安装过程非常简单。用 easy_install 或者 pip 安装就可以了。接下来我们需要写一个小脚本来看看它是如何工作的。这里有一个基于 metaPDF 的 github 页面:


from metapdf import MetaPdfReader

pdfOne = r'C:\Users\mdriscoll\Documents\reportlab-userguide.pdf'
x = MetaPdfReader()
metadata = x.read_metadata(open(pdfOne, 'rb'))
print metadata

在这里,我根据 Reportlab 用户指南 PDF 运行它。请注意,原始文件有一个打印错误,它使用了一个叫做“read”的东西来打开文件。我想,除非你跟踪了打开,否则没用。总之,这个脚本的输出如下:


{'/ModDate': u'D:20120629155504', '/CreationDate': u'D:20120629155504', '/Producer': u'GPL Ghostscript 8.15', '/Title': u'reportlab-userguide.pdf', '/Creator': u'Adobe Acrobat 10.1.3', '/Author': u'mdriscoll'}

我真的不明白这份文件的作者是怎么被改的,但我确定我不是作者。我也不太明白为什么关键字段会有正斜杠。查看这个模块的源代码,似乎这就是它所能做的一切。这有点令人失望。也许通过吸引人们对这个库的注意,我们可以让开发人员在其中写入更多的功能?

Nature.com 推广的 Python!

原文:https://www.blog.pythonlibrary.org/2015/02/12/python-promoted-by-nature-com/

我最近偶然发现这样一个事实:Nature.com 正在将 Python 作为一门值得学习的编程语言来推广。基本上文章推荐科学家应该学习 Python。有一个有趣的链接,上面写着麻省理工学院正在将 Python 作为计算机科学的入门课程进行教学。

反正文章讲的是 Python 精彩社区及其丰富的教育资源。这里还有一篇关于 IPython 笔记本的文章的链接!我认为这篇文章真的很有趣。看看吧!

Python:运行 Ping、Traceroute 等等

原文:https://www.blog.pythonlibrary.org/2010/06/05/python-running-ping-traceroute-and-more/

去年,我需要想出一种方法来用 Python 获取以下信息:获取路由表,从 ping 一系列 IP 中捕获数据,运行 tracert 并获取有关安装的 NIC 的信息。这些都需要在 Windows 机器上完成,因为它是诊断脚本的一部分,试图找出为什么机器(通常是笔记本电脑)无法连接到我们的 VPN。我最终创建了一个 wxPython GUI,让用户可以轻松运行,但是这些脚本没有 wx 也可以很好地工作。让我们看看他们长什么样!

主脚本

首先,我们将看一下整个剧本,然后检查每一个重要的部分。如果你想使用下面的代码,你需要 wxPythonPyWin32 包。


import os
import subprocess
import sys
import time
import win32com.client
import win32net
import wx

filename = r"C:\logs\nic-diag.log"

class RedirectText:
    def __init__(self,aWxTextCtrl):
        self.out=aWxTextCtrl

        if not os.path.exists(r"C:\logs"):
            os.mkdir(r"C:\logs")
        self.filename = open(filename, "w")

    def write(self,string):
        self.out.WriteText(string)
        if self.filename.closed:
            pass
        else:
            self.filename.write(string)

class MyForm(wx.Frame):

    #---------------------------------------------------------------------- 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Diagnostic Tool")

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
                          style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        # log.Disable()
        btn = wx.Button(panel, wx.ID_ANY, 'Run Diagnostics')
        self.Bind(wx.EVT_BUTTON, self.onRun, btn)

        # Add widgets to a sizer        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

        # redirect text here
        self.redir=RedirectText(log)
        sys.stdout=self.redir

    #----------------------------------------------------------------------    
    def runDiagnostics(self):
        """
        Run some diagnostics to get the machine name, ip address, mac,
        gateway, DNS, route tables, etc
        """
        # create the route table:
        # based on the following list comp from http://win32com.goermezer.de/content/view/220/284/
        # route_table = [elem.strip().split() for elem in os.popen("route print").read().split("Metric\n")[1].split("\n") if re.match("^[0-9]", elem.strip())]
        route_table = []
        proc = subprocess.Popen("route print", shell=True,
                        stdout=subprocess.PIPE)
        while True:
            line = proc.stdout.readline()
            route_table.append(line.strip().split())
            if not line: break
        proc.wait()

        print "Log Created at %s" % time.ctime()
        print "----------------------------------------------------------------------------------------------"
        info = win32net.NetWkstaGetInfo(None, 102)
        self.compname = info["computername"]
        print "Computer name: %s\n" % self.compname

        print "----------------------------------------------------------------------------------------------"
        print "Route Table:"
        print "%20s\t %15s\t %15s\t %15s\t %s" % ("Network Destination", "Netmask",
                                          "Gateway", "Interface", "Metric")
        for route in route_table:
            if len(route) == 5:
                dst, mask, gateway, interface, metric = route
                print "%20s\t %15s\t %15s\t %15s\t %s" % (dst, mask, gateway, interface, metric)

        print "----------------------------------------------------------------------------------------------\n"
        ips = ["65.55.17.26", "67.205.46.185", "67.195.160.76"]
        for ip in ips:
            self.pingIP(ip)
            print
            self.tracertIP(ip)
            print "\n----------------------------------------------------------"
        self.getNICInfo()
        print "############ END OF LOG ############"

    #----------------------------------------------------------------------   
    def pingIP(self, ip):
        proc = subprocess.Popen("ping %s" % ip, shell=True, 
                                stdout=subprocess.PIPE) 
        print
        while True:
            line = proc.stdout.readline()                        
            wx.Yield()
            if line.strip() == "":
                pass
            else:
                print line.strip()
            if not line: break
        proc.wait()

    #----------------------------------------------------------------------
    def tracertIP(self, ip):
        proc = subprocess.Popen("tracert -d %s" % ip, shell=True, 
                                stdout=subprocess.PIPE)
        print 
        while True:
            line = proc.stdout.readline()
            wx.Yield()
            if line.strip() == "":
                pass
            else:
                print line.strip()
            if not line: break
        proc.wait()

    #----------------------------------------------------------------------            
    def getNICInfo(self):
        """
        http://www.microsoft.com/technet/scriptcenter/scripts/python/pyindex.mspx?mfr=true
        """
        print "\nInterface information:\n"
        strComputer = "."
        objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator")
        objSWbemServices = objWMIService.ConnectServer(strComputer,"root\cimv2")
        colItems = objSWbemServices.ExecQuery("Select * from Win32_NetworkAdapterConfiguration")
        numOfNics = len(colItems)
        count = 1
        for objItem in colItems:
            # if the IP interface is enabled, grab its info
            print "***Interface %s of %s***" % (count, numOfNics)
            if objItem.IPEnabled == True:                
                print "Arp Always Source Route: ", objItem.ArpAlwaysSourceRoute
                print "Arp Use EtherSNAP: ", objItem.ArpUseEtherSNAP
                print "Caption: ", objItem.Caption
                print "Database Path: ", objItem.DatabasePath
                print "Dead GW Detect Enabled: ", objItem.DeadGWDetectEnabled
                z = objItem.DefaultIPGateway
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "Default IP Gateway: ", x
                print "Default TOS: ", objItem.DefaultTOS
                print "Default TTL: ", objItem.DefaultTTL
                print "Description: ", objItem.Description
                print "DHCP Enabled: ", objItem.DHCPEnabled
                print "DHCP Lease Expires: ", objItem.DHCPLeaseExpires
                print "DHCP Lease Obtained: ", objItem.DHCPLeaseObtained
                print "DHCP Server: ", objItem.DHCPServer
                print "DNS Domain: ", objItem.DNSDomain
                z = objItem.DNSDomainSuffixSearchOrder
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "DNS Domain Suffix Search Order: ", x
                print "DNS Enabled For WINS Resolution: ", objItem.DNSEnabledForWINSResolution
                print "DNS Host Name: ", objItem.DNSHostName
                z = objItem.DNSServerSearchOrder
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "DNS Server Search Order: ", x
                print "Domain DNS Registration Enabled: ", objItem.DomainDNSRegistrationEnabled
                print "Forward Buffer Memory: ", objItem.ForwardBufferMemory
                print "Full DNS Registration Enabled: ", objItem.FullDNSRegistrationEnabled
                z = objItem.GatewayCostMetric
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "Gateway Cost Metric: ", x
                print "IGMP Level: ", objItem.IGMPLevel
                print "Index: ", objItem.Index
                z = objItem.IPAddress
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IP Address: ", x
                print "IP Connection Metric: ", objItem.IPConnectionMetric
                print "IP Enabled: ", objItem.IPEnabled
                print "IP Filter Security Enabled: ", objItem.IPFilterSecurityEnabled
                print "IP Port Security Enabled: ", objItem.IPPortSecurityEnabled
                z = objItem.IPSecPermitIPProtocols
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IP Sec Permit IP Protocols: ", x
                z = objItem.IPSecPermitTCPPorts
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IP Sec Permit TCP Ports: ", x
                z = objItem.IPSecPermitUDPPorts
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IPSec Permit UDP Ports: ", x
                z = objItem.IPSubnet
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IP Subnet: ", x
                print "IP Use Zero Broadcast: ", objItem.IPUseZeroBroadcast
                print "IPX Address: ", objItem.IPXAddress
                print "IPX Enabled: ", objItem.IPXEnabled
                z = objItem.IPXFrameType
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IPX Frame Type: ", x
                print "IPX Media Type: ", objItem.IPXMediaType
                z = objItem.IPXNetworkNumber
                if z is None:
                    a = 1
                else:
                    for x in z:
                        print "IPX Network Number: ", x
                print "IPX Virtual Net Number: ", objItem.IPXVirtualNetNumber
                print "Keep Alive Interval: ", objItem.KeepAliveInterval
                print "Keep Alive Time: ", objItem.KeepAliveTime
                print "MAC Address: ", objItem.MACAddress
                print "MTU: ", objItem.MTU
                print "Num Forward Packets: ", objItem.NumForwardPackets
                print "PMTUBH Detect Enabled: ", objItem.PMTUBHDetectEnabled
                print "PMTU Discovery Enabled: ", objItem.PMTUDiscoveryEnabled
                print "Service Name: ", objItem.ServiceName
                print "Setting ID: ", objItem.SettingID
                print "Tcpip Netbios Options: ", objItem.TcpipNetbiosOptions
                print "Tcp Max Connect Retransmissions: ", objItem.TcpMaxConnectRetransmissions
                print "Tcp Max Data Retransmissions: ", objItem.TcpMaxDataRetransmissions
                print "Tcp Num Connections: ", objItem.TcpNumConnections
                print "Tcp Use RFC1122 Urgent Pointer: ", objItem.TcpUseRFC1122UrgentPointer
                print "Tcp Window Size: ", objItem.TcpWindowSize
                print "WINS Enable LMHosts Lookup: ", objItem.WINSEnableLMHostsLookup
                print "WINS Host Lookup File: ", objItem.WINSHostLookupFile
                print "WINS Primary Server: ", objItem.WINSPrimaryServer
                print "WINS Scope ID: ", objItem.WINSScopeID
                print "WINS Secondary Server: ", objItem.WINSSecondaryServer
                print "-------------------------------------------------------\n"
            else:
                print "Interface is disabled!\n"
            count += 1

    #----------------------------------------------------------------------
    def onRun(self, event):
        self.runDiagnostics()
        self.redir.filename.close()
        # Restore stdout to normal
        sys.stdout = sys.__stdout__

#----------------------------------------------------------------------         
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

与大多数 Python 程序一样,这个程序从各种导入开始。接下来我们创建一个简单的类(RedirectText ),我们将使用它来帮助我们将 stdout 重定向到 wx。TextCtrl 和一个日志文件。这通过传入 wx 的实例来实现。TextCtrl,然后设置“sys.stdout”以指向它(请参见 MyForm 类中的 init 方法)。在 RedirectText 类之后,我们有了 MyForm 类,在那里我们创建了 wxPython GUI。实际上 GUI 本身并没有太多的东西。只是一个多行文本控件和一个面板上的按钮,但这就是我们所需要的。这个类的其余部分由收集所有我们需要的信息并将其记录到屏幕和文件中的方法组成。

现在就来看看那些方法吧!注意,这些方法是从 runDiagnostics 方法调用的,该方法是从 onRun 按钮事件处理程序启动的。

获取路由表(又名:IP 路由)

当我研究如何做到这一点时,我在另一个博客上发现了以下脚本:


import os, re
route_table = [elem.strip().split() for elem in os.popen("route print").read().split("Metric\n")[1].split("\n") if re.match("^[0-9]", elem.strip())]

我发现这很难理解,所以我重新写了一遍(或者找到了另一个例子,但忘了记下来),如下所示:


route_table = []
proc = subprocess.Popen("route print", shell=True,
                stdout=subprocess.PIPE)
while True:
    line = proc.stdout.readline()
    route_table.append(line.strip().split())
    if not line: break
proc.wait()

print "----------------------------------------------------------------------------------------------"
print "Route Table:"
print "%20s\t %15s\t %15s\t %15s\t %s" % ("Network Destination", "Netmask",
                                  "Gateway", "Interface", "Metric")
for route in route_table:
    if len(route) == 5:
        dst, mask, gateway, interface, metric = route
        print "%20s\t %15s\t %15s\t %15s\t %s" % (dst, mask, gateway, interface, metric)

我发现上面的代码更容易阅读和理解。它所做的只是使用子流程模块运行“route print”并将结果写入 stdout。不要被上面的 proc.stdout 迷惑了。这是进程的标准输出,不是普通的标准输出。我们希望将数据重定向到普通的标准输出!为此,我们读取 proc 的 stdout(或者有人会说是管道),并将每一行数据追加到一个列表中。然后,我们使用 Python 的字符串格式创建一个很好的定制输出。现在我们来看看如何使用 Python 运行 Ping 和 Tracert。

使用 Python 运行 Ping / Tracert

使用 Python 执行 ping 操作非常简单。我们只需要子流程模块来完成这项工作,正如您在下面的代码片段中看到的:


def pingIP(self, ip):
    proc = subprocess.Popen("ping %s" % ip, shell=True, 
                            stdout=subprocess.PIPE) 
    print
    while True:
        line = proc.stdout.readline()                        
        wx.Yield()
        if line.strip() == "":
            pass
        else:
            print line.strip()
        if not line: break
    proc.wait()

在这段代码中,我们使用 wx。实时将 ping 结果发送到我们的文本控件。如果我们没有这样做,那么在 ping 完成运行之前,我们不会收到任何 ping 结果。注意,我们也使用无限循环来获取结果。一旦结果不再出现,我们就跳出这个循环。如果您查看 tracert 代码,您会发现唯一的区别在于 out 子流程。Popen 命令。这将是重构的一个很好的候选,但是我将把它作为一个练习留给读者。

使用 Python 获取网卡信息

微软在他们的 Technet 子网站上有一整套 Python 脚本,我最终使用它们来获取我们电脑中网络接口卡(NIC)的各种有用信息。我不会在这里重复代码,因为它很长,我们已经有了。然而,这很容易理解,我怀疑如果你知道自己在做什么,你可以通过 WMI 获得同样的信息。我们感兴趣的主要部分是 MAC 和 IP 地址。让我们从长代码中提取这些信息,看看它有多容易获得:


strComputer = "."
objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator")
objSWbemServices = objWMIService.ConnectServer(strComputer,"root\cimv2")
colItems = objSWbemServices.ExecQuery("Select * from Win32_NetworkAdapterConfiguration")
numOfNics = len(colItems)

for objItem in colItems:
    z = objItem.IPAddress
    if z is None:
        a = 1
    else:
        for x in z:
             print "IP Address: ", x
    print "MAC Address: ", objItem.MACAddress

很简单,是吧?快看。您使用类似 SQL 的语法来运行查询。这就是为什么我认为你可以使用 WMI(事实上,这可能是它在以一种迟钝的方式做)。不管怎样,这就是全部了。

包扎

现在,您知道了从 PC 获取各种网络信息的秘密,以及如何将子进程的管道重定向到日志文件和 wxPython 文本控件。你如何选择使用这些信息取决于你自己。

Python -与 Delorean 共度时光

原文:https://www.blog.pythonlibrary.org/2014/09/03/python-taking-time-with-delorean/

最近我写了关于 arrow 项目的文章,我的一个读者提到了另一个被称为 Delorean 的与日期时间相关的项目。因此,在本文中,我们将花一些时间来研究 delorean 项目。这将是一篇高水平的文章,因为没有理由重写 delorean 的文档。


入门指南

要安装 Delorean,您需要的只是 pip 和管理员权限。下面是安装软件包的典型方法:


pip install delorean

安装时,您会注意到 Delorean 有几个依赖项:pytz 和 python-dateutil。幸运的是,如果您还没有这些软件,pip 也会为您安装。


使用 Delorean

实际上使用 Delorean 非常简单。我们来看几个例子。我们先来看看 Delorean 如何处理时区:


>>> from delorean import Delorean
>>> CST = "US/Central"
>>> d = Delorean(timezone=CST)
>>> d
Delorean(datetime=2014-09-03 08:01:12.112257-05:00, timezone=US/Central)
>>> e = Delorean(timezone=EST)
>>> e
Delorean(datetime=2014-09-03 09:02:00.537070-04:00, timezone=US/Eastern)
>>> d.shift(EST)
Delorean(datetime=2014-09-03 09:01:12.112257-04:00, timezone=US/Eastern)

这里我们可以看到 Delorean 如何使用字符串来设置时区,以及创建具有不同时区的对象是多么容易。我们还可以看到如何在时区之间转换。接下来,我们将检查偏移:


>>> d.next_day(1)
Delorean(datetime=2014-09-04 08:01:12.112257-05:00, timezone=US/Central)
>>> d.next_day(-2)
Delorean(datetime=2014-09-01 08:01:12.112257-05:00, timezone=US/Central)

如你所见,在时间中前进和后退是非常容易的。你需要做的就是调用 Delorean 的 next_day() 方法。如果您需要使用 Python 的 datetime 模块和 Delorean 对象,那么您可能会想看看 Delorean 的 epoch()naive() 方法:


>>> d.epoch()
1409749272.112257
>>> d.naive()
datetime.datetime(2014, 9, 3, 13, 1, 12, 112257)

正如您可能猜到的,epoch 方法返回从 epoch 开始的秒数。另一方面,naive 方法返回一个 datetime.datetime 对象。

Delorean 更有趣的特性之一是它能够使用自然语言来获取与您创建的日期对象相关的某些日期:


>>> d.next_tuesday()
Delorean(datetime=2014-09-09 09:01:12.112257-04:00, timezone=US/Eastern)
>>> d.next_friday()
Delorean(datetime=2014-09-05 09:01:12.112257-04:00, timezone=US/Eastern)
>>> d.last_sunday()
Delorean(datetime=2014-08-31 09:01:12.112257-04:00, timezone=US/Eastern)

这不是很方便吗?Delorean 的另一个简洁的特性是它的 stops()函数:


>>> from delorean import stops
>>> import delorean
>>> for stop in stops(freq=delorean.HOURLY, count=10):
        print stop

Delorean(datetime=2014-09-03 13:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 14:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 15:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 16:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 17:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 18:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 19:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 20:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 21:18:51+00:00, timezone=UTC)
Delorean(datetime=2014-09-03 22:18:51+00:00, timezone=UTC)

您可以使用 Delorean 为不同的秒、分、小时、天、周、月和年创建一组 Delorean 对象。您还可以包括时区。


包扎

Delorean 附带了其他有趣的功能,如截断和解析日期时间字符串。你绝对应该尝试一下这个项目,看看使用起来有多有趣!


相关阅读

Python 文本到语音转换:让你的电脑说话

原文:https://www.blog.pythonlibrary.org/2010/04/02/python-test-to-speech-making-your-pc-talk/

在我被目前的工作录用后不久,我的老板发给我一个关于 Python 的脚本(我想是基于这篇文章)和一个叫做 pyTTS 的文本到语音转换模块。这是在 Python 2.5 发布之后。无论如何,它基本上是 win32com 模块的一个很好的包装器,可以与微软语音 API (SAPI)通信。

我不会详细介绍 pyTTS,因为我上面链接的那篇文章已经介绍过了,但是我会给你一个快速的介绍。如果你去作者的网站你会发现他已经转移到屏幕阅读技术,并创建了一个名为 Clique 的程序。我不确定这是不是用 Python 写的。我在网站上寻找可以下载该软件的地方,最终找到了这个:【http://sourceforge.net/projects/uncassist/files/

据我所知,他只正式支持 Python 2.3-2.5。然而,由于 pyTTS 基本上只是包装了对 SAPI 的 win32com 调用,并且 PyWin32 模块支持 Python 2.x-3.x,所以我认为让 pyTTS 与新版本一起工作是相当容易的。

注意:你将需要微软 SAPI 5.1 可再发行版额外 MS voicesPyWin32

让我们快速看一下如何使用本模块:


import pyTTS
tts = pyTTS.Create()
tts.SetVoiceByName('MSSam')
tts.Speak("Hello, fellow Python programmer")

你不必把声音设置成我认为的默认声音,但这样做很有趣。Speak 方法接受各种标志作为它的第二个参数。一个例子是 pyTTS.tts_async,它将说话置于异步模式。你也可以通过这样做来改变音量:tts。体积= 50。您可以选择 0-100%之间的任何值。

如果你看这篇文章,它会教你如何让 pyTTS 念出你喂它的单词。

下面是您如何使用 PyWin32 完成上面的大部分示例:


from win32com.client import constants
import win32com.client
speaker = win32com.client.Dispatch("SAPI.SpVoice", constants.SVSFlagsAsync)
speaker.Speak("Hello, fellow Python programmer")

在研究本文时,我注意到 pyTTS 背后的开发人员还开发了一个跨平台的文本到语音转换模块,名为 pyttsx 。我没有用过,但是我鼓励你试一试。其他值得一看的模块有 pySpeech 和这个让 Python 识别语音的酷配方:【http://www.surguy.net/articles/speechrecognition.xml】T4

好吧,这更多的是对 Python 中酷的语音相关模块的调查,而不是对代码的调查。不过,我希望这将证明有助于你的努力。

用 doctest 进行 Python 测试

原文:https://www.blog.pythonlibrary.org/2014/03/17/python-testing-with-doctest/

Python 在其标准库中包含了几个用于测试的模块: doctestunittest 。在这篇文章中,我们将会看到文档测试。doctest 模块将在代码中搜索类似于交互式 Python 会话的文本片段。然后,它将执行这些会话,以验证它们完全按照编写的那样工作。这意味着,如果您在 docstring 中编写了一个示例,显示了带有尾随空格或制表符的输出,那么函数的实际输出也必须带有尾随空格。大多数时候,docstring 是您想要放置测试的地方。将涵盖 doctest 的以下方面:

  • 如何从终端运行 doctest
  • 如何在模块内部使用 doctest
  • 如何从单独的文件运行 doctest

我们开始吧!


通过终端运行 doctest

我们将从创建一个非常简单的函数开始,这个函数将使赋予它的值翻倍。我们将在函数的 docstring 中包含几个测试。代码如下:


#----------------------------------------------------------------------
def double(a):
    """
    >>> double(4)
    8
    >>> double(9)
    18
    """
    return a*2

现在我们只需要在 doctest 中运行这段代码。打开终端(或命令行),将目录切换到包含脚本的文件夹。我将我的副本保存为 dtest1.py 。下面是我所做的截图:

doctest

您会注意到,在第一个示例中,我执行了以下内容:


python -m doctest dtest1.py

运行测试,但屏幕上没有显示任何内容。当您看不到任何打印内容时,这意味着所有测试都成功通过。第二个示例显示了以下命令:


python -m doctest -v dtest1.py

“-v”意味着我们想要详细的输出,这正是我们所收到的。再次打开代码,在 docstring 中的“18”后面添加一个空格。然后重新运行测试。以下是我收到的输出:

doctest_error

错误消息说它期望“18 ”,而它得到了“18”。这是怎么回事?我们在 docstring 的“18”后面添加了一个空格,所以 doctest 实际上希望数字“18”后面跟一个空格。另外,注意不要将字典作为 docstring 示例中的输出。字典可以按任何顺序排列,所以它与实际输出匹配的可能性不是很大。


在模块内运行 doctest

让我们稍微修改一下这个例子,这样我们可以导入 doctest 模块并使用它的 testmod 函数。


#----------------------------------------------------------------------
def double(a):
    """
    >>> double(4)
    8
    >>> double(9)
    18
    """
    return a*2

#----------------------------------------------------------------------
if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=True)

这里我们导入 doctest 并调用 doctest.testmod,我们给它传递关键字参数 verbose=True ,这样我们可以看到一些输出。否则,这个脚本将运行,没有任何输出,表明测试运行成功。

如果您不想对 verbose 选项进行硬编码,也可以在命令行上进行:


python dtest2.py -v

现在我们准备学习如何将测试放到一个单独的文件中。


从单独的文件运行 doctest

doctest 模块还支持将测试放到一个单独的文件中。这允许我们从代码中分离出测试。让我们从前面的例子中剥离测试,并将它们放入一个名为 tests.txt 的文本文件中:


The following are tests for dtest2.py

>>> from dtest2 import double
>>> double(4)
8
>>> double(9)
18

让我们在命令行上运行这个测试文件。方法如下:

doctest_from_file

您会注意到用文本文件调用 doctest 的语法与用 Python 文件调用它的语法是一样的。结果也一样。在这种情况下,有三个测试而不是两个,因为我们还导入了一个模块。您还可以运行 Python 解释器中文本文件中的测试。这里有一个例子:

doctest_from_file_intepreter

这里我们只是导入 doctest 并调用它的 testfile 方法。注意,您还需要将文件名或路径传递给 testfile 函数。它将返回一个 TestResults 对象,该对象包含有多少测试被尝试以及有多少测试失败。


包扎

至此,您应该能够理解如何在自己的代码中有效地使用 doctest 模块。您应该去阅读一下 doctest 文档,因为那里有关于选项标志和异常的额外信息,您可能会觉得有用。开心快乐编码!


下载代码


相关阅读

python-datefinder 包

原文:https://www.blog.pythonlibrary.org/2016/02/04/python-the-datefinder-package/

本周早些时候,我偶然发现了另一个有趣的包,叫做 datefinder 。这个包背后的思想是,它可以接受任何包含日期的字符串,并将它们转换成 Python datetime 对象的列表。我在以前的工作中会喜欢这个包,在那里我做了大量的文本文件和数据库查询解析,因为很多时候找到日期并将其转换成我可以轻松使用的格式是非常麻烦的。

无论如何,要安装这个方便的软件包,你需要做的就是:


pip install datefinder

我应该注意到,当我运行这个程序时,它最后还安装了以下软件包:

  • pyyaml-3.11 型自动步枪
  • 日期解析器-0.3.2
  • jdatetime-1.7.2
  • python-dateutil-2.4.2
  • pytz-2015.7
  • regex-2016.1.10
  • 六-10 . 0
  • umalqurra-0.2

因为所有这些额外的东西,你可能想先把这个包安装到一个 virtualenv 中。让我们来看看一些代码。这是我尝试的一个快速演示:


>>> import datefinder
>>> data = '''Your appointment is on July 14th, 2016\. Your bill is due 05/05/2016'''
>>> matches = datefinder.find_dates(data)
>>> for match in matches:
...     print(match)
... 
2016-07-14 00:00:00
2016-05-05 00:00:00

如您所见,它与这两种常见的日期格式配合得非常好。我过去不得不支持的另一种格式是相当典型的 ISO 8601 日期格式。让我们看看 datefinder 如何处理这个问题。


>>> data = 'Your report is due: 2016-02-04T20:16:26+00:00'
>>> matches = datefinder.find_dates(x)
>>> for i in matches: 
...     print(i)
... 
2016-02-04 00:00:00
2016-02-04 20:16:26

有趣的是,这种特殊版本的 ISO 8601 格式会导致 datefinder 返回两个匹配项。第一个只是日期,而第二个既有日期又有时间。无论如何,希望你会发现这个包对你的项目有用。玩得开心!

蟒蛇过去常常拍摄黑洞的照片

原文:https://www.blog.pythonlibrary.org/2019/04/11/python-used-to-take-photo-of-black-hole/

科学家们使用了一种新的算法来拍摄黑洞的照片。对我来说,最令人兴奋的部分之一是他们使用了大量的 Python 库来完成这项神奇的工作。

这是他们在论文中提到的列表:

  • Numpy (van der Walt 等人,2011 年)
  • (琼斯等人,2001 年)
  • 熊猫(McKinney 2010)
  • jupyter(kluwer 等人,2016 年)
  • Matplotlib (Hunter 2007 年)。
  • Astropy(Astropy 协作等 2013,2018)

他们还使用了自己定制的 Python 代码,这些代码可以在 Github 上找到

如果你对拍摄这张照片背后的想法有更外行的解释感兴趣,有一个来自其中一位研究人员的很好的 ted 演讲:

https://www.youtube.com/embed/BIvezCVcsYs?feature=oembed


相关链接

  • Reddit Python group 讨论这些进展
  • 研究人员
    的照片显示 Matplotlib 正在运行。

Python 用户组- Pyowa

原文:https://www.blog.pythonlibrary.org/2008/09/27/python-users-groups-pyowa/

我最近在爱荷华州成立了一个 Python 用户组,因为那里似乎没有,我觉得这会很有趣。2008 年 9 月 24 日,上周三,我们举行了第一次会议。包括我在内,我们有八个人在那里。在这个群体中,只有一半人知道如何用 Python 编程。因此,我们决定举办一个 Python 速成班是个好主意,让我们的其他成员在下一次会议上跟上进度。

我目前正在计划我们应该涵盖的主题以及如何划分教学职责。我认为这将是一次有趣的经历,有很大的成长空间。目前,我想复习一下可重复项、条件、内置等。我想我们还会去构建一个解析器和一些非常简单的 GUI 东西。我甚至有一个志愿者想要展示他的 django 技能。

如果可以的话,我一定会写下进展如何,我们涵盖了哪些内容,并在网上发布我们的材料。

Python:用乌龟画圆

原文:https://www.blog.pythonlibrary.org/2012/08/06/python-using-turtles-for-drawing/

我目前正在撰写一本大学课程 Python 书籍的书评,该书使用 Python turtle 模块和 Tkinter 来帮助教授 Python 编程语言。希望我能在月底前写完那本书。同时,这让我决定尝试一下海龟模块。在这篇文章中,我们将看看如何让乌龟画出典型的奥运会标志,然后尝试在添加新功能的同时清理代码。

海龟入门

幸运的是,没有需要安装的包。Python 中包含了 turtle 模块。你要做的就是导入它。这是一个非常简单的脚本,它会在屏幕上画一个圆。


import turtle

myTurtle = turtle.Turtle()
myTurtle.circle(50)
turtle.getscreen()._root.mainloop()

如您所见,您需要创建一个乌龟实例,然后让它画一个圆。可悲的是,默认的海龟实际上看起来像一个鼠标箭头。幸运的是,您可以通过简单地传递一个形状字符串来改变这种情况: turtle。龟(shape="turtle") 。让我们继续前进,创造一个真正简单的奥林匹克标志!

海龟奥运会

当你第一次开始使用海龟模块的时候,很容易就设置好位置,随意画圈。然而,你很快就会发现海龟在圆圈之间画线,通常会把事情搞得一团糟,所以你要确保处理好这一点。这是我们制作这个符号的第一次尝试:


import turtle

myTurtle = turtle.Turtle(shape="turtle")
myTurtle.circle(50)

myTurtle.penup()
myTurtle.setposition(-120, 0)
myTurtle.pendown()
myTurtle.circle(50)

myTurtle.penup()
myTurtle.setposition(60,60)
myTurtle.pendown()
myTurtle.circle(50)

myTurtle.penup()
myTurtle.setposition(-60, 60)
myTurtle.pendown()
myTurtle.circle(50)

myTurtle.penup()
myTurtle.setposition(-180, 60)
myTurtle.pendown()
myTurtle.circle(50)

turtle.getscreen()._root.mainloop()

是的,这个脚本中有很多冗余代码。幸运的是,这也是非常明显的事情。你必须记住,你基本上是在一个 x/y 网格上绘制,屏幕的正中心是(0,0)。让我们试着让这段代码不那么混乱。在这个重构的例子中,我们将代码转换成了一个类:


import turtle

class MyTurtle(turtle.Turtle):
    """"""

    def __init__(self):
        """Turtle Constructor"""
        turtle.Turtle.__init__(self, shape="turtle")

    def drawCircle(self, x, y, radius=50):
        """
        Moves the turtle to the correct position and draws a circle
        """
        self.penup()
        self.setposition(x, y)
        self.pendown()
        self.circle(radius)

    def drawOlympicSymbol(self):
        """
        Iterates over a set of positions to draw the Olympics logo
        """
        positions = [(0, 0), (-120, 0), (60,60),
                     (-60, 60), (-180, 60)]
        for position in positions:
            self.drawCircle(position[0], position[1])

if __name__ == "__main__":
    t = MyTurtle()
    t.drawOlympicSymbol()
    turtle.getscreen()._root.mainloop()

这允许我们创建一个 drawCircle 方法,在这里我们可以放置所有的定位和笔的移动,这使得代码更加简洁。然而,输出仍然很平淡。让我们添加一些颜色的符号,并添加一些文字!

让爬行动物改变颜色

turtle 模块非常灵活,给了我们足够的余地来改变它的颜色和绘制各种字体的文本,因为它是基于 Tkinter 的。让我们来看看一些代码,看看它到底有多简单:


import turtle

class MyTurtle(turtle.Turtle):
    """"""

    def __init__(self):
        """Turtle Constructor"""
        turtle.Turtle.__init__(self, shape="turtle")
        screen = turtle.Screen()
        screen.bgcolor("lightgrey")
        self.pensize(3)

    def drawCircle(self, x, y, color, radius=50):
        """
        Moves the turtle to the correct position and draws a circle
        """
        self.penup()
        self.setposition(x, y)
        self.pendown()
        self.color(color)
        self.circle(radius)

    def drawOlympicSymbol(self):
        """
        Iterates over a set of positions to draw the Olympics logo
        """
        positions = [(0, 0, "blue"), (-120, 0, "purple"), (60,60, "red"),
                     (-60, 60, "yellow"), (-180, 60, "green")]
        for x, y, color in positions:
            self.drawCircle(x, y, color)

        self.drawText()

    def drawText(self):
        """
        Draw text to the screen
        """
        self.penup()
        self.setposition(-60, 0)
        self.setheading(0)
        self.pendown()
        self.color("black")
        self.write("London 2012", font=("Arial", 16, "bold"))

if __name__ == "__main__":
    t = MyTurtle()
    t.drawOlympicSymbol()
    turtle.getscreen()._root.mainloop()

为了添加颜色,我们向我们的 drawCircle 方法添加了一个新的参数,并向元组的位置列表添加了后续的颜色(现在它实际上应该被命名为其他名称)。你会注意到,要改变颜色,我们所要做的就是调用 color 方法并传递给它我们想要的东西。我们还添加了一个 drawText 方法来添加文本绘制功能。注意,当我们绘制文本时,我们使用 turtle 的 write 方法,该方法允许我们设置字体系列、字体大小等等。另请注意,需要设置标题,以便文本以正确的方向绘制。在这种情况下,零(0)等于东。我们还改变了背景颜色和笔的宽度,使标志更加突出。黄色在白色的背景下看起来很淡。

包扎

你现在对真正有趣的海龟模块有了一点了解。如果你稍微挖掘一下,你会发现有一堆脚本可以让海龟画出各种复杂的东西。我个人希望玩得更多,看看我偶然发现的一些海龟方面的项目。玩得开心!

附加阅读

源代码

Python 视频系列:内置模块

原文:https://www.blog.pythonlibrary.org/2022/06/02/python-video-series-the-builtins-module/

在这个视频教程中,你将了解 Python 的内置模块

https://www.youtube.com/embed/SWKaTSjY2y4?feature=oembed

Python:使用散景的可视化

原文:https://www.blog.pythonlibrary.org/2016/07/27/python-visualization-with-bokeh/

散景包是一个交互式可视化库,它使用 web 浏览器进行演示。它的目标是提供 D3.js 风格的图形,看起来优雅且易于构建。散景支持大型和流式数据集。您可能会使用这个库来创建绘图/图形。它的主要竞争对手之一似乎是

注意:这不会是一个关于散景库的深入教程,因为它能够处理的不同图形和可视化的数量相当大。相反,本文的目的是让您体验一下这个有趣的库能做什么。

让我们花点时间来安装它。最简单的方法是使用 pip 或 conda。以下是 pip 的使用方法:


pip install bokeh

这将安装散景及其所有依赖项。因此,您可能希望将散景安装到 virtualenv 中,但这取决于您。现在让我们看一个简单的例子。将下面的代码保存到一个您认为合适的文件中。


from bokeh.plotting import figure, output_file, show

output_file("/path/to/test.html")

x = range(1, 6)
y = [10, 5, 7, 1, 6]
plot = figure(title='Line example', x_axis_label='x', y_axis_label='y')
plot.line(x, y, legend='Test', line_width=4)
show(plot)

这里我们只是从散景库中导入一些项目。我们只是告诉它在哪里保存输出。您会注意到输出是 HTML。然后,我们为 x 轴和 y 轴创建一些值,这样我们就可以创建绘图。然后我们实际上创建了图形对象,并给它一个标题和两个轴的标签。最后,我们绘制直线,给它一个图例和线宽,并显示图形。show 命令实际上会在默认浏览器中打开您的绘图。您最终应该会看到这样的内容:

bokeh_line

散景也支持 Jupyter 笔记本,唯一的变化是你需要使用输出 _ 笔记本而不是输出 _ 文件

散景快速入门指南在网格图上有一系列正弦波的简洁示例。我将这个例子简化为一个正弦波。请注意,您需要安装 NumPy,以下示例才能正常工作:


import numpy as np

from bokeh.layouts import gridplot
from bokeh.plotting import figure, output_file, show

N = 100
x = np.linspace(0, 4*np.pi, N)
y0 = np.sin(x)

output_file('sinewave.html')

sine = figure(width=500, plot_height=500, title='Sine')
sine.circle(x, y0, size=10, color="navy", alpha=0.5)

p = gridplot([[sine]], toolbar_location=None)

show(p)

这个例子和上一个例子的主要区别在于,我们使用 NumPy 来生成数据点,并且我们将图形放在一个 gridplot 中,而不仅仅是绘制图形本身。当您运行这段代码时,您应该会得到一个类似如下的图:

bokeh_sine_wave

如果你不喜欢圆形,那么你会很高兴知道散景支持其他形状,如方形、三角形和其他几种形状。


包扎

Bokeh 项目非常有趣,它提供了一个简单易用的 API 来创建图表、绘图和其他数据可视化。文档整理得很好,包括许多展示这个包能为您做什么的例子。浏览一下文档是非常值得的,这样您就可以看到其他一些图形是什么样子,以及生成如此好的结果的代码示例有多短。我唯一的不满是,散景没有办法以编程方式保存图像文件。这似乎是一个长期的问题,他们几年来一直试图解决这个问题。希望他们能尽快找到支持该特性的方法。否则我觉得真的很酷!

Python 连续三年被评为最佳编程语言

原文:https://www.blog.pythonlibrary.org/2011/12/07/python-voted-best-programming-language-3-years-running/

《Linux 杂志》的读者很有品位。这是他们第三年将 Python 选为最佳编程语言。奇怪的是,C++是亚军。我个人上学的时候很喜欢 C++,但是两种语言差别很大。另一方面,Python 与 C/C++的接口非常好,所以那本杂志的读者可能喜欢混合使用这两种语言。你还会注意到,他们也将 Python 选为最佳脚本语言

祝贺 Python 社区和 PSF!

向史蒂夫·霍尔登致敬,他在 Python.org 的新闻源上提到了这一点。

Python、Windows 和打印机

原文:https://www.blog.pythonlibrary.org/2010/02/14/python-windows-and-printers/

除了软件开发之外,我还做大量的技术支持工作。在我们的小店里,我们可以对任何与技术相关的问题进行故障诊断,从网络到软件到打印机。我认为最烦人的一点是试图让打印机按照用户想要的方式工作。另一个问题是为用户设置打印机,这些用户必须在 PC 之间漫游,这是他们工作的一部分。这些用户通常只需要在任何给定时间位于其特定位置的打印机。很难适应这种类型的用户,尤其是如果电脑被 24/7 使用的话,我的情况就是如此。这就是 Python 的用武之地。

在本文中,我将向您展示如何访问机器上当前安装的打印机,更改默认打印机并安装另一台打印机。我还将向您展示如何访问有关已安装打印机的各种信息,因为这些信息有助于编写其他管理脚本。

要继续学习,您需要 Python 2.4 - 3.x 和 PyWin32 包

今天的第一个技巧是,让我们看看我们的电脑上目前安装了哪些打印机:


import win32print
printers = win32print.EnumPrinters(5)
print printers

可以在 EnumPrinters 调用中使用不同的整数来获取更多或更少的信息。更多信息见文档(你可能也需要看看 MSDN)。无论如何,这是一个示例输出:

((8388608, 'SnagIt 9,SnagIt 9 Printer,', 'SnagIt 9', ''), (8388608, 'Samsung ML-2250 Series PCL 6,Samsung ML-2250 Series PCL 6,', 'Samsung ML-2250 Series PCL 6', ''), (8388608, 'PDFCreator,PDFCreator,', 'PDFCreator', 'eDoc Printer'), (8388608, 'Microsoft XPS Document Writer,Microsoft XPS Document Writer,', 'Microsoft XPS Document Writer', ''))

如您所见,EnumPrinters 调用返回一个具有嵌套元组的元组。如果我没记错的话,如果打印机是网络打印机,那么最后一个参数将是 UNC 路径。在我工作的地方,我们不得不淘汰一些装有打印机的服务器,并需要一种方法来更改用户的打印机设置,以便它们指向新的路径。使用上面收集的信息使这变得容易多了。

例如,如果我的脚本遍历该列表,发现一台打印机正在使用一个过时的 UNC 路径,我可以这样做来修复它:


import win32print
win32print.DeletePrinterConnection('\\\\oldUNC\path\to\printer')
win32print.AddPrinterConnection('\\\\newUNC\path\to\printer')

安装打印机的另一种方法是使用低级命令行调用子进程模块:


import subprocess
subprocess.call(r'rundll32 printui.dll PrintUIEntry /in /q /n \\UNC\path\to\printer')

对于我上面提到的漫游用户的情况,我通常还需要设置默认打印机,这样用户就不会意外地打印到不同的部门。我发现有两种方法非常有效。如果您知道打印机的名称,您可以使用以下内容:


import win32print
win32print.SetDefaultPrinter('EPSON Stylus C86 Series')

在上面的代码中,我将默认值设置为 Epson。该名称应该与 Windows 中“打印机和传真”对话框中显示的名称完全相同(在 Windows XP 上,转到“开始”、“设置”、“打印机和传真”)。另一种方法是使用另一个子流程调用:


import subprocess
subprocess.call(r'rundll32 printui.dll PrintUIEntry /y /n \\UNC\path\to\printer')

win32print 还支持许多其他功能。您可以启动和停止打印作业,设置打印作业的优先级,获取打印机的配置,安排作业等等。我希望这能对你有所帮助。

延伸阅读

python:2018 年全球最受欢迎的语言

原文:https://www.blog.pythonlibrary.org/2018/10/09/python-worlds-most-popular-language-in-2018/

根据经济学家的说法,Python“正在成为世界上最流行的编码语言”。

下面的图表显示了这种语言的受欢迎程度:

那篇文章中有很多有趣的信息,在这篇与文章相关的 Reddit 帖子中也有一些有趣的对话。

Python 的日历模块(视频)

原文:https://www.blog.pythonlibrary.org/2022/06/01/pythons-calendar-module-video/

在此视频中学习 Python 神奇日历模块的基础知识:

https://www.youtube.com/embed/_OYMvoRoTio?feature=oembed

Python 的创造者 Guido Van Rossom 加入微软

原文:https://www.blog.pythonlibrary.org/2020/11/13/pythons-creator-guido-van-rossom-joins-microsoft/

吉多·范·罗苏姆昨天宣布,他即将退休,加入微软的开发者部门:

Reddit 社区对这一消息反应不一。

我个人希望是积极的事情。虽然我曾希望 Guido 在谷歌时会将 Python 引入 Android 开发,但这并没有发生,我希望他能在微软以其他方式推广 Python。或许 Python 可以作为 Visual Basic for Applications(VBA)for Microsoft Office 的一种替代方案。如果能够使用 Python 直接编写 MS Office 脚本,而不是使用 COM 方法,那就太棒了。

有很多抱怨说在 Windows 上用 Python 开发比 Linux 和 Mac 更难。如果 Guido 能够在微软发挥他的影响力,也许这种情况会继续改变。与 10 年前相比,现在在 Windows 上开始使用 Python 当然要容易得多。所以我对它寄予厚望,希望它只会变得更好!

微软的影响力很大。如果他们开始比以前更多地推广 Python 和开源,那么我认为这是一件积极的事情。

Python 的新秘密模块

原文:https://www.blog.pythonlibrary.org/2017/02/16/pythons-new-secrets-module/

Python 3.6 增加了一个名为 secrets 的新模块,该模块被设计为“提供一种显而易见的方式来可靠地生成适合于管理秘密的加密性强的伪随机值,如帐户认证、令牌等”。Python 的随机模块从来不是为加密用途而设计的,而是为建模和模拟而设计的。当然,您总是可以使用 Python 操作系统模块中的 urandom() 函数:


>>> import os
>>> os.urandom(8)
'\x9c\xc2WCzS\x95\xc8'

但是现在我们有了 secrets 模块,我们可以创建自己的“加密的强伪随机值”。这里有一个简单的例子:


>>> import secrets
>>> import string
>>> characters = string.ascii_letters + string.digits
>>> bad_password = ''.join(secrets.choice(characters) for i in range(8))
>>> bad_password
'SRvM54Z1'

在这个例子中,我们导入了秘密字符串模块。接下来,我们创建一个大写字母和整数的字符串。最后,我们使用 secrets 模块的 choice()方法随机选择字符来生成一个错误的密码。我称之为错误密码的原因是因为我们没有在密码中添加符号。与许多人使用的相比,这实际上是一个相当不错的选择。我的一位读者指出,这可能被认为不好的另一个原因是,用户可能只是把它写在一张纸上。虽然这可能是真的,但使用字典单词通常是非常不鼓励的,所以你应该学会像这样使用密码或投资一个安全的密码管理器。


生成带有秘密的令牌

secrets 模块还提供了几种生成令牌的方法。以下是一些例子:


>>>: secrets.token_bytes()
b'\xd1Od\xe0\xe4\xf8Rn\x8cO\xa7XV\x1cb\xd6\x11\xa0\xcaK'

>>> secrets.token_bytes(8)
b'\xfc,9y\xbe]\x0e\xfb'

>>> secrets.token_hex(16)
'6cf3baf51c12ebfcbe26d08b6bbe1ac0'

>>> secrets.token_urlsafe(16)
'5t_jLGlV8yp2Q5tolvBesQ'

token_bytes 函数将返回一个包含 nbytes 字节数的随机字节串。在第一个例子中,我没有提供字节数,所以 Python 为我选择了一个合理的数字。然后我又试着调用了一次,要了 8 个字节。我们尝试的下一个函数是 token_hex ,它将返回一个十六进制的随机字符串。最后一个函数是 token_urlsafe ,它将返回一个随机的 URL 安全文本字符串。文本也是 Base64 编码的!请注意,在实践中,您可能应该为您的令牌使用至少 32 个字节,以防止强力攻击( source )。


包扎

secrets 模块是 Python 的一个有价值的补充。坦率地说,我认为像这样的东西早就应该添加进去了。但至少现在我们有了它,我们可以安全地生成加密的强令牌和密码。花点时间看看这个模块的文档,因为它有一些有趣的食谱可以玩。


相关阅读

Python 的语句和上下文管理器

原文:https://www.blog.pythonlibrary.org/2021/04/07/pythons-with-statement-and-context-managers/

几年前,Python 2.5 中出现了一个特殊的新关键字,即带有和语句的 。这个新关键字允许开发人员创建上下文管理器。但是等等!什么是上下文管理器?它们是方便的构造,允许你自动设置和拆除某些东西。例如,你可能想打开一个文件,在里面写一些东西,然后关闭它。这可能是上下文管理器的经典例子。事实上,当您使用带有 语句的 打开一个文件时,Python 会自动为您创建一个:

with open(path, 'w') as f_obj:
    f_obj.write(some_data)

回到 Python 2.4,您必须用老式的方法来做:

f_obj = open(path, 'w')
f_obj.write(some_data)
f_obj.close()

这在幕后的工作方式是通过使用 Python 的一些神奇方法: enterexit 。让我们尝试创建您自己的上下文管理器来演示这一切是如何工作的!

创建上下文管理器类

这里不用重写 Python 的 open 方法,而是创建一个上下文管理器,它可以创建一个 SQLite 数据库连接,并在连接完成后关闭它。这里有一个简单的例子:

import sqlite3

class DataConn:
    """"""

    def __init__(self, db_name):
        """Constructor"""
        self.db_name = db_name

    def __enter__(self):
        """
        Open the database connection
        """
        self.conn = sqlite3.connect(self.db_name)
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Close the connection
        """
        self.conn.close()
        if exc_val:
            raise

if __name__ == '__main__':
    db = '/home/mdriscoll/test.db'
    with DataConn(db) as conn:
        cursor = conn.cursor()

在上面的代码中,您创建了一个获取 SQLite 数据库文件路径的类。 enter 方法自动执行,创建并返回数据库连接对象。现在您已经有了,您可以创建一个游标并写入数据库或查询它。当您用 语句退出 时,它会导致 exit 方法执行并关闭连接。

让我们尝试使用另一种方法创建一个上下文管理器。

使用 contextlib 创建上下文管理器

Python 2.5 不仅用 语句添加了 ,还添加了 contextlib 模块。这允许您使用 contextlib 的 contextmanager 函数作为装饰器来创建上下文管理器。

让我们试着创建一个打开和关闭文件的上下文管理器:

from contextlib import contextmanager

@contextmanager
def file_open(path):
    try:
        f_obj = open(path, 'w')
        yield f_obj
    except OSError:
        print("We had an error!")
    finally:
        print('Closing file')
        f_obj.close()

if __name__ == '__main__':
    with file_open('/home/mdriscoll/test.txt') as fobj:
        fobj.write('Testing context managers')

这里你从的 contextlib 中导入的 contextmanager ,并用它来修饰你的 file_open() 函数。这允许你使用 Python 的 语句调用 file_open ()。在您的函数中,您打开文件,然后将它输出,以便调用函数可以使用它。

一旦带有语句的结束,控制返回到 file_open() ,并继续执行 yield 语句之后的代码。这导致执行 finally 语句,关闭文件。如果在处理文件时碰巧有一个 OSError ,它会被捕获,并且 finally 语句仍然会关闭文件处理程序。

contextlib.closing()

contextlib 模块附带了一些其他方便的实用程序。第一个是 closing 类,它将在代码块完成时关闭这个东西。Python 文档提供了一个类似于以下示例的示例:

from contextlib import contextmanager

@contextmanager
def closing(db):
    try:
        yield db.conn()
    finally:
        db.close()

基本上,您所做的是创建一个封装在 contextmanager 中的关闭函数。这相当于结束类所做的工作。不同之处在于,您可以在 with 语句中使用结束类本身,而不是装饰器。

这将会是这样的:

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('http://www.google.com')) as webpage:
    for line in webpage:
        # process the line
        pass

在这个例子中,你打开一个 URL,但是用你的结束类把它包装起来。这将导致一旦你从语句的代码块中掉出,网页的句柄就会被关闭。

**### context lib . suppress(*异常)

另一个方便的小工具是 Python 3.4 中添加的 suppress 类。这个上下文管理器工具背后的思想是它可以抑制任意数量的异常。一个常见的例子是当您想要忽略 FileNotFoundError 异常时。如果您要编写以下上下文管理器,它将不起作用:

>>> with open('fauxfile.txt') as fobj:
        for line in fobj:
            print(line)

Traceback (most recent call last):
  Python Shell, prompt 4, line 1
builtins.FileNotFoundError: [Errno 2] No such file or directory: 'fauxfile.txt'

这个上下文管理器不处理这个异常。如果您想忽略此错误,则可以执行以下操作:

from contextlib import suppress

with suppress(FileNotFoundError):
    with open('fauxfile.txt') as fobj:
        for line in fobj:
            print(line)

在这里,您导入 suppress 并向其传递您想要忽略的异常,在本例中是 FileNotFoundError 异常。如果运行这段代码,不会发生任何事情,因为文件不存在,但也不会引发错误。应该注意的是,这个上下文管理器是重入。这将在本文后面解释。

contextlib.redirect_stdout / redirect_stderr

contextlib 库有两个用于重定向 stdout 和 stderr 的工具,它们分别是在 Python 3.4 和 3.5 中添加的。在添加这些工具之前,如果您想重定向 stdout,您应该这样做:

path = '/path/to/text.txt'

with open(path, 'w') as fobj:
    sys.stdout = fobj
    help(sum)

使用 contextlib 模块,您现在可以执行以下操作:

from contextlib import redirect_stdout

path = '/path/to/text.txt'
with open(path, 'w') as fobj:
    with redirect_stdout(fobj):
        help(redirect_stdout)

在这两个例子中,您将 stdout 重定向到一个文件。当您调用 Python 的 help() 时,它不是打印到 stdout,而是直接保存到文件中。您还可以从 Tkinter 或 wxPython 等用户界面工具包中将 stdout 重定向到某种缓冲区或文本控件类型的小部件。

退出堆栈

ExitStack 是一个上下文管理器,它允许你很容易地以编程方式结合其他上下文管理器和清理功能。起初这听起来有点令人困惑,所以让我们来看看 Python 文档中的一个例子,以帮助您更好地理解这个想法:

>>> from contextlib import ExitStack
>>> with ExitStack() as stack:
        file_objects = [stack.enter_context(open(filename))
            for filename in filenames]
                    ]

这段代码基本上在 list comprehension 中创建了一系列上下文管理器。 ExitStack 维护了一个注册回调的堆栈,当实例关闭时,它会以相反的顺序调用这些回调,这发生在用语句退出的底部时。

在 Python 文档中有许多关于 contextlib 的简洁示例,您可以从中了解如下主题:

  • enter 方法捕获异常

  • 支持可变数量的上下文管理器

  • 取代任何 try-finally 的使用

  • 还有更多!

你应该去看看,这样你就能很好地感受到这个类有多强大。

包扎

上下文管理器非常有趣,并且总是很方便。例如,我在自动化测试中一直使用它们来打开和关闭对话框。现在,您应该能够使用 Python 的一些内置工具来创建自己的上下文管理器。请务必花时间阅读 contextlib 上的 Python 文档,因为还有很多本章没有涉及的其他信息。开心快乐编码!**

Python 的 _winreg:编辑 Windows 注册表

原文:https://www.blog.pythonlibrary.org/2010/03/20/pythons-_winreg-editing-the-windows-registry/

Python 的标准库以包含大量方便的模块和包而闻名,这些模块和包无需安装任何其他东西就可以使用。这是它的标准库经常被称为“包含电池”的主要原因之一。因此,Python 包含一个用于编辑 Windows 注册表的 Windows 专用模块也就不足为奇了。这个特殊的模块有一个奇怪的名字 _winreg (奇怪是因为它以下划线开头)。在本文中,我们将学习使用这个“电池”使用注册表的基本知识。

从注册表中读取

使用 Python 从注册表中读取数据非常容易。在下面的示例中,我们将找出 Outlook Express 的安装位置:


from _winreg import *
key = OpenKey(HKEY_LOCAL_MACHINE, r'Software\Microsoft\Outlook Express', 0, KEY_ALL_ACCESS)
QueryValueEx(key, "InstallRoot")

在我的机器上,这将返回以下元组:(u ' % program files % \ \ Outlook Express ',2)。元组由值和所述值的注册类型组成。还有另外两种可以使用的查询方法,称为 QueryInfoKey 和 QueryValue。前者以三个整数的形式提供关于键本身的信息,而后者只检索名称为空的键的第一个值的数据。文档建议尽可能使用 QueryValueEx。

我们可能也应该快速解释一下上面的代码中发生了什么。OpenKey 函数采用一个 HKEY*常量、一个子密钥路径字符串、一个保留整数(必须为零)和安全掩码。在本例中,我们传入了 KEY_ALL_ACCESS,这使我们可以完全控制那个键。因为我们所做的只是读取它,我们可能应该只使用 KEY_READ。至于 QueryValueEx 做什么,它只接受一个 key 对象和我们要查询的字段名。

写入注册表

如果你最近一直在阅读这篇博客,那么你可能已经看到了用于写入注册表的 _winreg 模块。这只是为您准备的,所以您可以跳过这一部分。我们将从一个实际的例子开始。在下面的代码片段中,我们将设置 Internet Explorer 的主页。一如既往,请注意编辑注册表项可能是危险的。重要提示:在试图编辑注册表之前,请务必备份注册表。现在,继续表演吧!


keyVal = r'Software\Microsoft\Internet Explorer\Main'
try:
    key = OpenKey(HKEY_CURRENT_USER, keyVal, 0, KEY_ALL_ACCESS)
except:
    key = CreateKey(HKEY_CURRENT_USER, keyVal)
SetValueEx(key, "Start Page", 0, REG_SZ, "https://www.blog.pythonlibrary.org/")
CloseKey(key)

在上面的代码中,我们尝试打开下面的键:HKEY _ 当前 _ 用户\软件\微软\Internet Explorer\Main 并将“起始页”的值设置为这个博客。如果打开失败,通常是因为键不存在,所以我们尝试在异常处理程序中创建键。然后,我们使用 SetValueEx 来实际设置值,像所有优秀的程序员一样,我们完成后进行清理并关闭键。如果您跳过了 CloseKey 命令,在这种情况下您会很好,因为脚本已经完成,Python 会为您完成。但是,如果您继续使用这个键,您可能会有访问冲突,因为它已经打开了。因此,教训是当你编辑完一个键时,总是要关闭它。

其他 _winreg 方法

在 _winreg 库中还有其他几个方法值得指出。当您需要删除一个键时,DeleteKey 方法非常方便。不幸的是,我有时需要递归地删除键,比如卸载出错,而 _winreg 没有内置的方法来删除键。当然,你可以自己写,或者你可以下载一个像 YARW (另一个注册表包装器)这样的包装器来帮你写。

DeleteValue 类似于 DeleteKey,只是您只删除一个值。是的,很明显。如果您想编写自己的递归键删除代码,那么您可能想看看 EnumKey 和 EnumValue,因为它们分别枚举键和值。让我们快速看一下如何使用 EnumKey:


from _winreg import EnumKey, HKEY_USERS

try:
    i = 0
    while True:
        subkey = EnumKey(HKEY_USERS, i)
        print subkey
        i += 1
except WindowsError:
    # WindowsError: [Errno 259] No more data is available    
    pass

上面的代码将遍历 HKEY _ 用户配置单元,将子项输出到标准输出,直到到达配置单元的末尾,并引发 WindowsError。当然,这不会下降到子项,但我将把它作为一个练习留给读者去解决。

我们在这里要讨论的最后一个方法是 ConnectRegistry。如果我们需要编辑远程机器的注册表,这是很有帮助的。它只接受两个参数:计算机名和要连接的密钥(例如 HKEY_LOCAL_MACHINE 或类似的)。请注意,当连接到远程机器时,您只能编辑某些键,而其他键不可用。

包扎

我希望这能对你有所帮助,并为你未来的项目提供许多好的想法。我有许多使用这个奇妙库的登录脚本和一些使用 YARW 的脚本。到目前为止,它非常有用,我希望它对你也一样。

更新(2011 年 10 月 14 日):我们现在在卡里森·加尔帝诺的博客上有这篇文章的巴西葡萄牙语翻译

进一步阅读

PyWin32: adodbapi 和 MS Access

原文:https://www.blog.pythonlibrary.org/2011/02/01/pywin32-adodbapi-and-ms-access/

上周, PyWin32 邮件列表上有一个有趣的帖子,关于如何在没有实际安装 Access 的情况下用 Python 读取 Microsoft Access 数据库。Vernon Cole 有解决方案,但我注意到 Google 似乎没有很好地索引 PyWin32 列表,所以我决定在这里写一下。

我把他的代码稍微修改了一下,使其更加清晰,并用 Microsoft Access XP(可从下面下载)创建了一个蹩脚的数据库文件。 adodbapi 模块(不要与 adodb 模块混淆)的源代码发行版还在其“test”文件夹中包含一个测试数据库,您也可以使用它。总之,代码如下:


import adodbapi

database = "db1.mdb"
constr = 'Provider=Microsoft.Jet.OLEDB.4.0; Data Source=%s'  % database
tablename = "address"

# connect to the database
conn = adodbapi.connect(constr)

# create a cursor
cur = conn.cursor()

# extract all the data
sql = "select * from %s" % tablename
cur.execute(sql)

# show the result
result = cur.fetchall()
for item in result:
    print item

# close the cursor and connection
cur.close()
conn.close()

此代码已在以下方面进行了测试:

  • 安装了 Python 2.5.4 和 adodbapi 2.4.0 并安装了 Microsoft Access 的 Windows XP Professional
  • 带 Python 2.6.4、adodbapi 2.2.6 的 Windows 7 家庭高级版(32 位),不带 Microsoft Access

下载

PyWin32:获取 Windows 事件日志

原文:https://www.blog.pythonlibrary.org/2010/07/27/pywin32-getting-windows-event-logs/

前几天,我关注的一个邮件列表上有一个关于访问 Windows 事件日志的帖子。我认为这是一个有趣的话题,所以我去寻找例子,并在 ActiveState 上找到了一个非常好的例子。在这篇文章中,你会发现我的发现。

直接进入代码可能是最简单的。请注意,除了 Python 之外,您唯一需要的是 PyWin32 包。一旦你明白了这一点,你就可以继续下去了:


import codecs
import os
import sys
import time
import traceback
import win32con
import win32evtlog
import win32evtlogutil
import winerror

#----------------------------------------------------------------------
def getAllEvents(server, logtypes, basePath):
    """
    """
    if not server:
        serverName = "localhost"
    else: 
        serverName = server
    for logtype in logtypes:
        path = os.path.join(basePath, "%s_%s_log.log" % (serverName, logtype))
        getEventLogs(server, logtype, path)

#----------------------------------------------------------------------
def getEventLogs(server, logtype, logPath):
    """
    Get the event logs from the specified machine according to the
    logtype (Example: Application) and save it to the appropriately
    named log file
    """
    print "Logging %s events" % logtype
    log = codecs.open(logPath, encoding='utf-8', mode='w')
    line_break = '-' * 80

    log.write("\n%s Log of %s Events\n" % (server, logtype))
    log.write("Created: %s\n\n" % time.ctime())
    log.write("\n" + line_break + "\n")
    hand = win32evtlog.OpenEventLog(server,logtype)
    total = win32evtlog.GetNumberOfEventLogRecords(hand)
    print "Total events in %s = %s" % (logtype, total)
    flags = win32evtlog.EVENTLOG_BACKWARDS_READ|win32evtlog.EVENTLOG_SEQUENTIAL_READ
    events = win32evtlog.ReadEventLog(hand,flags,0)
    evt_dict={win32con.EVENTLOG_AUDIT_FAILURE:'EVENTLOG_AUDIT_FAILURE',
              win32con.EVENTLOG_AUDIT_SUCCESS:'EVENTLOG_AUDIT_SUCCESS',
              win32con.EVENTLOG_INFORMATION_TYPE:'EVENTLOG_INFORMATION_TYPE',
              win32con.EVENTLOG_WARNING_TYPE:'EVENTLOG_WARNING_TYPE',
              win32con.EVENTLOG_ERROR_TYPE:'EVENTLOG_ERROR_TYPE'}

    try:
        events=1
        while events:
            events=win32evtlog.ReadEventLog(hand,flags,0)

            for ev_obj in events:
                the_time = ev_obj.TimeGenerated.Format() #'12/23/99 15:54:09'
                evt_id = str(winerror.HRESULT_CODE(ev_obj.EventID))
                computer = str(ev_obj.ComputerName)
                cat = ev_obj.EventCategory
        ##        seconds=date2sec(the_time)
                record = ev_obj.RecordNumber
                msg = win32evtlogutil.SafeFormatMessage(ev_obj, logtype)

                source = str(ev_obj.SourceName)
                if not ev_obj.EventType in evt_dict.keys():
                    evt_type = "unknown"
                else:
                    evt_type = str(evt_dict[ev_obj.EventType])
                log.write("Event Date/Time: %s\n" % the_time)
                log.write("Event ID / Type: %s / %s\n" % (evt_id, evt_type))
                log.write("Record #%s\n" % record)
                log.write("Source: %s\n\n" % source)
                log.write(msg)
                log.write("\n\n")
                log.write(line_break)
                log.write("\n\n")
    except:
        print traceback.print_exc(sys.exc_info())

    print "Log creation finished. Location of log is %s" % logPath

if __name__ == "__main__":
    server = None  # None = local machine
    logTypes = ["System", "Application", "Security"]
    getAllEvents(server, logTypes, "C:\downloads")

这种类型的脚本有几个潜在的警告。我以管理员的身份在我的电脑上测试了这段代码,并以域管理员的身份在工作中测试了这段代码。我没有作为任何其他类型的用户测试它。因此,如果您在运行这段代码时遇到问题,请检查您的权限。我在 Windows XP 和 Windows 7 上测试了这一点。在 Windows 7 上,UAC 似乎不会阻止这种活动,所以它和 XP 一样容易使用。然而,Windows 7 的事件在代码的消息部分有一些 unicode,而 XP 没有。注意这一点,并相应地处理它。

无论如何,让我们打开这个脚本,看看它是如何工作的。首先,我们有一些进口产品。我们使用编解码器模块以 utf-8 编码日志文件,以防消息中有一些狡猾的 unicode。我们使用 PyWin32 的 win32evtlog 模块打开事件日志并从中提取信息。根据我在开头提到的文章,要从日志中获取所有事件,需要调用 win32evtlog。重复 ReadEventLog 直到它停止返回事件。因此,我们使用 while 循环。在 while 循环中,我们使用一个 for 循环来遍历事件,并提取事件 ID、记录号、事件消息、事件源和一些其他的花絮。我们记录它,然后退出循环的,同时循环调用 win32evtlog。再次读取事件日志

我们使用 traceback 模块打印出脚本运行过程中出现的任何错误。这就是全部了!

包扎

如您所见,使用 PyWin32 包很容易。如果你卡住了,它有一些很棒的文档。如果文档不够好,你可以求助于 MSDN。PyWin32 是 Windows API 的轻量级包装,所以使用 MSDN 的指令相当简单。无论如何,我希望你学到了很多,并会发现它很有帮助。

进一步阅读

如何将一个窗口放到最前面

原文:https://www.blog.pythonlibrary.org/2014/10/20/pywin32-how-to-bring-a-window-to-front/

我最近看到有人问如何在 Windows 中把一个窗口放在最前面,我意识到我有一些旧的未发布的代码可能会帮助别人完成这项任务。很久以前,Tim Golden(可能还有 PyWin32 邮件列表上的其他一些人)向我展示了如何在 windows XP 上让 Windows 出现在最前面,尽管应该注意它也可以在 Windows 7 上工作。如果您想继续学习,您需要下载并安装您自己的 PyWin32 副本。

我们需要选择一些东西放在前面。我喜欢用记事本进行测试,因为我知道它会出现在现有的每一个 Windows 桌面上。打开记事本,然后把其他应用程序的窗口放在它前面。

现在我们准备看一些代码:


import win32gui

def windowEnumerationHandler(hwnd, top_windows):
    top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))

if __name__ == "__main__":
    results = []
    top_windows = []
    win32gui.EnumWindows(windowEnumerationHandler, top_windows)
    for i in top_windows:
        if "notepad" in i[1].lower():
            print i
            win32gui.ShowWindow(i[0],5)
            win32gui.SetForegroundWindow(i[0])
            break

对于这个小脚本,我们只需要 PyWin32 的 win32gui 模块。我们编写了一个小函数,它接受一个窗口句柄和一个 Python 列表。然后我们调用 win32gui 的 EnumWindows 方法,该方法接受一个回调和一个额外的参数,该参数是一个 Python 对象。根据文档,EnumWindows 方法“通过将句柄传递给每个窗口,依次传递给应用程序定义的回调函数,枚举屏幕上的所有顶级窗口”。所以我们把我们的方法传递给它,它枚举窗口,把每个窗口的句柄加上我们的 Python 列表传递给我们的函数。这有点像一个乱七八糟的室内设计师。

一旦完成,你的 top_windows 列表将会充满大量的项目,其中大部分你甚至不知道正在运行。如果你愿意,你可以打印我们报告并检查你的结果。这真的很有趣。但是出于我们的目的,我们将跳过它,只循环遍历列表,查找单词“Notepad”。一旦我们找到它,我们使用 win32gui 的 ShowWindowSetForegroundWindow 方法将应用程序带到前台。

请注意,确实需要寻找一个唯一的字符串,以便调出正确的窗口。如果您运行多个记事本实例并打开不同文件,会发生什么情况?使用当前代码,您将把找到的第一个 Notepad 实例向前移动,这可能不是您想要的。

你可能想知道为什么有人会愿意首先这么做。在我的例子中,我曾经有一个项目,我必须将一个特定的窗口放到前台,并使用 SendKeys 自动输入它。这是一段丑陋脆弱的代码,我不希望它发生在任何人身上。幸运的是,现在有更好的工具来处理这类事情,比如 pywinauto ,但是您可能仍然会发现这段代码对您遇到的一些深奥的事情很有帮助。玩得开心!

注意:这段代码是在 Windows 7 上使用 Python 2.7.8 和 PyWin32 219 测试的。

PyWin32:如何获得应用程序的版本号

原文:https://www.blog.pythonlibrary.org/2014/10/23/pywin32-how-to-get-an-applications-version-number/

有时你需要知道你正在使用的软件版本。找到这些信息的正常方法通常是打开程序,进入帮助菜单,点击关于菜单项。但这是一个 Python 博客,我们想通过编程来实现!要在 Windows 机器上做到这一点,我们需要 PyWin32 。在本文中,我们将研究两种不同的获取应用程序版本号的方法。


使用 win32api 获取版本

首先,我们将使用 PyWin32 的 win32api 模块获取版本号。其实挺好用的。让我们来看看:


from win32api import GetFileVersionInfo, LOWORD, HIWORD

def get_version_number(filename):
    try:
        info = GetFileVersionInfo (filename, "\\")
        ms = info['FileVersionMS']
        ls = info['FileVersionLS']
        return HIWORD (ms), LOWORD (ms), HIWORD (ls), LOWORD (ls)
    except:
        return "Unknown version"

if __name__ == "__main__":
    version = ".".join([str (i) for i in get_version_number (
        r'C:\Program Files\Internet Explorer\iexplore.exe')])
    print version

这里我们用一个路径调用 GetFileVersionInfo ,然后尝试解析结果。如果我们不能解析它,那就意味着这个方法没有返回任何有用的东西,这将导致一个异常被抛出。我们捕捉异常并返回一个字符串,告诉我们找不到版本号。对于本例,我们检查安装了哪个版本的 Internet Explorer。


使用 win32com 获取版本

为了让事情变得更有趣,在下面的例子中,我们使用 PyWin32 的 win32com 模块来检查 Google Chrome 的版本号。让我们来看看:


# based on http://stackoverflow.com/questions/580924/python-windows-file-version-attribute
from win32com.client import Dispatch

def get_version_via_com(filename):
    parser = Dispatch("Scripting.FileSystemObject")
    version = parser.GetFileVersion(filename)
    return version

if __name__ == "__main__":
    path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
    print get_version_via_com(path)

我们在这里所做的就是导入 win32com 的 Dispatch 类并创建该类的一个实例。接下来,我们调用它的 GetFileVersion 方法,并将路径传递给我们的可执行文件。最后,我们返回结果,这个结果或者是数字,或者是一条消息,表明没有可用的版本信息。我更喜欢第二种方法,因为当没有找到版本信息时,它会自动返回一条消息。


包扎

现在您知道了如何在 Windows 上检查应用程序版本号。如果您需要检查关键软件是否需要升级,或者您需要确保它没有升级,因为一些其他应用程序需要旧版本,这可能会很有帮助。


相关阅读

PyWin32:如何在 Windows 上设置桌面背景

原文:https://www.blog.pythonlibrary.org/2014/10/22/pywin32-how-to-set-desktop-background/

在我做系统管理员的时候,我们考虑在登录时将用户的窗口桌面背景设置为特定的图像。因为我负责用 Python 编写的登录脚本,所以我决定做一些研究,看看是否有办法做到这一点。在本文中,我们将研究完成这项任务的两种不同方法。本文中的代码是在 Windows 7 上使用 Python 2.7.8 和 PyWin32 219 测试的。

对于第一种方法,您需要下载一份 PyWin32 并安装它。现在让我们看看代码:


# based on http://dzone.com/snippets/set-windows-desktop-wallpaper
import win32api, win32con, win32gui

#----------------------------------------------------------------------
def setWallpaper(path):
    key = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER,"Control Panel\\Desktop",0,win32con.KEY_SET_VALUE)
    win32api.RegSetValueEx(key, "WallpaperStyle", 0, win32con.REG_SZ, "0")
    win32api.RegSetValueEx(key, "TileWallpaper", 0, win32con.REG_SZ, "0")
    win32gui.SystemParametersInfo(win32con.SPI_SETDESKWALLPAPER, path, 1+2)

if __name__ == "__main__":
    path = r'C:\Users\Public\Pictures\Sample Pictures\Jellyfish.jpg'
    setWallpaper(path)

在这个例子中,我们使用微软随 Windows 提供的一个示例图像。在上面的代码中,我们编辑了一个 Windows 注册表项。如果你愿意,可以使用 Python 自己的 _winreg 模块来完成前 3 行。最后一行告诉 Windows 将桌面设置为我们提供的图像。

现在让我们看看另一种方法,它利用了 ctypes 模块和 PyWin32。


import ctypes
import win32con

def setWallpaperWithCtypes(path):
    # This code is based on the following two links
    # http://mail.python.org/pipermail/python-win32/2005-January/002893.html
    # http://code.activestate.com/recipes/435877-change-the-wallpaper-under-windows/
    cs = ctypes.c_buffer(path)
    ok = ctypes.windll.user32.SystemParametersInfoA(win32con.SPI_SETDESKWALLPAPER, 0, cs, 0)

if __name__ == "__main__":
    path = r'C:\Users\Public\Pictures\Sample Pictures\Jellyfish.jpg'
    setWallpaperWithCtypes(path)

在这段代码中,我们创建了一个 buffer 对象,然后我们将它传递给与上一个示例中基本相同的命令,即 SystemParametersInfoA 。您会注意到,在后一种情况下,我们不需要编辑注册表。如果您查看示例代码中列出的链接,您会注意到一些用户发现 Windows XP 只允许将位图设置为桌面背景。我在 Windows 7 上用 JPEG 格式进行了测试,效果不错。

现在你可以创建自己的脚本来随机改变桌面的背景!或者,您可能只是在自己的系统管理职责中使用这些知识。玩得开心!

PyWin32 -如何监控打印队列

原文:https://www.blog.pythonlibrary.org/2013/12/19/pywin32-monitor-print-queue/

前几天,我试图找出一种方法来监控 Windows 上的打印队列。手头的任务是记录哪些文档被成功地送到了打印机。这个想法是,当打印完成时,文档将被存档。要做这类事情,你需要 PyWin32 (又名:Python for Windows extensions)。在本文中,我们将查看一个检查打印队列的简单脚本。

代码如下:


import time
import win32print

#----------------------------------------------------------------------
def print_job_checker():
    """
    Prints out all jobs in the print queue every 5 seconds
    """
    jobs = [1]
    while jobs:
        jobs = []
        for p in win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL,
                                         None, 1):
            flags, desc, name, comment = p

            phandle = win32print.OpenPrinter(name)
            print_jobs = win32print.EnumJobs(phandle, 0, -1, 1)
            if print_jobs:
                jobs.extend(list(print_jobs))
            for job in print_jobs:
                print "printer name => " + name
                document = job["pDocument"]
                print "Document name => " + document
            win32print.ClosePrinter(phandle)

        time.sleep(5)
    print "No more jobs!"

#----------------------------------------------------------------------
if __name__ == "__main__":
    print_job_checker()

首先我们导入 win32print 和时间模块。我们需要 win32print 来访问打印机。我们创建了一个潜在的无限循环来检查打印队列中的作业。如果作业列表为空,这意味着打印队列中没有任何内容,该功能将退出。在上面的代码中,我们使用了 win32print。enum prits()循环检查安装在机器上的打印机。第一个参数是一个标志(win32print。PRINTER_ENUM_LOCAL),第二个是名称(或者本例中没有名称),第三个是信息级别。我们可以使用几个标志,比如 PRINTER_ENUM_SHARED、PRINTER_ENUM_LOCAL 或 PRINTER_ENUM_CONNECTIONS。我使用 PRINTER_ENUM_LOCAL,因为它返回了一个打印机名,其格式可以与 win32print 的 OpenPrinter 方法一起使用。我们这样做是为了通过句柄向打印机查询信息。为了获得打印作业的列表,我们调用 win32print。带有打印句柄的 EnumJobs()。然后我们遍历它们,打印出打印机名和文档名。你不需要做所有这些打印,但我发现它在我写代码的时候很有帮助。出于测试目的,我建议打开打印队列并将其设置为“暂停”,这样您就可以在准备好之前阻止打印纸。这使您仍然可以将项目添加到可以查询的打印队列中。

我在检查之间放置了 5 秒钟的延迟,以确保队列中没有新的更新。如果有打印作业,它会再次检查队列。否则它会跳出循环。

希望你会发现这段代码对你正在做的事情有用。尽情享受吧!

附加说明

本周 PyDev:Qumisha Goss

原文:https://www.blog.pythonlibrary.org/2018/06/18/qumisha-goss/

本周,我们欢迎 Qumisha Goss(@ QatalystGoss)成为我们本周的 PyDev。q 是来自底特律的图书管理员,他在今年的美国 PyCon 上做了我所见过的最好的主题演讲之一。出于某种原因,上传那天早上的主题演讲的人没有将主题演讲相互分开,也没有将上午的闪电谈话分开,所以你必须通过官方视频寻找大约 2/3 的方式才能找到 Q 的主题演讲:

https://www.youtube.com/embed/VJ0vibC_Hl0?feature=oembed

我个人认为你应该花点时间看看这个视频。但是如果你没有时间,你仍然可以阅读这个对这个了不起的人的简短采访。

你能告诉我们一些关于你自己的情况吗(爱好、教育等)

我叫奎米莎·戈斯,是底特律公共图书馆的图书管理员。我在卡尔文学院学习历史和古典研究。我痴迷于神话,然后是罗马帝国的工程学。我想成为工程师,然后成为档案管理员,现在我是图书管理员。

你为什么开始使用 Python?

在我被鼓励在图书馆开设儿童编程课程后,我开始使用 python。我开始使用“一小时代码”和 Code.org 资源,但是当孩子们对此感到厌烦时,我自学了 Python 来教他们一些更难、更有弹性的东西。

你还知道哪些编程语言,你最喜欢哪一种?

Python 实际上是我唯一使用过 SQL 的语言,尽管是为了常规的图书馆业务。

你现在在做什么项目?

目前,我正在参加帕克曼程序员暑期项目。今年夏天,这个项目被称为“代码:种植”,我们将鼓励孩子们到户外种植一个花园,然后通过制作延时相机和对土壤湿度传感器进行编程,使用代码来监控他们的花园。

哪些 Python 库是你最喜欢的(核心或第三方)?

  • 因为我和孩子们一起工作。
  • sqllcemy(SQL 语法)

在开源项目中,你学到了哪三件最重要的事情?

  • Python 社区非常令人鼓舞
  • 没有人什么都知道
  • 寻求帮助并得到帮助是没问题的。

你从事开源工作的动机是什么?

我个人认为学习应该是免费和开放的,开源为一些通常没有机会获得所需资源的人提供了公平竞争的机会。

你还有什么想说的吗?

我们在图书馆用物理计算做了很多工作,因为我们发现除了计算机之外,有一些东西可以让他们接触,这有助于学习过程。所以我们经常用树莓 Pis 和 Micro bits。

感谢您接受采访!

拉斯皮斯顿今天开始!

原文:https://www.blog.pythonlibrary.org/2012/07/20/raspithon-starts-today/

来自世界各地的一些青少年决定学习 Python,在从今天开始到明天结束的冲刺中用树莓派编写一个游戏。他们也接受捐赠给树莓派基金会。如果你愿意,你可以关注他们的直播。Raspberry Pi 是一个 ARM GNU/Linux 盒子,售价 25 美元。我认为这是一个有趣的项目,我的读者可能会感兴趣。

用 Python 和 xlrd 读取 Excel 电子表格

原文:https://www.blog.pythonlibrary.org/2014/04/30/reading-excel-spreadsheets-with-python-and-xlrd/

上个月,我们学习了如何创建 Microsoft Excel(即。xls)文件使用 xlwt 包。今天我们将学习如何阅读。xls/*。xlsx 文件使用一个名为 xlrd 的包。xlrd 包可以在 Linux 和 Mac 上运行,也可以在 Windows 上运行。当您需要在 Linux 服务器上处理 Excel 文件时,这非常有用。

我们将从阅读我们在上一篇文章中创建的第一个 Excel 文件开始。

我们开始吧!


阅读 Excel 电子表格

在这一节中,我们将看到一个函数,它演示了读取 Excel 文件的不同方法。下面是代码示例:


import xlrd

#----------------------------------------------------------------------
def open_file(path):
    """
    Open and read an Excel file
    """
    book = xlrd.open_workbook(path)

    # print number of sheets
    print book.nsheets

    # print sheet names
    print book.sheet_names()

    # get the first worksheet
    first_sheet = book.sheet_by_index(0)

    # read a row
    print first_sheet.row_values(0)

    # read a cell
    cell = first_sheet.cell(0,0)
    print cell
    print cell.value

    # read a row slice
    print first_sheet.row_slice(rowx=0,
                                start_colx=0,
                                end_colx=2)

#----------------------------------------------------------------------
if __name__ == "__main__":
    path = "test.xls"
    open_file(path)

让我们把它分解一下。首先我们导入 xlrd ,然后在我们的函数中,我们打开传入的 Excel 工作簿。接下来的几行显示了如何反思这本书。我们找出工作簿中有多少工作表,并打印出它们的名称。接下来,我们通过 sheet_by_index 方法提取第一个工作表。我们可以使用 row_values 方法从工作表中读取一整行。如果我们想获得一个特定单元格的值,我们可以调用单元格方法,并向其传递行和列索引。最后,我们使用 xlrd 的 row_slice 方法来读取行的一部分。如您所见,最后一个方法接受一个行索引以及开始和结束列索引来确定要返回的内容。row_slice 方法返回单元格实例的列表。

这使得迭代一组单元格变得非常容易。这里有一个小片段来演示:


cells = first_sheet.row_slice(rowx=0,
                              start_colx=0,
                              end_colx=2)
for cell in cells:
    print cell.value

xlrd 包支持以下类型的单元格:文本、数字(即浮点)、日期(任何“看起来”像日期的数字格式)、布尔、错误和空/空白。该包还支持从命名单元格中提取数据,尽管该项目并不支持所有类型的命名单元格。参考文本对它到底不支持什么有点含糊。

如果你需要复制单元格格式,你需要下载 xlutils 包


包扎

至此,你应该足够了解如何阅读大多数使用微软 XLS 格式构建的 Excel 文件。还有另一个包也支持读取 xls/xlsx 文件,名为 openpyxl 项目。你也许会想试试看。


相关阅读

使用 Python 读取 OpenVPN 状态数据(第 2 页,共 3 页)

原文:https://www.blog.pythonlibrary.org/2008/04/05/reading-openvpn-status-data-with-python-2-of-3/

这是关于使用 wxPython + PyWin32 从 Windows 上的 OpenVPN 会话获取输出的 3 部分系列文章的第 2 部分。在本文中,我将展示如何用 Python 启动 OpenVPN,以及如何观察 OpenVPN 向其写入数据日志的文件。

如果上次不在,你需要去蒂姆·戈尔登网站下载 watch_directory.py 文件。一旦你得到了它,在你最喜欢的文本编辑器中打开文件,并复制如下内容:


# watch_directory code
ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed to something",
  5 : "Renamed from something"
}

def watch_path (path_to_watch, include_subdirectories=False):
    FILE_LIST_DIRECTORY = 0x0001
    hDir = win32file.CreateFile (
        path_to_watch,
        FILE_LIST_DIRECTORY,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None
        )
    while 1:
        results = win32file.ReadDirectoryChangesW (
            hDir,
            1024,
            include_subdirectories,
            win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
            win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
            win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
            win32con.FILE_NOTIFY_CHANGE_SIZE |
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
            win32con.FILE_NOTIFY_CHANGE_SECURITY,
            None,
            None
            )
        for action, file in results:
            full_filename = os.path.join (path_to_watch, file)
            if not os.path.exists (full_filename):
                file_type = ""
            elif os.path.isdir (full_filename):
                file_type = 'folder'
            else:
                file_type = 'file'
            yield (file_type, full_filename, ACTIONS.get (action, "Unknown"))

class Watcher (threading.Thread):

    def __init__ (self, path_to_watch, results_queue, **kwds):
        threading.Thread.__init__ (self, **kwds)
        self.setDaemon (1)
        self.path_to_watch = path_to_watch
        self.results_queue = results_queue
        self.start ()

    def run (self):
        for result in watch_path (self.path_to_watch):
            self.results_queue.put (result)

Golden 的网站详细解释了该代码,所以我不打算赘述,但该代码的基本要点是,它监视对目录的特定更改,然后返回被更改的文件的名称和更改的类型。默认情况下,OpenVPN 将其日志文件写入以下位置:“C:\ Program Files \ OpenVPN \ log \ mcisvpn . log”。每当我的程序被警告日志目录中有任何变化时,它打开“mcisvpn.log”文件,读取它,然后将新数据附加到文本小部件。

因为我只想从文件中读取新数据,所以我必须跟踪我在文件中的位置。下面是一个函数,它展示了我如何跟踪文件的变化,以及我如何跟踪脚本在文件中读取的最后一个位置。此外,这个“watcher”脚本在一个单独的线程中运行,以保持 wxPython GUI 的响应性。


def _resultProducer(self, jobID, abortEvent):
    """
    GUI will freeze if this method is not called in separate thread.
    """
    PATH_TO_WATCH = [r'C:\Program Files\OpenVPN\log']
    try: path_to_watch = sys.argv[1].split (",") or PATH_TO_WATCH
    except: path_to_watch = PATH_TO_WATCH
    path_to_watch = [os.path.abspath (p) for p in path_to_watch]

    print "Watching %s at %s" % (", ".join (path_to_watch), time.asctime ())
    # create a Queue object that is updated with the file(s) that is/are changed               
    files_changed = Queue.Queue ()
    for p in path_to_watch:
        Watcher (p, files_changed)

    filepath = os.path.join(PATH_TO_WATCH[0], 'mcisvpn.log')    
    f = open(filepath)
    for line in f.readlines():
        print line

    # get the last position before closing the file
    last_pos = f.tell()
    f.close()

    while not abortEvent():
        try:
            file_type, filename, action = files_changed.get_nowait ()
            # if the change was an update, seek to the last position read
            # and read the update
            if action == 'Updated':
                f = open(filepath)
                f.seek(last_pos)
                for line in f.readlines():
                    if line != '\n':
                        print line
                        f.close()
                # get the last position before closing the file
                last_pos = f.tell()
                f.close()
        except Queue.Empty:
            pass
        time.sleep (1)

    return jobID

上面的代码还包括来自标题为“DelayedResult”的 wxPython 演示的一些片段。它负责为我启动和停止线程,虽然我不能完全肯定它干净利落地杀死了 Golden 的 Watcher 类。我只是不知道如何确定;但似乎很管用。

下一次我将展示我的 wxPython 代码,这样您就可以看到如何将这些不同的部分集成在一起。

你可以在下面下载我的代码:

使用 Python 读取 OpenVPN 状态数据(第 3 页,共 3 页)

原文:https://www.blog.pythonlibrary.org/2008/04/08/reading-openvpn-status-data-with-python-3-of-3/

在前两篇文章中,我们一直在讨论如何将 OpenVPN 与 Python 结合使用。在这最后一篇文章中,我将展示如何用一些 wxPython 代码将它们整合到一个 GUI 中。我还将讨论一些重要的片段。

第一个需要注意的代码片段是在 run 方法中。当我们运行程序时,我们需要确保 OpenVPN 服务正在运行,否则日志文件将不会被更新。所以你会注意到使用了 win32serviceutil 的 StartService 方法。我们将它放在一个 try 语句中,以捕捉如果 OpenVPN 服务已经在运行、没有找到或无法启动时可能发生的错误。通常情况下,你不应该使用 bare,因为它会掩盖程序中的其他错误;然而,我无法找到合适的错误代码来使用,所以我将留给读者。


def run(self):
    """
    Run the openvpn.exe script
    """
    vpnname='MCISVPN'
    configfile='mcisvpn.conf'
    defaultgw=''
    vpnserver=''
    vpnserverip = ''

    print 'Starting OpenVPN Service...',
    try:
        win32serviceutil.StartService('OpenVPN Service', None)
    except Exception, e:
        print e
    print 'success!'

    delayedresult.startWorker(self._resultConsumer, self._resultProducer, 
                              wargs=(self.jobID,self.abortEvent), jobID=self.jobID)

在尝试启动 OpenVPN 服务之后,我使用 wxPython 提供的线程模型来运行我上次提到的 Golden 的 watcher.py 代码,并在日志文件中跟踪我的位置。

以下是完整的主要 GUI 代码:


from vpnTBIcon import VPNIconCtrl

import os
import sys
import Queue
import threading
import time
import win32file
import win32con
import win32serviceutil
import wx
import wx.lib.delayedresult as delayedresult

ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed to something",
  5 : "Renamed from something"
}

def watch_path (path_to_watch, include_subdirectories=False):
    FILE_LIST_DIRECTORY = 0x0001
    hDir = win32file.CreateFile (
        path_to_watch,
        FILE_LIST_DIRECTORY,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None
        )
    while 1:
        results = win32file.ReadDirectoryChangesW (
            hDir,
            1024,
            include_subdirectories,
            win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
            win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
            win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
            win32con.FILE_NOTIFY_CHANGE_SIZE |
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
            win32con.FILE_NOTIFY_CHANGE_SECURITY,
            None,
            None
            )
        for action, file in results:
            full_filename = os.path.join (path_to_watch, file)
            if not os.path.exists (full_filename):
                file_type = ""
            elif os.path.isdir (full_filename):
                file_type = 'folder'
            else:
                file_type = 'file'
            yield (file_type, full_filename, ACTIONS.get (action, "Unknown"))

class Watcher (threading.Thread):

    def __init__ (self, path_to_watch, results_queue, **kwds):
        threading.Thread.__init__ (self, **kwds)
        self.setDaemon (1)
        self.path_to_watch = path_to_watch
        self.results_queue = results_queue
        self.start ()

    def run (self):
        for result in watch_path (self.path_to_watch):
            self.results_queue.put (result)

# -------------------------------------------------------------------------
# GUI Code starts here

class vpnGUI(wx.App):
    """
    wx application that wraps jrb's vpn script to allow it
    to run in the system tray
    """
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)

        self.frame = wx.Frame(None, wx.ID_ANY, title='Voicemail', size=(800,500) )

        self.panel = wx.Panel(self.frame, wx.ID_ANY)

        self.abortEvent = delayedresult.AbortEvent()

        # Set defaults
        # ----------------------------------------------------------------------------------------
        self.jobID = 0

        # Create widget controls
        # ----------------------------------------------------------------------------------------
        # redirect stdout
        self.log = wx.TextCtrl(self.panel, wx.ID_ANY, size=(1000,500),
                               style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        redir=RedirectText(self.log)
        sys.stdout=redir

        closeBtn = wx.Button(self.panel, wx.ID_ANY, 'Close')

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.log, 1, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(closeBtn, 0, wx.ALL|wx.CENTER, 5)
        self.panel.SetSizer(mainSizer)

        # Bind events
        # ----------------------------------------------------------------------------------------
        self.Bind(wx.EVT_BUTTON, self.onClose, closeBtn)
        self.Bind(wx.EVT_ICONIZE, self.onMinimize)

        # create the system tray icon:
        try:            
            self.tbicon = VPNIconCtrl(self.frame)                        
        except Exception, e:
            print 'Icon creation exception => %s' % e
            self.tbicon = None

        # comment this line out if you don't want to show the
        # GUI when the program is run
        self.frame.Show(True) # make the frame visible

        self.run()

    def run(self):
        """
        Run the openvpn service
        """
        vpnname='MCISVPN'
        configfile='mcisvpn.conf'
        defaultgw=''
        vpnserver=''
        vpnserverip = ''

        print 'Starting OpenVPN Service...',
        try:
            win32serviceutil.StartService('OpenVPN Service', None)
        except Exception, e:
            print e
        print 'success!'

        delayedresult.startWorker(self._resultConsumer, self._resultProducer, 
                                  wargs=(self.jobID,self.abortEvent), jobID=self.jobID)

    def _resultProducer(self, jobID, abortEvent):
        """
        GUI will freeze if this method is not called in separate thread.
        """
        PATH_TO_WATCH = [r'C:\Program Files\OpenVPN\log']
        try: path_to_watch = sys.argv[1].split (",") or PATH_TO_WATCH
        except: path_to_watch = PATH_TO_WATCH
        path_to_watch = [os.path.abspath (p) for p in path_to_watch]

        print "Watching %s at %s" % (", ".join (path_to_watch), time.asctime ())
        files_changed = Queue.Queue ()
        for p in path_to_watch:
            Watcher (p, files_changed)

        filepath = os.path.join(PATH_TO_WATCH[0], 'mcisvpn.log')
        print 'filepath => ' + filepath
        f = open(filepath)
        for line in f.readlines():
            print line
        last_pos = f.tell()
        f.close()

        while not abortEvent():
            try:
                file_type, filename, action = files_changed.get_nowait ()
                if action == 'Updated':
                    print 'Last pos => ', last_pos
                    f = open(filepath)
                    f.seek(last_pos)
                    for line in f.readlines():
                        if line != '\n':
                            print line

                    last_pos = f.tell()
                    f.close()

            except Queue.Empty:
                pass
            time.sleep (1)

        return jobID

    def _resultConsumer(self, delayedResult):
        jobID = delayedResult.getJobID()
        assert jobID == self.jobID
        try:
            result = delayedResult.get()
        except Exception, exc:
            print "Result for job %s raised exception: %s" % (jobID, exc) 
            return

    def onMinimize(self, event):
        """ Minimize to tray """
        self.frame.Hide()

    def onClose(self, event):
        """
        Close the program
        """

        # recover stdout
        sys.stdout=sys.__stdout__

        # stop OpenVPN service
        try:
            print 'Stopping OpenVPN service...'
            win32serviceutil.StopService('OpenVPN Service', None)
        except Exception, e:
            print e        

        # stop the threads
        self.abortEvent.set()
        # remove the icon from the tray
        self.tbicon.Destroy()
        # close the frame
        self.frame.Close()

class RedirectText:
    def __init__(self,textDisplay):
        self.out=textDisplay

    def write(self,string):
        self.out.WriteText(string)

###### Run script! ######
if __name__ == "__main__":
    app = vpnGUI()
    app.MainLoop()

您会注意到将 init 中的 stdout 重定向到我们的文本控件小部件。为了写入小部件,我们使用 Python 的打印内置。我们在 onClose 事件处理程序中重置了 stdout。该处理程序还会停止 OpenVPN 服务,销毁系统托盘图标并关闭程序。

这就是真正的意义所在。下面有一些链接,供那些想更深入了解这些工具的人使用。

资源

这些示例的来源

使用 Python 读取 OpenVPN 状态数据(第 1 页,共 3 页)

原文:https://www.blog.pythonlibrary.org/2008/04/03/reading-openvpn-status-data-with-python/

我正在做一个关于使用 wxPython 和 PyWin32 从正在运行的 OpenVPN 会话中捕获输出的 3 部分系列文章。

我在工作中使用 OpenVPN 连接电脑。我注意到我们当前启动 OpenVPN 的方法是在控制台窗口中,这样就可以监控程序的输出。如果用户碰巧关闭了所述窗口,它将结束 VPN 会话。我认为这是愚蠢的,所以我决定尝试使用 wxPython 包装接口,这样我可以最小化它到系统托盘中,如果我有问题的话,可以根据需要将它带回来检查输出。如果你想跟着做,你需要以下东西:

都拿到了吗?好的。我们继续。首先,创建一个文件夹来存放您的脚本。我们实际上需要一对夫妇来做这件事。

首先,我们要创建一个系统托盘图标。

第一步:选择一个图标(我用的是塔玛林系列中的一个)

步骤 2:一旦有了图标,我们将使用一个名为 img2py 的 wxPython 实用程序,它将把图标或图片转换成 Python 文件。安装 wxPython:\ \ path \ to \ Python 25 \ Lib \ site-packages \ wx-2.8-MSW-unicode \ wx \ tools 后,可以在 Python 文件夹中找到它(根据您的系统需要进行调整)

步骤 3:将图标文件移动到步骤 2 中的目录,并通过单击开始、运行和键入 cmd 打开命令窗口。导航到上面的目录(使用 cd 命令)并运行以下命令:python img 2 py . py-I myicon . ico icon . py

步骤 4:完成后,将 icon.py 文件复制到您创建的保存脚本的文件夹中。这将与一些处理图标化和右键菜单的代码结合在一起。

现在我们将创建系统托盘图标响应鼠标事件所需的逻辑。我在 wxPython 演示中找到了一些代码,它们完成了我所做的大部分工作。所以我复制了一下,稍微修改了一下,适合我的需求。您可以在下面看到最终结果:


import wx
from vpnIcon import getIcon

class VPNIconCtrl(wx.TaskBarIcon):
    TBMENU_RESTORE = wx.NewId()
    TBMENU_CLOSE   = wx.NewId()
    TBMENU_CHANGE  = wx.NewId()

    def __init__(self, frame):
        wx.TaskBarIcon.__init__(self)
        self.frame = frame        

        # Set the image
        tbIcon = getIcon()

        # Give the icon a tooltip
        self.SetIcon(tbIcon, "VPN Status")
        self.imgidx = 1

        # bind some events
        self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
        self.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=self.TBMENU_RESTORE)
        self.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)        

    def CreatePopupMenu(self):
        """
        This method is called by the base class when it needs to popup
        the menu for the default EVT_RIGHT_DOWN event.  Just create
        the menu how you want it and return it from this function,
        the base class takes care of the rest.
        """
        menu = wx.Menu()
        menu.Append(self.TBMENU_RESTORE, "View Status")
        menu.AppendSeparator()
        menu.Append(self.TBMENU_CLOSE, "Close Program")

        return menu

    def OnTaskBarActivate(self, evt):
        if self.frame.IsIconized():
            self.frame.Iconize(False)
        if not self.frame.IsShown():
            self.frame.Show(True)
        self.frame.Raise()

    def OnTaskBarClose(self, evt):
        self.Destroy()
        self.frame.Close()

下一次,我们将讨论您需要了解的 win32 代码,在最后一部分,我们将创建 GUI 并将其余部分放在一起。

用 OpenPyXL 和 Python 读取电子表格

原文:https://www.blog.pythonlibrary.org/2021/07/20/reading-spreadsheets-with-openpyxl-and-python/

您将对 Microsoft Excel 文档执行一些基本操作。其中最基本的是从 Excel 文件中读取数据。您将学习如何从 Excel 电子表格中获取数据。

编者按:本文基于《用 Python 自动化 Excel》一书中的一章。你可以在 Gumroad 或者 Kickstarter 上订购一份。

在开始使用 Python 自动化 Excel 之前,您应该了解一些常用术语:

  • 电子表格或工作簿–文件本身(。xls 或者。xlsx)。
  • 工作表–工作簿中的一张内容表。电子表格可以包含多个工作表。
  • 列–以字母标记的垂直数据行,以“A”开头。
  • 行–用数字标记的水平数据行,从 1 开始。
  • 单元格–列和行的组合,如“A1”。

现在你对词汇有了一些基本的理解,你可以继续了。

在本章中,您将学习如何执行以下任务:

  • 打开电子表格
  • 读取特定单元格
  • 从特定行读取单元格
  • 从特定列读取单元格
  • 从多行或多列读取单元格
  • 从区域中读取单元格
  • 读取所有工作表中的所有单元格

您可以从学习如何在下一节打开工作簿开始!

打开电子表格

您需要的第一个项目是一个 Microsoft Excel 文件。你可以使用这个 GitHub 代码库 里的文件。在第 2 章文件夹中有一个名为books.xlsx的文件,您将在这里使用。

里面有两张纸。以下是第一张表单的屏幕截图:

Book Worksheet

为了完整起见,下面是第二张表单的截图:

Sales Worksheet

注意:这些表格中的数据并不准确,但它们有助于学习如何使用 OpenPyXL。

现在您已经准备好开始编码了!打开您最喜欢的 Python 编辑器,创建一个名为open_workbook.py的新文件。然后将以下代码添加到您的文件中:

# open_workbook.py

from openpyxl import load_workbook

def open_workbook(path):
    workbook = load_workbook(filename=path)
    print(f"Worksheet names: {workbook.sheetnames}")
    sheet = workbook.active
    print(sheet)
    print(f"The title of the Worksheet is: {sheet.title}")

if __name__ == "__main__":
    open_workbook("books.xlsx")

这段代码的第一步是从openpyxl包中导入load_workbook()load_workbook()函数将加载你的 Excel 文件,并以 Python 对象的形式返回。然后,您可以像对待 Python 中的任何其他对象一样,与该 Python 对象进行交互。

您可以通过访问sheetnames属性获得 Excel 文件中的工作表列表。该列表包含 Excel 文件中从左到右的工作表标题。您的代码将打印出这个列表。

接下来,获取当前活动的工作表。如果您的工作簿只有一个工作表,则该工作表将是活动工作表。如果您的工作簿有多个工作表,就像这个工作表一样,那么最后一个工作表将是活动的工作表。

函数的最后两行打印出Worksheet对象和活动工作表的标题。

但是,如果您想选择一个特定的工作表来工作呢?要了解如何完成这一点,创建一个新文件并将其命名为read_specific_sheet.py

然后输入以下代码:

# read_specific_sheet.py

from openpyxl import load_workbook

def open_workbook(path, sheet_name):
    workbook = load_workbook(filename=path)
    if sheet_name in workbook.sheetnames:
        sheet = workbook[sheet_name]
        print(f"The title of the Worksheet is: {sheet.title}")
        print(f"Cells that contain data: {sheet.calculate_dimension()}")

if __name__ == "__main__":
    open_workbook("books.xlsx", sheet_name="Sales")

您的函数open_workbook()现在接受一个sheet_namesheet_name是与要读取的工作表标题相匹配的字符串。您检查一下sheet_name是否在代码的workbook.sheetnames中。如果是,您可以通过使用workbook[sheet_name]访问该工作表来选择它。

然后打印出工作表的标题,以验证您是否有正确的工作表。你也叫新东西:calculate_dimension()。该方法返回工作表中包含数据的单元格。在这种情况下,它将打印出“A1:D4”中有数据。

现在,您已经准备好继续学习如何从细胞本身读取数据。

读取特定单元格

使用 OpenPyXL 读取单元格有很多不同的方法。首先,您将学习如何读取特定单元格的内容。

在 Python 编辑器中创建一个新文件,并将其命名为reading_specific_cells.py。然后输入以下代码:

# reading_specific_cells.py

from openpyxl import load_workbook

def get_cell_info(path):
    workbook = load_workbook(filename=path)
    sheet = workbook.active
    print(sheet)
    print(f'The title of the Worksheet is: {sheet.title}')
    print(f'The value of A2 is {sheet["A2"].value}')
    print(f'The value of A3 is {sheet["A3"].value}')
    cell = sheet['B3']
    print(f'The variable "cell" is {cell.value}')

if __name__ == '__main__':
    get_cell_info('books.xlsx')

在本例中,有三个硬编码单元:A2、A3 和 B3。您可以通过使用类似字典的访问来访问它们的值:sheet["A2"].value。或者,您可以将sheet["A2"]赋给一个变量,然后执行类似于cell.value的操作来获取单元格的值。

您可以在上面的代码中看到这两种方法。

运行此代码时,您应该会看到以下输出:

<Worksheet "Sales">
The title of the Worksheet is: Sales
The value of A2 is 'Python 101'
The value of A3 is 'wxPython Recipes'
The variable "cell" is 5

此输出显示了如何使用 Python 轻松地从 Excel 中提取特定的单元格值。

现在您已经准备好学习如何从特定的单元格行中读取数据了!

从特定行读取单元格

在大多数情况下,您可能希望一次读取工作表中的多个单元格。OpenPyXL 还提供了一种一次获取整行的方法。

继续创建一个新文件。可以命名为reading_row_cells.py。然后将以下代码添加到您的程序中:

# reading_row_cells.py

from openpyxl import load_workbook

def iterating_row(path, sheet_name, row):
    workbook = load_workbook(filename=path)
    if sheet_name not in workbook.sheetnames:
        print(f"'{sheet_name}' not found. Quitting.")
        return

    sheet = workbook[sheet_name]
    for cell in sheet[row]:
        print(f"{cell.column_letter}{cell.row} = {cell.value}")

if __name__ == "__main__":
    iterating_row("books.xlsx", sheet_name="Sheet 1 - Books",
                  row=2)

在这个例子中,你传入行号 2 。您可以迭代行中的值,如下所示:

for cell in sheet[row]:
    ...

这使得从一行中获取值变得非常简单。当您运行这段代码时,您将得到以下输出:

A2 = Title
B2 = Author
C2 = Publisher
D2 = Publishing Date
E2 = ISBN
F2 = None
G2 = None

最后两个值都是。如果您不想得到 None 值,您应该在打印之前添加一些额外的处理来检查该值是否为 None。你可以试着通过练习自己找出答案。

现在,您已经准备好学习如何从特定列中获取单元格了!

从特定列读取单元格

从特定列读取数据也是一个常见的用例,您应该知道如何完成。例如,您可能有一个只包含汇总的列,而您只需要提取该特定列。

要了解如何做到这一点,请创建一个新文件并将其命名为reading_column_cells.py。然后输入以下代码:

# reading_column_cells.py

from openpyxl import load_workbook

def iterating_column(path, sheet_name, col):
    workbook = load_workbook(filename=path)
    if sheet_name not in workbook.sheetnames:
        print(f"'{sheet_name}' not found. Quitting.")
        return

    sheet = workbook[sheet_name]
    for cell in sheet[col]:
        print(f"{cell.column_letter}{cell.row} = {cell.value}")

if __name__ == "__main__":
    iterating_column("books.xlsx", sheet_name="Sheet 1 - Books",
                    col="A")

这段代码与上一节中的代码非常相似。这里的不同之处在于,您用sheet[col]替换了sheet[row],并对其进行迭代。

在本例中,您将列设置为“A”。当您运行此代码时,您将获得以下输出:

A1 = Books
A2 = Title
A3 = Python 101
A4 = wxPython Recipes
A5 = Python Interviews
A6 = None
A7 = None
A8 = None
A9 = None
A10 = None
A11 = None
A12 = None
A13 = None
A14 = None
A15 = None
A16 = None
A17 = None
A18 = None
A19 = None
A20 = None
A21 = None
A22 = None
A23 = None

同样,一些列没有数据(即“无”)。您可以编辑此代码以忽略空单元格,只处理有内容的单元格。

现在让我们看看如何迭代多列或多行!

从多行或多列读取单元格

OpenPyXL 的工作表对象提供了两种方法来遍历行和列。这是两种方法:

  • iter_rows()
  • iter_cols()

这些方法在 OpenPyXL 的文档中有很好的记录。这两种方法都采用以下参数:

  • min_col(int)-最小的列索引(从 1 开始的索引)
  • min_row(int)–最小行索引(从 1 开始的索引)
  • max_col(int)-最大列索引(从 1 开始的索引)
  • max_row(int)–最大行索引(从 1 开始的索引)
  • values_only(bool)-是否只返回单元格值

使用 min 和 max 行和列参数来告诉 OpenPyXL 要迭代哪些行和列。通过将values_only设置为 True,可以让 OpenPyXL 返回单元格中的数据。如果设置为 False,iter_rows()iter_cols()将返回单元格对象。

看看实际代码是如何工作的总是好的。记住这一点,创建一个名为iterating_over_cells_in_rows.py的新文件,并向其中添加以下代码:

# iterating_over_cells_in_rows.py

from openpyxl import load_workbook

def iterating_over_values(path, sheet_name):
    workbook = load_workbook(filename=path)
    if sheet_name not in workbook.sheetnames:
        print(f"'{sheet_name}' not found. Quitting.")
        return

    sheet = workbook[sheet_name]
    for value in sheet.iter_rows(
        min_row=1, max_row=3, min_col=1, max_col=3,
        values_only=True):
        print(value)

if __name__ == "__main__":
    iterating_over_values("books.xlsx", sheet_name="Sheet 1 - Books")

在这里,您像在前面的示例中一样加载工作簿。您获得想要从中提取数据的工作表名称,然后使用iter_rows()获得数据行。在本例中,将最小行数设置为 1,最大行数设置为 3。这意味着您将获取您指定的 Excel 表中的前三行。

然后,您还要将列设置为 1(最小)到 3(最大)。最后,你把values_only设置成True

当您运行此代码时,您将获得以下输出:

('Books', None, None)
('Title', 'Author', 'Publisher')
('Python 101', 'Mike Driscoll', 'Mouse vs Python') 

您的程序将打印出 Excel 电子表格中前三行的前三列。您的程序将这些行打印为元组,其中包含三项。您将使用iter_rows()作为一种快速的方法,通过 Python 迭代 Excel 电子表格中的行和列。

现在,您已经准备好学习如何读取特定范围内的单元格。

从区域中读取单元格

Excel 允许您使用以下格式指定单元格区域:(col)(row):(col)(row)。换句话说,你可以用 A1 说你想从 A 列 1 行开始。如果你想指定一个范围,你可以使用这样的东西: A1:B6 。这告诉 Excel 您正在选择从 A1 开始到 B6 的单元格。

继续创建一个名为read_cells_from_range.py的新文件。然后向其中添加以下代码:

# read_cells_from_range.py

import openpyxl
from openpyxl import load_workbook

def iterating_over_values(path, sheet_name, cell_range):
    workbook = load_workbook(filename=path)
    if sheet_name not in workbook.sheetnames:
        print(f"'{sheet_name}' not found. Quitting.")
        return

    sheet = workbook[sheet_name]
    for column in sheet[cell_range]:
        for cell in column:
            if isinstance(cell, openpyxl.cell.cell.MergedCell):
                # Skip this cell
                continue
            print(f"{cell.column_letter}{cell.row} = {cell.value}")

if __name__ == "__main__":
    iterating_over_values("books.xlsx", sheet_name="Sheet 1 - Books",
                          cell_range="A1:B6")

在这里,您传入您的cell_range并使用下面的嵌套for循环迭代该范围:

for column in sheet[cell_range]:
    for cell in column:

您检查您正在提取的单元格是否是一个MergedCell。如果是,你跳过它。否则,打印出单元格名称及其值。

运行此代码时,您应该会看到以下输出:

A1 = Books
A2 = Title
B2 = Author
A3 = Python 101
B3 = Mike Driscoll
A4 = wxPython Recipes
B4 = Mike Driscoll
A5 = Python Interviews
B5 = Mike Driscoll
A6 = None
B6 = None

这很有效。您应该花点时间尝试一些其他的范围变化,看看它如何改变输出。

注意:虽然“Sheet 1 - Books”的图像看起来像是单元格 A1 不同于合并的单元格 B1-G1,但 A1 实际上是该合并单元格的一部分。

您将创建的最后一个代码示例将读取 Excel 文档中的所有数据!

读取所有工作表中的所有单元格

Microsoft Excel 不像 CSV 文件或常规文本文件那样容易阅读。这是因为 Excel 需要存储每个单元格的数据,包括位置、格式和值,这些值可以是数字、日期、图像、链接等。因此,读取一个 Excel 文件需要做更多的工作!然而,openpyxl 为我们做了所有这些艰苦的工作。

遍历 Excel 文件的自然方法是从左到右读取工作表,在每个工作表中,从上到下逐行读取。这就是您将在本节中学习的内容。

您将把在前面章节中学到的知识应用到这里。创建一个新文件,命名为read_all_data.py。然后输入以下代码:

# read_all_data.py

import openpyxl
from openpyxl import load_workbook

def read_all_data(path):
    workbook = load_workbook(filename=path)
    for sheet_name in workbook.sheetnames:
        sheet = workbook[sheet_name]
        print(f"Title = {sheet.title}")
        for row in sheet.rows:
            for cell in row:
                if isinstance(cell, openpyxl.cell.cell.MergedCell):
                    # Skip this cell
                    continue

                print(f"{cell.column_letter}{cell.row} = {cell.value}")

if __name__ == "__main__":
    read_all_data("books.xlsx")

这里,您像以前一样加载工作簿,但是这次您在sheetnames上循环。您可以在选择时打印出每个工作表的名称。您使用嵌套的for循环遍历行和单元格,从电子表格中提取数据。

再一次,你跳过了MergedCells,因为它们的值是None——实际值在与MergedCell合并的普通单元格中。如果运行这段代码,您会看到它打印出了两个工作表中的所有数据。

您可以通过使用iter_rows()来稍微简化这段代码。打开一个新文件,命名为read_all_data_values.py。然后输入以下内容:

# read_all_data_values.py

import openpyxl
from openpyxl import load_workbook

def read_all_data(path):
    workbook = load_workbook(filename=path)
    for sheet_name in workbook.sheetnames:
        sheet = workbook[sheet_name]
        print(f"Title = {sheet.title}")
        for value in sheet.iter_rows(values_only=True):
            print(value)

if __name__ == "__main__":
    read_all_data("books.xlsx")

在这段代码中,您再次循环 Excel 文档中的工作表名称。然而,不是在行和列上循环,而是使用iter_rows()只在行上循环。您将values_only设置为True,这将为每一行返回一组值。您也不需要为iter_rows()设置最小和最大行数或列数,因为您想要获得所有数据。

当您运行这段代码时,您会看到它打印出每个工作表的名称,然后是该工作表中的所有数据,逐行打印。在您自己的 Excel 工作表中尝试一下,看看这段代码能做什么!

包扎

OpenPyXL 允许您以多种不同的方式读取 Excel 工作表及其数据。您可以用最少的代码从电子表格中快速提取值。

在本章中,您学习了如何执行以下操作:

  • 打开电子表格
  • 读取特定单元格
  • 从特定行读取单元格
  • 从特定列读取单元格
  • 从多行或多列读取单元格
  • 从区域中读取单元格
  • 读取所有工作表中的所有单元格

现在您已经准备好学习如何使用 OpenPyXL 创建 Excel 电子表格。这是本系列下一篇文章的主题!

真正的 Python:高级 Web 开发预览章节

原文:https://www.blog.pythonlibrary.org/2014/01/11/real-python-advanced-web-development-preview-chapter/

以 Django 1.6 为特色的Real Python:Advanced Web DevelopmentKickStarter campaign 今天发布了一个名为“软件工艺”的预览章节。这是本书的第一章。PDF 下载包括 33 页。

你也可以去以下地址领取:http://www.realpython.com/preview/。如果你还没有,你仍然可以再支持他们的Kickstarter3 天。

真实 Python 播客采访

原文:https://www.blog.pythonlibrary.org/2020/07/31/real-python-podcast-interview/

我在最新的真实 Python 播客上,在那里我谈论了我的 ReportLab book ,wxPython,以及更多。

我参加的播客节目叫做第 20 集:用 ReportLab 在 Python 中构建 pdf。看看吧,也可以在评论中随意提问。

相关文章

录制 Pyowa 会议

原文:https://www.blog.pythonlibrary.org/2009/05/16/recording-a-pyowa-meeting/

这个月,我上传完了为五月的 Pyowa 会议录制的视频。我原以为做一些记录和上传会相对容易些,但我想当我买了一台高清相机时,我让它变得比我需要的更难了。这是我现在如何记录我们会议的故事。

所以,在二月份我做了一些关于摄像机的研究。我一直对得到一个感兴趣,Pyowa 似乎给了我一个完美的借口。另外,我还梦想着和我的兄弟们一起录制一些涂鸦视频。无论如何,我以为我在索尼产品线中找到了完美的相机,但后来当我去买它时,这种愚蠢的相机不是随处可得的。因此,我最终选择了佳能 HG20,它可以录下高清晰度的图像,并且有一个很好的 12 倍变焦镜头。我还买了一台录像机。不幸的是,佳能从冷脚转向了专有的热脚技术,我的冷麦克风不适合它(“脚”是相机顶部一个看起来很奇怪的端口,你可以在那里挂上麦克风、灯和其他设备)。因此,在三月份的会议上,我把麦克风放在自己的三脚架上,一根长长的电缆连接到我的相机。正如你在这里看到的,它工作得非常好:http://pyowa.blip.tv/file/2093166/。(仅供参考:我试图在这个博客中嵌入视频,但我读过的每一篇关于如何做的教程都不起作用...如果你有线索,给我写封短信。)

我已经找到了一个转换器来安装摄像头上的麦克风,但它不起作用。接受我的建议,不要把你的钱浪费在那些垃圾上!因此,在 5 月份的会议上,我决定放弃 Rode mic,而使用佳能 DM-100 (DM = Directional Mic ),因为如果人们经常走动,试图将麦克风和相机对准他们会很痛苦,DM-100 可以安装在相机上,从而简化了情况。我认为音频质量差不多,但如果你想比较,这里有一个五月演讲的链接:http://pyowa.blip.tv/file/2114129/

我遇到的下一个问题是 Vixia 在 AVCHD(有 mts 的扩展)中的记录,这是我从未听说过的。因此,更多的研究!我发现 Adobe Premiere Elements 7 可以编辑这种格式,因为我听说过 Adobe Premiere,最新软件的评论也很好,所以我买了它。我在一台装有 Windows Vista 的笔记本电脑上运行这个程序,这是一台英特尔酷睿 2 双核 T7250 (2 Ghz),内存为 4 GB。Premiere 即使在空闲时也是一个资源猪,但它在编码时不会耗尽所有的处理器能力;只有 80%。不幸的是,Premiere 不喜欢你在它工作的时候运行其他程序。我让它冻结了几次,甚至导致 Vista 锁定。首映也不是很好地从冬眠中醒来。Premiere 确实有一些很好的功能,但我决定买 Cyberlink 的 Power Director Ultra 7,因为对它的评论使 Power Director 听起来更新手友好。

Cyberlink 在运行或编码时不会像 Adobe 那样消耗大量 RAM,但它会 100%地运行 CPU。奇怪的是,当 Power Director 工作时,我可以继续在我的笔记本电脑上乱搞。我应该指出的是,我确实成功地让 Director 冻结了,但那是因为我从我的 PC 中取出了它认为需要的闪存驱动器。我可以说 Power Director 更容易使用,并且允许制作人编辑比 Premiere Elements 允许的更多的轨道。

下一个问题是找一个地方放我的视频。Cyberlink 的软件认为 Youtube 只接受 10 分钟的视频,因此,它不会将任何超过 10 分钟的视频转换为 flash 视频。所以我最后做的是在 Power Director 中创建所有的标题和垃圾,并将 MTS 文件转换为 DV-AVI 格式。然后我拿着那个文件,在 Premiere 里打开,用它转换成 flash。但后来我又撞上了另一堵墙!Youtube 上那些被炮轰的人也不接受文件大小。因此,我决定跟随 PyCon 的脚步,改用 blip.tv 作为我的主持人。

所以你有它。这就是我如何录制 Pyowa,然后制作一个上传到 blip 的视频。让我知道,如果你有任何更好的录音,主持技巧或想法,或者如果你有任何问题。

重新推出鼠标 Vs Python YouTube 频道

原文:https://www.blog.pythonlibrary.org/2020/06/23/relaunch-of-mouse-vs-python-youtube-channel/

我最近决定重新推出鼠标 vs Python YouTube 频道。我正在编写 Python 101 第二版的新教程,并开始在常规 Python 教程和 wxPython 教程上添加一些新内容。

以下是一些新内容的示例:

https://www.youtube.com/embed/2mDTBHODJR0?feature=oembed

https://www.youtube.com/embed/2WJnM6WiZMo?feature=oembed

https://www.youtube.com/embed/e4n8OLn_Yek?feature=oembed

我对其他 Python 系列有很多想法,但我也欢迎你的建议。请随时让我知道你想在评论中看到什么,在 YouTube 频道或 T2 推特上。

ReportLab 101 -向画布添加灰色阴影(视频)

原文:https://www.blog.pythonlibrary.org/2020/12/15/reportlab-101-adding-shades-of-gray-to-the-canvas-video/

在本视频教程中,您将了解如何使用 ReportLab 的画布对象创建不同的灰度:

https://www.youtube.com/embed/jmNB3-4Hca4?feature=oembed

在 Leanpub 或亚马逊网站上获取我的报告实验室书籍。或者看看我的其他 Python 书籍

相关文章

在这个网站上还有许多关于 ReportLab 的其他文章。以下是几个例子:

ReportLab 101 -在画布上创建字体(视频)

原文:https://www.blog.pythonlibrary.org/2020/10/14/reportlab-101-creating-fonts-on-the-canvas-video/

在本教程中,您将学习如何在使用 Python 生成 pdf 时使用 ReportLab 的标准内置字体。

https://www.youtube.com/embed/8SvW4YQ5uhw?feature=oembed

报告实验室 101:画布介绍(视频)

原文:https://www.blog.pythonlibrary.org/2020/07/16/reportlab-101-intro-to-the-canvas/

在本视频中,您将了解 ReportLab 的 Canvas 对象。您可以使用 ReportLab 通过 Python 创建 pdf,本教程将向您展示一种实现方法

https://www.youtube.com/embed/CNr9e0XN4HE?feature=oembed

| | Want to learn more about working with PDFs in Python? Then check out my book:

ReportLab:使用 Python 处理 PDF

在 Leanpub 上立即购买 |

ReportLab 101:文本对象

原文:https://www.blog.pythonlibrary.org/2018/02/06/reportlab-101-the-textobject/

ReportLab 工具包为您提供了多种在 pdf 上生成文本的方法。我见过的最流行的例子是使用画布方法或使用鸭嘴兽。你最有可能看到的画布方法是拉绳。这里有一个例子:


from reportlab.pdfgen import canvas

c = canvas.Canvas("hello.pdf")
c.drawString(100, 100, "Welcome to Reportlab!")
c.showPage()
c.save()

基本上就是在给定的 x/y 坐标上画一个字符串。使用鸭嘴兽要复杂得多:


from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet

def hello():
    doc = SimpleDocTemplate("hello_platypus.pdf",
                            pagesize=letter,
                            rightMargin=72,
                            leftMargin=72,
                            topMargin=72,
                            bottomMargin=18)
    styles = getSampleStyleSheet()

    flowables = []

    text = "Hello, I'm a Paragraph"
    para = Paragraph(text, style=styles["Normal"])
    flowables.append(para)

    doc.build(flowables)

if __name__ == '__main__':
    hello()

你会注意到,大多数时候当你使用鸭嘴兽的时候,你会需要使用一个模板,一个样式和一个段落或者其他可流动的东西。但是让我们回到画布上。它实际上有另一种生成文本的方法,ReportLab 调用 textobject 。坦白地说,我从来不需要这些,因为 ReportLab 的段落类可以让你更好地控制文本的呈现。但是如果你依赖于使用低级画布来生成你的 PDF,那么你会想知道 textobject 会使 PDF 生成更快,因为它不使用对拉绳的单独调用。


文本对象

以我的经验来看,学习新东西的最好方法就是试着写一些简单的演示。所以让我们写一些代码,看看 textobject 是如何工作的:


# textobject_demo.py

from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

def textobject_demo():
    my_canvas = canvas.Canvas("txt_obj.pdf",
                              pagesize=letter)
    # Create textobject
    textobject = my_canvas.beginText()

    # Set text location (x, y)
    textobject.setTextOrigin(10, 730)

    # Set font face and size
    textobject.setFont('Times-Roman', 12)

    # Write a line of text + carriage return
    textobject.textLine(text='Python rocks!')

    # Change text color
    textobject.setFillColor(colors.red)

    # Write red text
    textobject.textLine(text='Python rocks in red!')

    # Write text to the canvas
    my_canvas.drawText(textobject)

    my_canvas.save()

if __name__ == '__main__':
    textobject_demo()

这里我们了解到,要创建一个 textobject ,我们需要调用画布的 beginText 方法。如果你碰巧打印出了 textobject,你会发现它在技术上是reportlab . pdf gen . textobject . pdftextobject的一个实例。无论如何,现在我们有了一个 textobject,我们可以通过调用 setTextOrigin 来设置它的光标位置。然后我们像之前看到的那样设置字体和大小。下一个新项目是对 textLine 的调用,这将允许您向缓冲区写入一个字符串,外加一个基本上是回车的内容。该方法的 docstring 声明它使“文本光标下移”,但在我看来这相当于回车。还有一个 textLines 方法,允许您写出多行字符串。

接下来我们要做的是通过调用 setFillColor 来设置字体颜色。在本例中,我们将下一个文本字符串设置为红色。最后一步是调用 drawText ,它将实际绘制你的 textobject 中的任何内容。如果你跳过调用 drawText ,那么你的文本将不会被写出,你可能会得到一个空的 PDF 文档。

从 textobject 中可以调用许多其他方法。例如,如果你想移动光标的位置,而不是下一行,你可以调用 moveCursor 。让我们来看看:


from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

def textobject_cursor():
    canvas_obj = canvas.Canvas("textobj_cursor.pdf", pagesize=letter)

    # Create textobject
    textobject = canvas_obj.beginText()

    # Set text location (x, y)
    textobject.setTextOrigin(10, 730)

    for indent in range(4):
        textobject.textLine('ReportLab cursor demo')
        textobject.moveCursor(15, 15)

    canvas_obj.drawText(textobject)
    canvas_obj.save()

if __name__ == '__main__':
    textobject_cursor()

在这里,我们只是建立了一个循环,它将打印出相同的字符串四次,但是在四个不同的位置。您会注意到,在循环的每次迭代中,我们将光标向右移动 15 点,向下移动 15 点。是的,当使用一个文本对象时,一个正的 y 数将使你向下移动。

现在,假设你想改变字符间距;你需要做的就是调用 setCharSpace 。事实上,你可以用 textobject 做很多有趣的间距技巧,比如使用 setWordSpace 改变 word 之间的间距,或者通过调用 setLeading 改变行与行之间的间距。让我们来看看如何改变文本的间距:


from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

def textobject_char_spacing():
    canvas_obj = canvas.Canvas("textobj_char_spacing.pdf",
                               pagesize=letter)

    # Create textobject
    textobject = canvas_obj.beginText()

    # Set text location (x, y)
    textobject.setTextOrigin(10, 730)

    spacing = 0
    for indent in range(8):
        textobject.setCharSpace(spacing)
        line = '{} - ReportLab spacing demo'.format(spacing)
        textobject.textLine(line)
        spacing += 0.7

    canvas_obj.drawText(textobject)
    canvas_obj.save()

if __name__ == '__main__':
    textobject_char_spacing()

在本例中,我们将循环因子增加到 8 次迭代,并在每次循环中调用 setCharSpace() 。我们从零间距开始,然后在每次迭代中增加 0.7。您可以在这里看到结果:

现在让我们看看应用单词间距如何影响我们的文本:


from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

def wordspacer():
    canvas_obj = canvas.Canvas("textobj_word_spacing.pdf",
                               pagesize=letter)

    # Create textobject
    textobject = canvas_obj.beginText()

    # Set text location (x, y)
    textobject.setTextOrigin(10, 730)

    word_spacing = 0
    for indent in range(8):
        textobject.setWordSpace(word_spacing)
        line = '{} - ReportLab spacing demo'.format(word_spacing)
        textobject.textLine(line)
        word_spacing += 1.5

    canvas_obj.drawText(textobject)
    canvas_obj.save()

if __name__ == '__main__':      
    wordspacer()

这个例子与前一个非常相似,但是你会注意到我们调用的是 setWordSpace() 而不是 setCharSpace() ,在这个例子中我们将间距增加了 1.5 倍。生成的文本如下所示:

如果你想创建一个上标或下标,那么你需要在你的文本对象上调用setRise。让我们创建一个演示,演示如何在 ReportLab 中设置上升:


from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

def apply_scripting(textobject, text, rise):
    textobject.setFont("Helvetica-Oblique", 8)
    textobject.setRise(rise)
    textobject.textOut(text)
    textobject.setFont("Helvetica-Oblique", 12)
    textobject.setRise(0)

def main():
    canvas_obj = canvas.Canvas("textobj_rising.pdf",
                               pagesize=letter)

    # Create textobject
    textobject = canvas_obj.beginText()
    textobject.setFont("Helvetica-Oblique", 12)

    # Set text location (x, y)
    textobject.setTextOrigin(10, 730)

    textobject.textOut('ReportLab ')
    apply_scripting(textobject, 'superscript ', 7)

    textobject.textOut('and ')

    apply_scripting(textobject, 'subscript ', -7)

    canvas_obj.drawText(textobject)
    canvas_obj.save()

if __name__ == '__main__':
    main()

这里我们创建了几个函数, apply_scriptingmain 。主函数将创建我们的画布和我们需要的所有其他部分。然后我们写出一些普通的文本。接下来的几行是我们应用上标(正)和下标(负)的地方。注意,我们需要将上标和下标之间的升程设置回零,以使单词“and”出现在正确的位置。一旦应用了一个上升的值,它将从该点开始继续应用。因此,您需要将它重置为零,以确保文本保持在正常位置。您还会注意到,我们将上标和下标的字体大小设置为小于常规文本。以下是运行此示例的结果:

查看 ReportLab 的用户指南,了解您可以做的更多有趣的事情,或者查看源代码本身。


包扎

我们在本教程中介绍了很多信息,但是您现在应该对如何在画布上使用 ReportLab 的 textobject 有了很好的理解。它非常有用,可以让你的文本格式变得非常简单。另一个很大的好处是它比多次调用 drawText() 要快。有机会一定要试一试!

注意:本教程是基于我最近的一本书的一部分, ReportLab:用 Python 处理 PDF


相关阅读

ReportLab 101 -使用文本对象(视频)

原文:https://www.blog.pythonlibrary.org/2020/11/17/reportlab-101-using-the-textobject-video/

在本视频教程中,您将学习如何使用 ReportLab 的 textobject 在画布上操作和格式化文本。

https://www.youtube.com/embed/B5OgK99pTCU?feature=oembed

如果你喜欢在书面教程中学习,你可以看看我的另一个教程, ReportLab 101:文本对象

购买报告实验室书籍

ReportLab: PDF Processing with Python

你可以在 ReportLab 上找到一整本书并支持这个网站。在亚马逊Leanpub 上有售。

ReportLab:使用 Python 向 PDF 添加图表

原文:https://www.blog.pythonlibrary.org/2019/04/08/reportlab-adding-a-chart-to-a-pdf-with-python/

ReportLab 工具包支持向 pdf 添加许多不同的图表和图形。事实上,我在之前的文章中已经谈到了其中的一些。然而,我看到的大多数例子,包括我自己文章中的例子,都没有显示如何将图表作为可流动的对象插入。

这意味着大多数示例向您展示了如何创建包含图表的单页 PDF。大多数开发人员都希望能够创建一些文本,可能是一个表格,并将图表与这些元素一起插入。图表后面通常还会有附加文本。

在本文中,您将学习如何做到这一点。


添加图表

让我们创建一个简单的饼图,并将其添加到一个简单的 PDF 中。您将创建的这个 PDF 将在图表前后有一个句子。

代码如下:

from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.validators import Auto
from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.shapes import Drawing, String
from reportlab.platypus import SimpleDocTemplate, Paragraph

def add_legend(draw_obj, chart, data):
    legend = Legend()
    legend.alignment = 'right'
    legend.x = 10
    legend.y = 70
    legend.colorNamePairs = Auto(obj=chart)
    draw_obj.add(legend)

def pie_chart_with_legend():
    data = list(range(15, 105, 15))
    drawing = Drawing(width=400, height=200)
    my_title = String(170, 40, 'My Pie Chart', fontSize=14)
    pie = Pie()
    pie.sideLabels = True
    pie.x = 150
    pie.y = 65
    pie.data = data
    pie.labels = [letter for letter in 'abcdefg']
    pie.slices.strokeWidth = 0.5
    drawing.add(my_title)
    drawing.add(pie)
    add_legend(drawing, pie, data)
    return drawing

def main():
    doc = SimpleDocTemplate('flowable_with_chart.pdf')

    elements = []
    styles = getSampleStyleSheet()
    ptext = Paragraph('Text before the chart', styles["Normal"])
    elements.append(ptext)

    chart = pie_chart_with_legend()
    elements.append(chart)

    ptext = Paragraph('Text after the chart', styles["Normal"])
    elements.append(ptext)
    doc.build(elements)

if __name__ == '__main__':
    main()

需要大量的进口。请随意在 ReportLab 的文档中查找这些内容。相反,我将把重点放在函数上。第一个函数将为图表创建一个图例。它设置图表的 x/y 坐标,并使用 chart 对象自动确定图例中的颜色。

第二个函数将创建饼图本身。ReportLab 中的图表放在绘图对象中。因此,您先创建一个绘图对象,然后创建一个饼图对象。接下来,设置它的位置,并向图表中添加一些数据。在将图表添加到绘图中之前,还需要添加标签并设置图表的描边宽度。

最后一个功能叫做 main() 。在这里,您将使用 ReportLab 的段落对象创建一个文档模板并添加一串文本。然后,您调用饼图创建函数来获取可以添加到您正在调用的元素列表中的图形。最后,您添加另一个段落,然后构建文档并将其写入磁盘。

PDF 的内容应该是这样的:


包扎

这是一篇相当短的文章,但我希望它能帮助您了解如何使用 Python 和 ReportLab 将图表插入到 pdf 中。如果您对添加其他类型的图表感兴趣,您应该查看 ReportLab 的文档或查看下面的文章之一。


相关阅读

Reportlab -关于字体的一切

原文:https://www.blog.pythonlibrary.org/2013/07/19/reportlab-all-about-fonts/

有没有想过如何在 Reportlab 中嵌入自定义字体?或者你只是想切换字体或改变字体的颜色。在本教程中,我们将看看所有这些问题。您需要出去下载一份 Reportlab,因为它不是标准 Python 安装的一部分。可以去 PyPI 或者官方 Reportlab 网站领取礼包。Reportlab 仅适用于 Python 2.5-2.7,因此请记住这一点。一旦你准备好了,我们可以继续。

如何在 pdf 中嵌入字体

我们将在这个例子中使用名为 free3of9 的条形码字体。你可以在很多网站上找到它,包括这个 one 。我以前在制作中使用过这种字体。需要注意的是,Reportlab 其实自己也可以做条形码,所以你真的完全不需要这个字体。然而,出于演示的目的,我们将在本例中使用它。在你下载了字体并把它放在某个地方后,我们可以继续。你准备好了吗?然后让我们看一些代码:


from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
from reportlab.platypus import Paragraph
from reportlab.lib.units import mm

#----------------------------------------------------------------------
def createBarcode(path):
    """
    Demo to show how to embed a barcode font
    """
    style = getSampleStyleSheet()
    width, height = letter
    c = canvas.Canvas(path, pagesize=letter)
    barcode_font = r"/full/path/to/free3of9.ttf"
    pdfmetrics.registerFont(TTFont("Free 3 of 9 Regular", barcode_font))

    barcode_string = '%s'
    barcode_string = barcode_string % "1234567890"

    p = Paragraph(barcode_string, style=style["Normal"])
    p.wrapOn(c, width, height)
    p.drawOn(c, 20, 750, mm)

    c.save()

if __name__ == "__main__":
    createBarcode(r"/path/to/barcode.pdf")

首先,我们需要从 Reportlab 的不同部分导入几个项目。接下来,我们需要执行以下调用,向 Reportlab 注册字体:


pdfmetrics.registerFont(TTFont("Free 3 of 9 Regular", barcode_font))

偶尔由于某些原因这不起作用。也许 ttf 文件定义不当或已损坏。我真的不知道。但是如果你幸运的话,这种字体可能带有 afm 和 pfb 文件。如果是这样,你可以这样注册。我发现这一点时,我正在玩我用来创建支票的 MICR 字体的演示副本。如果你愿意,你可以得到试用版

为了使用它,我不得不这样做:


# add micr font
afm = 'path/to/MICRCheckPrixa.afm'
pfb = 'path/to/MICRCheckPrixa.pfb'
face = pdfmetrics.EmbeddedType1Face(afm, pfb)
faceName = "MICRCheckPrixa"
pdfmetrics.registerTypeFace(face)
justFont = pdfmetrics.Font(faceName, faceName, 'WinAnsiEncoding')
pdfmetrics.registerFont(justFont)

大多数时候,第一种方法是有效的。但是,您应该了解这种后备方法,以防遇到 TrueType 字体的奇怪问题。现在我们准备学习 Reportlab 的标准字体。

如何在 Reportlab 中切换字体

Reportlab 内部支持多种字体。您可以将它们视为标准字体或默认字体。我们将基于他们的一个例子创建一段简单的代码,创建一个包含不同字体的文档。代码如下:


import sys
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfgen import canvas
import string

def standardFonts():
    """
    Create a PDF with all the standard fonts
    """
    for enc in ['MacRoman', 'WinAnsi']:
        canv = canvas.Canvas(
                'StandardFonts_%s.pdf' % enc,
                )
        canv.setPageCompression(0)

        x = 0
        y = 744
        for faceName in pdfmetrics.standardFonts:
            if faceName in ['Symbol', 'ZapfDingbats']:
                encLabel = faceName+'Encoding'
            else:
                encLabel = enc + 'Encoding'

            fontName = faceName + '-' + encLabel
            pdfmetrics.registerFont(pdfmetrics.Font(fontName,
                                        faceName,
                                        encLabel)
                        )

            canv.setFont('Times-Bold', 18)
            canv.drawString(80, y, fontName)

            y -= 20

            alpha = "abcdefghijklmnopqrstuvwxyz"
            canv.setFont(fontName, 14)
            canv.drawString(x+85, y, alpha)

            y -= 20

        canv.save()

if __name__ == "__main__":
    standardFonts()

该脚本将创建两个 pdf:standard fonts _ macroman . pdf 和 StandardFonts_WinAnsi.pdf。正如您所看到的,我们只是使用一对嵌套的 for 循环来提取各种字体,并将它们注册到 Reportlab。这显示了一种设置字体的方法。实际上还有另一种方法,我们不需要注册字体。Reportlab 支持一些 XML 标记,因此您可以使用这些标记来设置字体。让我们来看看这段代码:


from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.platypus import Paragraph

#----------------------------------------------------------------------
def settingFontsDemo(path):
    """
    Demo to show how to use fonts in Paragraphs
    """
    p_font = 12
    c = canvas.Canvas(path, pagesize=letter)

    ptext = """Welcome to Reportlab! (helvetica)
    """ % p_font
    createParagraph(c, ptext, 20, 750)

    ptext = """Welcome to Reportlab! (courier)
    """ % p_font
    createParagraph(c, ptext, 20, 730)

    ptext = """Welcome to Reportlab! (times-roman)
    """ % p_font
    createParagraph(c, ptext, 20, 710)

    c.save()

#----------------------------------------------------------------------
def createParagraph(c, text, x, y):
    """"""
    style = getSampleStyleSheet()
    width, height = letter
    p = Paragraph(text, style=style["Normal"])
    p.wrapOn(c, width, height)
    p.drawOn(c, x, y, mm)

if __name__ == "__main__":
    settingFontsDemo("/path/to/fontDemo.pdf")

如果您运行上面的代码,您应该会得到如下所示的内容:

reportlab_font_demo

如您所见,我们所要做的就是使用一个字体标签并指定字体名称。我还没有找到 Reportlab 支持的默认字体的完整列表,所以您必须自己尝试。然而,我认为这是主要的三个,加上一些变种。您还会注意到,Reportlab 不区分字体名称的大小写。注意:我们使用 createParagraph 方法只是为了减少代码重复。

现在我们准备讨论改变字体颜色!

如何在 Reportlab 中更改字体颜色

如果您使用我们在上一个示例中使用的标记方法,在 Reportlab 中更改字体的颜色是非常容易的。你所要做的就是添加字体定义并指定一种颜色。我们将只修改本例的最后一段代码:


from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.platypus import Paragraph

#----------------------------------------------------------------------
def colorFontsDemo(path):
    """
    Demo to show how to use fonts in Paragraphs
    """
    p_font = 12
    c = canvas.Canvas(path, pagesize=letter)

    ptext = """Welcome to Reportlab! (helvetica)
    """ % p_font
    createParagraph(c, ptext, 20, 750)

    ptext = """Welcome to Reportlab! (courier)
    """ % p_font
    createParagraph(c, ptext, 20, 730)

    ptext = """Welcome to Reportlab! (times-roman)
    """ % p_font
    createParagraph(c, ptext, 20, 710)

    c.save()

#----------------------------------------------------------------------
def createParagraph(c, text, x, y):
    """"""
    style = getSampleStyleSheet()
    width, height = letter
    p = Paragraph(text, style=style["Normal"])
    p.wrapOn(c, width, height)
    p.drawOn(c, x, y, mm)

if __name__ == "__main__":
    colorFontsDemo(r"/path/to/fontColorDemo.pdf")

如果您运行上面的代码,您将得到一个与上一个非常相似的文档,除了每一行都是不同的颜色。

包扎

现在您知道如何在 Reportlab 中操作字体了。如果你想学习额外的格式化技巧,我推荐你看看 Reportlab 用户指南的段落 XML 标记标签部分,在第 6 章。您将了解如何加粗、下划线、斜体和删除线,以及如何做上标和下标。《用户指南》中还有关于字体的附加信息,尤其是关于为画布对象设定字体的信息。祝你好运,编码快乐!

相关文章

下载源代码

ReportLab 图书章节采样器

原文:https://www.blog.pythonlibrary.org/2018/02/13/reportlab-book-chapter-sampler/

我觉得制作这本书的样本会很有趣,这样你就可以了解这本书会是什么样子。所以我为你创建了一个包含这本书前三章的 PDF。

下载样品

请注意,这个示例的格式不太正确,因为我必须从更完整的版本中生成它,所以 PDF 的目录表显示的不仅仅是文档中的实际内容。

此外,我刚刚在周末突破了 100 页的界限。我正在完成第五章,如果一切顺利的话,我将在本周开始写另外几章。

感谢您的支持!迈克

ReportLab 图书封面故事

原文:https://www.blog.pythonlibrary.org/2018/02/12/reportlab-book-cover-story/

我真的喜欢为我的书设计有趣的封面。我也喜欢为每本书寻找新的艺术家,这样他们看起来都很独特。不过,我确实计划在某个时候重用一两个艺术家。

无论如何,为了 ReportLab 的书,我偶然发现了 Therese Larsson 的网站,我真的很喜欢她在艺术作品中的灯光设计。她来自瑞典,曾在一些大公司工作过,包括迪士尼、谷歌和阿迪达斯。你可以阅读更多关于她在 T2 的行为。

最后,我委托她制作封面,并描述了我想要的东西。这是最初的草图:

ReportLab 封面草图

我认为这是我的愿景的一个非常好的版本,所以我批准了这个概念。下一个正在制作的封面是这样的:

虽然仍然有点粗糙,但我真的很喜欢它的进展,即使在这个早期版本的封面上,你也可以看出灯光是整洁的。

在这个版本中,我们已经完成了大部分的主要角色,场景也很好的组合在一起。

在这里,我们给我们的主要角色添加了更多的细节,还添加了一个背景鼠标。

这是封面的最终版本(没有标题)。我还是很喜欢封面出来的样子。我迫不及待地想看到它出版!

如果你想支持这本书的发展,请查看 Kickstarter !

报告实验室图书资助+目录

原文:https://www.blog.pythonlibrary.org/2018/02/02/reportlab-book-funded-toc/

在整理了人们在 Kickstarter 活动期间给我的各种想法后,我决定巩固我的目录。我已经计划涵盖 ReportLab 用户指南中 80-90%或更多的内容,但要更深入,因为我认为这些主题中的大部分应该以书籍的形式涵盖。这本书的其余部分是一些如何打字的章节和其他处理 pdf 的 Python 包。考虑到这一点,下面是目录的样子:

第一部分-报告实验室工具包

  • 第一章-画布
  • 第 2 章-字体
  • 第三章-鸭嘴兽
  • 第 4 章-段落
  • 第 5 章-表格
  • 第 6 章-其他流动资产
  • 第 7 章-自定义流程
  • 第 8 章-图表/图形
  • 第 9 章-其他图形
  • 第 10 章- PDF 特殊功能(表单、链接、加密)
  • 第 11 章-条形码/二维码

第二部分-教程/操作指南

  • 第 12 章-将 XML 转换成多页 pdf
  • 第 13 章-自定义页眉和页脚,页码
  • 第 14 章-创建目录表(延伸目标)
  • 第 15 章-从 pdf 导出数据(pdf miner)(拉伸目标)
  • 第 16 章——用 Python 填充 PDF 表单(PDF Forms)—(拉伸目标)
  • 第 17 章- PyPDF2 / pdfrw
  • 第 18 章-将标记转换成 PDF (rst2pdf,html2pdf 等)—(拉伸目标)
  • 第 19 章- pyfpdf,ReportLab 的替代方案

请注意,章节标题可能会更改。还要注意,我已经将一些章节标记为“延伸目标”章节。它们可能会也可能不会增加,这取决于我们是否达到我们的延伸目标。

延伸目标

我的长期目标是达到 6000 美元或 500 名支持者。如果我们点击其中任何一个,那么上面所有的章节都会被添加进去。如果我们不这样做,那么我会评估我们有多接近,我可能会进行一项调查,看看哪两章我们会保留,哪两章会被淘汰。

我想注意的最后一件事是,这本书的前 3 章本身就有超过 60 页的内容,所以即使我只写了这本书的第一部分(即 11 或 12 章),这本书的长度仍然超过 200 页。

如果你想早点看到这本书,那么请去看看 Kickstarter !

报告实验室图书 Kickstarter 的-还剩 2 天

原文:https://www.blog.pythonlibrary.org/2018/02/25/reportlab-book-kickstarters-2-days-left/

我的 report lab bookKickstarter只剩下 2 天多一点了。这是你购买签名书的唯一机会,也可能是获得电子书最便宜的方式。

我目前已经完成了 7 章,第 8 章接近完成。仅这些章节就有 170 多页。我希望您能看看,因为 ReportLab 是一种使用 Python 设计 PDF 格式的动态报告的有趣方式。

Reportlab:将数百张图像转换为 pdf

原文:https://www.blog.pythonlibrary.org/2012/01/07/reportlab-converting-hundreds-of-images-into-pdfs/

我最近被要求将几百张图片转换成 PDF 页面。我的一个朋友画漫画,我的兄弟希望能够在平板电脑上阅读。唉,如果你有一堆像这样命名的文件:

'Jia_01.Jpg', 'Jia_02.Jpg', 'Jia_09.Jpg', 'Jia_10.Jpg', 'Jia_11.Jpg', 'Jia_101.Jpg'

安卓平板电脑会把它们重新排序成这样:

'Jia_01.Jpg', 'Jia_02.Jpg', 'Jia_09.Jpg', 'Jia_10.Jpg', 'Jia_101.Jpg', 'Jia_11.Jpg'

你拥有的无序文件越多,就越令人困惑。可悲的是,即使 Python 也是这样分类文件的。我尝试直接在上使用 glob 模块,然后对结果进行排序,得到了完全相同的问题。所以我要做的第一件事就是找到某种排序算法,能够正确地对它们进行排序。需要注意的是,Windows 7 可以在其文件系统中正确地对文件进行排序,尽管 Python 不能。

在 Google 上搜索了一下,我在 StackOverflow 上找到了下面的脚本:


import re

#----------------------------------------------------------------------
def sorted_nicely( l ): 
    """     
    Sort the given iterable in the way that humans expect.
    """ 
    convert = lambda text: int(text) if text.isdigit() else text 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

效果非常好!现在我只需要找到一种方法将每个漫画页面放在他们自己的 PDF 页面上。幸运的是, reportlab 库使得这个任务很容易完成。您只需要迭代这些图像,然后一次一个地将它们插入到页面中。只看代码更容易,所以让我们这样做:


import glob
import os
import re

from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Image, PageBreak
from reportlab.lib.units import inch

#----------------------------------------------------------------------
def sorted_nicely( l ): 
    """ 
    # http://stackoverflow.com/questions/2669059/how-to-sort-alpha-numeric-set-in-python

    Sort the given iterable in the way that humans expect.
    """ 
    convert = lambda text: int(text) if text.isdigit() else text 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

#----------------------------------------------------------------------
def create_comic(fname, front_cover, back_cover, path):
    """"""
    filename = os.path.join(path, fname + ".pdf")
    doc = SimpleDocTemplate(filename,pagesize=letter,
                            rightMargin=72,leftMargin=72,
                            topMargin=72,bottomMargin=18)
    Story=[]
    width = 7.5*inch
    height = 9.5*inch    

    pictures = sorted_nicely(glob.glob(path + "\\%s*" % fname))

    Story.append(Image(front_cover, width, height))
    Story.append(PageBreak())

    x = 0
    page_nums = {100:'%s_101-200.pdf', 200:'%s_201-300.pdf',
                 300:'%s_301-400.pdf', 400:'%s_401-500.pdf',
                 500:'%s_end.pdf'}
    for pic in pictures:
        parts = pic.split("\\")
        p = parts[-1].split("%s" % fname)
        page_num = int(p[-1].split(".")[0])
        print "page_num => ", page_num

        im = Image(pic, width, height)
        Story.append(im)
        Story.append(PageBreak())

        if page_num in page_nums.keys():
            print "%s created" % filename 
            doc.build(Story)
            filename = os.path.join(path, page_nums[page_num] % fname)
            doc = SimpleDocTemplate(filename,
                                    pagesize=letter,
                                    rightMargin=72,leftMargin=72,
                                    topMargin=72,bottomMargin=18)
            Story=[]
        print pic
        x += 1

    Story.append(Image(back_cover, width, height))
    doc.build(Story)
    print "%s created" % filename

#----------------------------------------------------------------------
if __name__ == "__main__":
    path = r"C:\Users\Mike\Desktop\Sam's Comics"
    front_cover = os.path.join(path, "FrontCover.jpg")
    back_cover = os.path.join(path, "BackCover2.jpg")
    create_comic("Jia_", front_cover, back_cover, path) 

让我们把它分解一下。像往常一样,您需要一些必要的导入,这些导入是代码工作所必需的。你会注意到我们之前提到的排序良好的函数也在这段代码中。主函数名为 create_comic ,接受四个参数:fname、front_cover、back_cover、path。如果您以前使用过 reportlab 工具包,那么您会认出 SimpleDocTemplate 和 Story list,因为它们直接来自 reportlab 教程。

无论如何,您循环遍历排序后的图片,并将图像和 PageBreak 对象一起添加到文章中。循环中有一个条件的原因是,我发现如果我试图用所有 400 多张图片构建 PDF,我会遇到内存错误。所以我把它分成一系列不超过 100 页的 PDF 文档。在文档的最后,您必须调用 doc 对象的 build 方法来实际创建 PDF 文档。

现在你知道我是如何将一大堆图片写入多个 PDF 文档的了。理论上,您可以使用 PyPdf 将所有生成的 Pdf 编织成一个 PDF,但是我没有尝试。您可能会遇到另一个内存错误。我将把它留给读者作为练习。

源代码

Reportlab:如何创建横向页面

原文:https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/

前几天,我需要用 Reportlab 完成一个有趣的任务。我需要创建一个横向的 PDF,保存时必须旋转 90 度。为了使文档的布局更容易,我创建了一个带有标志的类,它允许我以横向保存文档或将其翻转为纵向。在本文中,我们将看看我的代码,看看它需要什么。如果你愿意跟随,我推荐你下载一份 ReportlabpyPdf (或者pyPdf F2)。

Reportlab 页面方向

reportlab_landscape

至少有两种方法可以告诉 Reportlab 使用横向方向。第一个是一个叫做 landscape 的便利函数,你可以从 reportlab.lib.pagesizes 导入它。你可以这样使用它:

from reportlab.lib.pagesizes import landscape, letter
from reportlab.pdfgen import canvas

self.c = canvas
self.c.setPageSize( landscape(letter) )

设置横向的另一种方法是直接设置页面大小:

from reportlab.lib.pagesizes import landscape
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch

self.c = canvas
self.c.setPageSize( (11*inch, 8.5*inch) )

您可以通过这样做来使它更通用:

from reportlab.lib.pagesizes import landscape
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch

width, height = letter

self.c = canvas
self.c.setPageSize( (height, width) )

这可能更有意义,尤其是如果您想使用其他流行的页面大小,如 A4。现在,让我们花点时间来看一个完整的例子:


import pyPdf
import StringIO

from reportlab.lib import utils
from reportlab.lib.pagesizes import landscape, letter
from reportlab.platypus import (Image, SimpleDocTemplate, 
                                Paragraph, Spacer)
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch, mm

########################################################################
class LandscapeMaker(object):
    """
    Demo to show how to work with Reportlab in Landscape orientation
    """

    #----------------------------------------------------------------------
    def __init__(self, rotate=False):
        """Constructor"""
        self.logo_path = "snakehead.jpg"
        self.pdf_file = "rotated.pdf"
        self.rotate = rotate
        self.story = [Spacer(0, 0.1*inch)]
        self.styles = getSampleStyleSheet()

        self.width, self.height = letter

    #----------------------------------------------------------------------
    def coord(self, x, y, unit=1):
        """
        Helper class to help position flowables in Canvas objects
        (http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab)
        """
        x, y = x * unit, self.height -  y * unit
        return x, y

    #----------------------------------------------------------------------
    def create_pdf(self, canvas, doc):
        """
        Create the PDF
        """
        self.c = canvas
        self.c.setPageSize( landscape(letter) )

        # add a logo and set size
        logo = self.scaleImage(self.logo_path, maxSize=90)
        logo.wrapOn(self.c, self.width, self.height)
        logo.drawOn(self.c, *self.coord(10, 113, mm))

        # draw a box around the logo
        self.c.setLineWidth(2)
        self.c.rect(20, 460, width=270, height=100)

        ptext = "**Python is amazing!!!**"
        p = Paragraph(ptext, style=self.styles["Normal"])
        p.wrapOn(self.c, self.width, self.height)
        p.drawOn(self.c, *self.coord(45, 101, mm))

    #----------------------------------------------------------------------
    def save(self):
        """
        Save the PDF
        """
        if not self.rotate:
            self.doc = SimpleDocTemplate(self.pdf_file, pagesize=letter,
                                         leftMargin=0.8*inch)
        else:
            fileObj = StringIO.StringIO()
            self.doc = SimpleDocTemplate(fileObj, pagesize=letter,
                                         leftMargin=0.8*inch)

        self.doc.build(self.story, 
                       onFirstPage=self.create_pdf)

        if self.rotate:
            fileObj.seek(0)
            pdf = pyPdf.PdfFileReader(fileObj)
            output = pyPdf.PdfFileWriter()
            for page in range(pdf.getNumPages()):
                pdf_page = pdf.getPage(page)
                pdf_page.rotateClockwise(90)
                output.addPage(pdf_page)

            output.write(file(self.pdf_file, "wb"))

    #----------------------------------------------------------------------
    def scaleImage(self, img_path, maxSize=None):
        """
        Scales the image
        """
        img = utils.ImageReader(img_path)
        img.fp.close()

        if not maxSize:
            maxSize = 125

        iw, ih = img.getSize()

        if iw > ih:
            newW = maxSize
            newH = maxSize * ih / iw
        else:
            newH = maxSize
            newW = maxSize * iw / ih

        return Image(img_path, newW, newH)

#----------------------------------------------------------------------
if __name__ == "__main__":
    pdf = LandscapeMaker()
    pdf.save()
    print "PDF created!"

如果您运行上面的代码(并且您有一个要使用的徽标),您将看到与文章开头的截图非常相似的内容。由于文本和图像是水平的,这使得文档的布局更加容易。让我们花几分钟来解析代码。在 init 中,我们设置了几个项目,如 logo、PDF 文件的名称、是否旋转以及其他几个项目。 coord 方法是我在 StackOverflow 上发现的,它有助于更容易地定位流。 create_pdf 方法是最神奇的地方。它调用我们导入的风景函数。这个函数还在文档上绘制徽标、矩形和单词。

下一个方法是保存方法。如果我们不做旋转,我们创建一个 SimpleDocTemplate,把 PDF 文件名传递给它,然后构建文档。另一方面,如果我们打开旋转,那么我们使用 Python 的 StringIO 库创建一个 file 对象,这样我们就可以在内存中操作 PDF。基本上,我们将数据写入内存,然后我们寻找到 faux 文件的开头,这样我们就可以用 pyPdf 读取它。接下来,我们创建一个 pyPdf writer 对象。最后,我们逐页遍历内存中的 PDF,并在写出之前旋转每一页。

最后一个方法是 wxPython 小组给我的一个方便的方法,我用它来缩放图像。很多时候,你会发现自己的图像对于你的目的来说太大了,你需要缩小它们来适应。这就是这个方法的全部功能。

一旦你得到了你想要的一切,你可以把最后的代码改成如下:

#----------------------------------------------------------------------
if __name__ == "__main__":
    pdf = LandscapeMaker(rotate=True)
    pdf.save()
    print "PDF created!"

这将导致脚本进行旋转,输出应该如下所示:

reportlab_landscape_rotated

包扎

在 Python 和 Reportlab 中创建和编辑横向方向的 pdf 实际上非常容易。在这一点上,你应该能够做到泰然自若!祝你好运,编码快乐!

相关文章

Python 3 的报告实验室到了!

原文:https://www.blog.pythonlibrary.org/2014/04/28/reportlab-for-python-3-is-here/

Reportlab 最近发布了 3.1 版本,现在完全支持 Python 3 和 Python 2.7。他们实际上在大约一个月前发布了 Python 3 兼容版本,但这个版本听起来像是他们已经解决了最初版本的错误,因为这个版本也支持他们的商业客户。我发现这很令人兴奋,因为我最喜欢的 Python 包之一终于支持 Python 3。我自己还没有迁移到 Python 3,因为我使用了太多只适用于 Python 2 的包(还因为我还没有在使用 Python 3 的地方工作过)。到目前为止,Reportlab 3.1 中还没有什么令人敬畏的新功能,但是你可以通读他们的发行说明并自己做出决定。

Reportlab 的一个很酷的新特性是,现在可以用 pip 或 easy_install 安装它。他们还引入了 Python wheel 包作为他们的主要安装类型,尽管您仍然可以下载源代码。开源下载见 PyPI。

看看吧,让我知道你的想法!

Reportlab -如何添加图表/图形

原文:https://www.blog.pythonlibrary.org/2016/02/18/reportlab-how-to-add-charts-graphs/

Reportlab 是用 Python 创建 pdf 的非常好的工具。一个鲜为人知的事实是,他们现在支持添加图表或图形到您的 PDF。以前,如果您想要该功能,您必须自己完成所有的绘图代码。不幸的是,Reportlab 指南并没有真正解释如何使用他们的图表,支持哪些类型的图表,或者它们的参数/属性等是什么。尽管他们在他们的网站上有一些示例代码片段。在本文中,我们将通过几个简单的例子向您展示如何使用 Reportlab 的图表功能。

创建条形图

reportlab_barchart

一个常见的需求是能够创建一个条形图。Reportlab 提供的例子非常酷,但是非常冗长。让我们看一个简单的例子:

from reportlab.lib.colors import PCMYKColor
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart

#----------------------------------------------------------------------
def create_bar_graph():
    """
    Creates a bar graph in a PDF
    """
    d = Drawing(280, 250)
    bar = VerticalBarChart()
    bar.x = 50
    bar.y = 85
    data = [[1,2,3,None,None,None,5],
            [10,5,2,6,8,3,5],
            [5,7,2,8,8,2,5],
            [2,10,2,1,8,9,5],
            ]
    bar.data = data
    bar.categoryAxis.categoryNames = ['Year1', 'Year2', 'Year3',
                                      'Year4', 'Year5', 'Year6',
                                      'Year7']

    bar.bars[0].fillColor   = PCMYKColor(0,100,100,40,alpha=85)
    bar.bars[1].fillColor   = PCMYKColor(23,51,0,4,alpha=85)
    bar.bars.fillColor       = PCMYKColor(100,0,90,50,alpha=85)

    d.add(bar, '')

    d.save(formats=['pdf'], outDir='.', fnRoot='test')

if __name__ == '__main__':
    create_bar_graph()

这里我们从 Reportlab 包中导入一些我们需要的项目。我们最关心的是绘图垂直条形图。绘图类允许我们创建一个画布来绘图。当我们创建它时,我们必须指定所谓的画布有多大。然后我们创建一个 VerticalBarChart 的实例,并通过 x/y 坐标告诉它应该在什么位置。接下来,我们将一些数据和一些类别名称添加到图表中,这些名称位于 x 轴上。最后,我们使用 Reportlab 的 PCMYKColor 类为条形图中的各个条形设置一些颜色,并将图表添加到绘图中。

最后,我们将条形图保存到磁盘。格式有点怪,但是根据源代码,你可以告诉它把图表保存成多种格式(pdf,eps,svg,ps,各种图片格式)。您还需要通过 fnRoot 告诉它将文件保存到哪个目录,并可选地告诉它文件名应该是什么。在创建条形图时,您可以设置和调整许多其他属性,但我们不会在这里讨论它们。查看前面提到的一些 Reportlab 示例。

如果您运行上面的代码,您应该会看到一个 PDF,其内容类似于本节开头的屏幕截图。现在让我们继续学习如何创建饼图!


创建饼图

reportlab_pie

使用 Reportlab 创建饼图稍微容易一些。我们来看看吧!

from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.piecharts import Pie

#----------------------------------------------------------------------
def create_pie_chart():
    """"""
    d = Drawing()
    pie = Pie()
    pie.x = 200
    pie.y = 65
    pie_data = [10, 20, 30, 40]
    pie.labels = [letter for letter in 'abcd']
    pie.slices.strokeWidth = 0.5
    pie.slices[3].popout = 20
    d.add(pie)
    d.save(formats=['pdf'], outDir='.', fnRoot='test-pie')

if __name__ == '__main__':
    create_pie_chart()

这里我们只从 Reportlab 导入两个项目,即前面提到的绘图类和饼图图表类。和以前一样,我们创建了 Drawing 类的一个实例,这次是 Pie 类。同样,我们也通过设置 x/y 坐标来定位饼图。数据更有趣一点,因为饼图代表百分比,所以它必须加起来是 100,所以要确保你的值加起来是合适的。接下来我们添加一些标签,并告诉它线条的宽度应该是多少。为了好玩,我们告诉它从饼图中弹出第 4 个元素(注意:它是从零开始的)。最后,我们将图表添加到绘图中并保存它。


添加图例

reportlab_legend

图例在图表中很常见。幸运的是,Reportlab 提供了一种相当简单的方法来添加图例。让我们修改我们的饼图代码,并添加一个图例!

from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.shapes import Drawing
from reportlab.lib.validators import Auto

#----------------------------------------------------------------------
def add_legend(draw_obj, chart, data):
    """"""
    legend = Legend()
    legend.alignment = 'right'
    legend.x = 10
    legend.y = 70
    legend.colorNamePairs = Auto(obj=chart)
    draw_obj.add(legend)

#----------------------------------------------------------------------
def create_pie_chart(legend=False):
    """"""
    data = [10, 20, 30, 40]
    d = Drawing()
    pie = Pie()
    # required by Auto
    pie._seriesCount = 4

    if legend:
        add_legend(d, pie, data)

    pie.x = 150
    pie.y = 65
    pie_data = data
    pie.labels = [letter for letter in 'abcd']
    pie.slices.strokeWidth = 0.5
    pie.slices[3].popout = 20
    d.add(pie)
    d.save(formats=['pdf'], outDir='.', fnRoot='test-pie')

if __name__ == '__main__':
    create_pie_chart(True)

这一次,我们导入了一个传奇类和一个自动类。我们还创建了一个 add_legend 函数,使添加图例变得更容易,并封装了代码,使其更容易更新。在这个函数中,我们设置了图例的对齐方式和位置。然后,我们使用 Reportlab 的自动验证器将图例中的正确颜色映射到条形图中。最后,我们将图例添加到绘图中,并将 PDF 保存到磁盘中。


包扎

Reportlab 中还有许多其他类型的图表和子图表。当谈到 Reportlab 可以对图表做什么时,本文只是触及了皮毛。例如,您可以指定条间距和宽度、字体、x 和 y 的各种轴设置、x 和 y 的标签等等。你绝对应该查看 Reportlab 的官方样本,以了解如何使用他们的软件包。您可能还需要深入源代码,弄清楚一些部分是如何组合在一起的,以及各种参数的含义。这是一个非常强大的包,但是文档有点粗糙。


相关阅读

Reportlab:如何添加页码

原文:https://www.blog.pythonlibrary.org/2013/08/12/reportlab-how-to-add-page-numbers/

您是否曾经需要在实验室生成的 PDF 报告中添加页码,但不知道如何添加?你来对地方了。我们将看看在三种不同的情况下如何添加页码:

  1. 如何仅用画布对象添加页码
  2. 如何使用 SimpleDocTemplate 添加页码
  3. 如何添加“第#页,共#页”(即第 1 页,共 10 页)

你准备好了吗?让我们开始吧!

仅用画布添加页码

canvas_page_num

canvas 对象有一个非常简单的方法来获取内置的页码。这里有一个例子:


from reportlab.pdfgen import canvas

#----------------------------------------------------------------------
def createMultiPage():
    """
    Create a multi-page document
    """
    c = canvas.Canvas("canvas_page_num.pdf")

    for i in range(5):
        page_num = c.getPageNumber()
        text = "This is page %s" % page_num
        c.drawString(100, 750, text)
        c.showPage()
    c.save()

#----------------------------------------------------------------------
if __name__ == "__main__":
    createMultiPage()

如您所见,您所要做的就是调用 canvas 对象的 getPageNumber ()来获取页面的页码。在上面的代码中,我们创建了一个五页文档,每页有一个文本字符串。现在我们准备继续学习如何用 Reportlab 的模板添加页码。

如何在 Reportlab 的文档模板中添加页码

doc_page_num

在本例中,我们将使用 Reportlab 的 SimpleDocTemplate。其实真的很好用。我们将从上一篇文章中提取一些代码来帮助填充文档,使它看起来比上一篇文章更有趣。代码如下:


from reportlab.lib.enums import TA_JUSTIFY
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm

#----------------------------------------------------------------------
def addPageNumber(canvas, doc):
    """
    Add the page number
    """
    page_num = canvas.getPageNumber()
    text = "Page #%s" % page_num
    canvas.drawRightString(200*mm, 20*mm, text)

#----------------------------------------------------------------------
def createMultiPage():
    """
    Create a multi-page document
    """
    doc = SimpleDocTemplate("doc_page_num.pdf",pagesize=letter,
                            rightMargin=72,leftMargin=72,
                            topMargin=72,bottomMargin=18)
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))

    Story = []

    magName = "Pythonista"
    issueNum = 12
    subPrice = "99.00"
    limitedDate = "03/05/2010"
    freeGift = "tin foil hat"
    full_name = "Marvin Jones"
    address_parts = ["411 State St.", "Reno, NV 80158"]

    for page in range(5):
        # Create return address
        ptext = '%s' % full_name
        Story.append(Paragraph(ptext, styles["Normal"]))       
        for part in address_parts:
            ptext = '%s' % part.strip()
            Story.append(Paragraph(ptext, styles["Normal"]))

        Story.append(Spacer(1, 12))
        ptext = 'Dear %s:' % full_name.split()[0].strip()
        Story.append(Paragraph(ptext, styles["Normal"]))
        Story.append(Spacer(1, 12))

        ptext = """We would like to welcome you to our subscriber base 
        for %s Magazine! You will receive %s issues at the excellent introductory 
        price of $%s. Please respond by %s to start receiving your subscription 
        and get the following free gift: %s.""" 
        ptext = ptext % (magName, issueNum, subPrice, limitedDate, freeGift)
        Story.append(Paragraph(ptext, styles["Justify"]))
        Story.append(Spacer(1, 12))

        ptext = 'Thank you very much and we look forward to serving you.'
        Story.append(Paragraph(ptext, styles["Justify"]))
        Story.append(Spacer(1, 12))
        ptext = 'Sincerely,'
        Story.append(Paragraph(ptext, styles["Normal"]))
        Story.append(Spacer(1, 48))
        ptext = 'Ima Sucker'
        Story.append(Paragraph(ptext, styles["Normal"]))
        Story.append(Spacer(1, 12))
        Story.append(PageBreak())

    doc.build(Story, onFirstPage=addPageNumber, onLaterPages=addPageNumber)

#----------------------------------------------------------------------
if __name__ == "__main__":
    createMultiPage()

在这种情况下,我们需要创建一个我们可以调用的简单的 addPageNumber 函数。接下来,我们使用 Reportlab flowables 创建一个多页文档,在本例中,它是一系列段落对象。我们还实例化了一个 SimpleDocTemplate,并调用它的 build 方法。在这个调用中,我们告诉它为第一页和所有其他页面调用我们的 addPageNumber 函数。这允许我们动态地给每个页面添加一个页码!

如何在 Reportlab 中添加“总页数”

doc_page_num2

Reportlab 没有内置的方法来添加页码,例如“第 1 页,共 5 页”或其他内容。所以我去看看是否有人解决了这个难题,并在 ActiveState 上找到了两个食谱。你可以在这里或者在这里阅读关于他们的。我们将从这些食谱中提取例子,并将它们与上一节中的代码结合起来。


from reportlab.lib.enums import TA_JUSTIFY
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm

########################################################################
class PageNumCanvas(canvas.Canvas):
    """
    http://code.activestate.com/recipes/546511-page-x-of-y-with-reportlab/
    http://code.activestate.com/recipes/576832/
    """

    #----------------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """Constructor"""
        canvas.Canvas.__init__(self, *args, **kwargs)
        self.pages = []

    #----------------------------------------------------------------------
    def showPage(self):
        """
        On a page break, add information to the list
        """
        self.pages.append(dict(self.__dict__))
        self._startPage()

    #----------------------------------------------------------------------
    def save(self):
        """
        Add the page number to each page (page x of y)
        """
        page_count = len(self.pages)

        for page in self.pages:
            self.__dict__.update(page)
            self.draw_page_number(page_count)
            canvas.Canvas.showPage(self)

        canvas.Canvas.save(self)

    #----------------------------------------------------------------------
    def draw_page_number(self, page_count):
        """
        Add the page number
        """
        page = "Page %s of %s" % (self._pageNumber, page_count)
        self.setFont("Helvetica", 9)
        self.drawRightString(195*mm, 272*mm, page)

#----------------------------------------------------------------------
def createMultiPage():
    """
    Create a multi-page document
    """
    doc = SimpleDocTemplate("doc_page_num_v2.pdf",pagesize=letter,
                            rightMargin=72,leftMargin=72,
                            topMargin=72,bottomMargin=18)
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))

    Story = []

    magName = "Pythonista"
    issueNum = 12
    subPrice = "99.00"
    limitedDate = "03/05/2010"
    freeGift = "tin foil hat"
    full_name = "Marvin Jones"
    address_parts = ["411 State St.", "Reno, NV 80158"]

    for page in range(5):
        # Create return address
        ptext = '%s' % full_name
        Story.append(Paragraph(ptext, styles["Normal"]))       
        for part in address_parts:
            ptext = '%s' % part.strip()
            Story.append(Paragraph(ptext, styles["Normal"]))

        Story.append(Spacer(1, 12))
        ptext = 'Dear %s:' % full_name.split()[0].strip()
        Story.append(Paragraph(ptext, styles["Normal"]))
        Story.append(Spacer(1, 12))

        ptext = """We would like to welcome you to our subscriber base 
        for %s Magazine! You will receive %s issues at the excellent introductory 
        price of $%s. Please respond by %s to start receiving your subscription 
        and get the following free gift: %s.""" 
        ptext = ptext % (magName, issueNum, subPrice, limitedDate, freeGift)
        Story.append(Paragraph(ptext, styles["Justify"]))
        Story.append(Spacer(1, 12))

        ptext = 'Thank you very much and we look forward to serving you.'
        Story.append(Paragraph(ptext, styles["Justify"]))
        Story.append(Spacer(1, 12))
        ptext = 'Sincerely,'
        Story.append(Paragraph(ptext, styles["Normal"]))
        Story.append(Spacer(1, 48))
        ptext = 'Ima Sucker'
        Story.append(Paragraph(ptext, styles["Normal"]))
        Story.append(Spacer(1, 12))
        Story.append(PageBreak())

    doc.build(Story, canvasmaker=PageNumCanvas)

#----------------------------------------------------------------------
if __name__ == "__main__":
    createMultiPage()

让我们快速看一下我们的变化。首先我们子类化 canvas。Canvas 并覆盖它的两个方法来跟踪列表中创建的页面数量。然后,当文档保存自身时,它会遍历列表中的所有页面,并为每个页面添加适当的。为了让文档使用这个画布,我们将该类传递给 canvasmaker 参数,您将在 createMultiPage 函数的最后一行中看到: doc.build(Story,canvasmaker=PageNumCanvas) 。就是这样!

包扎

今天,您已经学习了几种向 Reportlab PDFs 添加页码的方法。现在出去开始编写一些简洁的代码吧!

下载源代码

Reportlab:如何组合静态内容和多页表

原文:https://www.blog.pythonlibrary.org/2013/08/09/reportlab-how-to-combine-static-content-and-multipage-tables/

这个星期,我试图弄清楚如何让 Reportlab 做一些我以前从未尝试过的事情。也就是说,我想创建大约半页的静态文本,然后有一个行项目表,它可能会填充页面的其余部分,并在此后继续 N 页。问题是将 Reportlab 的 canvas 对象与 flowables 混合在一起可能会很混乱。Reportlab 在其用户指南中略微谈到了使用模板,但它只展示了如何添加页眉和页脚类型信息。这实际上是我需要的所有信息,但我花了很长时间才意识到这一点。我在 Reportlab 邮件列表上询问如何做这类事情。在我写这篇文章的时候,没有人告诉我该怎么做。总之,我自己想出来的,现在我要给你看!如果你想继续下去,你可能需要自己去拿一份免费的报告实验室

钻研代码

reportlab_multipage

我不得不钻研 Reportlab 的源代码,以找出如何完成这项有趣的活动。你真正需要做的是在你的类中创建一个方法来完成你所有的静态画布绘制。完成后,创建一个带有 Reportlab 间隔符的 list 对象,告诉 Reportlab 它需要跳过您绘制的区域。然后,您可以创建表格对象并将其添加到列表中。最后,您只需要构建您的文档。是的,如果你是 Reportlab 的新手,你可能会觉得这些听起来像希腊语。向您展示更简单,所以请查看下面的代码:


from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle, TA_CENTER
from reportlab.lib.units import inch, mm
from reportlab.pdfgen import canvas
from reportlab.platypus import Paragraph, Table, SimpleDocTemplate, Spacer

########################################################################
class Test(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.width, self.height = letter
        self.styles = getSampleStyleSheet()

    #----------------------------------------------------------------------
    def coord(self, x, y, unit=1):
        """
        http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab
        Helper class to help position flowables in Canvas objects
        """
        x, y = x * unit, self.height -  y * unit
        return x, y

    #----------------------------------------------------------------------
    def run(self):
        """
        Run the report
        """
        self.doc = SimpleDocTemplate("test.pdf")
        self.story = [Spacer(1, 2.5*inch)]
        self.createLineItems()

        self.doc.build(self.story, onFirstPage=self.createDocument)
        print "finished!"

    #----------------------------------------------------------------------
    def createDocument(self, canvas, doc):
        """
        Create the document
        """
        self.c = canvas
        normal = self.styles["Normal"]

        header_text = "This is a test header"
        p = Paragraph(header_text, normal)
        p.wrapOn(self.c, self.width, self.height)
        p.drawOn(self.c, *self.coord(100, 12, mm))

        ptext = """Lorem ipsum dolor sit amet, consectetur adipisicing elit,
        sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
        Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 
        nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
        reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
        pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 
        culpa qui officia deserunt mollit anim id est laborum."""

        p = Paragraph(ptext, style=normal)
        p.wrapOn(self.c, self.width-50, self.height)
        p.drawOn(self.c, 30, 700)

        ptext = """
        At vero eos et accusamus et iusto odio dignissimos ducimus qui 
        blanditiis praesentium voluptatum deleniti atque corrupti quos dolores 
        et quas molestias excepturi sint occaecati cupiditate non provident, 
        similique sunt in culpa qui officia deserunt mollitia animi, id est laborum
        et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. 
        Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit
        quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est,
        omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut 
        rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et 
        molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus,
        ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis
        doloribus asperiores repellat.
        """
        p = Paragraph(ptext, style=normal)
        p.wrapOn(self.c, self.width-50, self.height)
        p.drawOn(self.c, 30, 600)

    #----------------------------------------------------------------------
    def createLineItems(self):
        """
        Create the line items
        """
        text_data = ["Line", "DOS", "Procedure
/Modifier",
                     "Description", "Units", "Billed
Charges",
                     "Type1
Reductions", "Type2
Reductions",
                     "Type3
Reductions", "Allowance",
                     "Qualify
Code"]
        d = []
        font_size = 8
        centered = ParagraphStyle(name="centered", alignment=TA_CENTER)
        for text in text_data:
            ptext = "**%s**" % (font_size, text)
            p = Paragraph(ptext, centered)
            d.append(p)

        data = [d]

        line_num = 1

        formatted_line_data = []

        for x in range(200):
            line_data = [str(line_num), "04/12/2013", "73090", 
                         "Test Reflexes", "1", "131.00", "0.00", 
                         "0.00", "0.00", "0.00", "1234"]

            for item in line_data:
                ptext = "%s" % (font_size-1, item)
                p = Paragraph(ptext, centered)
                formatted_line_data.append(p)
            data.append(formatted_line_data)
            formatted_line_data = []
            line_num += 1

        table = Table(data, colWidths=[20, 40, 45, 120, 30, 40, 
                                       50, 50, 50, 50, 30])

        self.story.append(table)

#----------------------------------------------------------------------
if __name__ == "__main__":
    t = Test()
    t.run()

现在我们需要花一点时间回顾一下这里发生了什么。首先,我们从 Reportlab 导入一大堆不同的项目。接下来,我们创建测试类。我们初始化一些东西,然后我们开始得到好的东西。 coord 方法是我在 StackOverflow 上发现的有趣的东西,它对在画布对象上定位 flowables 有很大帮助。我们将跳过这个方法,直接进入运行。在这里,我们创建了文档对象和故事列表。你会注意到我们已经在里面放了一个 2.5 英寸宽的垫片。这是为画布保留的空间量。接下来,我们调用我们的 createLineItems 方法,该方法创建一个 200 行的表格对象,并将其添加到我们的故事中。

然后我们调用 doc 对象的 build 方法,告诉它执行 createDocument 。正如你可能已经猜到的, build 实际上会创建 PDF 本身。createDocument 方法包含画布绘制代码。一旦一切都完成了,我们就向 stdout 输出一条消息,让用户知道他们的新文档已经可以查看了!

包扎

至此,您已经具备了编写静态内容和流动内容混搭所需的知识。您可能想知道,您还可以向 build 方法传递 onLastPages 参数,告诉它调用您自己的另一个方法。大多数显示 onFirstPage 和 onLastPages 的示例将它们用于页眉和页脚。也许这是他们的主要目的,但你可以为自己所用。如果你去挖掘源代码,你会发现你也可以传递一个页面模板列表,这可以使真正复杂的布局变得容易得多。无论如何,我希望这篇文章对您有所帮助,并且您能够在自己的代码中使用这些新信息。玩得开心!

相关文章

Reportlab -如何使用 Python 在 pdf 中创建条形码

原文:https://www.blog.pythonlibrary.org/2013/03/25/reportlab-how-to-create-barcodes-in-your-pdfs-with-python/

Reportlab 库是用 Python 生成 pdf 的一个很好的方式。最近注意到它有做条形码的能力。我听说过它能够生成二维码,但我并没有真正深入了解它还能做什么。在本教程中,我们将了解 Reportlab 可以生成的一些条形码。如果你还没有 Reportlab,在进入本文之前,去他们的网站获取。

Reportlab 的条形码库

Reportlab 提供了几种不同类型的条形码:code39(即 code 3 of 9)、code93、code 128、EANBC、QR 和 USPS。我也看到了一个叫做“fourstate”的,但是我不知道如何让它工作。在其中一些类型下,还有子类型,如标准、扩展或多宽度。我没有太多的运气让多宽度的那个为 code128 条形码工作,因为它一直给我一个属性错误,所以我们就忽略那个。如果你知道怎么做,请在评论中或通过我的联系方式告诉我。如果有人能告诉我如何添加那个或 fourstate 条形码,我会更新这篇文章。

不管怎样,最好的学习方法就是写一些代码。这里有一个非常简单的例子:


from reportlab.graphics.barcode import code39, code128, code93
from reportlab.graphics.barcode import eanbc, qr, usps
from reportlab.graphics.shapes import Drawing 
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.graphics import renderPDF

#----------------------------------------------------------------------
def createBarCodes():
    """
    Create barcode examples and embed in a PDF
    """
    c = canvas.Canvas("barcodes.pdf", pagesize=letter)

    barcode_value = "1234567890"

    barcode39 = code39.Extended39(barcode_value)
    barcode39Std = code39.Standard39(barcode_value, barHeight=20, stop=1)

    # code93 also has an Extended and MultiWidth version
    barcode93 = code93.Standard93(barcode_value)

    barcode128 = code128.Code128(barcode_value)
    # the multiwidth barcode appears to be broken 
    #barcode128Multi = code128.MultiWidthBarcode(barcode_value)

    barcode_usps = usps.POSTNET("50158-9999")

    codes = [barcode39, barcode39Std, barcode93, barcode128, barcode_usps]

    x = 1 * mm
    y = 285 * mm
    x1 = 6.4 * mm

    for code in codes:
        code.drawOn(c, x, y)
        y = y - 15 * mm

    # draw the eanbc8 code
    barcode_eanbc8 = eanbc.Ean8BarcodeWidget(barcode_value)
    bounds = barcode_eanbc8.getBounds()
    width = bounds[2] - bounds[0]
    height = bounds[3] - bounds[1]
    d = Drawing(50, 10)
    d.add(barcode_eanbc8)
    renderPDF.draw(d, c, 15, 555)

    # draw the eanbc13 code
    barcode_eanbc13 = eanbc.Ean13BarcodeWidget(barcode_value)
    bounds = barcode_eanbc13.getBounds()
    width = bounds[2] - bounds[0]
    height = bounds[3] - bounds[1]
    d = Drawing(50, 10)
    d.add(barcode_eanbc13)
    renderPDF.draw(d, c, 15, 465)

    # draw a QR code
    qr_code = qr.QrCodeWidget('www.mousevspython.com')
    bounds = qr_code.getBounds()
    width = bounds[2] - bounds[0]
    height = bounds[3] - bounds[1]
    d = Drawing(45, 45, transform=[45./width,0,0,45./height,0,0])
    d.add(qr_code)
    renderPDF.draw(d, c, 15, 405)

    c.save()

if __name__ == "__main__":
    createBarCodes()

让我们把它分解一下。代码 39。Extended39 除了价值本身并没有接受太多东西。另一方面,代码 39。标准 39,代码 93。标准 93 和代码 128。Code128 都有基本相同的 API。你可以改变酒吧宽度,酒吧高度,打开开始/停止符号,并添加“安静”区。usps 条形码模块提供两种类型的条形码:FIM 和 POSTNET。FIM 或 Facing ID 标志只编码一个字母(A-D),我个人并不觉得很有趣。所以我只展示 POSTNET 版本,这应该是美国人非常熟悉的,因为它出现在大多数信封的底部。POSTNET 对邮政编码进行编码!

接下来的三个条形码使用不同的 API 在我通过 StackOverflow 发现的 PDF 上绘制它们。基本上,你创建一个一定大小的图形对象,然后将条形码添加到图形中。最后,您使用 renderPDF 模块将绘图放到 PDF 上。这很复杂,但是效果很好。EANBC 代码是你会在一些制成品上看到的代码,比如纸巾盒。

如果你想看看上面代码的结果,你可以在这里下载 PDF。

包扎

此时,您应该能够在 pdf 中创建自己的条形码。Reportlab 非常方便,我希望您会发现这个额外的工具对您的工作有所帮助。

附加阅读

获取来源!

Reportlab:如何创建自定义流

原文:https://www.blog.pythonlibrary.org/2014/03/10/reportlab-how-to-create-custom-flowables/

Reportlab 是一个非常灵活的用于 Python 的 PDF 创建包。您可以使用绝对定位或使用可流动的对象(如段落、表格或框架)来布局您的文档。你甚至可以将两者混合在一起!在这篇文章中,我们将看看如何创建一些自定义的流。例如,如果您需要在文档中添加一条线来划分新部分的起点,该怎么办?没有真正的内置流动,所以我们将设计自己的。我们还将设计一个可流动的,有一条线和一个里面有文本的盒子。

我们开始吧!


产生可流动的线

创建一条可流动的线其实很简单。基本上,你只需要子类化 Reportlab 的可流动的类,并告诉它画线。以下是基于 Reportlab 邮件列表中的一个例子


from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (Flowable, Paragraph,
                                SimpleDocTemplate, Spacer)

########################################################################
class MCLine(Flowable):
    """
    Line flowable --- draws a line in a flowable
    http://two.pairlist.net/pipermail/reportlab-users/2005-February/003695.html
    """

    #----------------------------------------------------------------------
    def __init__(self, width, height=0):
        Flowable.__init__(self)
        self.width = width
        self.height = height

    #----------------------------------------------------------------------
    def __repr__(self):
        return "Line(w=%s)" % self.width

    #----------------------------------------------------------------------
    def draw(self):
        """
        draw the line
        """
        self.canv.line(0, self.height, self.width, self.height)

#----------------------------------------------------------------------
def create_pdf():
    """
    Create a pdf
    """
    story=[]
    doc = SimpleDocTemplate("test.pdf",pagesize=letter)
    styles=getSampleStyleSheet()
    spacer = Spacer(0, 0.25*inch)

    ptext = '%s' % "Section #1"
    story.append(Paragraph(ptext, styles["Normal"]))
    story.append(spacer)

    line = MCLine(500)
    story.append(line)
    story.append(spacer)

    ptext = '%s' % "Section #2"
    story.append(Paragraph(ptext, styles["Normal"]))

    doc.build(story)

#----------------------------------------------------------------------
if __name__ == "__main__":
    create_pdf()

如果您运行此代码,您应该会得到一个类似如下的 PDF:

rep_flowable_line

create_pdf 函数中的代码基于 Reportlab 附带的模板创建一个文档。然后,我们创建一些流并将它们添加到一个普通的 Python 列表中。我们希望在我们创建的两个假“部分”周围有一点空间,所以我们在可流动线的前后添加了一个间隔物。然后我们构建文档,瞧!我们有一个新制作的 PDF!


创建带边框的文本框+线条可流动

rep_flowable_box

最近,我需要创建一个文本框,它有一个边框和一条从顶部到右边的线,我需要能够将它作为一个可流动的文件添加到我的文档中。它看起来有点像这个 ASCII 艺术作品:

-----------------------------------------
| foobar |
----------

这需要一点实验,但我最终想到了以下解决方案:

from reportlab.lib.pagesizes import letter
from reportlab.platypus import Flowable, SimpleDocTemplate, Spacer
from reportlab.lib.units import inch

########################################################################
class BoxyLine(Flowable):
    """
    Draw a box + line + text

    -----------------------------------------
    | foobar |
    ---------

    """

    #----------------------------------------------------------------------
    def __init__(self, x=0, y=-15, width=40, height=15, text=""):
        Flowable.__init__(self)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.text = text

    #----------------------------------------------------------------------
    def draw(self):
        """
        Draw the shape, text, etc
        """
        self.canv.rect(self.x, self.y, self.width, self.height)
        self.canv.line(self.x, 0, 500, 0)
        self.canv.drawString(self.x+5, self.y+3, self.text)

doc = SimpleDocTemplate("test2.pdf",pagesize=letter)
story=[]

box = BoxyLine(text="foo")
story.append(box)
story.append(Spacer(0, 1*inch))
box = BoxyLine(text="bar")
story.append(box)

doc.build(story)

让我们把它分解一下。首先,我们再一次将易流动类细分。这一次我们添加了一些额外的参数,这样我们可以告诉它改变框的大小和线条的宽度,以及显示一些文本。然后在 draw 方法中,我们将文本欺骗到正确的位置。如果您改变了框的大小,那么您可能需要更改行或文本的位置。我最后稍微增强了一下,这样我就可以使用一个段落对象来代替画布的拉带方法。这是如何工作的:

from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch, mm
from reportlab.platypus import (Flowable, Paragraph,
                                SimpleDocTemplate, Spacer)

########################################################################
class BoxyLine(Flowable):
    """
    Draw a box + line + text

    -----------------------------------------
    | foobar |
    ---------

    """

    #----------------------------------------------------------------------
    def __init__(self, x=0, y=-15, width=40, height=15, text=""):
        Flowable.__init__(self)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.text = text
        self.styles = getSampleStyleSheet()

    #----------------------------------------------------------------------
    def coord(self, x, y, unit=1):
        """
        http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab
        Helper class to help position flowables in Canvas objects
        """
        x, y = x * unit, self.height -  y * unit
        return x, y

    #----------------------------------------------------------------------
    def draw(self):
        """
        Draw the shape, text, etc
        """
        self.canv.rect(self.x, self.y, self.width, self.height)
        self.canv.line(self.x, 0, 500, 0)

        p = Paragraph(self.text, style=self.styles["Normal"])
        p.wrapOn(self.canv, self.width, self.height)
        p.drawOn(self.canv, *self.coord(self.x+2, 10, mm))

doc = SimpleDocTemplate("test3.pdf",pagesize=letter)
story=[]

box = BoxyLine(text="foo")
story.append(box)
story.append(Spacer(0, 1*inch))
box = BoxyLine(text="bar")
story.append(box)

doc.build(story)

使用段落而不是 drawString 的主要优点是,您现在可以使用 Reportlab 的类似 HTML 的标记来控制使用的字体和字体大小:

txt = "This is a 10 point font"

我个人认为这比使用画布的字体相关方法更简单。


包扎

现在您知道如何使用 Reportlab 的可流动类来创建您自己的自定义可流动类。这为您创建自己的 PDF 文档提供了额外的灵活性。


附加阅读

Reportlab:混合固定内容和流动内容

原文:https://www.blog.pythonlibrary.org/2012/06/27/reportlab-mixing-fixed-content-and-flowables/

最近,我需要能够使用 Reportlab 的可流动内容,但要将它们放在固定的位置。你们中的一些人可能想知道我为什么要这么做。关于 flowables 的好处,就像段落一样,是它们很容易被设计。如果我能加粗某样东西或把某样东西放在中心,并把它放在一个固定的位置,那将会很棒!这花了很多谷歌和试验和错误,但我终于得到了一个像样的模板放在一起,我可以使用邮件。在本文中,我也将向您展示如何做到这一点。

入门指南

你需要确保你有报告实验室,否则你最终会一无所获。可以去这里抢。在你等待下载的时候,你可以继续阅读这篇文章或者去做一些其他有意义的事情。你现在准备好了吗?那就让我们开始吧!

现在我们只需要举个例子。幸运的是,我在工作中一直在做一些事情,所以我可以用下面这种愚蠢且不完整的格式信来掩饰。仔细研究代码,因为你永远不知道什么时候会有测试。


from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm, inch
from reportlab.pdfgen import canvas
from reportlab.platypus import Image, Paragraph, Table

########################################################################
class LetterMaker(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, pdf_file, org, seconds):
        self.c = canvas.Canvas(pdf_file, pagesize=letter)
        self.styles = getSampleStyleSheet()
        self.width, self.height = letter
        self.organization = org
        self.seconds  = seconds

    #----------------------------------------------------------------------
    def createDocument(self):
        """"""
        voffset = 65

        # create return address
        address = """ Jack Spratt

        222 Ioway Blvd, Suite 100

        Galls, TX 75081-4016
        """
        p = Paragraph(address, self.styles["Normal"])        

        # add a logo and size it
        logo = Image("snakehead.jpg")
        logo.drawHeight = 2*inch
        logo.drawWidth = 2*inch
##        logo.wrapOn(self.c, self.width, self.height)
##        logo.drawOn(self.c, *self.coord(140, 60, mm))
##        

        data = [[p, logo]]
        table = Table(data, colWidths=4*inch)
        table.setStyle([("VALIGN", (0,0), (0,0), "TOP")])
        table.wrapOn(self.c, self.width, self.height)
        table.drawOn(self.c, *self.coord(18, 60, mm))

        # insert body of letter
        ptext = "Dear Sir or Madam:"
        self.createParagraph(ptext, 20, voffset+35)

        ptext = """
        The document you are holding is a set of requirements for your next mission, should you
        choose to accept it. In any event, this document will self-destruct %s seconds after you
        read it. Yes, %s can tell when you're done...usually.
        """ % (self.seconds, self.organization)
        p = Paragraph(ptext, self.styles["Normal"])
        p.wrapOn(self.c, self.width-70, self.height)
        p.drawOn(self.c, *self.coord(20, voffset+48, mm))

    #----------------------------------------------------------------------
    def coord(self, x, y, unit=1):
        """
        # http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab
        Helper class to help position flowables in Canvas objects
        """
        x, y = x * unit, self.height -  y * unit
        return x, y    

    #----------------------------------------------------------------------
    def createParagraph(self, ptext, x, y, style=None):
        """"""
        if not style:
            style = self.styles["Normal"]
        p = Paragraph(ptext, style=style)
        p.wrapOn(self.c, self.width, self.height)
        p.drawOn(self.c, *self.coord(x, y, mm))

    #----------------------------------------------------------------------
    def savePDF(self):
        """"""
        self.c.save()   

#----------------------------------------------------------------------
if __name__ == "__main__":
    doc = LetterMaker("example.pdf", "The MVP", 10)
    doc.createDocument()
    doc.savePDF()

现在您已经看到了代码,所以我们将花一点时间来看看它是如何工作的。首先,我们创建一个 Canvas 对象,不用 LetterMaker 类也可以使用。我们还创建了一个风格字典,并设置了一些其他的类变量。在 createDocument 方法中,我们使用一些类似 HTML 的标签创建一个段落(一个地址)来控制字体和换行行为。然后,我们创建一个徽标,并在将两个项目放入 Reportlab Table 对象之前调整其大小。你会注意到,我留下了几行注释,展示了如何在没有桌子的情况下放置徽标。我们使用坐标方法来帮助定位可流动的。我在 StackOverflow 上找到了它,觉得它非常方便。

信的正文使用了一点字符串替换,并将结果放入另一个段落。我们还使用存储的偏移量来帮助我们定位。我发现为代码的某些部分存储几个偏移量非常有用。如果你小心地使用它们,那么你可以只改变几个偏移量来移动文档中的内容,而不必编辑每个元素的位置。如果您需要绘制线条或形状,您可以使用 canvas 对象以通常的方式来完成。

包扎

我希望这段代码能够帮助您创建 PDF。我不得不承认,我把它贴在这里,既是为了你自己,也是为了我自己的未来。我有点难过,我不得不从它身上剥离这么多,但我的组织不会很喜欢它,如果我张贴原件。不管怎样,现在您已经有了用 Python 创建一些漂亮的 PDF 文档的工具。现在你只需要走出去,做到这一点!

进一步阅读

ReportLab:现在可以用 Python 发布 PDF 了!

原文:https://www.blog.pythonlibrary.org/2018/06/18/reportlab-pdf-publishing-with-python-is-now-available/

我的新书《ReportLab:用 Python 处理 PDF》现在已经可以购买了。

ReportLab 自 2000 年以来一直存在,并且一直是 Python 开发人员用来创建 PDF 格式报告的主要包。这是一个非常强大的软件包,可以在所有主要平台上运行。这本书还将向读者介绍其他 Python PDF 包。

你可以在以下网上零售商处买到这本书:

Reportlab 表格-使用 Python 在 pdf 中创建表格

原文:https://www.blog.pythonlibrary.org/2010/09/21/reportlab-tables-creating-tables-in-pdfs-with-python/

回到今年 3 月,我在 Reportlab 上写了一篇简单的教程,report lab 是一个方便的第三方 Python 包,允许开发人员以编程方式创建 pdf。最近,我收到了一个关于如何在 Reportlab 中处理表格的请求。由于我的 Reportlab 文章如此受欢迎,我认为可能值得花大力气来弄清楚表格。在本文中,我将尝试向您展示向 Reportlab 生成的 pdf 中插入表格的基础知识。

我对 Reportlab 的几个问题之一是他们的用户指南。它展示了一些很好的例子,但是它们几乎总是不完整的。点击此处下载用户指南并开始阅读第七章(关于表格的章节),您将很快看到以下代码片段:


LIST_STYLE = TableStyle(
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)

当然,这个代码片段是完全不可运行的,而且它周围的文本对于弄清楚如何导入表格样式颜色也是毫无用处的。听起来你可以从流动类中得到所有的表格材料(第 76 页-第 7 章第一页)。不幸的是,没有“报告实验室”。可流动”。你要知道,所有的流动都来自一种叫做鸭嘴兽的东西。我们就此打住,谈谈什么是“可流动的”。用户指南是这样说的:

Flowables 是可以绘制的东西,它有 wrap、draw 和 split 方法。Flowable 是要绘制的对象的抽象基类,实例知道其大小,并在自己的坐标系中绘制(这需要在调用 Flowable.draw 方法时,基础 API 提供绝对坐标系)。要获得一个实例,请使用 f=Flowable()。(第 62 页第 5.3 节)。请注意,该页面上没有显示如何获得可流动类。我只是通过搜索它们的来源才知道是怎么回事。语法如下:from reportlab . platypus import flow

几年前,正是这类事情让我几乎放弃使用 Reportlab。幸运的是,我的老板让我明白了这一点,并有一些真实的例子供我效仿。好了,说够了。继续学习表格教程!

Reportlab 表入门

实际上,我们将从 Reportlab 用户指南中获取一些表格示例,并使它们可运行。我将尝试解释代码中发生了什么,但是您也可以参考指南。让我们从第 78 页标题为表格样式单元格格式命令的例子开始。第一个真实的例子展示了如何创建一个有多种颜色的表格,但是没有可见的网格。让我们让它运行起来!


from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle

doc = SimpleDocTemplate(" simple _ table . pdf ",pagesize=letter)

可流动对象的容器

元素= []

data =[' 00 ',' 01 ',' 02 ',' 03 ',' 04'],
['10 ',' 11 ',' 12 ',' 13 ',' 14'],
['20 ',' 21 ',' 22 ',' 23 ',' 24'],
['30 ',' 31 ',' 32 ',' 33 ',' 34 ']]
t =表格(数据)
t . set style([('背景',(1,1),(-2,-2),TableStyle

如果您运行此代码,您应该会在 PDF 的顶部看到类似这样的内容:

table_with_no_grid.png

如果您查看代码,您会注意到我们有一系列的导入。我们从“reportlab.lib”中导入颜色,从“reportlab.lib.pagesizes”中导入字母,从“reportlab.platypus”中导入 SimpleDocTemplate表格表格样式。这些导入是不言自明的,但是它们也帮助我们熟悉 Reportlab 代码的布局方式。如果我们需要其他可流动的内容,比如 Paragraph,我们可以假设它来自“reportlab.platypus ”,因为 Table 是可流动的。如果你试一试,你会发现这个假设是正确的。

接下来,我们使用 SimpleDocTemplate 类创建一个文档模板。第一个参数是我们想要创建的 PDF 的路径,第二个参数是页面大小。在这个例子中,我们只输入文档的名称。这将导致脚本将 PDF 放在运行它的同一个文件夹中。出于我从未见过解释的原因,你使用一个列表来保存可流动数据。在这段代码中,我们称我们的列表为“元素”。在用户指南中,他们称之为“故事”。

数据列表保存了将要放入我们的表中的数据。列表是列表的列表。表格将有 5 列宽(嵌套列表的长度)和 4 行高(嵌套列表的数量)。我们将这个列表传递给我们的类,在内存中创建这个表,然后调用我们的表实例的 setStyle 方法来改变样式。为此,我们传递给它一个 TableStyle 类,其中包含我们想要应用的样式。在本例中,我们希望将第 2 行第 2 列的单元格应用到第 3 行第 2 列的单元格中。注意,列和行是从零开始的,所以 0 = 1,1 = 2,等等。还要注意,这个例子使用了(-2,-2),而不是更容易理解的(3,2)。这说明您也可以从右下角指定设置,而不只是从左上角做所有的事情。不幸的是,从右下角开始的坐标系是从(-1,-1)开始的,这使得我们更难理解。

然后,我们将前两列中的文本颜色设置为红色。我承认,我不太清楚这是如何工作的,因为结束坐标似乎不适合背景颜色。我将让我的读者来解释。

最后,我们将表添加到元素列表中,然后调用文档实例的 build 方法,将元素列表作为唯一的参数。这将导致构建文档并创建 PDF。现在你有一个 PDF 表格!

向表格中添加网格

table_with_grid.png

让我们以 Reportlab 文档中的下一个示例为例,看看我们能否让它也运行起来。下一个例子展示了如何在表格中添加一个可见的网格,以及如何在单元格中定位文本。


from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, inch
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle

doc = SimpleDocTemplate(" simple _ table _ grid . pdf ",pagesize=letter)

可流动对象的容器

elements = []

data =[' 00 ',' 01 ',' 02 ',' 03 ',' 04'],
['10 ',' 11 ',' 12 ',' 13 ',' 14'],
['20 ',' 21 ',' 22 ',' 23 ',' 24'],
['30 ',' 31 ',' 32 ',' 33 ',' 34']]
t=Table(data,5 *[0.4 *英寸],4 *[0.4 *英寸])
t.setStyle(TableStyle

elements.append(t)

将文档写入磁盘

doc.build(elements)

我们从页面大小库中导入了一个新的内容:英寸。“英寸”只是给了我们一个简单的方法来设置 PDF 的尺寸和边距。在本例中,我们用它来设置表格的列宽和行高。下一个变化是在代码的 setStyle 部分,这是让表格看起来像你想要的样子的关键。 TableStyle 类完全控制表格的外观。例如,前两行将中间的六个单元格向右对齐,并将其涂成红色。接下来的两行将第一列中的四个单元格设置为蓝色,并将单元格顶部的文本设置为蓝色。你可以自己算出三条线。

最后两行代码绘制了内部网格和网格周围的方框。然后,我们将表添加到元素列表中,最后,构建文档。

创建复杂的单元格值

我们要看的最后一个主要示例是 Reportlab 用户指南中的复杂单元格值示例。这个程序向您展示了如何在单元格中插入其他 Reportlab 流动数据。让我们快速浏览一下!


from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, inch
from reportlab.platypus import Image, Paragraph, SimpleDocTemplate, Table
from reportlab.lib.styles import getSampleStyleSheet

doc = SimpleDocTemplate(" complex _ cell _ values . pdf ",pagesize=letter)
#可流动对象的容器
elements = []

styleSheet = getSampleStyleSheet()

I = Image(' repl ogo . gif ')
I . draw height = 1.25 * inch * I . draw height/I . draw width
I . draw width = 1.25 * inch
P0 = Paragraph(' '
**A pa<font color="red">r</font>A*graph***
<super><font color="yellow">1</font></super>' ' ',
样式表[" body text "])
P = Paragraph(' ' ' '

t=Table(data,style=[('GRID ',(1,1),(-2,-2),1,colors.green),
('BOX ',(0,0),(1,-1),2,colors.red),
('LINEABOVE ',(1,2),(-2,2),1,colors.blue),
('LINEBEFORE ',(2,1),(2,-2),1,colors.pink),
('BACKGROUND ',(0,0,(0,0,1),colors.pink),

elements.append(t)
#将文档写入磁盘
doc.build(elements)

这个片段有几个新的导入。首先,我们需要访问图像段落,它们是鸭嘴兽库的一部分。我们还需要能够使用 getSampleStyleSheet ,所以我们从 styles lib 中导入它。段落是可流动的,它允许我们使用类似 HTML 的语法来设计文本和插入图像,尽管在这个例子中我们没有以这种方式插入图像。

无论如何,风格是最重要的。在本例中,我们采用了一种快捷方式,使用 Table 对象的 style 参数将样式直接传递给 Table 对象,而不是使用 Table 的“setStyle”方法和 TableStyle 类。如果我们那样做,结果会是一样的。此时,您应该能够自己解析样式了。唯一真正的区别是我们将段落和图像实例添加到数据列表的位置。

其他零碎的东西

关于与前面的例子不太相符的表,还有几件事情需要提及。Reportlab 提供了在页面上容纳不下太多行时拆分表的功能。默认情况下,这是启用的。根据文档,目前没有按列拆分的方法,所以不要尝试插入超宽的列。

该工作台还有重复箭头重复控制参数。repeatRows 参数控制拆分时要重复多少行。repeatCols 参数当前未实现。老实说,我甚至不确定为什么要包括这个参数。它已经在那里有一段时间了,但仍然没有任何作用。

包扎

现在,您应该已经为使用 Reportlab 工具包创建 PDF 表格打下了良好的基础。这篇文章只是触及了 Reportlab 的皮毛,所以请务必阅读文档、测试和他们网站上的各种其他示例,以真正理解这个方便的工具包的强大功能。

进一步阅读

  • Reportlab 开源
  • 一个报告实验室

下载

重启扭曲的反应堆

原文:https://www.blog.pythonlibrary.org/2016/09/14/restarting-a-twisted-reactor/

几周前我开始使用 twisted。对于不知道的人来说, twisted 是“用 Python 写的事件驱动联网引擎”。如果您以前从未做过异步编程,那么学习曲线会非常陡峭。在我工作的项目中,我遇到了一种情况,我认为我需要重新启动扭曲的反应堆。根据我在网上找到的一切,重启反应堆是不支持的。但是我很固执,所以我试着找到一种方法。

重启扭曲的反应堆

让我们从创建一个非常标准的 twisted 服务器开始。我们将子类化 LineReceiver ,它基本上是一个接受整行文本的 TCP 服务器,尽管它也可以处理原始数据。让我们看一下代码:


from twisted.internet import reactor
from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import Factory

PORT = 9000

class LineServer(LineReceiver):

    def connectionMade(self):
        """
        Overridden event handler that is called when a connection to the 
        server was made
        """
        print "server received a connection!"

    def connectionLost(self, reason):
        """
        Overridden event handler that is called when the connection 
        between the server and the client is lost
        @param reason: Reason for loss of connection
        """
        print "Connection lost"
        print reason

    def lineReceived(self, data):
        """
        Overridden event handler for when a line of data is 
        received from client
        @param data: The data received from the client
        """
        print 'in lineReceived'
        print 'data => ' + data

class ServerFactory(Factory):
    protocol = LineServer

if __name__ == '__main__':
    factory = ServerFactory()
    reactor.listenTCP(PORT, factory)
    reactor.run()

所有驼峰式方法都是被覆盖的 twisted 方法。我只是把它们打出来,让它们在被调用时打印到 stdout。现在让我们制作一个客户端,它有一个我们可以重启几次的反应器:


import time
import twisted.internet

from twisted.internet import reactor, protocol
from twisted.protocols.basic import LineOnlyReceiver

HOST = 'localhost'
PORT = 9000

class Client:
    """
    Client class wrapper
    """
    def __init__(self, new_user):
        self.new_user = new_user

        self.factory = MyClientFactory()

    def connect(self, server_address=HOST):
        """
        Connect to the server
        @param server_address: The server address
        """
        reactor.connectTCP(server_address, PORT, self.factory,
            timeout=30)

class MyProtocol(LineOnlyReceiver):

    def connectionMade(self):
        """
        Overridden event handler that is fired when a connection
        is made to the server
        """
        print "client connected!"
        self.run_long_running_process()

    def lineReceived(self, data):
        """
        Gets the data from the server
        @param data: The data received from the server
        """
        print "Received data: " + data

    def connectionLost(self, reason):
        """
        Connection lost event handler
        @param reason: The reason the client lost connection 
            with the server
        """
        print "Connection lost"

    def run_long_running_process(self):
        """
        Run the process
        """
        print 'running process'
        time.sleep(5)
        print "process finished!"
        self.transport.write('finished' + '\r\n')
        reactor.callLater(5, reactor.stop)

class MyClientFactory(protocol.ClientFactory):
    protocol = MyProtocol

if __name__ == '__main__':
    # run reactor multiple times
    tries = 3
    while tries:
        client = Client(new_user=True)
        client.connect('localhost')
        try:
            reactor.run()
            tries -= 1
            print "tries " + str(tries)
        except Exception, e:
            print e
            import sys
            del sys.modules['twisted.internet.reactor']
            from twisted.internet import reactor
            from twisted.internet import default
            default.install()

这里我们创建了一个简单的客户端,它也只接受文本行( LineOnlyReceiver )。重启反应堆的魔力在于代码末尾的 while 循环。实际上,我在 twisted 的 reactor.py 文件的异常处理程序中找到了代码,这给了我灵感。基本上我们正在做的是导入 Python 的 sys 模块。然后我们从 sys.modules 中删除反应器,这允许我们重新移植它并重新安装默认反应器。如果您在一个终端上运行服务器,在另一个终端上运行客户机,您会看到客户机重新连接了三次。

包扎

正如我在开始时提到的,我对 twisted 还很陌生。你应该做的不是重启反应器,而是在另一个线程中运行它。或者您可以使用它的一个延迟调用或延迟线程来绕过重启反应器的“需要”。坦白地说,本文中的方法在某些情况下甚至不起作用。实际上,我曾试图在一个用 contextlib 的****context managerdecorator 修饰的函数中重启反应器,但这不知何故阻止了代码正确运行。不管怎样,我认为这是一种重新加载模块的有趣方式。

使用 Python 重启电脑

原文:https://www.blog.pythonlibrary.org/2010/03/27/restarting-pcs-with-python/

你有没有想过重新启动你的 Windows 电脑没有按下开始,关机或 CTRL+ALT+DEL?重启你讨厌的同事的电脑怎么样...就是那个不知道什么时候该闭嘴的人?Python 给出了答案,这个博客将告诉你如何去做!注意:我并不建议你随意重启邻居的电脑...

使用 PyWin32 重新启动

无论如何,当我第一次学习 Python 时,我偶然发现了一个关于如何做到这一点的 ActiveState 方法。当然,现在我似乎找不到那个配方了,但是我在这里找到了一个相似的:http://code.activestate.com/recipes/360649/.因此,我们将首先从这个方法开始,然后看一个稍微不同的方法。如果你想跟着做,那么你需要确保你有 PyWin32 包

这是我从我模糊的过去得到的食谱:


# rebootServer.py

import win32security
import win32api
import sys
import time
from ntsecuritycon import *

def AdjustPrivilege(priv, enable=1):
    # Get the process token
    flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
    htoken = win32security.OpenProcessToken(win32api.GetCurrentProcess(), flags)
    # Get the ID for the system shutdown privilege.
    idd = win32security.LookupPrivilegeValue(None, priv)
    # Now obtain the privilege for this process.
    # Create a list of the privileges to be added.
    if enable:
        newPrivileges = [(idd, SE_PRIVILEGE_ENABLED)]
    else:
        newPrivileges = [(idd, 0)]
    # and make the adjustment
    win32security.AdjustTokenPrivileges(htoken, 0, newPrivileges)

def RebootServer(message='Rebooting', timeout=30, bForce=0, bReboot=1):
    AdjustPrivilege(SE_SHUTDOWN_NAME)
    try:
        win32api.InitiateSystemShutdown(None, message, timeout, bForce, bReboot)
    finally:
        # Now we remove the privilege we just added.
        AdjustPrivilege(SE_SHUTDOWN_NAME, 0)

def AbortReboot():
    AdjustPrivilege(SE_SHUTDOWN_NAME)
    try:
        win32api.AbortSystemShotdown(None)
    finally:
        AdjustPrivilege(SE_SHUTDOWN_NAME, 0)

if __name__ == '__main__':
    RebootServer()
    time.sleep(10)
    print 'Aborting shutdown'
    AbortReboot()

在这个代码片段中,我实际上去掉了重启远程机器的功能,因为我不想这么做。如果要重新启动网络上的计算机,请更改以下行:


win32api.InitiateSystemShutdown(None, message, timeout, bForce, bReboot)


win32api.InitiateSystemShutdown("someMachineName", message, timeout, bForce, bReboot)

或者只需向 RebootServer 函数添加一个主机参数,并根据需要修改 InitiateSystemShutdown。如果你只是传递 None 进去,那么你只是重新启动你自己的机器。 AdjustPrivilege 方法用于改变进程的特权,这样它就可以关闭电脑。我认为这只适用于你作为一个拥有有限权限的用户运行脚本的情况,但是我几乎从来没有这样运行过,所以我不能确定。我记得菜谱上说您需要删除您添加的特权,这就是 finally 语句的作用。然而,我认为一旦重启完成,这些特权无论如何都会被取消。

用普通的 Python 重启

启动关机的另一种方法是学习 Windows 命令行的魔力。有一个关机命令,你可以把你的运行对话框(进入开始->运行),这将重新启动你的电脑。这里有一个我说的:shutdown -r -t 1 注意这只会重启本地机器!

现在我们只需要弄清楚如何让 Python 为我们调用它。可能最简单的方法是导入 os 模块并调用它的系统方法。让我们来看看:


import os
os.system("shutdown -r -t 1")

这比 PyWin32 方法短得多,但我不知道它是否同样健壮。我在一个快速而肮脏的 wxPython 应用程序中使用了这个方法,这个应用程序是我为工作中的 Sun Ray 虚拟机创建的。Sun Ray 系统的最大问题之一是用户使用远程桌面连接到他们的虚拟机。远程桌面的一部分是隐藏关机按钮,所以用户没有简单的方法来重新启动他们的电脑,除非他们安装了某种修复程序。有时候重启机器是一件好事,所以我将上面的脚本放入下面的应用程序中:


import os
import wx
from wx.lib.buttons import GenBitmapButton

########################################################################
class RestarterPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent)

        img = wx.Bitmap("cute-logoff.png")
        logoutBtn = GenBitmapButton(self, wx.ID_ANY, img, style=wx.BORDER_NONE)
        logoutBtn.Bind(wx.EVT_BUTTON, self.onLogout)

        img = wx.Bitmap("quick_restart.png")
        restartBtn = GenBitmapButton(self, wx.ID_ANY, img, style=wx.BORDER_NONE)
        restartBtn.Bind(wx.EVT_BUTTON, self.onRestart)

        cancelBtn = wx.Button(self, label="Cancel")
        cancelBtn.Bind(wx.EVT_BUTTON, self.onCancel)

        vSizer = wx.BoxSizer(wx.VERTICAL)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.buttonBuilder("Logout", logoutBtn), 0, wx.CENTER)
        sizer.Add(self.buttonBuilder("Restart", restartBtn), 0, wx.CENTER)
        vSizer.Add(sizer, 0, wx.CENTER)
        vSizer.Add(cancelBtn, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
        self.SetSizer(vSizer)

    #----------------------------------------------------------------------
    def buttonBuilder(self, label, button):
        """
        Creates a button with a label underneath it and puts them into
        a vertical BoxSizer, which is then returned
        """
        font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(button, 0, wx.ALL, 3)

        lbl = wx.StaticText(self, label=label)
        lbl.SetFont(font)
        sizer.Add(lbl, 0, wx.CENTER|wx.BOTTOM, 4)

        return sizer

    #----------------------------------------------------------------------
    def onCancel(self, event):
        """
        Close the dialog
        """
        self.GetParent().Close()

    #----------------------------------------------------------------------
    def onLogout(self, event):
        """
        Logs the current user out
        """
        os.system("shutdown -t 0 -l")

    #----------------------------------------------------------------------
    def onRestart(self, event):
        """
        Restarts the PC
        """
        os.system("shutdown -r -t 1")

########################################################################
class RestarterFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, size=(273, 169))
        panel = RestarterPanel(self)
        self.Center()

########################################################################
class Main(wx.App):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, redirect=False, filename=None):
        """Constructor"""
        wx.App.__init__(self, redirect, filename)
        dlg = RestarterFrame()
        dlg.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = Main()
    app.MainLoop()

我看了这个项目的很多图标,我想我最后只是搜索了一下关机注销图标,然后下载了我最喜欢的图标。这两个都是免费的或者有知识共享许可。

现在您知道了重启本地和远程机器的技巧。明智地使用这些知识!

在 ReportLab 中旋转图像

原文:https://www.blog.pythonlibrary.org/2019/09/05/rotating-images-in-reportlab/

创建 PDF 时,有时您希望旋转 ReportLab 中的图像或其他对象。例如,出于水印目的,您可能想要将图像旋转 45 度。或者您可能需要一个沿 PDF 的一个边缘垂直排列的图像。

你可以通过使用 ReportLab 的 canvas 方法或者使用你可以在 platypus 中找到的更高级的 Flowables 来旋转图像。模块。让我们先来看看如何直接用画布来做这件事!


使用画布旋转图像

使用画布旋转图像有点令人困惑。原因是当你旋转画布时,如果不小心的话,你可能会无意中旋转画布上的其他元素。

我们来看看代码:


# image_on_canvas.py

from reportlab.lib import utils
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

def add_image(image_path):
    img = utils.ImageReader(image_path)
    img_width, img_height = img.getSize()
    aspect = img_height / float(img_width)

    my_canvas = canvas.Canvas("canvas_image.pdf",
                              pagesize=letter)
    my_canvas.saveState()
    my_canvas.rotate(45)
    my_canvas.drawImage(image_path, 150, 10,
                        width=100, height=(100 * aspect))
    my_canvas.restoreState()
    my_canvas.save()

if __name__ == '__main__':
    image_path = 'snakehead.jpg'
    add_image(image_path)

这里使用 ReportLab 的 utils 函数提取图像的宽度和高度。然后创建画布并保存其当前状态。这将允许您从现在开始修改画布,然后在以后恢复它。如果你想在旋转后的图像前有一些文本或形状,你可以把代码放在调用 saveState() 之前。

保存画布状态后,可以使用画布的 rotate() 方法将画布旋转 45 度。然后你把图像画到画布上。最后,使用 restoreState() 将状态恢复到旋转前的状态。

注意:当旋转画布时,x/y 位置现在是 45 度角,所以你在画布上定位图像时必须考虑到这一点。

当我运行这段代码时,我得到了一个如下所示的文档:

Rotated image with ReportLab

你也可以在这里下载 PDF

现在让我们来看看如何做同样的事情使用流动!


使用可流动图像旋转图像

Flowables 是 ReportLab 中的对象,来自它们的 platypus 模块,代表使用脚本的页面布局和排版。这个模块基本上是 canvas 方法的一个高级接口,它抽象了绘图位,使得创建多页文档更加简单。

在 ReportLab 中使用可流动对象创建旋转图像的最快方法是创建图像可流动对象的子类。我们来看看吧!


from reportlab.lib.pagesizes import letter
from reportlab.platypus import Image, SimpleDocTemplate

class RotatedImage(Image):

    def wrap(self, availWidth, availHeight):
        height, width = Image.wrap(self, availHeight, availWidth)
        return width, height

    def draw(self):
        self.canv.rotate(45)
        Image.draw(self)

doc = SimpleDocTemplate("image_with_rotation.pdf", pagesize=letter)
flowables = []

img = RotatedImage('snakehead.jpg',
                   width=50, height=50
                   )
img.hAlign = 'CENTER'
flowables.append(img)
doc.build(flowables)

在这里,您子类化 Image 并覆盖 wrap()draw() 方法。你将关心的主要部分是在 draw()方法中,在那里你调用 self.canv.rotate(45) 。Image 类有自己的画布,您可以对其进行操作。在这种情况下,您告诉它您希望总是以 45 度角绘制图像。

接下来创建一个文档模板,并创建一个 RotatedImage 的实例。然后你告诉图像在页面上居中。最后你建立()文档。

当您运行此代码时,您应该看到以下内容:

Rotated image with ReportLab Flowable

如果你想看文件,你可以在这里得到实际的 PDF 文件。


包扎

现在您知道如何使用 ReportLab 旋转图像了。您学习了如何使用低级画布方法旋转图像,还学习了如何旋转可流动的图像。您也可以使用这些知识来旋转其他类型的对象。例如,您可以使用相同的方法旋转文本和形状。你总是要旋转画布来获得所需的效果。开心快乐编码!

| | 想了解更多关于使用 Python 处理 pdf 的信息吗?然后看看我的书:

ReportLab:使用 Python 处理 PDF

在 Leanpub 上立即购买 |


相关阅读

《创建 wxPython 应用程序》一书中的示例章节

原文:https://www.blog.pythonlibrary.org/2019/01/22/sample-chapters-from-creating-wxpython-applications-book/

我的新书在 Kickstarter 上的宣传活动进行得很顺利,所以我想和你分享这本书的一些章节会很有趣。你可以在这里查看《T2》的前几章 PDF 格式。

我也一直在做一些实验,关于 Kickstarter 的延伸目标的书中其他章节给出的一些想法。我还没有做出任何具体的决定,但我确实认为与美国宇航局网站的 API 互动听起来很有趣,似乎也很容易做到。

我也会研究其他想法的可行性。

非常感谢你的支持!

使用 EZGmail 和 Python 发送电子邮件

原文:https://www.blog.pythonlibrary.org/2019/06/04/sending-email-with-ezgmail-and-python/

你有没有想过用 Python 编程语言用 GMail 发邮件?2018 年,用 Python 自动化枯燥的东西的畅销书作者 Al Sweigart 创建了一个名为 EZGmail 的包。你也可以使用谷歌自己的绑定来做这类事情,但是这比使用 EZGmail 要复杂得多。

在本文中,我们将快速了解如何使用这个包。


安装

您的第一步是使用 pip 安装 EZGmail。方法如下:


pip install ezgmail

然后转到https://developers.google.com/gmail/api/quickstart/python,点击启用 Gmail API 按钮。这将允许你下载一个 credentials.json 文件,并给你一个客户端 ID 和客户端密码。您可以在 Google 的 Python API 客户端使用后一种凭证,如果需要,您可以在这里管理这些凭证

现在,将凭证文件复制到您计划编写代码的位置。然后,您需要在您的终端中运行 Python,运行位置与您下载的凭证文件的位置相同。

下一步是运行ezgmail.init(). This will open up a web browser to Gmail where it will ask you to allow access to your application. If you grant access, EZGmail will download a tokens file so that it doesn't need to have you reauthorize it every time you use it.

要验证一切都正常工作,您可以运行以下代码:


>>> ezgmail.EMAIL_ADDRESS
'your_email_address@gmail.com'

这应该会打印出您的 Gmail 帐户名称。


使用 EZGmail 发送电子邮件

您可以使用 EZGmail 发送和阅读电子邮件。

让我们看一个发送电子邮件的简单例子:


>>> email = 'joe@schmo.com'
>>> subject = 'Test from EZGmail'
>>> text = 'This is only a test'
>>> ezgmail.send(email, subject, text)

这将向您指定的帐户发送一封带有主题和文本的电子邮件。您还可以传入要发送的附件列表。最后,EZGmail 支持抄送和密件抄送,尽管如果你使用它们或电子邮件字段本身发送到多个地址,参数只接受字符串。这意味着电子邮件地址需要在字符串中用逗号分隔,而不是电子邮件地址列表。


阅读 Gmail

你也可以用 EZGmail 阅读邮件。最好的两种方法是使用recent() and unread() methods.``

以下是一些例子:


>>> recent = ezgmail.recent()
>>> unread = ezgmail.unread()

这些方法中的每一个都返回一个列表GmailThread objects. A GmailThread has the following attributes:``

  • 信息
  • 发报机
  • 接受者
  • 科目
  • 身体
  • 时间戳

您可以遍历这些列表,并根据需要提取任意或所有这些项目。

还有一个方便的功能,您可以使用它来打印您的电子邮件摘要:


>>> ezgmail.summary(unread)

当您运行这段代码时,EZGmail 可能需要一段时间来下载和解析电子邮件。但是请注意,默认情况下,它最多只能下载 25 封电子邮件的数据。如果需要,您可以将最大值更改为一个更高的数字,但是要注意有一个数据配额限制


包扎

EZGmail 包非常简洁。本教程没有涉及到它,但是你也可以使用 EZGmail 通过search() function. Be sure to give the package a try or at least study the source code. Happy coding!来搜索你的邮件


相关阅读

  • 如何用 Python 发送电子邮件

使用 wxPython 缩短 URL

原文:https://www.blog.pythonlibrary.org/2009/12/24/shortening-urls-with-wxpython/

今年夏天,我在 Ars Technica 上偶然发现了一篇关于使用 PyGTK 创建 URL 缩短器的文章。我认为这很有趣,但是我不使用 PyGTK。所以在那时,我决定使用 wxPython 编写自己的代码,并使用本文的代码来缩短代码。我很快拼凑了一些东西,然后把这篇文章放在一边,有点忘记了。今天,我决定继续完成它,并创建一个应用程序,可以使用其他流行的网址缩短程序来缩短网址。

apink ars

首先,我将向你展示我的原始程序,它基本上是模仿 Ars Technica 的。


import re
import urllib
import urllib2
import wx

class ArsShortener(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          'wxArsShortner', size=(300,70))

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)

        self.txt = wx.TextCtrl(panel, wx.ID_ANY, "", size=(300, -1))
        self.txt.Bind(wx.EVT_TEXT, self.onTextChange)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.txt, 0, wx.EXPAND, 5)
        panel.SetSizer(sizer)

    #----------------------------------------------------------------------
    def onTextChange(self, event):
        """"""
        text = self.txt.GetValue()
        textLength = len(text)
        if re.match("^https?://[^ ]+", text) and textLength > 20:
            apiurl = "http://is.gd/api.php?" + urllib.urlencode(dict(longurl=text))
            shorturl = urllib2.urlopen(apiurl).read() 
            self.txt.SetValue(shorturl)

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = ArsShortener()
    frame.Show()
    app.MainLoop()

正如您所看到的,几乎一半的代码与 wxPython 相关,而另一半来自 Ars 文章。首先,我们导入 Python 正则表达式模块 re,用于验证目的。它检查粘贴的 URL 以确保它是 URL 的正确格式,而不仅仅是垃圾(参见 onTextChange 方法)。urllib 模块用于访问 is.gd 网站,并向其传递我们想要缩短的 URL。一旦我们得到结果,我们调用文本控件的 SetValue 方法向用户显示它。条件语句还检查 URL 的长度是否足够长,以保证缩短它。

你会注意到我有三行注释掉了。我这样做是为了让应用程序可以调整大小,并让文本控件随着框架一起增长,但后来认为这可能是蹩脚的。因此,我将其注释掉。然而,我想你,亲爱的读者,可能会发现知道如何做到这一点是有用的。

缩短其他网站的 URL

现在我们来看看我的扩展示例。它可以使用 is.gd、bit.ly 或古老的 tinyurl 来缩短 url。对于本文的这一部分,除了 wx 之外,您还需要以下模块:

我确信这些网站也有使用 urllib 模块的方法,但是我想看看这些第三方模块是否简化了事情。请注意,如果你想使用 bit.ly,他们要求你注册一个 API 密匙。我不会公布我的,但我会告诉你如何在我的程序中使用它。所以,事不宜迟,继续表演吧:


import re
import urllib
import urllib2
import wx

bitlyFlag = True
tinyurlFlag = True

try:
    import bitly
except ImportError:
    bitlyFlag = False

try:
    import tinyurl
except ImportError:
    tinyurlFlag = False

########################################################################
class MainPanel(wx.Panel):
    """
    """

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.frame = parent

        # create the widgets
        self.createLayout()

    #----------------------------------------------------------------------
    def createLayout(self):
        """
        Create widgets and lay them out
        """
        choices = ["is.gd"]
        if bitlyFlag:
            choices.append("bit.ly")
        if tinyurlFlag:
            choices.append("tinyurl")
        choices.sort()

        # create the widgets
        self.urlCbo = wx.ComboBox(self, wx.ID_ANY, "is.gd", 
                                  choices=choices,
                                  size=wx.DefaultSize,
                                  style=wx.CB_DROPDOWN)
        self.inputUrlTxt = wx.TextCtrl(self, value="Paste long url here")
        self.inputUrlTxt.Bind(wx.EVT_SET_FOCUS, self.onFocus)
        self.outputUrlTxt = wx.TextCtrl(self, style=wx.TE_READONLY)

        shortenBtn = wx.Button(self, label="Shorten URL")
        shortenBtn.Bind(wx.EVT_BUTTON, self.onShorten)
        copyBtn = wx.Button(self, label="Copy to Clipboard")
        copyBtn.Bind(wx.EVT_BUTTON, self.onCopy)

        # create the sizers
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        # layout the widgets
        mainSizer.Add(self.urlCbo, 0, wx.ALL, 5)
        mainSizer.Add(self.inputUrlTxt, 0, 
                      wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(self.outputUrlTxt, 0, 
                      wx.ALL|wx.EXPAND, 5)
        btnSizer.Add(shortenBtn, 0, wx.ALL|wx.CENTER, 5)
        btnSizer.Add(copyBtn, 0, wx.ALL|wx.CENTER, 5)
        mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
        self.SetSizer(mainSizer)

    #----------------------------------------------------------------------
    def onCopy(self, event):
        """
        Copies data to the clipboard or displays an error
        dialog if the clipboard is inaccessible.
        """
        text = self.outputUrlTxt.GetValue()
        self.do = wx.TextDataObject()
        self.do.SetText(text)
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(self.do)
            wx.TheClipboard.Close()
            status = "Copied %s to clipboard" % text
            self.frame.statusbar.SetStatusText(status)
        else:
            wx.MessageBox("Unable to open the clipboard", "Error")

    #----------------------------------------------------------------------
    def onFocus(self, event):
        """
        When control is given the focus, it is cleared
        """
        self.inputUrlTxt.SetValue("")

    #----------------------------------------------------------------------
    def onShorten(self, event):
        """
        Shortens a url using the service specified.
        Then sets the text control to the new url.
        """
        text = self.inputUrlTxt.GetValue()
        textLength = len(text)

        if re.match("^https?://[^ ]+", text) and textLength > 20:
            pass
        else:
            wx.MessageBox("URL is already tiny!", "Error")
            return

        url = self.urlCbo.GetValue()
        if url == "is.gd":
            self.shortenWithIsGd(text)
        elif url == "bit.ly":
            self.shortenWithBitly(text)
        elif url == "tinyurl":
            self.shortenWithTinyurl(text)

    #----------------------------------------------------------------------
    def shortenWithBitly(self, text):
        """
        Shortens the URL in the text control using bit.ly

        Requires a bit.ly account and API key
        """
        bitly.API_LOGIN = "username"
        bitly.API_KEY = "api_key"
        url = bitly.shorten(text)
        self.outputUrlTxt.SetValue(url)

    #----------------------------------------------------------------------
    def shortenWithIsGd(self, text):
        """
        Shortens the URL with is.gd using urllib and urllib2
        """

        apiurl = "http://is.gd/api.php?" + urllib.urlencode(dict(longurl=text))
        shorturl = urllib2.urlopen(apiurl).read() 
        self.outputUrlTxt.SetValue(shorturl)

    #----------------------------------------------------------------------
    def shortenWithTinyurl(self, text):
        """
        Shortens the URL with tinyurl
        """
        print "in tinyurl"
        url = tinyurl.create_one(text)
        self.outputUrlTxt.SetValue(url)

########################################################################
class UrlFrame(wx.Frame):
    """
    wx.Frame class
    """
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        title = "URL Shortener"
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          title=title, size=(650, 220))
        panel = MainPanel(self)
        self.statusbar = self.CreateStatusBar()
        self.SetMinSize((650, 220))       

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = UrlFrame()
    frame.Show()
    app.MainLoop()

这段代码比我的简单示例要长得多,但是它内置了更多的逻辑。我马上实现了一些异常处理,以防程序员没有安装 bitly 或 tinyurl 模块。如果没有,则设置一个标志,防止添加这些选项。您将在 MainPanel 类的 createLayout 方法中看到这一点。这就是我们将选项添加到组合框将使用的选择列表的地方。根据您安装的内容,您会在下拉列表中看到一到三个选项。

下一个有趣的地方是输入 url 文本控件绑定到焦点事件的地方。当我们粘贴一个 url 到文本控件中时,我们用它来清除文本控件。还要注意,输出文本控件被设置为只读模式。这可以防止用户弄乱新的 url。最后,我们到达最后两个部件:缩短 URL 的按钮和复制到剪贴板的按钮。

让我们快速看一下 onCopy 方法中发生了什么,因为它是下一个:


def onCopy(self, event):
    """
    Copies data to the clipboard or displays an error
    dialog if the clipboard is inaccessible.
    """
    text = self.outputUrlTxt.GetValue()
    self.do = wx.TextDataObject()
    self.do.SetText(text)
    if wx.TheClipboard.Open():
        wx.TheClipboard.SetData(self.do)
        wx.TheClipboard.Close()
        status = "Copied %s to clipboard" % text
        self.frame.statusbar.SetStatusText(status)
    else:
        wx.MessageBox("Unable to open the clipboard", "Error")

如您所见,这从输入文本控件中获取了当前文本,并从中创建了一个 TextDataObject。然后我们尝试打开剪贴板,如果成功,我们使用剪贴板的 SetData 方法将 TextDataObject 放入其中。最后,我们通过改变框架的状态栏文本来提醒用户我们做了什么。

在 onShorten 方法中,我重用了 Ars 程序中的正则表达式来检查用户是否粘贴了有效的 url,我们还检查了 url 的长度,看它是否真的需要缩短。我们从 combobox 中获取 shortener url 类型,然后使用一个条件将我们想要缩短的 url 传递给适当的缩短方法。 shortenWithIsGd 方法与第一个例子基本相同,所以我们将跳过这个例子。 shortenWithBitly 方法显示我们需要设置 LOGIN 和 API_KEY 属性,然后才能缩短 url。一旦我们完成了,我们就调用 bitly 的缩短方法。在 shortenWithTinyurl 方法中,它甚至更简单:您在这里需要做的就是调用 Tinyurl 的 create_one 方法。

包扎

现在你知道了通过几种方法来缩短你的长 URL 的基础。您可以随意添加自己的特性或其他缩短 API 来改进您自己使用的应用程序。注意,还有另一个 Python 小模块。虽然它的主页上说它需要的只是 simplejson 模块,但是如果你真的试图使用这个模块,如果你没有安装 django ,你会收到一个错误。他们似乎意识到了这个问题,但出于这个原因,我选择使用 Opie 的 bitly 模块。祝编码愉快!

注意:这段代码是在 Windows Vista 上使用带有 Python 2.5 的 wxPython 2.8.10.1(unicode)进行测试的。

下载

Sizer 教程获取 Wiki

原文:https://www.blog.pythonlibrary.org/2008/05/29/sizer-tutorials-get-the-wiki/

按照计划,我把我的一些教程复制到了官方的 wxPython wiki 。目前我只有 sizer 教程在那里。

我正在做一个系列,展示如何从头开始构建一个简单的应用程序。希望我能把它的一点一滴记下来,让它更容易理解。如果是这样的话,在我得到一些反馈之后,我也会把这些零碎的东西放到维基上,这样我就可以让它们变得更好看一些。

您应该很快就会看到该系列的第一篇文章。希望到周末。

使用 Python 拆分和合并 pdf

原文:https://www.blog.pythonlibrary.org/2018/04/11/splitting-and-merging-pdfs-with-python/

PyPDF2 包允许你在现有的 PDF 上做很多有用的操作。在这篇文章中,我们将学习如何将一个 PDF 文件分割成多个更小的文件。我们还将学习如何把一系列的 PDF 文件组合成一个 PDF 文件。


入门指南

PyPDF2 不是 Python 标准库的一部分,所以您需要自己安装它。这样做的首选方法是使用 pip


pip install pypdf2

现在我们已经安装了 PyPDF2,让我们学习如何分割和合并 PDF!


分割 pdf

PyPDF2 包让您能够将一个 PDF 分割成多个 PDF。你只需要告诉它你想要多少页。对于这个例子,我们将从 IRS 下载一个 W9 表单,并遍历它的所有六个页面。我们将分裂出每一页,把它变成自己的独立的 PDF 文件。

让我们来看看如何实现:


# pdf_splitter.py

import os
from PyPDF2 import PdfFileReader, PdfFileWriter

def pdf_splitter(path):
    fname = os.path.splitext(os.path.basename(path))[0]

    pdf = PdfFileReader(path)
    for page in range(pdf.getNumPages()):
        pdf_writer = PdfFileWriter()
        pdf_writer.addPage(pdf.getPage(page))

        output_filename = '{}_page_{}.pdf'.format(
            fname, page+1)

        with open(output_filename, 'wb') as out:
            pdf_writer.write(out)

        print('Created: {}'.format(output_filename))

if __name__ == '__main__':
    path = 'w9.pdf'
    pdf_splitter(path)

对于这个例子,我们需要导入 PdfFileReaderpdffilerwriter。然后我们创建一个有趣的小函数,叫做 pdf_splitter 。它接受输入 PDF 的路径。这个函数的第一行将获取输入文件的名称,减去扩展名。接下来,我们打开 PDF 并创建一个 reader 对象。然后我们使用 reader 对象的 getNumPages 方法遍历所有页面。

在循环的内部,我们创建了一个 PdfFileWriter 的实例。然后,我们使用其 addPage 方法向 writer 对象添加一个页面。这个方法接受一个 page 对象,所以为了获取 page 对象,我们调用 reader 对象的 getPage 方法。现在我们已经向 writer 对象添加了一个页面。下一步是创建一个唯一的文件名,我们使用原始文件名加上单词“page”加上页码+ 1。我们添加 1 是因为 PyPDF2 的页码是从零开始的,所以第 0 页实际上是第 1 页。

最后,我们以写入二进制模式打开新文件名,并使用 PDF writer 对象的 write 方法将对象的内容写入磁盘。


将多个 pdf 合并在一起

现在我们有了一堆 pdf 文件,让我们学习如何把它们合并在一起。这样做的一个有用的用例是企业将他们的日报合并成一个 PDF。为了工作和娱乐,我需要合并 pdf。我脑海中浮现的一个项目是扫描文档。根据您使用的扫描仪,您可能最终会将一个文档扫描成多个 pdf,因此能够将它们再次合并在一起会非常棒。

当最初的 PyPdf 问世时,让它将多个 Pdf 合并在一起的唯一方法是这样的:


# pdf_merger.py

import glob
from PyPDF2 import PdfFileWriter, PdfFileReader

def merger(output_path, input_paths):
    pdf_writer = PdfFileWriter()

    for path in input_paths:
        pdf_reader = PdfFileReader(path)
        for page in range(pdf_reader.getNumPages()):
            pdf_writer.addPage(pdf_reader.getPage(page))

    with open(output_path, 'wb') as fh:
        pdf_writer.write(fh)

if __name__ == '__main__':
    paths = glob.glob('w9_*.pdf')
    paths.sort()
    merger('pdf_merger.pdf', paths)

这里我们创建了一个 PdfFileWriter 对象和几个 PdfFileReader 对象。对于每个 PDF 路径,我们创建一个 PdfFileReader 对象,然后遍历它的页面,将每个页面添加到我们的 writer 对象中。然后,我们将 writer 对象的内容写到磁盘上。

PyPDF2 通过创建一个 PdfFileMerger 对象使这变得简单了一点:


# pdf_merger2.py

import glob
from PyPDF2 import PdfFileMerger

def merger(output_path, input_paths):
    pdf_merger = PdfFileMerger()
    file_handles = []

    for path in input_paths:
        pdf_merger.append(path)

    with open(output_path, 'wb') as fileobj:
        pdf_merger.write(fileobj)

if __name__ == '__main__':
    paths = glob.glob('w9_*.pdf')
    paths.sort()
    merger('pdf_merger2.pdf', paths)

这里我们只需要创建 PdfFileMerger 对象,然后遍历 PDF 路径,将它们添加到我们的合并对象中。PyPDF2 将自动追加整个文档,因此您不需要自己遍历每个文档的所有页面。然后我们把它写到磁盘上。

PdfFileMerger 类也有一个可以使用的合并方法。它的代码定义如下:


def merge(self, position, fileobj, bookmark=None, pages=None, import_bookmarks=True):
        """
        Merges the pages from the given file into the output file at the
        specified page number.

        :param int position: The *page number* to insert this file. File will
            be inserted after the given number.

        :param fileobj: A File Object or an object that supports the standard read
            and seek methods similar to a File Object. Could also be a
            string representing a path to a PDF file.

        :param str bookmark: Optionally, you may specify a bookmark to be applied at
            the beginning of the included file by supplying the text of the bookmark.

        :param pages: can be a :ref:`Page Range ` or a ``(start, stop[, step])`` tuple
            to merge only the specified range of pages from the source
            document into the output document.

        :param bool import_bookmarks: You may prevent the source document's bookmarks
            from being imported by specifying this as ``False``.
        """ 

基本上,merge 方法允许您通过页码告诉 PyPDF 将页面合并到哪里。因此,如果您已经创建了一个包含 3 页的合并对象,您可以告诉合并对象在特定位置合并下一个文档。这允许开发者做一些非常复杂的合并操作。试试看,看看你能做什么!


包扎

PyPDF2 是一个强大而有用的软件包。多年来,我一直断断续续地使用它来处理各种家庭和工作项目。如果您需要操作现有的 pdf,那么这个包可能正好适合您!


相关阅读

SqlAlchemy 和 Microsoft Access

原文:https://www.blog.pythonlibrary.org/2010/10/10/sqlalchemy-and-microsoft-access/

更新(2010 年 10 月 12 日)-我的一个提醒读者告诉我,SqlAlchemy 0.6.x 目前不支持访问方言。阅读此处了解更多信息。

一两年前,我被要求将一些数据从一些旧的 Microsoft Access 文件转移到我们的 Microsoft SQL Server。因为我喜欢使用 SqlAlchemy,所以我决定看看它是否支持 Access。在这方面,当时的文档是相当无用的,但它似乎是可能的,我在 SqlAlchemy 的 Google group 上找到了一个关于它的帖子。

连接到 Microsoft Access 的代码非常简单。事情是这样的:


from sqlalchemy import create_engine
engine = create_engine(r'access:///C:/some/path/database.MDB')

看到这有多简单了吗?您只需告诉 SqlAlchemy 要连接哪种数据库,添加三个正斜杠,然后是文件的路径。一旦完成,您就可以对 Access 文件做任何事情,就像对普通数据库一样:


########################################################################
class TableName(Base):
    """
    MS Access database
    """
    __tablename__ = "ROW"
    __table_args__ = ({"autoload":True})  # load the database
    FILENUM = Column("FILE #", Integer, key="FILENUM")

在上面的代码中,我使用 SqlAlchemy 的声明性语法来自动加载数据库的结构。我不记得这个数据库是否有它的主键集,但我猜它没有,因为我必须添加最后一行。

无论如何,一旦有了连接,就可以像平常一样运行查询了。在我的例子中,我最终创建了一个模型文件来保存 Access 文件和 SQL Server 数据库的所有表定义,然后对 Access 文件执行 SELECT *操作,循环遍历结果并将每一行插入到 SQL Server 中。唯一需要注意的是,Access 比 SQL Server 更能容忍空值,所以我不得不编写一些特殊的处理方法来应对这种情况。

嗯,这真的是所有的事情。您可以查看我的其他 SqlAlchemy 教程,了解更多关于使用 SqlAlchemy 与数据库进行交互的一般信息。

SqlAlchemy:连接到预先存在的数据库

原文:https://www.blog.pythonlibrary.org/2010/09/10/sqlalchemy-connecting-to-pre-existing-databases/

用 Python 访问数据库是一个简单的过程。Python 甚至提供了一个内置在主发行版中的 sqlite 数据库库(从 2.5 开始)。我最喜欢用 Python 访问数据库的方法是使用第三方包 SqlAlchemy。SqlAlchemy 是一个对象关系映射器(ORM ),这意味着它采用 SQL 结构并使它们更像目标语言。在这种情况下,您最终使用 Python 语法执行 SQL,而不是直接执行 SQL,并且您可以使用相同的代码访问多个数据库后端(如果您小心的话)。

在本文中,我们将研究如何使用 SqlAlchemy 连接到预先存在的数据库。如果我的经验有任何启示的话,你可能会花更多的时间在你没有创建的数据库上,而不是在你创建的数据库上。本文将向您展示如何连接到它们。

SqlAlchemy 的自动加载

SqlAlchemy 有两种方法来定义数据库列。第一种方法是长方法,在这种方法中,定义每个字段及其类型。简单(或简短)的方法是使用 SqlAlchemy 的 autoload 功能,它将以一种相当神奇的方式自省表格并提取字段名称。我们将从 autoload 方法开始,然后在下一节展示这个漫长的过程。

在我们开始之前,需要注意有两种配置 SqlAlchemy 的方法:一种是手写的方法,另一种是声明性的(或“速记的”)方法。我们将两种方式都过一遍。让我们从长版本开始,然后“声明性地”做。


from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import mapper, sessionmaker

class Bookmarks(object):
    pass

#----------------------------------------------------------------------
def loadSession():
    """"""    
    dbPath = 'places.sqlite'
    engine = create_engine('sqlite:///%s' % dbPath, echo=True)

    metadata = MetaData(engine)
    moz_bookmarks = Table('moz_bookmarks', metadata, autoload=True)
    mapper(Bookmarks, moz_bookmarks)

    Session = sessionmaker(bind=engine)
    session = Session()
    return session

if __name__ == "__main__":
    session = loadSession()
    res = session.query(Bookmarks).all()
    res[1].title

在这个代码片段中,我们从 sqlalchemy 导入了一些方便的类和实用程序,允许我们定义引擎(一种数据库连接/接口)、元数据(一个表目录)和会话(一个允许我们查询的数据库“句柄”)。注意,我们有一个“places.sqlite”文件。这个数据库来自 Mozilla Firefox。如果您已经安装了它,请去寻找它,因为它为这类事情提供了一个极好的测试平台。在我的 Windows XP 机器上,它位于以下位置:“C:\ Documents and Settings \ Mike \ Application Data \ Mozilla \ Firefox \ Profiles \ f 7 csnzvk . default”。只需将该文件复制到您将用于本文中的脚本的位置。如果你试着在适当的地方使用它,并且打开了 Firefox,你可能会遇到问题,因为你的代码可能会中断 Firefox,反之亦然。

create_engine 调用中,我们将 echo 设置为 True 。这将导致 SqlAlchemy 将其生成的所有 SQL 发送到 stdout,这对于调试来说非常方便。我建议在将代码投入生产时将其设置为 False。就我们的目的而言,最有趣的一行如下:


moz_bookmarks = Table('moz_bookmarks', metadata, autoload=True)

这告诉 SqlAlchemy 尝试自动加载“moz_bookmarks”表。如果它是一个带有主键的合适的数据库,这将非常有用。如果该表没有主键,那么您可以这样做:


from sqlalchemy import Column, Integer
moz_bookmarks = Table('moz_bookmarks', metadata, 
                      Column("id", Integer, primary_key=True),
                      autoload=True)

这增加了一个名为“id”的额外列。这基本上是对数据库进行猴子修补。当我不得不使用供应商设计不良的数据库时,我成功地使用了这种方法。如果数据库支持,SqlAlchemy 也会自动递增它。映射器将表对象映射到书签类。然后我们创建一个绑定到引擎的会话,这样我们就可以进行查询。res = session.query(书签)。all() 基本上意味着 SELECT * FROM moz_bookmarks 并将结果作为书签对象列表返回。

有时坏的数据库会有一个明显应该是主键的字段。如果是这样的话,你可以只用那个名字而不用“id”。其他时候,您可以通过将主键设置为两列(即字段)来创建唯一键。您可以通过创建两列并将它们的“primary_key”都设置为 True 来实现。如果一个表有几十个字段,这比定义整个表要好得多。

让我们转到声明性自动加载方法:


from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///places.sqlite', echo=True)
Base = declarative_base(engine)
########################################################################
class Bookmarks(Base):
    """"""
    __tablename__ = 'moz_bookmarks'
    __table_args__ = {'autoload':True}

#----------------------------------------------------------------------
def loadSession():
    """"""
    metadata = Base.metadata
    Session = sessionmaker(bind=engine)
    session = Session()
    return session

if __name__ == "__main__":
    session = loadSession()
    res = session.query(Bookmarks).all()
    print res[1].title

声明性方法看起来有点不同,是吧?让我们试着打开新东西。首先,我们有一个新导入:来自 sqlalchemy . ext . declarative import declarative _ base。我们使用“declarative_base”来创建一个使用我们的引擎的类,然后我们从这个类创建书签子类。为了指定表名,我们使用了神奇的方法, tablename ,为了让它自动加载,我们创建了 table_args dict。然后在 loadSession 函数中,我们得到这样的元数据对象: metadata = Base.metadata 。代码的其余部分是相同的。声明性语法通常会简化代码,因为它将所有内容都放在一个类中,而不是分别创建一个类和一个表对象。如果您喜欢声明式风格,您可能还想看看 Elixir,这是一个 SqlAlchemy 扩展,在他们将这种风格添加到主包之前,它就具有声明性。

明确定义您的数据库

有时您不能使用 autoload,或者您只想完全控制表定义。SqlAlchemy 几乎可以让您轻松地做到这一点。我们将首先看手写版本,然后看声明式风格。


from sqlalchemy import create_engine, Column, MetaData, Table
from sqlalchemy import Integer, String, Text
from sqlalchemy.orm import mapper, sessionmaker

class Bookmarks(object):
    pass

#----------------------------------------------------------------------
def loadSession():
    """"""
    dbPath = 'places.sqlite'

    engine = create_engine('sqlite:///%s' % dbPath, echo=True)

    metadata = MetaData(engine)    
    moz_bookmarks = Table('moz_bookmarks', metadata, 
                          Column('id', Integer, primary_key=True),
                          Column('type', Integer),
                          Column('fk', Integer),
                          Column('parent', Integer),
                          Column('position', Integer),
                          Column('title', String),
                          Column('keyword_id', Integer),
                          Column('folder_type', Text),
                          Column('dateAdded', Integer),
                          Column('lastModified', Integer)
                          )

    mapper(Bookmarks, moz_bookmarks)

    Session = sessionmaker(bind=engine)
    session = Session()

if __name__ == "__main__":
    session = loadSession()
    res = session.query(Bookmarks).all()
    print res[1].title

这段代码与我们看到的代码并没有什么不同。我们最关心的部分是表定义。这里我们使用关键字 Column 来定义每一列。Column 类接受列的名称、类型、是否将其设置为主键以及该列是否可为空。它可能会接受一些其他的论点,但这些是你会看到最多的。这个例子没有显示可空位,因为我不知道 Firefox 表是否有这些约束。

无论如何,一旦定义了列,剩下的代码就和以前一样了。那么让我们转到声明式风格:


from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///places.sqlite', echo=True)
Base = declarative_base(engine)
########################################################################
class Places(Base):
    """"""
    __tablename__ = 'moz_places'

    id = Column(Integer, primary_key=True)
    url = Column(String)
    title = Column(String)
    rev_host = Column(String)
    visit_count = Column(Integer)
    hidden = Column(Integer)
    typed = Column(Integer)
    favicon_id = Column(Integer)
    frecency = Column(Integer)
    last_visit_date = Column(Integer)

    #----------------------------------------------------------------------
    def __init__(self, id, url, title, rev_host, visit_count,
                 hidden, typed, favicon_id, frecency, last_visit_date):
        """"""
        self.id = id
        self.url = url
        self.title = title
        self.rev_host = rev_host
        self.visit_count = visit_count
        self.hidden = hidden
        self.typed = typed
        self.favicon_id = favicon_id
        self.frecency = frecency
        self.last_visit_date = last_visit_date

    #----------------------------------------------------------------------
    def __repr__(self):
        """"""
        return "" % (self.id, self.title,
                                                 self.url)

#----------------------------------------------------------------------
def loadSession():
    """"""
    metadata = Base.metadata
    Session = sessionmaker(bind=engine)
    session = Session()
    return session

if __name__ == "__main__":
    session = loadSession()
    res = session.query(Places).all()
    print res[1].title 

我们对这个例子做了一点改动,将它设置为一个不同的表:“moz_places”。您会注意到,您通过创建类变量在这里设置了列。如果要访问实例的变量,需要在 init 中重新定义它们。如果你不这样做,你将会面临一些非常混乱的问题。repr 是一个“漂亮的打印”方法。当您打印其中一个“位置”对象时,您将得到 repr 返回的任何内容。除此之外,代码与我们看到的其他部分非常相似。

包扎

如您所见,使用 SqlAlchemy 连接数据库轻而易举。大多数情况下,您会提前知道数据库的设计是什么,要么通过文档,因为您自己构建了它,要么因为您有一些实用程序可以告诉您。当涉及到在 SqlAlchemy 中定义表时,拥有这些信息是很有帮助的,但是现在即使您事先不知道表的配置,您也知道应该尝试什么了!学 Python 的招数不好玩吗?下次见!

进一步阅读

下载

SqlAlchemy 编程错误 42000 和 MS SQL

原文:https://www.blog.pythonlibrary.org/2011/01/15/sqlalchemy-programmingerror-42000-and-ms-sql/

我最近一直在使用 Windows XP 上的 SqlAlchemy 编写软件清单脚本,以连接到 Microsoft SQL Server 2005 数据库中新创建的表。我使用 Aqua Data Studio 创建了这个表,以 SQL Administrator (sa)的身份登录,并认为一切都很好,直到我尝试向这个表提交一些数据。下面是我收到的一个奇怪的错误:


pyodbc.ProgrammingError: ('42000', '[42000] [Microsoft][ODBC SQL Server Driver][SQL Server]Cannot find the object "dbo.software" because it does not exist or you do not have permissions. (1088) (SQLExecDirectW)')

现在,是什么意思?我知道这个数据库存在,因为我刚刚用 Aqua Data Studio(一个数据库管理套件)创建了它,我给了自己以下权限:选择、插入、更新和删除。如果你用谷歌搜索这个错误,你会发现对一个线程的四个引用。他们在线程中没有真正的解决方案,尽管他们提到添加权限来改变模式可能会起作用。请注意,只有在使用声明性语法时,这个问题才会发生。无论如何,在这一点上,你可能想看我的代码,所以看一看:


from sqlalchemy import create_engine, Column, DateTime, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import datetime

uri = "mssql://username:pw@mssqlServerPath/Inventory"
engine = create_engine(uri)
engine.echo = True
Base = declarative_base(engine)

########################################################################
class Software(Base):
    """
    SqlAlchemy table representation of "software" login
    """
    __tablename__ = "software"
    __table_args__ = {"schema":"dbo"}

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100))
    date_added = Column(DateTime)
    date_checked = Column(DateTime)
    machine_name = Column(String(25))
    version = Column(String(25))

    #----------------------------------------------------------------------
    def __init__(self, name, date_added, date_checked, machine_name, version):
        """"""
        self.name = name
        self.date_added = date_added
        self.date_checked = date_checked
        self.machine_name = machine_name
        self.version = version

Session = sessionmaker(bind=engine)
session = Session()
now = datetime.datetime.now()
new_record = Software("Adobe Acrobat", now, now, "MCIS0467", "9.0")
session.add(new_record)
session.commit()

据我的老板所知,声明性语法使用基于身份的查询系统,如果没有设置身份,那么 SqlAlchemy 就找不到数据库。如果使用 SqlAlchemy 创建表,那么就不会有这个问题。无论如何,事实证明您必须使用 Microsoft SQL Server Management Studio 来纠正这种特殊情况。加载它并导航到正确的数据库和表,然后打开 columns 树。右键单击主键字段,然后选择“修改”。在屏幕底部有一个“列属性”标签。转到那里,向下滚动到 Identity Specification 并展开。最后,确保“(Is Identity)”字段设置为“是”。保存它,你就完成了!

这是一个非常奇怪和罕见的问题,所以你可能不会有它,但我认为这是一个有趣的问题,我想记录解决方案。注意:我在 Windows XP 上使用 Python 2.5 和 SqlAlchemy 0.6.6。

Python 101 - How to Work with a Database Using sqlite3

原文:https://www.blog.pythonlibrary.org/2021/09/30/sqlite/

Software developers have to work with data. More often than not, the data that you work with will need to be available to multiple developers as well as multiple users at once. The typical solution for this type of situation is to use a database. Databases hold data in a tabular format, which means that they have labeled columns and rows of data.

Most database software require you to install complex software on your local machine or on a server you have access to. Popular database software includes Microsoft SQL Server, PostgreSQL, and MySQL, among others. For the purposes of this article, you will focus on a very simple one known as SQLite. The reason you will use SQLite is that it is a file-based database system that is included with Python. You won't need to do any configuration or additional installation. This allows you to focus on the essentials of what a database is and how it functions, while avoiding the danger of getting lost in installation and setup details.

In this article, you will learn about the following:

  • Creating a SQLite Database
  • Adding Data to Your Database
  • Searching Your Database
  • Editing Data in Your Database
  • Deleting Data From Your Database

Let's start learning about how to use Python with a database now!

Creating a SQLite Database

There are 3rd party SQL connector packages to help you connect your Python code to all major databases. The Python standard library already comes with a sqlite3 library built-in, which is what you will be using. This means that you won't have to install anything extra in order to work through this article. You can read the documentation for the sqlite3 library here:

To start working with a database, you need to either connect to a pre-existing one or create a new one. For the purposes of this article, you will create a database. However, you will learn enough in this article to also load and interact with a pre-existing database if you want to.

SQLite supports the following types of data:

  • NULL
  • INTEGER
  • REAL
  • TEXT
  • BLOB

These are the data types that you can store in this type of database. If you want to read more about how Python data types translate to SQLite data types and vice-versa, see the following link:

Now it is time for you to create a database! Here is how you would create a SQLite database with Python:

import sqlite3

sqlite3.connect("library.db")

First, you import sqlite3 and then you use the connect() function, which takes the path to the database file as an argument. If the file does not exist, the sqlite3 module will create an empty database. Once the database file has been created, you need to add a table to be able to work with it. The basic SQL command you use for doing this is as follows:

CREATE TABLE table_name
(column_one TEXT, column_two TEXT, column_three TEXT)

Keywords in SQL are case-insensitive -- so CREATE == Create == create. Identifiers, however, might be case-sensitive -- it depends on the SQL engine being used and possibly what configuration settings are being used by that engine or by the database. If using a preexisting database, either check its documentation or just use the same case as it uses for table and field names.

You will be following the convention of KEYWORDS in UPPER-case, and identifiers in Mixed- or lower-case.

The CREATE TABLE command will create a table using the name specified. You follow that command with the name of each column as well as the column type. Columns can also be thought of as fields and column types as field types. The SQL code snippet above creates a three-column table where all the columns contain text. If you call this command and the table already exists in the database, you will receive an error.

You can create as many tables as the database allows. The number of rows and columns may have a limit from the database software, but most of the time you won't run into this limit.

If you combine the information you learned in the last two examples, you can create a database for storing information about books. Create a new file named create_database.py and enter the following code:

# create_database.py

import sqlite3

conn = sqlite3.connect("library.db")

cursor = conn.cursor()

# create a table
cursor.execute("""CREATE TABLE books
                  (title TEXT, author TEXT, release_date TEXT,
                   publisher TEXT, book_type TEXT)
               """)

To work with a SQLite database, you need to connect() to it and then create a cursor() object from that connection. The cursor is what you use to send SQL commands to your database via its execute() function. The last line of code above will use the SQL syntax you saw earlier to create a books table with five fields:

  • title - The title of the book as text
  • author - The author of the book as text
  • release_date - The date the book was released as text
  • publisher - The publisher of the book as text
  • book_type - The type of book (print, epub, PDF, etc)

Now you have a database that you can use, but it has no data. You will discover how to add data to your table in the next section!

Adding Data to Your Database

Adding data to a database is done using the INSERT INTO SQL commands. You use this command in combination with the name of the table that you wish to insert data into. This process will become clearer by looking at some code, so go ahead and create a file named add_data.py. Then add this code to it:

# add_data.py

import sqlite3

conn = sqlite3.connect("library.db")
cursor = conn.cursor()

# insert a record into the books table in the library database
cursor.execute("""INSERT INTO books
                  VALUES ('Python 101', 'Mike Driscoll', '9/01/2020',
                          'Mouse Vs Python', 'epub')"""
               )

# save data
conn.commit()

# insert multiple records using the more secure "?" method
books = [('Python Interviews', 'Mike Driscoll',
          '2/1/2018', 'Packt Publishing', 'softcover'),
         ('Automate the Boring Stuff with Python',
          'Al Sweigart', '', 'No Starch Press', 'PDF'),
         ('The Well-Grounded Python Developer',
          'Doug Farrell', '2020', 'Manning', 'Kindle')]
cursor.executemany("INSERT INTO books VALUES (?,?,?,?,?)", books)
conn.commit()

The first six lines show how to connect to the database and create the cursor as before. Then you use execute() to call INSERT INTO and pass it a series of five VALUES. To save that record to the database table, you need to call commit().

The last few lines of code show how to commit multiple records to the database at once using executemany(). You pass executemany() a SQL statement and a list of items to use with that SQL statement. While there are other ways of inserting data, using the "?" syntax as you did in the example above is the preferred way of passing values to the cursor as it prevents SQL injection attacks.

If you'd like to learn more about SQL Injection, Wikipedia is a good place to start:

Now you have data in your table, but you don't have a way to actually view that data. You will find out how to do that next!

Searching Your Database

Extracting data from a database is done primarily with the SELECT, FROM, and WHERE keywords. You will find that these commands are not too hard to use. You should create a new file named queries.py and enter the following code into it:

import sqlite3

def get_cursor():
    conn = sqlite3.connect("library.db")
    return conn.cursor()

def select_all_records_by_author(cursor, author):
    sql = "SELECT * FROM books WHERE author=?"
    cursor.execute(sql, [author])
    print(cursor.fetchall())  # or use fetchone()
    print("\nHere is a listing of the rows in the table\n")
    for row in cursor.execute("SELECT rowid, * FROM books ORDER BY author"):
        print(row)

def select_using_like(cursor, text):
    print("\nLIKE query results:\n")
    sql = f"""
    SELECT * FROM books
    WHERE title LIKE '{text}%'"""
    cursor.execute(sql)
    print(cursor.fetchall())

if __name__ == '__main__':
    cursor = get_cursor()
    select_all_records_by_author(cursor,
                                 author='Mike Driscoll')
    select_using_like(cursor, text='Python')

This code is a little long, so we will go over each function individually. Here is the first bit of code:

import sqlite3

def get_cursor():
    conn = sqlite3.connect("library.db")
    return conn.cursor()

The get_cursor() function is a useful function for connecting to the database and returning the cursor object. You could make it more generic by passing it the name of the database you wish to open.

The next function will show you how to get all the records for a particular author in the database table:

def select_all_records_by_author(cursor, author):
    sql = "SELECT * FROM books WHERE author=?"
    cursor.execute(sql, [author])
    print(cursor.fetchall())  # or use fetchone()
    print("\nHere is a listing of the rows in the table\n")
    for row in cursor.execute("SELECT rowid, * FROM books ORDER BY author"):
        print(row)

To get all the records from a database, you would use the following SQL command: SELECT * FROM books. SELECT, by default, returns the requested fields from every record in the database table. The asterisk is a wildcard character which means "I want all the fields". So SELECT and * combined will return all the data currently in a table. You usually do not want to do that! Tables can become quite large and trying to pull everything from it at once may adversely affect your database's, or your computer's, performance. Instead, you can use the WHERE clause to filter the SELECT to something more specific, and/or only select the fields you are interested in.

In this example, you filter the SELECT to a specific author. You are still selecting all the records, but it is unlikely for a single author to have contributed to too many rows to negatively affect performance. You then tell the cursor to fetchall(), which will fetch all the results from the SELECT call you made. You could use fetchone() to fetch only the first result from the SELECT.

The last two lines of code fetch all the entries in the books table along with their rowids, and orders the results by the author name. The output from this function looks like this:

Here is a listing of the rows in the table

(3, 'Automate the Boring Stuff with Python', 'Al Sweigart', '', 'No Starch Press', 'PDF')
(4, 'The Well-Grounded Python Developer', 'Doug Farrell', '2020', 'Manning', 'Kindle')
(1, 'Python 101', 'Mike Driscoll', '9/01/2020', 'Mouse Vs Python', 'epub')
(2, 'Python Interviews', 'Mike Driscoll', '2/1/2018', 'Packt Publishing', 'softcover')

You can see that when you sort by author, it sorts using the entire string rather than by the last name. If you are looking for a challenge, you can try to figure out how you might store the data to make it possible to sort by the last name. Alternatively, you could write more complex SQL queries or process the results in Python to sort it in a nicer way.

The last function to look at is select_using_like():

def select_using_like(cursor, text):
    print("\nLIKE query results:\n")
    sql = f"""
    SELECT * FROM books
    WHERE title LIKE '{text}%'"""
    cursor.execute(sql)
    print(cursor.fetchall())

This function demonstrates how to use the SQL command LIKE, which is kind of a filtered wildcard search. In this example, you tell it to look for a specific string with a percent sign following it. The percent sign is a wildcard, so it will look for any record that has a title that starts with the passed-in string.

When you run this function with the text set to "Python", you will see the following output:

LIKE query results:

[('Python 101', 'Mike Driscoll', '9/01/2020', 'Mouse Vs Python', 'epub'), 
('Python Interviews', 'Mike Driscoll', '2/1/2018', 'Packt Publishing', 'softcover')]

The last few lines of code are here to demonstrate what the functions do:

if __name__ == '__main__':
    cursor = get_cursor()
    select_all_records_by_author(cursor,
                                 author='Mike Driscoll')
    select_using_like(cursor, text='Python')

Here you grab the cursor object and pass it in to the other functions. Remember, you use the cursor to send commands to your database. In this example, you set the author for select_all_records_by_author() and the text for select_using_like(). These functions are a good way to make your code reusable.

Now you are ready to learn how to update data in your database!

Editing Data in Your Database

When it comes to editing data in a database, you will almost always be using the following SQL commands:

  • UPDATE - Used for updating a specific database table
  • SET - Used to update a specific field in the database table

{blurb, class tip} UPDATE, just like SELECT, works on all records in a table by default. Remember to use WHERE to limit the scope of the command! {/blurb}

To see how this works, create a file named update_record.py and add this code:

# update_record.py

import sqlite3

def update_author(old_name, new_name):
    conn = sqlite3.connect("library.db")
    cursor = conn.cursor()
    sql = f"""
    UPDATE books
    SET author = '{new_name}'
    WHERE author = '{old_name}'
    """
    cursor.execute(sql)
    conn.commit()

if __name__ == '__main__':
    update_author(
            old_name='Mike Driscoll',
            new_name='Michael Driscoll',
            )

In this example, you create update_author() which takes in the old author name to look for and the new author name to change it to. Then you connect to the database and create the cursor as you have in the previous examples. The SQL code here tells your database that you want to update the books table and set the author field to the new name where the author name currently equals the old name. Finally, you execute() and commit() the changes.

To test that this code worked, you can re-run the query code from the previous section and examine the output.

Now you're ready to learn how to delete data from your database!

Deleting Data From Your Database

Sometimes data must be removed from a database. For example, if you decide to stop being a customer at a bank, you would expect them to purge your information from their database after a certain period of time had elapsed. To delete from a database, you can use the DELETE command.

Go ahead and create a new file named delete_record.py and add the following code to see how deleting data works:

# delete_record.py

import sqlite3

def delete_author(author):
    conn = sqlite3.connect("library.db")
    cursor = conn.cursor()

    sql = f"""
    DELETE FROM books
    WHERE author = '{author}'
    """
    cursor.execute(sql)
    conn.commit()

if __name__ == '__main__':
    delete_author(author='Al Sweigart')

Here you create delete_author() which takes in the name of the author that you wish to remove from the database. The code in this example is nearly identical to the previous example except for the SQL statement itself. In the SQL query, you use DELETE FROM to tell the database which table to delete data from. Then you use the WHERE clause to tell it which field to use to select the target records. In this case, you tell your database to remove any records from the books table that match the author name.

You can verify that this code worked using the SQL query code from earlier in this article.

Wrapping Up

Working with databases can be a lot of work. This article covered only the basics of working with databases. Here you learned how to do the following:

  • Creating a SQLite Database
  • Adding Data to Your Database
  • Searching Your Database
  • Editing Data in Your Database
  • Deleting Data From Your Database

If you find SQL code a bit difficult to understand, you might want to check out an "object-relational mapper" package, such as SQLAlchemy or SQLObject. An object-relational mapper (ORM) turns Python statements into SQL code for you so that you are only writing Python code. Sometimes you may still need to drop down to bare SQL to get the efficiency you need from the database, but these ORMs can help speed up development and make things easier.

Here are some links for those two projects:

StackOverflow: Python 发展最快的语言

原文:https://www.blog.pythonlibrary.org/2017/09/11/stackoverflow-python-fastest-growing-language/

StackOverflow 上周发布了一份报告,他们在报告中称 Python 有充分的理由成为发展最快的主流编程语言。 Python 还在 IEEE Spectrum 网站上被评为 2017 年顶级编程语言之一,我在之前的文章中有所涉及。

Python 变得越来越流行,这让我很兴奋。自从我开始使用这门语言以来,我一直很喜欢它。它的强大加上它的简单性非常适合我的大脑。我很高兴看到 Python 在数据科学、教育和嵌入式应用领域继续增长(通过 MicroPython)。

你可以在下面阅读其他人对 StackOverflow 观点的看法:

被困在家里 Python 图书销售

原文:https://www.blog.pythonlibrary.org/2020/03/24/stuck-at-home/

Python 是世界上最流行的语言之一。我自己已经用了十多年了,并且还在不断地学习新的东西。

既然这么多人被困在家里,我想这可能是一个卖书的好时机。现在是学习 Python 或使用 Python 学习新技能的大好时机。

考虑到这一点,我出售我所有的书。只需使用下面的特殊链接即可获得销售价格:

每个链接上都有免费的章节。您可以点击左侧的阅读免费样品按钮下载样品。

关于这些书,你可以随时问我任何问题。

用 OpenPyXL 和 Python 设计 Excel 单元格样式

原文:https://www.blog.pythonlibrary.org/2021/08/11/styling-excel-cells-with-openpyxl-and-python/

OpenPyXL 让你能够以多种不同的方式来设计你的单元格。样式单元格会给你的电子表格带来活力!您的电子表格可以有一些流行和活力,这将有助于他们区别于他人。但是,不要过分!如果每个单元格都有不同的字体和颜色,你的电子表格会看起来一团糟。

您应该谨慎使用您在本文中学到的技能。你仍然有漂亮的电子表格可以和你的同事分享。如果你想了解更多关于 OpenPyXL 支持的样式,你应该查看他们的文档

在本文中,您将了解以下内容:

  • 使用字体
  • 设置校准
  • 添加边框
  • 更改单元格背景色
  • 将图像插入单元格
  • 样式合并单元格
  • 使用内置样式
  • 创建自定义命名样式

既然您已经知道了将要学习的内容,那么是时候开始了解如何使用 OpenPyXL 处理字体了!

使用字体

你在电脑上使用字体来设计你的文本。字体控制您在屏幕上看到的或打印出来的文本的大小、粗细、颜色和样式。你的电脑可以使用成千上万种字体。微软在其 Office 产品中包含了许多字体。

当你想用 OpenPyXL 设置字体时,你需要从openpyxl.styles导入Font类。下面是导入的方法:

from openpyxl.styles import Font

Font类接受许多参数。根据 OpenPyXL 的文档,下面是Font类的完整参数列表:

class openpyxl.styles.fonts.Font(name=None, sz=None, b=None, i=None, charset=None, u=None, 
    strike=None, color=None, scheme=None, family=None, size=None, bold=None, italic=None, 
    strikethrough=None, underline=None, vertAlign=None, outline=None, shadow=None, 
    condense=None, extend=None)

以下列表显示了您最有可能使用的参数及其默认值:

  • 名称= '口径'
  • 尺寸=11
  • 粗体=假
  • 斜体=假
  • 比较=无
  • 下划线= '无'
  • 罢工=假
  • color='FF000000 '

这些设置允许你设置大部分你需要的东西来使你的文本看起来更好。注意,OpenPyXL 中的颜色名称使用十六进制值来表示 RGB(红、绿、蓝)颜色值。您可以设定文本是否应该是粗体、斜体、下划线或删除线。

要了解如何在 OpenPyXL 中使用字体,请创建一个名为font_sizes.py的新文件,并向其中添加以下代码:

# font_sizes.py

import openpyxl
from openpyxl.styles import Font

def font_demo(path):
    workbook = openpyxl.Workbook()
    sheet = workbook.active
    cell = sheet["A1"]
    cell.font = Font(size=12)
    cell.value = "Hello"

    cell2 = sheet["A2"]
    cell2.font = Font(name="Arial", size=14, color="00FF0000")
    sheet["A2"] = "from"

    cell2 = sheet["A3"]
    cell2.font = Font(name="Tahoma", size=16, color="00339966")
    sheet["A3"] = "OpenPyXL"

    workbook.save(path)

if __name__ == "__main__":
    font_demo("font_demo.xlsx")

这段代码在三个不同的单元格中使用了三种不同的字体。在 A1 中,使用默认的 Calibri。然后在 A2 中,你将字体大小设置为 Arial,并将大小增加到 14 磅。最后,在 A3 中,你将字体改为 Tahoma,字号改为 16 磅。

对于第二种和第三种字体,您还可以更改文本颜色。在 A2 中,你设置颜色为红色,在 A3 中,你设置颜色为绿色。

当您运行这段代码时,您的输出将如下所示:

Different Fonts in Excel

尝试更改代码以使用其他字体或颜色。如果你想大胆一点,你应该试着把你的文字加粗或者斜体。

现在,您已经准备好学习文本对齐了。

设置校准

您可以使用openpyxl.styles.Alignment在 OpenPyXL 中设置对齐方式。使用这个类来旋转文本,设置文本换行和缩进。

下面是Alignment类使用的缺省值:

  • 水平= '常规'
  • 垂直= '底部'
  • text_rotation=0
  • wrap_text=False
  • shrink_to_fit=False
  • 缩进=0

你该练习一下了。打开 Python 编辑器,创建一个名为alignment.py的新文件。然后向其中添加以下代码:

# alignment.py

from openpyxl import Workbook
from openpyxl.styles import Alignment

def center_text(path, horizontal="center", vertical="center"):
    workbook = Workbook()
    sheet = workbook.active
    sheet["A1"] = "Hello"
    sheet["A1"].alignment = Alignment(horizontal=horizontal,
                                      vertical=vertical)
    sheet["A2"] = "from"
    sheet["A3"] = "OpenPyXL"
    sheet["A3"].alignment = Alignment(text_rotation=90)
    workbook.save(path)

if __name__ == "__main__":
    center_text("alignment.xlsx")

当您运行这段代码时,您将在 A1 中水平和垂直居中字符串。然后你使用默认的 A2 。最后,对于 A3 ,你将文本旋转 90 度。

尝试运行这段代码,您将会看到如下内容:

Aligning Text in Excel

那看起来不错!如果你花时间尝试不同的text_rotation值,那将是最好的。然后尝试用不同的值改变horizontalvertical参数。很快,你就能像专业人士一样调整你的文本了!

现在,您已经准备好学习如何为单元格添加边框了!

添加边框

OpenPyXL 让您能够设计单元格的边框。您可以为单元格的四边指定不同的边框样式。

您可以使用以下任何边框样式:

  • dashDot
  • dashDotDot
  • '虚线'
  • '虚线'
  • 双份
  • 头发
  • '中等'
  • “中型 DashDot”
  • ' mediumDashDotDot ',
  • '中间虚线'
  • '倾斜圆点'
  • '厚'
  • “瘦”

打开 Python 编辑器,创建一个名为border.py的新文件。然后在文件中输入以下代码:

# border.py

from openpyxl import Workbook
from openpyxl.styles import Border, Side

def border(path):
    pink = "00FF00FF"
    green = "00008000"
    thin = Side(border_style="thin", color=pink)
    double = Side(border_style="double", color=green)

    workbook = Workbook()
    sheet = workbook.active

    sheet["A1"] = "Hello"
    sheet["A1"].border = Border(top=double, left=thin, right=thin, bottom=double)
    sheet["A2"] = "from"
    sheet["A3"] = "OpenPyXL"
    sheet["A3"].border = Border(top=thin, left=double, right=double, bottom=thin)
    workbook.save(path)

if __name__ == "__main__":
    border("border.xlsx")

这段代码将为单元格 A1A3 添加一个边框。 A1 的顶部和底部使用“双”边框样式并且是绿色的,而单元格的侧面使用“细”边框样式并且是粉红色的。

单元格 A3 使用了相同的边框,但是将它们交换了位置,因此两边现在是绿色的,顶部和底部是粉红色的。

您可以通过在要使用的border_stylecolor中创建Side对象来获得这种效果。然后你将这些Side对象传递给一个Border类,它允许你单独设置一个单元格的四个边。要将Border应用于单元格,必须设置单元格的border属性。

当您运行此代码时,您将看到以下结果:

Adding a Border to a Cell

这张图片被放大了很多,所以你可以很容易地看到细胞的边界。如果您尝试用本节开始时提到的一些其他边框样式来修改这段代码,那将是最好的,这样您就可以看到您还能做些什么。

更改单元格背景色

您可以通过更改背景色来突出显示一个单元格或一组单元格。在大多数情况下,突出显示单元格比更改文本的字体或颜色更引人注目。OpenPyXL 提供了一个名为PatternFill的类,可以用来改变单元格的背景颜色。

PatternFill类接受以下参数(默认值如下所示):

  • patternType =无
  • fgColor=Color()
  • bgColor=Color()
  • fill _ type =无
  • start _ color =无
  • end _ color =无

有几种不同的填充类型可供您使用。以下是当前支持的填充类型列表:

  • '无'
  • “固体”
  • “天黑了”
  • 深灰色
  • '黑暗网格'
  • '黑暗水平'
  • “黑暗格子”
  • '黑暗'
  • '暗垂直'
  • 灰色 0625 '
  • 灰色 125 '
  • “闪电”
  • “浅灰色”
  • ' lightGrid '
  • '水平光线'
  • “灯架”
  • “lightUp”
  • '垂直光线'
  • “中等灰色”

现在,您已经有了足够的信息,可以尝试使用 OpenPyXL 设置单元格的背景色。在 Python 编辑器中打开一个新文件,并将其命名为background_colors.py。然后将这段代码添加到新文件中:

# background_colors.py

from openpyxl import Workbook
from openpyxl.styles import PatternFill

def background_colors(path):
    workbook = Workbook()
    sheet = workbook.active
    yellow = "00FFFF00"
    for rows in sheet.iter_rows(min_row=1, max_row=10, min_col=1, max_col=12):
        for cell in rows:
            if cell.row % 2:
                cell.fill = PatternFill(start_color=yellow, end_color=yellow,
                                        fill_type = "solid")
    workbook.save(path)

if __name__ == "__main__":
    background_colors("bg.xlsx")

这个例子将遍历 9 行 12 列。如果单元格位于奇数行,它会将每个单元格的背景色设置为黄色。背景颜色发生变化的单元格将从列 A 到列 L

当您想要设置单元格的背景颜色时,您可以将单元格的fill属性设置为PatternFill的一个实例。在这个例子中,您指定了一个start_color和一个end_color。您还将fill_type设置为“实心”。OpenPyXL 还支持使用一个GradientFill作为背景。

尝试运行这段代码。运行后,您将得到一个新的 Excel 文档,如下所示:

Alternating Row Color

以下是您可以用这段代码尝试的一些想法:

  • 更改受影响的行数或列数
  • 更改您要更改的颜色
  • 更新代码,用不同的颜色给偶数行着色
  • 尝试其他填充类型

一旦你完成了背景颜色的实验,你就可以学习在你的单元格中插入图像了!

将图像插入单元格

OpenPyXL 使得在 Excel 电子表格中插入图像变得简单明了。为了实现这种神奇的效果,您使用了Worksheet对象的add_image()方法。该方法接受两个参数:

  • img -您正在插入的图像文件的路径
  • anchor -提供一个单元格作为图像的左上锚点(可选)

在本例中,您将使用鼠标与 Python 徽标:

Mouse vs Python Logo

这本书的 GitHub 库有图片供您使用。

下载完图像后,创建一个新的 Python 文件,并将其命名为insert_image.py。然后添加以下内容:

# insert_image.py

from openpyxl import Workbook
from openpyxl.drawing.image import Image

def insert_image(path, image_path):
    workbook = Workbook()
    sheet = workbook.active
    img = Image("logo.png")
    sheet.add_image(img, "B1")
    workbook.save(path)

if __name__ == "__main__":
    insert_image("logo.xlsx", "logo.png")

在这里,您传递您想要插入的图像的路径。要插入图像,您需要调用add_image()。在本例中,您将使用单元格 B1 作为锚单元格进行硬编码。然后保存您的 Excel 电子表格。

如果你打开你的电子表格,你会看到它是这样的:

Inserting an Image in an Excel Cell

您可能不需要经常在 Excel 电子表格中插入图像,但这是一项非常好的技能。

样式合并单元格

合并单元格是将两个或多个相邻单元格合并成一个的单元格。如果要用 OpenPyXL 设置合并单元格的值,必须使用合并单元格最左上角的单元格。

您还必须使用这个特定的单元格来设置整个合并单元格的样式。您可以将在单个单元格中使用的所有样式和字体设置用于合并的单元格。但是,您必须将样式应用于左上角的单元格,以便它应用于整个合并的单元格。

如果你看到一些代码,你就会明白这是如何工作的。继续创建一个名为style_merged_cell.py的新文件。现在在您的文件中输入以下代码:

# style_merged_cell.py

from openpyxl import Workbook
from openpyxl.styles import Font, Border, Side, GradientFill, Alignment

def merge_style(path):
    workbook = Workbook()
    sheet = workbook.active
    sheet.merge_cells("A2:G4")
    top_left_cell = sheet["A2"]

    light_purple = "00CC99FF"
    green = "00008000"
    thin = Side(border_style="thin", color=light_purple)
    double = Side(border_style="double", color=green)

    top_left_cell.value = "Hello from PyOpenXL"
    top_left_cell.border = Border(top=double, left=thin, right=thin,
                                  bottom=double)
    top_left_cell.fill = GradientFill(stop=("000000", "FFFFFF"))
    top_left_cell.font = Font(b=True, color="FF0000", size=16)
    top_left_cell.alignment = Alignment(horizontal="center",
                                        vertical="center")
    workbook.save(path)

if __name__ == "__main__":
    merge_style("merged_style.xlsx")

在这里,您创建了一个从 A2 (左上角的单元格)到 G4 的合并单元格。然后设置单元格的值、边框、填充、字体和对齐方式。

当您运行这段代码时,您的新电子表格将如下所示:

Styling a Merged Cell

那看起来不是很好吗?您应该花些时间在合并的单元格上尝试一些不同的样式。比如说,可以用比这里用的灰色更好的渐变。

现在您已经准备好了解 OpenPyXL 的内置样式了!

使用内置样式

OpenPyXL 附带了多个您也可以使用的内置样式。你应该去官方文档而不是复制本书中所有的内置风格,因为这将是最新的风格名称来源。

然而,值得注意的是一些风格。例如,以下是您可以使用的数字格式样式:

  • '逗号'
  • 逗号[0]'
  • '货币'
  • 货币[0]'
  • '百分比'

您也可以应用文本样式。以下是这些风格的列表:

  • '标题'
  • 标题 1 '
  • 标题 2 '
  • 标题 3 '
  • 标题 4 '
  • '超链接'
  • '跟随的超链接'
  • '链接单元格'

OpenPyXL 还有其他几个内置的样式组。您应该查阅文档以了解所有支持的不同样式。

现在您已经了解了一些可以使用的内置样式,是时候编写一些代码了!创建一个新文件,命名为builtin_styls.py。然后输入以下代码:

# builtin_styles.py

from openpyxl import Workbook

def builtin_styles(path):
    workbook = Workbook()
    sheet = workbook.active
    sheet["A1"].value = "Hello"
    sheet["A1"].style = "Title"

    sheet["A2"].value = "from"
    sheet["A2"].style = "Headline 1"

    sheet["A3"].value = "OpenPyXL"
    sheet["A3"].style = "Headline 2"

    workbook.save(path)

if __name__ == "__main__":
    builtin_styles("builtin_styles.xlsx")

这里,您对三个不同的单元格应用了三种不同的样式。你具体用“标题”、“标题 1”、“标题 2”。

当您运行这段代码时,您将得到一个类似如下的电子表格:

Using  Built-in Styles

和往常一样,您应该尝试一些其他的内置样式。尝试它们是决定它们做什么以及它们是否对你有用的唯一方法。

但是等等!如果你想创造自己的风格呢?这是您将在下一节中介绍的内容!

创建自定义命名样式

您也可以使用 OpenPyXL 创建自定义的设计样式。要创建你的风格,你必须使用NamedStyle类。

NamedStyle类接受以下参数(也包括默认值):

  • name= "正常"
  • font=Font()
  • fill=PatternFill()
  • border=Border()
  • 对齐=对齐()
  • 数字格式=无
  • 保护=保护()
  • builtinId =无
  • 隐藏=假
  • xfId =无

你应该总是给你的NamedStyle提供你自己的name,以保持它的唯一性。继续创建一个新文件,命名为named_style.py。然后向其中添加以下代码:

# named_style.py

from openpyxl import Workbook
from openpyxl.styles import Font, Border, Side, NamedStyle

def named_style(path):
    workbook = Workbook()
    sheet = workbook.active

    red = "00FF0000"
    font = Font(bold=True, size=22)
    thick = Side(style="thick", color=red)
    border = Border(left=thick, right=thick, top=thick, bottom=thick)
    named_style = NamedStyle(name="highlight", font=font, border=border)

    sheet["A1"].value = "Hello"
    sheet["A1"].style = named_style

    sheet["A2"].value = "from"
    sheet["A3"].value = "OpenPyXL"

    workbook.save(path)

if __name__ == "__main__":
    named_style("named_style.xlsx")

在这里,您创建了一个Font()Side()Border()实例,并将其传递给您的NamedStyle()。一旦创建了自定义样式,就可以通过设置单元格的style属性将其应用于单元格。应用自定义样式的方式与应用内置样式的方式相同!

您对单元格 A1 应用了自定义样式。

当您运行这段代码时,您将得到一个类似如下的电子表格:

Using a Custom Named Style

现在轮到你了!编辑代码以使用Side样式,这将改变你的边框。或者创建多个Side实例,这样就可以使单元格的每一边都是唯一的。尝试不同的字体或添加自定义背景颜色!

包扎

使用 OpenPyXL 可以对单元格做很多不同的事情。本文中的信息使您能够以漂亮的方式格式化数据。

在本文中,您了解了以下主题:

  • 使用字体
  • 设置校准
  • 添加边框
  • 更改单元格背景色
  • 将图像插入单元格
  • 样式合并单元格
  • 使用内置样式
  • 创建自定义命名样式

您可以利用在本文中学到的信息来制作漂亮的电子表格。您可以通过更改单元格的背景颜色或字体来突出显示激动人心的数据。您还可以通过使用内置样式来更改单元格的格式。去试一试吧。

尝试本文中的代码,看看 OpenPyXL 在处理单元格时是多么强大和有价值。

相关阅读

夏季 Python 图书销售

原文:https://www.blog.pythonlibrary.org/2019/07/30/summer-python-book-sale/

夏天到了,现在是学习 Python 的好时机!为此,我将在下周举办一场我的 Python 书籍的销售活动。销售将于 8 月 6 日结束。所有的书在 Leanpub 上都是 9.99-14.99 美元!

All My Python Books


用 wxPython 创建 GUI 应用程序

用 wxPython 创建 GUI 应用程序是我最近的一本书。在这篇文章中,您将学习如何使用 wxPython 创建跨平台的桌面应用程序。使用此链接或点击上面的图片获得折扣。


Jupyter 笔记型电脑 101

Jupyter Notebook 是一个很好的教学工具,也是使用和学习 Python 和数据科学的有趣方式。我写了一本关于这个主题的很好的入门书,叫做《Jupyter 笔记本 101 。


使用 Python 处理 ReportLab - PDF

用 Python 创建和操作 pdf 很有趣!在使用 Python 的 ReportLab - PDF 处理中,您将了解如何使用 ReportLab 包创建 PDF。您还将学习如何使用 PyPDF2 和 pdfrw 以及其他一些方便的 PDF 相关 Python 包来操作预先存在的 PDF。


Python 201:中级 Python

Python 201: Intermediate Python

Python 201:中级 Python 是我的第一本书 Python 101 的续集,向读者讲授 Python 中的中级到高级主题。

创建 GUI 应用程序手册目录

原文:https://www.blog.pythonlibrary.org/2019/02/04/table-of-contents-for-creating-gui-applications-book/

我们进入了 Kickstarter 的最后一周,我想我应该给大家一个快速更新。我今天写完了关于制造计算器的一章,开始看第 7 章。

Creating GUI Applications with wxPython

我还想让你知道现在的目录是什么样的:

  • 第 1 章 wxPython 简介
  • 第 2 章-创建图像查看器
  • 第 3 章-增强图像查看器
  • 第 4 章-创建数据库查看器
  • 第 5 章-使用 wxPython 编辑数据库
  • 第 6 章-计算器
  • 第 7 章-归档程序(tarball 创建实用程序)
  • 第 8 章 MP3 标签编辑器
  • 第 9 章 XML 编辑器
  • 第 10 章 NASA 图像下载/搜索工具
  • 第 11 章 PDF 合并器/分割器

还有一章是关于为你的应用程序创建可执行文件和安装程序的,还有几个附录。

非常感谢你的支持!

迈克

谈谈 Python:与 Mike Driscoll 一起用 wxPython 构建桌面应用程序

原文:https://www.blog.pythonlibrary.org/2021/07/26/talk-python-building-desktop-apps-with-wxpython-with-mike-driscoll/

几周前,我在 Talk Python 播客上谈论用 wxPython 创建桌面应用程序。那次谈话的录音今天公布了。

在把注意力转移到我的书《用 wxPython 创建 GUI 应用程序》之前,我们讨论了 Python 中的许多 GUI 框架。

你可以在这里收听播客:

https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1092993550&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false

这场演讲还在 YouTube 上进行了直播。你可以在这里观看直播视频:

https://www.youtube.com/embed/jYCwK7050wc?feature=oembed

我在 2018 年的 Talk Python 上谈论 Python 的历史和我在第 156 集的另一本书。如果你有时间,一定要去看看!

谈论 Python 播客和 Twitter 问答

原文:https://www.blog.pythonlibrary.org/2018/03/26/talk-python-podcast-and-twitter-qa/

上周,我很荣幸成为了播客的一部分。在播客中,我们讨论了 Python 的历史及其未来。我们还讨论了我写的书,以及我如何使用 Kickstarter 与我的读者联系。我们花了一些时间回顾了我的新书《Python 采访 T2》和我为这本书采访的一些人。我们还谈到了我即将出版的书,ReportLab:Python 中的 PDF 处理,这本书将于 2018 年 6 月出版。

你可以在这里收听播客:

https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/418978716&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false

本周,我将在 3 月 28 日星期三下午 1 点(美国中部标准时间|英国夏令时|晚上 7 点|太平洋夏令时|上午 11 点)与史蒂夫·霍尔登和亚历克斯·马尔泰利一起做 Twitter 问答,以支持《Python 访谈》一书..只需用#PythonInterviewsQA @packtpub 标签发布您的问题!

测试 Jupyter 笔记本电脑

原文:https://www.blog.pythonlibrary.org/2018/10/16/testing-jupyter-notebooks/

你做的编程越多,你就越需要了解如何测试你的代码。你会听说极限编程和测试驱动开发(TDD)之类的东西。这些都是创建高质量代码的好方法。但是测试如何适应 Jupyter 呢?坦白说,真的没有。如果您想正确地测试您的代码,您应该在 Jupyter 之外编写代码,并在需要时将其导入单元格。这允许你使用 Python 的 unittest 模块或者 py.test 来为你的代码编写测试,与 Jupyter 分开。这也可以让你添加像 nose 这样的测试人员,或者使用 Travis CI 或 Jenkins 这样的工具将你的代码放入一个持续集成设置中。

然而,现在一切都失去了。您可以对您的 Jupyter 笔记本进行一些测试,即使您没有通过保持代码独立而获得的全部灵活性。我们将会看到一些想法,你可以用它们来做一些基本的测试。


执行并检查

一种流行的“测试”笔记本的方法是从命令行运行它,并将其输出发送到一个文件中。如果您想在命令行上执行,可以使用以下示例语法:


jupyter-nbconvert --to notebook --execute --output output_file_path input_file_path

当然,我们希望通过编程来实现这一点,并且希望能够捕获错误。为此,我们将从我的 exporting Jupyter Notebook 文章中获取我们的笔记本 runner 代码,并重用它。为了您的方便,再次提醒您:


# notebook_runner.py

import nbformat
import os

from nbconvert.preprocessors import ExecutePreprocessor

def run_notebook(notebook_path):
    nb_name, _ = os.path.splitext(os.path.basename(notebook_path))
    dirname = os.path.dirname(notebook_path)

    with open(notebook_path) as f:
        nb = nbformat.read(f, as_version=4)

    proc = ExecutePreprocessor(timeout=600, kernel_name='python3')
    proc.allow_errors = True

    proc.preprocess(nb, {'metadata': {'path': '/'}})
    output_path = os.path.join(dirname, '{}_all_output.ipynb'.format(nb_name))

    with open(output_path, mode='wt') as f:
        nbformat.write(nb, f)

    errors = []
    for cell in nb.cells:
        if 'outputs' in cell:
            for output in cell['outputs']:
                if output.output_type == 'error':
                    errors.append(output)

    return nb, errors

if __name__ == '__main__':
    nb, errors = run_notebook('Testing.ipynb')
    print(errors)

你会注意到我已经更新了运行新笔记本的代码。让我们继续创建一个包含两个代码单元的笔记本。创建笔记本后,将标题改为测试并保存。这将导致 Jupyter 将文件保存为 Testing.ipynb 。现在,在第一个单元格中输入以下代码:


def add(a, b):
    return a + b

add(5, 6)

并在单元格#2 中输入以下代码:


1 / 0

现在您可以运行笔记本跑步者代码了。当您这样做时,您应该得到以下输出:


[{'ename': 'ZeroDivisionError',
  'evalue': 'integer division or modulo by zero',
  'output_type': 'error',
  'traceback': ['\x1b[0;31m\x1b[0m',
                '\x1b[0;31mZeroDivisionError\x1b[0mTraceback (most recent call '
                'last)',
                '\x1b[0;32m\x1b[0m in '
                '\x1b[0;36m<module>\x1b[0;34m()\x1b[0m\n'
                '\x1b[0;32m----> 1\x1b[0;31m \x1b[0;36m1\x1b[0m '
                '\x1b[0;34m/\x1b[0m '
                '\x1b[0;36m0\x1b[0m\x1b[0;34m\x1b[0m\x1b[0m\n'
                '\x1b[0m',
                '\x1b[0;31mZeroDivisionError\x1b[0m: integer division or '
                'modulo by zero']}]

这表明我们有一些输出错误的代码。在这种情况下,我们确实预料到了,因为这是一个非常人为的例子。在您自己的代码中,您可能不希望任何代码输出错误。无论如何,这个笔记本跑步者脚本不足以进行真正的测试。您需要用测试代码包装这些代码。因此,让我们创建一个新文件,我们将把它保存到与笔记本 runner 代码相同的位置。我们将把这个脚本保存为“test_runner.py”。将以下代码放入新脚本中:


import unittest

import runner

class TestNotebook(unittest.TestCase):

    def test_runner(self):
        nb, errors = runner.run_notebook('Testing.ipynb')
        self.assertEqual(errors, [])

if __name__ == '__main__':
    unittest.main()

这段代码使用了 Python 的 unittest 模块。这里我们创建了一个测试类,其中有一个名为 test_runner 的测试函数。这个函数调用我们的笔记本 runner 并断言错误列表应该是空的。要运行此代码,请打开终端并导航到包含您的代码的文件夹。然后运行以下命令:


python test_runner.py

当我运行它时,我得到了以下输出:


F
======================================================================
FAIL: test_runner (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_runner.py", line 10, in test_runner
    self.assertEqual(errors, [])
AssertionError: Lists differ: [{'output_type': u'error', 'ev... != []

First list contains 1 additional elements.
First extra element 0:
{'ename': 'ZeroDivisionError',
 'evalue': 'integer division or modulo by zero',
 'output_type': 'error',
 'traceback': ['\x1b[0;31m---------------------------------------------------------------------------\x1b[0m',
               '\x1b[0;31mZeroDivisionError\x1b[0m                         '
               'Traceback (most recent call last)',
               '\x1b[0;32m\x1b[0m in '
               '\x1b[0;36m<module>\x1b[0;34m()\x1b[0m\n'
               '\x1b[0;32m----> 1\x1b[0;31m \x1b[0;36m1\x1b[0m '
               '\x1b[0;34m/\x1b[0m \x1b[0;36m0\x1b[0m\x1b[0;34m\x1b[0m\x1b[0m\n'
               '\x1b[0m',
               '\x1b[0;31mZeroDivisionError\x1b[0m: integer division or modulo '
               'by zero']}

Diff is 677 characters long. Set self.maxDiff to None to see it.

----------------------------------------------------------------------
Ran 1 test in 1.463s

FAILED (failures=1)

这清楚地表明我们的代码失败了。如果您移除有被零除问题的储存格,并重新执行您的测试,您应该会得到这个结果:


.
----------------------------------------------------------------------
Ran 1 test in 1.324s

OK

通过移除单元格(或者只是纠正该单元格中的错误),您可以通过测试。


py.test 插件

我发现了一个很好的插件,你可以使用它来帮助你简化工作流程。我指的是 Jupyter 的 py.test 插件,你可以在这里了解更多关于的内容。

基本上,它使 py.test 能够识别 Jupyter 笔记本,并检查存储的输入是否与存储的输出相匹配,以及笔记本是否运行无误。安装完 nbval 包后,可以像这样用 py.test 运行它(假设你已经安装了 py.test):


py.test --nbval

坦白地说,您可以在我们已经创建的测试文件上运行 py.test,而不需要任何命令,它将使用我们的测试代码。添加 nbval 的主要好处是,如果您这样做的话,就不需要在 Jupyter 周围添加包装器代码。


笔记本电脑内的测试

运行测试的另一种方法是在笔记本中包含一些测试。让我们向测试笔记本添加一个新单元,它包含以下代码:


import unittest

class TestNotebook(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(2, 3), 5)

这将最终在第一个单元格中测试 add 函数。我们可以在这里添加一些不同的测试。例如,我们可能想测试如果我们添加一个字符串类型和一个 None 类型会发生什么。但是你可能已经注意到,如果你试图运行这个单元格,你会得到输出。原因是我们还没有实例化这个类。我们需要调用 unittest.main 来完成这项工作。因此,虽然运行该单元格以将其放入 Jupyter 的内存是很好的,但我们实际上需要使用以下代码再添加一个单元格:


unittest.main(argv=[''], verbosity=2, exit=False)

这段代码应该放在您笔记本的最后一个单元格中,这样它就可以运行您添加的所有测试。它基本上是告诉 Python 以 2 的详细级别运行,并且不要退出。当您运行此代码时,您应该在笔记本中看到以下输出:


test_add (__main__.TestNotebook) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.003s

OK

你也可以在 Jupyter 笔记本里用 Python 的 doctest 模块做类似的事情。


包扎

正如我在开始时提到的,虽然您可以在 Jupyter 笔记本中测试您的代码,但是如果您只是在它之外测试您的代码,实际上会好得多。然而,有一些变通办法,因为有些人喜欢使用 Jupyter 进行文档记录,所以有一种方法来验证他们是否正常工作是很好的。在本章中,您学习了如何以编程方式运行笔记本,并验证输出是否符合您的预期。如果您愿意,还可以增强代码来验证某些错误是否存在。

您还学习了如何在笔记本单元格中直接使用 Python 的 unittest 模块。这确实提供了一些很好的灵活性,因为您现在可以在一个地方运行您的代码。明智地使用这些工具,它们会很好地为你服务。


相关阅读

wxPython 的“Book”控件(第 2 部分,共 2 部分)

原文:https://www.blog.pythonlibrary.org/2009/12/09/the-“book”-controls-of-wxpython-part-2-of-2/

在本系列的第一部分中,我写了 wxPython 包含的所有非 agw 笔记本小部件。在第二篇文章中,我将关注 wxPython 的 AGW 图书馆中的两个笔记本。AGW 代表高级通用小部件,一组用 Python 而不是包装的 C++代码编写的小部件。我个人认为《AGW》也是对其杰出作者安德里亚·加瓦那的回拨。无论如何,这篇评论中的两个小部件将是平板笔记本和另一个 AUI 笔记本。FlatNotebook 有一个很棒的演示,我将在这篇文章的大部分时间里讲述我基于它创建的演示。aui 笔记本是 agw.aui 的一部分。虽然 agw.aui 的演示很酷,但它主要关注的是 AUI,而不是笔记本。所以我会向你们展示我能从中收集到的信息。现在,让我们开始吧!

更新:当涉及到 AGW 相关的小工具时,API 略有变化。基本上 wxPython 2.8.11.0+中的一些样式标志现在需要特定于 agw 的样式标志。要使用它们,您需要使用 agwStyle 关键字。查看安德里亚的文档了解更多信息:http://xoomer.virgilio.it/infinity77/AGW_Docs/如果你遇到了错误,尝试先更改或发送到邮件列表。

令人惊叹的平板笔记本

Flatbook 控件是用 Python 编写的,而不是来自 wxWidgets 的包装小部件。随着 2009 年 2 月 16 日 wxPython 2.8.9.2 的发布,它被添加到了 wxPython 中。从那以后,Andrea Gavana 一直在更新 agw 库,进行了大量的修复。我的例子将与 wxPython 的 2.8.9.2+版本一起工作,但是我建议获得 agw 的 SVN 版本,并用它替换你的默认版本,因为已经有许多错误修复应用于 AUI 模块和其他几个模块。目前也在努力在代码中更好地记录这个库,所以你可能会发现这也很有帮助!

以下是平板笔记本的一些特性:

  • 5 种不同的标签样式
  • 这是一个通用控件(即纯 python ),所以很容易修改
  • 您可以使用鼠标中键关闭选项卡
  • 一个内置函数,用于在选项卡上添加右键弹出菜单
  • 隐藏关闭单个选项卡的“X”的方法
  • 支持禁用标签
  • 还有更多!有关更多信息,请参阅源代码和 wxPython 演示!

现在我们已经完成了一个免费的广告,让我们来看看实际的产品:

flatnotebookDemo

清单 1

import panelOne, panelTwo, panelThree
import wx
import wx.lib.agw.flatnotebook as fnb

########################################################################
class FlatNotebookDemo(fnb.FlatNotebook):
    """
    Flatnotebook class
    """

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY)

        pageOne = panelOne.TabPanel(self)
        pageTwo = panelTwo.TabPanel(self)
        pageThree = panelThree.TabPanel(self)

        self.AddPage(pageOne, "PageOne")
        self.AddPage(pageTwo, "PageTwo")
        self.AddPage(pageThree, "PageThree")  

########################################################################
class DemoFrame(wx.Frame):
    """
    Frame that holds all other widgets
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""        
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          "FlatNotebook Tutorial",
                          size=(600,400)
                          )
        panel = wx.Panel(self)

        notebook = FlatNotebookDemo(panel)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
        panel.SetSizer(sizer)
        self.Layout()

        self.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = DemoFrame()
    app.MainLoop()

在清单 1 中,我子类化了 FlatNotebook,并为页面使用了我的前一篇文章中的通用面板。你会注意到,FlatNotebook 有自己的 AddPage 方法,它模仿了 wx.Notebook。这并不奇怪,因为 FlatNotebook 的 API 是这样的,你应该可以用它来替代 wx.Notebook。当然,开箱即用,FlatNotebook 就有优势。如果你运行上面的演示,你会看到 FlatNotebook 允许用户重新排列标签,关闭标签,它还包括一些上一页/下一页按钮,以防你的标签太多而无法一次显示在屏幕上。

现在让我们来看看可以应用于 FlatNotebook 的各种风格:

flatnotebookStyleDemo

清单 2

import panelOne, panelTwo, panelThree
import wx
import wx.lib.agw.flatnotebook as fnb

########################################################################
class FlatNotebookDemo(fnb.FlatNotebook):
    """
    Flatnotebook class
    """

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY)

        pageOne = panelOne.TabPanel(self)
        pageTwo = panelTwo.TabPanel(self)
        pageThree = panelThree.TabPanel(self)

        self.AddPage(pageOne, "PageOne")
        self.AddPage(pageTwo, "PageTwo")
        self.AddPage(pageThree, "PageThree")  

########################################################################
class DemoFrame(wx.Frame):
    """
    Frame that holds all other widgets
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""        
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          "FlatNotebook Tutorial with Style",
                          size=(600,400)
                          )
        self.styleDict = {"Default":self.OnDefaultStyle,
                          "VC71":self.OnVC71Style,
                          "VC8":self.OnVC8Style,
                          "Fancy":self.OnFancyStyle,
                          "Firefox 2":self.OnFF2Style}
        choices = self.styleDict.keys()

        panel = wx.Panel(self)        
        self.notebook = FlatNotebookDemo(panel)
        self.styleCbo = wx.ComboBox(panel, wx.ID_ANY, "Default",
                                    wx.DefaultPosition, wx.DefaultSize,
                                    choices=choices, style=wx.CB_DROPDOWN)
        styleBtn = wx.Button(panel, wx.ID_ANY, "Change Style")
        styleBtn.Bind(wx.EVT_BUTTON, self.onStyle)

        # create some sizers
        sizer = wx.BoxSizer(wx.VERTICAL)
        hSizer = wx.BoxSizer(wx.HORIZONTAL)

        # add the widgets to the sizers
        sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 5)
        hSizer.Add(self.styleCbo, 0, wx.ALL|wx.CENTER, 5)
        hSizer.Add(styleBtn, 0, wx.ALL, 5)
        sizer.Add(wx.StaticLine(panel), 0, wx.ALL|wx.EXPAND, 5)
        sizer.Add(hSizer, 0, wx.ALL, 5)

        panel.SetSizer(sizer)
        self.Layout()

        self.Show()

    #----------------------------------------------------------------------
    def onStyle(self, event):
        """
        Changes the style of the tabs
        """
        print "in onStyle"
        style = self.styleCbo.GetValue()
        print style
        self.styleDict[style]()           

    # The following methods were taken from the wxPython 
    # demo for the FlatNotebook
    def OnFF2Style(self):

        style = self.notebook.GetWindowStyleFlag()

        # remove old tabs style
        mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
        style &= mirror

        style |= fnb.FNB_FF2

        self.notebook.SetWindowStyleFlag(style)

    def OnVC71Style(self):

        style = self.notebook.GetWindowStyleFlag()

        # remove old tabs style
        mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
        style &= mirror

        style |= fnb.FNB_VC71

        self.notebook.SetWindowStyleFlag(style)

    def OnVC8Style(self):

        style = self.notebook.GetWindowStyleFlag()

        # remove old tabs style
        mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
        style &= mirror

        # set new style
        style |= fnb.FNB_VC8

        self.notebook.SetWindowStyleFlag(style)

    def OnDefaultStyle(self):

        style = self.notebook.GetWindowStyleFlag()

        # remove old tabs style
        mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
        style &= mirror

        self.notebook.SetWindowStyleFlag(style)

    def OnFancyStyle(self):

        style = self.notebook.GetWindowStyleFlag()

        # remove old tabs style
        mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
        style &= mirror

        style |= fnb.FNB_FANCY_TABS
        self.notebook.SetWindowStyleFlag(style)

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = DemoFrame()
    app.MainLoop()

对于一个“简单”的例子来说,这是很多代码,但我认为它将帮助我们理解如何将选项卡样式应用到我们的小部件。我借用了 wxPython 演示中的大多数方法,以防您没有注意到。这段代码的主要讨论点是这些方法的内容,它们大部分是相同的。以下是从本节中摘录的主要片段:

style = self.notebook.GetWindowStyleFlag()

# remove old tabs style
mirror = ~(fnb.FNB_VC71 | fnb.FNB_VC8 | fnb.FNB_FANCY_TABS | fnb.FNB_FF2)
style &= mirror
style |= fnb.FNB_FF2
self.notebook.SetWindowStyleFlag(style)

首先,我们需要了解平板笔记本的当前风格。然后,我们在“镜像”行中使用一些奇特的魔法,创建一组我们想要删除的样式。“style &= mirror”这一行实际上是在删除,然后我们用“style |= fnb”添加我们想要的样式。FF2 FNB”。最后,我们使用 SetWindowStyleFlag()将样式实际应用到小部件。你可能想知道这些愚蠢的符号(如|、~、&)是怎么回事。这些被称为按位运算符。我自己不怎么使用它们,所以我推荐阅读 Python 文档以了解全部细节,因为我自己也不完全理解它们。

在我的下一个演示中,我创建了一个在 FlatNotebook 中添加和删除页面的方法。让我们看看如何:

flatnotebookPageDemo

清单 3

import panelOne, panelTwo, panelThree
import random
import wx
import wx.lib.agw.flatnotebook as fnb

########################################################################
class FlatNotebookDemo(fnb.FlatNotebook):
    """
    Flatnotebook class
    """
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY)

########################################################################
class DemoFrame(wx.Frame):
    """
    Frame that holds all other widgets
    """

    #----------------------------------------------------------------------
    def __init__(self, title="FlatNotebook Add/Remove Page Tutorial"):
        """Constructor"""        
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          title=title,
                          size=(600,400)
                          )
        self._newPageCounter = 0
        panel = wx.Panel(self)
        self.createRightClickMenu()

        # create some widgets
        self.notebook = FlatNotebookDemo(panel)
        addPageBtn = wx.Button(panel, label="Add Page")
        addPageBtn.Bind(wx.EVT_BUTTON, self.onAddPage)
        removePageBtn = wx.Button(panel, label="Remove Page")
        removePageBtn.Bind(wx.EVT_BUTTON, self.onDeletePage)
        self.notebook.SetRightClickMenu(self._rmenu)

        # create some sizers
        sizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        # layout the widgets
        sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 5)
        btnSizer.Add(addPageBtn, 0, wx.ALL, 5)
        btnSizer.Add(removePageBtn, 0, wx.ALL, 5)
        sizer.Add(btnSizer)
        panel.SetSizer(sizer)
        self.Layout()

        self.Show()

    #----------------------------------------------------------------------
    def createRightClickMenu(self):
        """
        Based on method from flatnotebook demo
        """
        self._rmenu = wx.Menu()
        item = wx.MenuItem(self._rmenu, wx.ID_ANY, 
                           "Close Tab\tCtrl+F4", 
                           "Close Tab")
        self.Bind(wx.EVT_MENU, self.onDeletePage, item)
        self._rmenu.AppendItem(item)

    #----------------------------------------------------------------------
    def onAddPage(self, event):
        """
        This method is based on the flatnotebook demo

        It adds a new page to the notebook
        """
        caption = "New Page Added #" + str(self._newPageCounter)
        self.Freeze()

        self.notebook.AddPage(self.createPage(caption), caption, True)
        self.Thaw()
        self._newPageCounter = self._newPageCounter + 1

    #----------------------------------------------------------------------
    def createPage(self, caption):
        """
        Creates a notebook page from one of three
        panels at random and returns the new page
        """
        panel_list = [panelOne, panelTwo, panelThree]
        obj = random.choice(panel_list)
        page = obj.TabPanel(self.notebook)
        return page

    #----------------------------------------------------------------------
    def onDeletePage(self, event):
        """
        This method is based on the flatnotebook demo

        It removes a page from the notebook
        """
        self.notebook.DeletePage(self.notebook.GetSelection())

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = DemoFrame()
    app.MainLoop()

上面的代码允许用户通过单击 Add Page 按钮添加任意多的页面。“删除页面”按钮将删除当前选中的任何页面。添加页面时,button handler 冻结框架并调用笔记本的 AddPage 方法。这调用了“createPage”方法,该方法随机获取我的一个预定义面板,实例化它并将其返回给 AddPage 方法。在返回到“onAddPage”方法时,帧被解冻,并且页面计数器递增。

Remove Page 按钮调用笔记本的 GetSelection()方法获取当前选中的选项卡,然后调用笔记本的 DeletePage()方法将其从笔记本中删除。

我启用的另一个有趣的功能是选项卡右键菜单,它为我们提供了另一种关闭选项卡的方式,尽管您也可以使用它来执行其他操作。要启用它,您只需调用笔记本的 SetRightClickMenu()方法并传入一个 wx。菜单对象。

还有大量其他功能供您探索。请务必在官方的 wxPython 演示中查看 FlatNotebook 演示,在那里您可以学习使用鼠标中键或通过双击来关闭标签,打开标签背景的渐变颜色,禁用标签,启用智能跳转(有点像 Windows 中的 alt+tab 菜单),在笔记本之间创建拖放标签等等!

AGW·AUI 笔记本

agwAuiNotebookDemo

Andrea Gavana 不厌其烦地创建了一个纯 python 版本的高级用户界面(AUI ),它提供了透视保存、可停靠的浮动子窗口、可定制的外观和感觉以及可拆分的 AUI 笔记本。他的笔记本将是这一部分的重点。AGW·AUI 的笔记本有很多功能,但我只打算复习一些基本知识。如果你想看到所有的特性,请务必阅读代码并查看官方 wxPython 演示中的演示。正如我在本教程开始时提到的,一定要从 SVN 下载 AUI (或整个 AGW)的最新版本,以获得所有的错误修复。

让我们来看看我在上面的截图中使用的简单例子:

清单 4

#----------------------------------------------------------------------
# agwAUINotebook.py
#
# Created: December 2009
#
# Author: Mike Driscoll - mike@pythonlibrary.org
#
# Note: Some code comes from the wxPython demo
#
#----------------------------------------------------------------------

import wx
import wx.lib.agw.aui as aui 

########################################################################
class TabPanelOne(wx.Panel):
    """
    A simple wx.Panel class
    """
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """"""
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)

        sizer = wx.BoxSizer(wx.VERTICAL)
        txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
        txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(txtOne, 0, wx.ALL, 5)
        sizer.Add(txtTwo, 0, wx.ALL, 5)

        self.SetSizer(sizer)

########################################################################
class DemoFrame(wx.Frame):
    """
    wx.Frame class
    """

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          "AGW AUI Notebook Tutorial",
                          size=(600,400))

        self._mgr = aui.AuiManager()

        # tell AuiManager to manage this frame
        self._mgr.SetManagedWindow(self)

        notebook = aui.AuiNotebook(self)
        panelOne = TabPanelOne(notebook)
        panelTwo = TabPanelOne(notebook)

        notebook.AddPage(panelOne, "PanelOne", False)
        notebook.AddPage(panelTwo, "PanelTwo", False)

        self._mgr.AddPane(notebook, 
                          aui.AuiPaneInfo().Name("notebook_content").
                          CenterPane().PaneBorder(False)) 
        self._mgr.Update()
        #notebook.EnableTab(1, False)

 #----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = DemoFrame()
    frame.Show()
    app.MainLoop()

这个笔记本和最初的 AuiNotebook 的第一个区别是它需要一个 AuiManager 对象。可能在原作背后也有类似的东西,但是我们不知道。无论如何,第一步是实例化 AuiManager,然后通过它的 SetManagedWindow()方法为它提供管理框架。现在我们可以添加 AUI 笔记本了。请注意,我们将框架作为笔记本的父对象传递,而不是 AuiManager。我认为原因是当 AuiManager 被赋予框架时,它就变成了顶级窗口。

等式的下一部分应该看起来很熟悉:AddPage()。让我们看看它接受什么:

AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None)

在我的代码中,我只传入前三个参数,但是您也可以添加一些位图和一个 wx。控件的窗口。接下来的部分有点棘手。我们需要调用 AuiManager 的 AddPane()方法来告诉 AuiManager 我们希望它“管理”一些东西(在本例中是笔记本)。我们还传入了第二个看起来有点混乱的论点:

aui.AuiPaneInfo().Name("notebook_content").CenterPane().PaneBorder(False)) 

该参数告诉 AuiManager 如何处理笔记本。在这种情况下,我们告诉它窗格(即笔记本)的名称是“notebook_content”,这是我们用来查找窗格的名称。我们还告诉 AuiManager,我们希望窗格处于居中的停靠位置,PaneBorder(False)命令告诉 AuiManager,我们希望在笔记本窗格周围绘制一个隐藏的边框。

我们的下一个例子将会更加复杂,它将向你展示如何改变一些笔记本的设置。

清单 5

import panelOne, panelTwo, panelThree
import wx
import wx.lib.agw.aui as aui

ID_NotebookArtGloss = 0
ID_NotebookArtSimple = 1
ID_NotebookArtVC71 = 2
ID_NotebookArtFF2 = 3
ID_NotebookArtVC8 = 4
ID_NotebookArtChrome = 5

########################################################################
class AUIManager(aui.AuiManager):
    """
    AUI Manager class
    """

    #----------------------------------------------------------------------
    def __init__(self, managed_window):
        """Constructor"""
        aui.AuiManager.__init__(self)
        self.SetManagedWindow(managed_window)

########################################################################
class AUINotebook(aui.AuiNotebook):
    """
    AUI Notebook class
    """

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        aui.AuiNotebook.__init__(self, parent=parent)
        self.default_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER
        self.SetWindowStyleFlag(self.default_style)

        # add some pages to the notebook
        pages = [panelOne, panelTwo, panelThree]

        x = 1
        for page in pages:
            label = "Tab #%i" % x
            tab = page.TabPanel(self)
            self.AddPage(tab, label, False)
            x += 1

########################################################################
class DemoFrame(wx.Frame):
    """
    wx.Frame class
    """
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        title = "AGW AUI Notebook Feature Tutorial"
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          title=title, size=(600,400))
        self.themeDict = {"Glossy Theme (Default)":0,
                          "Simple Theme":1,
                          "VC71 Theme":2,
                          "Firefox 2 Theme":3,
                          "VC8 Theme":4,
                          "Chrome Theme":5,
                          }

        # create the AUI manager
        self.aui_mgr = AUIManager(self)

        # create the AUI Notebook
        self.notebook = AUINotebook(self)

        self._notebook_style = self.notebook.default_style

        # add notebook to AUI manager
        self.aui_mgr.AddPane(self.notebook, 
                             aui.AuiPaneInfo().Name("notebook_content").
                             CenterPane().PaneBorder(False)) 
        self.aui_mgr.Update()

        # create menu and tool bars
        self.createMenu()
        self.createTB()

    #----------------------------------------------------------------------
    def createMenu(self):
        """
        Create the menu
        """
        def doBind(item, handler):
            """ Create menu events. """
            self.Bind(wx.EVT_MENU, handler, item)

        menubar = wx.MenuBar()

        fileMenu = wx.Menu()

        doBind( fileMenu.Append(wx.ID_ANY, "&Exit\tAlt+F4", 
                                "Exit Program"),self.onExit)

        optionsMenu = wx.Menu()

        doBind( optionsMenu.Append(wx.ID_ANY, 
                                   "Disable Current Tab"),
                self.onDisableTab)

        # add the menus to the menubar
        menubar.Append(fileMenu, "File")
        menubar.Append(optionsMenu, "Options")

        self.SetMenuBar(menubar)

    #----------------------------------------------------------------------
    def createTB(self):
        """
        Create the toolbar
        """
        TBFLAGS = ( wx.TB_HORIZONTAL
                    | wx.NO_BORDER
                    | wx.TB_FLAT )
        tb = self.CreateToolBar(TBFLAGS)
        keys = self.themeDict.keys()
        keys.sort()
        choices = keys
        cb = wx.ComboBox(tb, wx.ID_ANY, "Glossy Theme (Default)", 
                         choices=choices,
                         size=wx.DefaultSize,
                         style=wx.CB_DROPDOWN)
        cb.Bind(wx.EVT_COMBOBOX, self.onChangeTheme)
        tb.AddControl(cb)
        tb.AddSeparator()

        self.closeChoices = ["No Close Button", "Close Button At Right",
                             "Close Button On All Tabs",
                             "Close Button On Active Tab"]
        cb = wx.ComboBox(tb, wx.ID_ANY, 
                         self.closeChoices[3],
                         choices=self.closeChoices,
                         size=wx.DefaultSize, 
                         style=wx.CB_DROPDOWN)
        cb.Bind(wx.EVT_COMBOBOX, self.onChangeTabClose)
        tb.AddControl(cb)

        tb.Realize()

    #----------------------------------------------------------------------
    def onChangeTabClose(self, event):
        """
        Change how the close button behaves on a tab

        Note: Based partially on the agw AUI demo
        """
        choice = event.GetString()        
        self._notebook_style &= ~(aui.AUI_NB_CLOSE_BUTTON |
                                 aui.AUI_NB_CLOSE_ON_ACTIVE_TAB |
                                 aui.AUI_NB_CLOSE_ON_ALL_TABS)

        # note that this close button doesn't work for some reason
        if choice == "Close Button At Right":
            self._notebook_style ^= aui.AUI_NB_CLOSE_BUTTON 
        elif choice == "Close Button On All Tabs":
            self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ALL_TABS 
        elif choice == "Close Button On Active Tab":
            self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ACTIVE_TAB

        self.notebook.SetWindowStyleFlag(self._notebook_style)
        self.notebook.Refresh()
        self.notebook.Update()

    #----------------------------------------------------------------------
    def onChangeTheme(self, event):
        """
        Changes the notebook's theme

        Note: Based partially on the agw AUI demo
        """

        print event.GetString()
        evId = self.themeDict[event.GetString()]
        print evId

        all_panes = self.aui_mgr.GetAllPanes()

        for pane in all_panes:

            if isinstance(pane.window, aui.AuiNotebook):            
                nb = pane.window

                if evId == ID_NotebookArtGloss:

                    nb.SetArtProvider(aui.AuiDefaultTabArt())
                    self._notebook_theme = 0

                elif evId == ID_NotebookArtSimple:
                    nb.SetArtProvider(aui.AuiSimpleTabArt())
                    self._notebook_theme = 1

                elif evId == ID_NotebookArtVC71:
                    nb.SetArtProvider(aui.VC71TabArt())
                    self._notebook_theme = 2

                elif evId == ID_NotebookArtFF2:
                    nb.SetArtProvider(aui.FF2TabArt())
                    self._notebook_theme = 3

                elif evId == ID_NotebookArtVC8:
                    nb.SetArtProvider(aui.VC8TabArt())
                    self._notebook_theme = 4

                elif evId == ID_NotebookArtChrome:
                    nb.SetArtProvider(aui.ChromeTabArt())
                    self._notebook_theme = 5

                #nb.SetWindowStyleFlag(self._notebook_style)
                nb.Refresh()
                nb.Update()

    #----------------------------------------------------------------------
    def onDisableTab(self, event):
        """
        Disables the current tab
        """
        page = self.notebook.GetCurrentPage()
        page_idx = self.notebook.GetPageIndex(page)

        self.notebook.EnableTab(page_idx, False)
        self.notebook.AdvanceSelection()

    #----------------------------------------------------------------------
    def onExit(self, event):
        """
        Close the demo
        """
        self.Close()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = DemoFrame()
    frame.Show()
    app.MainLoop()

对于这个演示,我决定尝试创建 AuiManager 和 aui.AuiNotebook 的子类。虽然我认为如果您需要实例化多个 AuiManager 实例,这可能会有所帮助,但对于这个演示来说,除了向您展示如何做之外,它真的没有多大帮助。让我们一点一点地解开这个例子,看看它是如何工作的!

在 AuiManager 类中,我强制程序员传入要管理的窗口,它会自动调用 SetManagedWindow()。你也可以用 AuiManager 的其他功能来做这件事。在 AuiNotebook 的例子中,我使用它的 SetWindowStyleFlag()方法设置了一个默认样式,然后我向笔记本添加了一些页面。这给了我一个快速创建多个笔记本的简单快捷的方法。

演示框架完成了大部分工作。它创建一个主题字典供以后使用,实例化 AuiManager 和 AuiNotebook,并创建一个工具栏和 menubar。我们的重点是与菜单栏和工具栏相关的事件处理程序,因为它们会影响 AuiNotebook 的功能。我们感兴趣的第一个方法是 onChangeTabClose()。

清单 6

def onChangeTabClose(self, event):
    """
    Change how the close button behaves on a tab

    Note: Based partially on the agw AUI demo
    """
    choice = event.GetString()        
    self._notebook_style &= ~(aui.AUI_NB_CLOSE_BUTTON |
                             aui.AUI_NB_CLOSE_ON_ACTIVE_TAB |
                             aui.AUI_NB_CLOSE_ON_ALL_TABS)

    # note that this close button doesn't work for some reason
    if choice == "Close Button At Right":
        self._notebook_style ^= aui.AUI_NB_CLOSE_BUTTON 
    elif choice == "Close Button On All Tabs":
        self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ALL_TABS 
    elif choice == "Close Button On Active Tab":
        self._notebook_style ^= aui.AUI_NB_CLOSE_ON_ACTIVE_TAB

    self.notebook.SetWindowStyleFlag(self._notebook_style)
    self.notebook.Refresh()
    self.notebook.Update()

该事件处理程序从工具栏中第二个 combobox 生成的 combobox 事件中调用。它的目的是决定标签上关闭按钮的位置。首先,它通过调用“事件”来获取用户的选择。GetString()”。接下来,它使用一些按位运算符来清除关闭按钮相关的样式。如果我没看错的话,它用一个“notted”multi-“or”来“and”当前的笔记本风格。是的,很混乱。简单来说就是说三种风格(aui。AUI 关闭按钮。AUI_NB_CLOSE_ON_ACTIVE_TAB,AUI。AUI_NB_CLOSE_ON_ALL_TABS)将从当前笔记本样式中减去。

然后,我使用一个条件来决定实际应用于笔记本的样式。一旦将它添加到变量中,我使用笔记本的 SetWindowStyleFlag()来应用它,然后刷新和更新显示,以便用户可以看到更改。

现在我们来改变笔记本的风格:

清单 7

def onChangeTheme(self, event):
    """
    Changes the notebook's theme

    Note: Based partially on the agw AUI demo
    """
    evId = self.themeDict[event.GetString()]
    all_panes = self.aui_mgr.GetAllPanes()

    for pane in all_panes:

        if isinstance(pane.window, aui.AuiNotebook):
            nb = pane.window

            if evId == ID_NotebookArtGloss:

                nb.SetArtProvider(aui.AuiDefaultTabArt())

            elif evId == ID_NotebookArtSimple:
                nb.SetArtProvider(aui.AuiSimpleTabArt())

            elif evId == ID_NotebookArtVC71:
                nb.SetArtProvider(aui.VC71TabArt())

            elif evId == ID_NotebookArtFF2:
                nb.SetArtProvider(aui.FF2TabArt())

            elif evId == ID_NotebookArtVC8:
                nb.SetArtProvider(aui.VC8TabArt())

            elif evId == ID_NotebookArtChrome:
                nb.SetArtProvider(aui.ChromeTabArt())

            nb.Refresh()
            nb.Update()

事件处理程序是从第一个工具栏的 combobox 事件中调用的。它也通过事件获取用户的选择。GetString(),然后使用该字符串作为我的主题字典的键。字典返回一个分配给“evId”的整数。接下来,AuiManager 实例调用 GetAllPanes()来获取笔记本中所有页面的列表。最后,处理程序在页面上循环,并使用嵌套的条件调用 SetArtProvider()来更改笔记本。为了展示这些变化,我们调用了笔记本的刷新和更新方法。

本演示中我要介绍的最后一个方法是“onDisableTab”:

清单 8

def onDisableTab(self, event):
    """
    Disables the current tab
    """
    page = self.notebook.GetCurrentPage()
    page_idx = self.notebook.GetPageIndex(page)

    self.notebook.EnableTab(page_idx, False)
    self.notebook.AdvanceSelection()

这个事件处理程序由一个菜单事件触发,是一段非常简单的代码。首先,我们调用笔记本的 GetCurrentPage()方法,然后将结果传递给笔记本的 GetPageIndex()方法。现在我们有了页面索引,我们可以通过笔记本的 EnableTab 方法使用它来禁用它。如您所见,通过传递 False,我们禁用了页面。您还可以使用 EnableTab 方法通过传递 True 来重新启用该选项卡。

包扎

还有许多其他方法会影响这两款笔记本的行为。这将需要更多的文章来涵盖一切。请务必下载 wxPython 演示和 SVN 版本的代码,以充分利用这些精彩的笔记本,并了解我在这里没有涉及的内容。例如,我没有讨论各个小部件的事件、选项卡位置(底部、顶部等),或者可以锁定 AuiNotebook 的许多不同的功能。还要注意 AuiNotebook 支持“透视图”。官方演示有例子,这里就不复制了。记住,FlatNotebook 和 AGW AuiNotebook 都是纯 python,所以如果你懂 python,你可以自己动手。

注意:所有代码都是在 windows XP/Vista 上用 wxPython 2.8.10.1(unicode)和 Python 2.5 测试的,使用的是 AGW 的最新 SVN 版本。这些代码在其他操作系统上应该也能很好地工作。如果没有,请通过电子邮件通知我 wxPython 邮件列表

延伸阅读

下载量

posted @ 2024-11-02 15:55  绝不原创的飞龙  阅读(13)  评论(0编辑  收藏  举报