分类 软件开发 下的文章

在本系列的第五部分,学习如何增加一个坏蛋与你的好人战斗。

在本系列的前几篇文章中(参见 第一部分第二部分第三部分 以及 第四部分),你已经学习了如何使用 Pygame 和 Python 在一个空白的视频游戏世界中生成一个可玩的角色。但没有恶棍,英雄又将如何?

如果你没有敌人,那将会是一个非常无聊的游戏。所以在此篇文章中,你将为你的游戏添加一个敌人并构建一个用于创建关卡的框架。

在对玩家妖精实现全部功能之前,就来实现一个敌人似乎就很奇怪。但你已经学到了很多东西,创造恶棍与与创造玩家妖精非常相似。所以放轻松,使用你已经掌握的知识,看看能挑起怎样一些麻烦。

针对本次训练,你能够从 Open Game Art 下载一些预创建的素材。此处是我使用的一些素材:

  • 印加花砖(LCTT 译注:游戏中使用的花砖贴图)
  • 一些侵略者
  • 妖精、角色、物体以及特效

创造敌方妖精

是的,不管你意识到与否,你其实已经知道如何去实现敌人。这个过程与创造一个玩家妖精非常相似:

  1. 创建一个类用于敌人生成
  2. 创建 update 方法使得敌人能够检测碰撞
  3. 创建 move 方法使得敌人能够四处游荡

从类入手。从概念上看,它与你的 Player 类大体相同。你设置一张或者一组图片,然后设置妖精的初始位置。

在继续下一步之前,确保你有一张你的敌人的图像,即使只是一张临时图像。将图像放在你的游戏项目的 images 目录(你放置你的玩家图像的相同目录)。

如果所有的活物都拥有动画,那么游戏看起来会好得多。为敌方妖精设置动画与为玩家妖精设置动画具有相同的方式。但现在,为了保持简单,我们使用一个没有动画的妖精。

在你代码 objects 节的顶部,使用以下代码创建一个叫做 Enemy 的类:

class Enemy(pygame.sprite.Sprite):
    '''
    生成一个敌人
    '''
    def __init__(self,x,y,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img))
        self.image.convert_alpha()
        self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

如果你想让你的敌人动起来,使用让你的玩家拥有动画的 相同方式

生成一个敌人

你能够通过告诉类,妖精应使用哪张图像,应出现在世界上的什么地方,来生成不只一个敌人。这意味着,你能够使用相同的敌人类,在游戏世界的任意地方生成任意数量的敌方妖精。你需要做的仅仅是调用这个类,并告诉它应使用哪张图像,以及你期望生成点的 X 和 Y 坐标。

再次,这从原则上与生成一个玩家精灵相似。在你脚本的 setup 节添加如下代码:

enemy   = Enemy(20,200,'yeti.png')  # 生成敌人
enemy_list = pygame.sprite.Group()  # 创建敌人组
enemy_list.add(enemy)               # 将敌人加入敌人组

在示例代码中,X 坐标为 20,Y 坐标为 200。你可能需要根据你的敌方妖精的大小,来调整这些数字,但尽量生成在一个范围内,使得你的玩家妖精能够碰到它。Yeti.png 是用于敌人的图像。

接下来,将敌人组的所有敌人绘制在屏幕上。现在,你只有一个敌人,如果你想要更多你可以稍后添加。一但你将一个敌人加入敌人组,它就会在主循环中被绘制在屏幕上。中间这一行是你需要添加的新行:

    player_list.draw(world)
    enemy_list.draw(world)  # 刷新敌人
    pygame.display.flip()

启动你的游戏,你的敌人会出现在游戏世界中你选择的 X 和 Y 坐标处。

关卡一

你的游戏仍处在襁褓期,但你可能想要为它添加另一个关卡。为你的程序做好未来规划非常重要,因为随着你学会更多的编程技巧,你的程序也会随之成长。即使你现在仍没有一个完整的关卡,你也应该按照假设会有很多关卡来编程。

思考一下“关卡”是什么。你如何知道你是在游戏中的一个特定关卡中呢?

你可以把关卡想成一系列项目的集合。就像你刚刚创建的这个平台中,一个关卡,包含了平台、敌人放置、战利品等的一个特定排列。你可以创建一个类,用来在你的玩家附近创建关卡。最终,当你创建了一个以上的关卡,你就可以在你的玩家达到特定目标时,使用这个类生成下一个关卡。

将你写的用于生成敌人及其群组的代码,移动到一个每次生成新关卡时都会被调用的新函数中。你需要做一些修改,使得每次你创建新关卡时,你都能够创建一些敌人。

class Level():
    def bad(lvl,eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # 生成敌人
            enemy_list = pygame.sprite.Group() # 生成敌人组
            enemy_list.add(enemy)              # 将敌人加入敌人组
        if lvl == 2:
            print("Level " + str(lvl) )

        return enemy_list

return 语句确保了当你调用 Level.bad 方法时,你将会得到一个 enemy_list 变量包含了所有你定义的敌人。

因为你现在将创造敌人作为每个关卡的一部分,你的 setup 部分也需要做些更改。不同于创造一个敌人,取而代之的是你必须去定义敌人在那里生成,以及敌人属于哪个关卡。

eloc = []
eloc = [200,20]
enemy_list = Level.bad( 1, eloc )

再次运行游戏来确认你的关卡生成正确。与往常一样,你应该会看到你的玩家,并且能看到你在本章节中添加的敌人。

痛击敌人

一个敌人如果对玩家没有效果,那么它不太算得上是一个敌人。当玩家与敌人发生碰撞时,他们通常会对玩家造成伤害。

因为你可能想要去跟踪玩家的生命值,因此碰撞检测发生在 Player 类,而不是 Enemy 类中。当然如果你想,你也可以跟踪敌人的生命值。它们之间的逻辑与代码大体相似,现在,我们只需要跟踪玩家的生命值。

为了跟踪玩家的生命值,你必须为它确定一个变量。代码示例中的第一行是上下文提示,那么将第二行代码添加到你的 Player 类中:

        self.frame  = 0
        self.health = 10

在你 Player 类的 update 方法中,添加如下代码块:

        hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in hit_list:
            self.health -= 1
            print(self.health)

这段代码使用 Pygame 的 sprite.spritecollide 方法,建立了一个碰撞检测器,称作 enemy_hit。每当它的父类妖精(生成检测器的玩家妖精)的碰撞区触碰到 enemy_list 中的任一妖精的碰撞区时,碰撞检测器都会发出一个信号。当这个信号被接收,for 循环就会被触发,同时扣除一点玩家生命值。

一旦这段代码出现在你 Player 类的 update 方法,并且 update 方法在你的主循环中被调用,Pygame 会在每个时钟滴答中检测一次碰撞。

移动敌人

如果你愿意,静止不动的敌人也可以很有用,比如能够对你的玩家造成伤害的尖刺和陷阱。但如果敌人能够四处徘徊,那么游戏将更富有挑战。

与玩家妖精不同,敌方妖精不是由玩家控制,因此它必须自动移动。

最终,你的游戏世界将会滚动。那么,如何在游戏世界自身滚动的情况下,使游戏世界中的敌人前后移动呢?

举个例子,你告诉你的敌方妖精向右移动 10 步,向左移动 10 步。但敌方妖精不会计数,因此你需要创建一个变量来跟踪你的敌人已经移动了多少步,并根据计数变量的值来向左或向右移动你的敌人。

首先,在你的 Enemy 类中创建计数变量。添加以下代码示例中的最后一行代码:

        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = 0 # 计数变量

然后,在你的 Enemy 类中创建一个 move 方法。使用 if-else 循环来创建一个所谓的死循环:

  • 如果计数在 0 到 100 之间,向右移动;
  • 如果计数在 100 到 200 之间,向左移动;
  • 如果计数大于 200,则将计数重置为 0。

死循环没有终点,因为循环判断条件永远为真,所以它将永远循环下去。在此情况下,计数器总是介于 0 到 100 或 100 到 200 之间,因此敌人会永远地从左向右再从右向左移动。

你用于敌人在每个方向上移动距离的具体值,取决于你的屏幕尺寸,更确切地说,取决于你的敌人移动的平台大小。从较小的值开始,依据习惯逐步提高数值。首先进行如下尝试:

    def move(self):
        '''
        敌人移动
        '''
        distance = 80
        speed = 8

        if self.counter >= 0 and self.counter <= distance:
            self.rect.x += speed
        elif self.counter >= distance and self.counter <= distance*2:
            self.rect.x -= speed
        else:
            self.counter = 0

        self.counter += 1

你可以根据需要调整距离和速度。

当你现在启动游戏,这段代码有效果吗?

当然不,你应该也知道原因。你必须在主循环中调用 move 方法。如下示例代码中的第一行是上下文提示,那么添加最后两行代码:

    enemy_list.draw(world) #refresh enemy
    for e in enemy_list:
        e.move()

启动你的游戏看看当你打击敌人时发生了什么。你可能需要调整妖精的生成地点,使得你的玩家和敌人能够碰撞。当他们发生碰撞时,查看 IDLENinja-IDE 的控制台,你可以看到生命值正在被扣除。

你应该已经注意到,在你的玩家和敌人接触时,生命值在时刻被扣除。这是一个问题,但你将在对 Python 进行更多练习以后解决它。

现在,尝试添加更多敌人。记得将每个敌人加入 enemy_list。作为一个练习,看看你能否想到如何改变不同敌方妖精的移动距离。


via: https://opensource.com/article/18/5/pygame-enemy

作者:Seth Kenlon 选题:lujun9972 译者:cycoe 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本系列的第四部分,学习如何编写移动游戏角色的控制代码。

在这个系列的第一篇文章中,我解释了如何使用 Python 创建一个简单的基于文本的骰子游戏。在第二部分中,我向你们展示了如何从头开始构建游戏,即从 创建游戏的环境 开始。然后在第三部分,我们创建了一个玩家妖精,并且使它在你的(而不是空的)游戏世界内生成。你可能已经注意到,如果你不能移动你的角色,那么游戏不是那么有趣。在本篇文章中,我们将使用 Pygame 来添加键盘控制,如此一来你就可以控制你的角色的移动。

在 Pygame 中有许多函数可以用来添加(除键盘外的)其他控制,但如果你正在敲击 Python 代码,那么你一定是有一个键盘的,这将成为我们接下来会使用的控制方式。一旦你理解了键盘控制,你可以自己去探索其他选项。

在本系列的第二篇文章中,你已经为退出游戏创建了一个按键,移动角色的(按键)原则也是相同的。但是,使你的角色移动起来要稍微复杂一点。

让我们从简单的部分入手:设置控制器按键。

为控制你的玩家妖精设置按键

在 IDLE、Ninja-IDE 或文本编辑器中打开你的 Python 游戏脚本。

因为游戏需要时刻“监听”键盘事件,所以你写的代码需要连续运行。你知道应该把需要在游戏周期中持续运行的代码放在哪里吗?

如果你回答“放在主循环中”,那么你是正确的!记住除非代码在循环中,否则(大多数情况下)它只会运行仅一次。如果它被写在一个从未被使用的类或函数中,它可能根本不会运行。

要使 Python 监听传入的按键,将如下代码添加到主循环。目前的代码还不能产生任何的效果,所以使用 print 语句来表示成功的信号。这是一种常见的调试技术。

while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print('left')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right')
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print('left stop')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right stop')
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False    

一些人偏好使用键盘字母 WASD 来控制玩家角色,而另一些偏好使用方向键。因此确保你包含了两种选项。

注意:当你在编程时,同时考虑所有用户是非常重要的。如果你写代码只是为了自己运行,那么很可能你会成为你写的程序的唯一用户。更重要的是,如果你想找一个通过写代码赚钱的工作,你写的代码就应该让所有人都能运行。给你的用户选择权,比如提供使用方向键或 WASD 的选项,是一个优秀程序员的标志。

使用 Python 启动你的游戏,并在你按下“上下左右”方向键或 ADW 键的时候查看控制台窗口的输出。

$ python ./your-name_game.py
  left
  left stop
  right
  right stop
  jump

这验证了 Pygame 可以正确地检测按键。现在是时候来完成使妖精移动的艰巨任务了。

编写玩家移动函数

为了使你的妖精移动起来,你必须为你的妖精创建一个属性代表移动。当你的妖精没有在移动时,这个变量被设为 0

如果你正在为你的妖精设置动画,或者你决定在将来为它设置动画,你还必须跟踪帧来使走路循环保持在轨迹上。

Player 类中创建如下变量。开头两行作为上下文对照(如果你一直跟着做,你的代码中就已经有这两行),因此只需要添加最后三行:

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0 # 沿 X 方向移动
        self.movey = 0 # 沿 Y 方向移动
        self.frame = 0 # 帧计数

设置好了这些变量,是时候去为妖精移动编写代码了。

玩家妖精不需要时刻响应控制,有时它并没有在移动。控制妖精的代码,仅仅只是玩家妖精所有能做的事情中的一小部分。在 Python 中,当你想要使一个对象做某件事并独立于剩余其他代码时,你可以将你的新代码放入一个函数。Python 的函数以关键词 def 开头,(该关键词)代表了定义函数。

在你的 Player 类中创建如下函数,来为你的妖精在屏幕上的位置增加几个像素。现在先不要担心你增加几个像素,这将在后续的代码中确定。

    def control(self,x,y):
        '''
        控制玩家移动
        '''
        self.movex += x
        self.movey += y

为了在 Pygame 中移动妖精,你需要告诉 Python 在新的位置重绘妖精,以及这个新位置在哪里。

因为玩家妖精并不总是在移动,所以更新只需要是 Player 类中的一个函数。将此函数添加前面创建的 control 函数之后。

要使妖精看起来像是在行走(或者飞行,或是你的妖精应该做的任何事),你需要在按下适当的键时改变它在屏幕上的位置。要让它在屏幕上移动,你需要将它的位置(由 self.rect.xself.rect.y 属性指定)重新定义为当前位置加上已应用的任意 movexmovey。(移动的像素数量将在后续进行设置。)

    def update(self):
        '''
        更新妖精位置
        '''
        self.rect.x = self.rect.x + self.movex        

对 Y 方向做同样的处理:

        self.rect.y = self.rect.y + self.movey

对于动画,在妖精移动时推进动画帧,并使用相应的动画帧作为玩家的图像:

        # 向左移动
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # 向右移动
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]

通过设置一个变量来告诉代码为你的妖精位置增加多少像素,然后在触发你的玩家妖精的函数时使用这个变量。

首先,在你的设置部分创建这个变量。在如下代码中,开头两行是上下文对照,因此只需要在你的脚本中增加第三行代码:

player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10  # 移动多少个像素

现在你已经有了适当的函数和变量,使用你的按键来触发函数并将变量传递给你的妖精。

为此,将主循环中的 print 语句替换为玩家妖精的名字(player)、函数(.control)以及你希望玩家妖精在每个循环中沿 X 轴和 Y 轴移动的步数。

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

记住,steps 变量代表了当一个按键被按下时,你的妖精会移动多少个像素。如果当你按下 D 或右方向键时,你的妖精的位置增加了 10 个像素。那么当你停止按下这个键时,你必须(将 step)减 10(-steps)来使你的妖精的动量回到 0。

现在尝试你的游戏。注意:它不会像你预想的那样运行。

为什么你的妖精仍无法移动?因为主循环还没有调用 update 函数。

将如下代码加入到你的主循环中来告诉 Python 更新你的玩家妖精的位置。增加带注释的那行:

    player.update()  # 更新玩家位置
    player_list.draw(world)
    pygame.display.flip()
    clock.tick(fps)

再次启动你的游戏来见证你的玩家妖精在你的命令下在屏幕上来回移动。现在还没有垂直方向的移动,因为这部分函数会被重力控制,不过这是另一篇文章中的课程了。

与此同时,如果你拥有一个摇杆,你可以尝试阅读 Pygame 中 joystick 模块相关的文档,看看你是否能通过这种方式让你的妖精移动起来。或者,看看你是否能通过鼠标与你的妖精互动。

最重要的是,玩的开心!

本教程中用到的所有代码

为了方便查阅,以下是目前本系列文章用到的所有代码。

#!/usr/bin/env python3
# 绘制世界
# 添加玩家和玩家控制
# 添加玩家移动控制

# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

import pygame
import sys
import os

'''
Objects
'''

class Player(pygame.sprite.Sprite):
    '''
    生成玩家
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.images = []
        for i in range(1,5):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.images.append(img)
            self.image = self.images[0]
            self.rect  = self.image.get_rect()

    def control(self,x,y):
        '''
        控制玩家移动
        '''
        self.movex += x
        self.movey += y

    def update(self):
        '''
        更新妖精位置
        '''

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # 向左移动
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # 向右移动
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]


'''
设置
'''
worldx = 960
worldy = 720

fps = 40        # 帧刷新率
ani = 4        # 动画循环
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)

world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player()   # 生成玩家
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10      # 移动速度

'''
主循环
'''
while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.update()
    player_list.draw(world) # 更新玩家位置
    pygame.display.flip()
    clock.tick(fps)

你已经学了很多,但还仍有许多可以做。在接下来的几篇文章中,你将实现添加敌方妖精、模拟重力等等。与此同时,练习 Python 吧!


via: https://opensource.com/article/17/12/game-python-moving-player

作者:Seth Kenlon 选题:lujun9972 译者:cycoe 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在我们覆盖 7 个 PyPI 库的系列文章中了解更多解决 Python 问题的信息。

Python是当今使用最多流行的编程语言之一,因为:它是开源的,它具有广泛的用途(例如 Web 编程、业务应用、游戏、科学编程等等),它有一个充满活力和专注的社区支持它。这个社区是我们在 Python Package Index(PyPI)中提供如此庞大、多样化的软件包的原因,用以扩展和改进 Python。并解决不可避免的问题。

在本系列中,我们将介绍七个可以帮助你解决常见 Python 问题的 PyPI 库。今天,我们将研究 attrs,这是一个帮助你快速编写简洁、正确的代码的 Python 包。

attrs

如果你已经写过一段时间的 Python,那么你可能习惯这样写代码:

class Book(object):

    def __init__(self, isbn, name, author):
        self.isbn = isbn
        self.name = name
        self.author = author

接着写一个 __repr__ 函数。否则,很难记录 Book 的实例:

def __repr__(self):
    return f"Book({self.isbn}, {self.name}, {self.author})"

接下来你会写一个好看的 docstring 来记录期望的类型。但是你注意到你忘了添加 editionpublished_year 属性,所以你必须在五个地方修改它们。

如果你不必这么做如何?

@attr.s(auto_attribs=True)
class Book(object):
    isbn: str
    name: str
    author: str
    published_year: int
    edition: int

使用新的类型注释语法注释类型属性,attrs 会检测注释并创建一个类。

ISBN 有特定格式。如果我们想强行使用该格式怎么办?

@attr.s(auto_attribs=True)
class Book(object):
    isbn: str = attr.ib()
    @isbn.validator
    def pattern_match(self, attribute, value):
        m = re.match(r"^(\d{3}-)\d{1,3}-\d{2,3}-\d{1,7}-\d$", value)
        if not m:
            raise ValueError("incorrect format for isbn", value)
    name: str 
    author: str
    published_year: int
    edition: int

attrs 库也对不可变式编程支持良好。将第一行改成 @attr.s(auto_attribs=True, frozen=True) 意味着 Book 现在是不可变的:尝试修改一个属性将会引发一个异常。相反,比如,如果希望将发布日期向后一年,我们可以修改成 attr.evolve(old_book, published_year=old_book.published_year+1) 来得到一个新的实例。

本系列的下一篇文章我们将来看下 singledispatch,一个能让你向 Python 库添加方法的库。

查看本系列先前的文章:


via: https://opensource.com/article/19/5/python-attrs

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

学习如何使用 spaCy、vaderSentiment、Flask 和 Python 来为你的作品添加情感分析能力。

本系列的第一部分提供了情感分析工作原理的一些背景知识,现在让我们研究如何将这些功能添加到你的设计中。

探索 Python 库 spaCy 和 vaderSentiment

前提条件

  • 一个终端 shell
  • shell 中的 Python 语言二进制文件(3.4+ 版本)
  • 用于安装 Python 包的 pip 命令
  • (可选)一个 Python 虚拟环境使你的工作与系统隔离开来

配置环境

在开始编写代码之前,你需要安装 spaCyvaderSentiment 包来设置 Python 环境,同时下载一个语言模型来帮助你分析。幸运的是,大部分操作都容易在命令行中完成。

在 shell 中,输入以下命令来安装 spaCy 和 vaderSentiment 包:

pip install spacy vaderSentiment

命令安装完成后,安装 spaCy 可用于文本分析的语言模型。以下命令将使用 spaCy 模块下载并安装英语模型

python -m spacy download en_core_web_sm

安装了这些库和模型之后,就可以开始编码了。

一个简单的文本分析

使用 Python 解释器交互模式 编写一些代码来分析单个文本片段。首先启动 Python 环境:

$ python
Python 3.6.8 (default, Jan 31 2019, 09:38:34)
[GCC 8.2.1 20181215 (Red Hat 8.2.1-6)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

(你的 Python 解释器版本打印可能与此不同。)

1、导入所需模块:

>>> import spacy
>>> from vaderSentiment import vaderSentiment 

2、从 spaCy 加载英语语言模型:

>>> english = spacy.load("en_core_web_sm")

3、处理一段文本。本例展示了一个非常简单的句子,我们希望它能给我们带来些许积极的情感:

>>> result = english("I like to eat applesauce with sugar and cinnamon.")

4、从处理后的结果中收集句子。SpaCy 已识别并处理短语中的实体,这一步为每个句子生成情感(即时在本例中只有一个句子):

>>> sentences = [str(s) for s in result.sents]

5、使用 vaderSentiments 创建一个分析器:

>>> analyzer = vaderSentiment.SentimentIntensityAnalyzer()

6、对句子进行情感分析:

>>> sentiment = [analyzer.polarity_scores(str(s)) for s in sentences]

sentiment 变量现在包含例句的极性分数。打印出这个值,看看它是如何分析这个句子的。

>>> print(sentiment)
[{'neg': 0.0, 'neu': 0.737, 'pos': 0.263, 'compound': 0.3612}]

这个结构是什么意思?

表面上,这是一个只有一个字典对象的数组。如果有多个句子,那么每个句子都会对应一个字典对象。字典中有四个键对应不同类型的情感。neg 键表示负面情感,因为在本例中没有报告任何负面情感,0.0 值证明了这一点。neu 键表示中性情感,它的得分相当高,为 0.737(最高为 1.0)。pos 键代表积极情感,得分适中,为 0.263。最后,cmpound 键代表文本的总体得分,它可以从负数到正数,0.3612 表示积极方面的情感多一点。

要查看这些值可能如何变化,你可以使用已输入的代码做一个小实验。以下代码块显示了如何对类似句子的情感评分的评估。

>>> result = english("I love applesauce!")
>>> sentences = [str(s) for s in result.sents]
>>> sentiment = [analyzer.polarity_scores(str(s)) for s in sentences]
>>> print(sentiment)
[{'neg': 0.0, 'neu': 0.182, 'pos': 0.818, 'compound': 0.6696}]

你可以看到,通过将例句改为非常积极的句子,sentiment 的值发生了巨大变化。

建立一个情感分析服务

现在你已经为情感分析组装了基本的代码块,让我们将这些东西转化为一个简单的服务。

在这个演示中,你将使用 Python Flask 包 创建一个 RESTful HTTP 服务器。此服务将接受英文文本数据并返回情感分析结果。请注意,此示例服务是用于学习所涉及的技术,而不是用于投入生产的东西。

前提条件

  • 一个终端 shell
  • shell 中的 Python 语言二进制文件(3.4+ 版本)
  • 安装 Python 包的 pip 命令
  • curl 命令
  • 一个文本编辑器
  • (可选) 一个 Python 虚拟环境使你的工作与系统隔离开来

配置环境

这个环境几乎与上一节中的环境相同,唯一的区别是在 Python 环境中添加了 Flask 包。

1、安装所需依赖项:

pip install spacy vaderSentiment flask

2、安装 spaCy 的英语语言模型:

python -m spacy download en_core_web_sm

创建应用程序文件

打开编辑器,创建一个名为 app.py 的文件。添加以下内容 (不用担心,我们将解释每一行)

import flask
import spacy
import vaderSentiment.vaderSentiment as vader

app = flask.Flask(__name__)
analyzer = vader.SentimentIntensityAnalyzer()
english = spacy.load("en_core_web_sm")

def get_sentiments(text):
    result = english(text)
    sentences = [str(sent) for sent in result.sents]
    sentiments = [analyzer.polarity_scores(str(s)) for s in sentences]
    return sentiments

@app.route("/", methods=["POST", "GET"])
def index():
    if flask.request.method == "GET":
        return "To access this service send a POST request to this URL with" \
                " the text you want analyzed in the body."
    body = flask.request.data.decode("utf-8")
    sentiments = get_sentiments(body)
    return flask.json.dumps(sentiments)

虽然这个源文件不是很大,但它非常密集。让我们来看看这个应用程序的各个部分,并解释它们在做什么。

import flask
import spacy
import vaderSentiment.vaderSentiment as vader

前三行引入了执行语言分析和 HTTP 框架所需的包。

app = flask.Flask(__name__)
analyzer = vader.SentimentIntensityAnalyzer()
english = spacy.load("en_core_web_sm")

接下来的三行代码创建了一些全局变量。第一个变量 app,它是 Flask 用于创建 HTTP 路由的主要入口点。第二个变量 analyzer 与上一个示例中使用的类型相同,它将用于生成情感分数。最后一个变量 english 也与上一个示例中使用的类型相同,它将用于注释和标记初始文本输入。

你可能想知道为什么全局声明这些变量。对于 app 变量,这是许多 Flask 应用程序的标准过程。但是,对于 analyzerenglish 变量,将它们设置为全局变量的决定是基于与所涉及的类关联的加载时间。虽然加载时间可能看起来很短,但是当它在 HTTP 服务器的上下文中运行时,这些延迟会对性能产生负面影响。

def get_sentiments(text):
    result = english(text)
    sentences = [str(sent) for sent in result.sents]
    sentiments = [analyzer.polarity_scores(str(s)) for s in sentences]
    return sentiments

这部分是服务的核心 —— 一个用于从一串文本生成情感值的函数。你可以看到此函数中的操作对应于你之前在 Python 解释器中运行的命令。这里它们被封装在一个函数定义中,text 源作为文本变量传入,最后 sentiments 变量返回给调用者。

@app.route("/", methods=["POST", "GET"])
def index():
  if flask.request.method == "GET":
      return "To access this service send a POST request to this URL with" \
              " the text you want analyzed in the body."
  body = flask.request.data.decode("utf-8")
  sentiments = get_sentiments(body)
  return flask.json.dumps(sentiments)

源文件的最后一个函数包含了指导 Flask 如何为服务配置 HTTP 服务器的逻辑。它从一行开始,该行将 HTTP 路由 / 与请求方法 POSTGET 相关联。

在函数定义行之后,if 子句将检测请求方法是否为 GET。如果用户向服务发送此请求,那么下面的行将返回一条指示如何访问服务器的文本消息。这主要是为了方便最终用户。

下一行使用 flask.request 对象来获取请求的主体,该主体应包含要处理的文本字符串。decode 函数将字节数组转换为可用的格式化字符串。经过解码的文本消息被传递给 get_sentiments 函数以生成情感分数。最后,分数通过 HTTP 框架返回给用户。

你现在应该保存文件,如果尚未保存,那么返回 shell。

运行情感服务

一切就绪后,使用 Flask 的内置调试服务器运行服务非常简单。要启动该服务,请从与源文件相同的目录中输入以下命令:

FLASK_APP=app.py flask run

现在,你将在 shell 中看到来自服务器的一些输出,并且服务器将处于运行状态。要测试服务器是否正在运行,你需要打开第二个 shell 并使用 curl 命令。

首先,输入以下命令检查是否打印了指令信息:

curl http://localhost:5000

你应该看到说明消息:

To access this service send a POST request to this URI with the text you want analyzed in the body.

接下来,运行以下命令发送测试消息,查看情感分析:

curl http://localhost:5000 --header "Content-Type: application/json" --data "I love applesauce!"

你从服务器获得的响应应类似于以下内容:

[{"compound": 0.6696, "neg": 0.0, "neu": 0.182, "pos": 0.818}]

恭喜!你现在已经实现了一个 RESTful HTTP 情感分析服务。你可以在 GitHub 上找到此服务的参考实现和本文中的所有代码

继续探索

现在你已经了解了自然语言处理和情感分析背后的原理和机制,下面是进一步发现探索该主题的一些方法。

在 OpenShift 上创建流式情感分析器

虽然创建本地应用程序来研究情绪分析很方便,但是接下来需要能够部署应用程序以实现更广泛的用途。按照Radnaalytics.io 提供的指导和代码进行操作,你将学习如何创建一个情感分析仪,可以容器化并部署到 Kubernetes 平台。你还将了解如何将 Apache Kafka 用作事件驱动消息传递的框架,以及如何将 Apache Spark 用作情绪分析的分布式计算平台。

使用 Twitter API 发现实时数据

虽然 Radanalytics.io 实验室可以生成合成推文流,但你可以不受限于合成数据。事实上,拥有 Twitter 账户的任何人都可以使用 Tweepy Python 包访问 Twitter 流媒体 API 对推文进行情感分析。


via: https://opensource.com/article/19/4/social-media-sentiment-analysis-python-scalable

作者:Michael McCune 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在我们覆盖 7 个 PyPI 库的系列文章中了解解决 Python 问题的更多信息。

Python 是当今使用最多的流行编程语言之一,因为:它是开源的,它有广泛的用途(例如 Web 编程、业务应用、游戏、科学编程等等),它有一个充满活力和专注的社区支持它。这个社区可以让我们在 Python Package Index(PyPI)中有如此庞大、多样化的软件包,用以扩展和改进 Python 并解决不可避免的问题。

在本系列中,我们将介绍七个可以帮助你解决常见 Python 问题的 PyPI 库。在第一篇文章中,我们了解了 Cython。今天,我们将使用 Black 这个代码格式化工具。

Black

有时创意可能是一件美妙的事情。有时它只是一种痛苦。我喜欢创造性地解决难题,但我希望我的 Python 格式尽可能一致。没有人对使用“有趣”缩进的代码印象深刻。

但是比不一致的格式更糟糕的是除了检查格式之外什么都没有做的代码审查。这对审查者来说很烦人,对于被审查者来说甚至更烦人。当你的 linter 告诉你代码缩进不正确时,但没有提示正确的缩进量,这也会令人气愤。

使用 Black,它不会告诉你做什么,它是一个优良、勤奋的机器人:它将为你修复代码。

要了解它如何工作的,请随意写一些非常不一致的内容,例如:

def add(a, b): return a+b

def mult(a, b):
      return \
        a              *        b

Black 抱怨了么?并没有,它为你修复了!

$ black math 
reformatted math
All done! ✨ ? ✨
1 file reformatted.
$ cat math 
def add(a, b):
    return a + b


def mult(a, b):
    return a * b

Black 确实提供了报错而不是修复的选项,甚至还有输出 diff 编辑样式的选项。这些选项在持续集成 (CI)系统中非常有用,可以在本地强制运行 Black。此外,如果 diff 输出被记录到 CI 输出中,你可以直接将其粘贴到 patch 中,以便在极少数情况下你需要修复输出,但无法本地安装 Black 使用。

$ black --check --diff bad 
--- math 2019-04-09 17:24:22.747815 +0000
+++ math 2019-04-09 17:26:04.269451 +0000
@@ -1,7 +1,7 @@
-def add(a, b): return a + b
+def add(a, b):
+    return a + b
 
 
 def mult(a, b):
-          return \
-                  a             *             b
+    return a * b
 
would reformat math
All done! ? ? ?
1 file would be reformatted.
$ echo $?
1

在本系列的下一篇文章中,我们将介绍 attrs ,这是一个可以帮助你快速编写简洁、正确的代码的库。

(题图:Subgrafik San


via: https://opensource.com/article/19/5/python-black

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在我们这个包含了 7 个 PyPI 库的系列文章中学习解决常见的 Python 问题的方法。

Python 是当今使用最多的流行编程语言之一,因为:它是开源的,它有广泛的用途(例如 Web 编程、业务应用、游戏、科学编程等等),它有一个充满活力和专注的社区支持它。这个社区可以让我们在 Python Package Index(PyPI)中有如此庞大、多样化的软件包,用以扩展和改进 Python 并解决不可避免的问题。

在本系列中,我们将介绍七个可以帮助你解决常见 Python 问题的 PyPI 库。首先是 Cython,一个简化 Python 编写 C 扩展的语言。

Cython

使用 Python 很有趣,但有时,用它编写的程序可能很慢。所有的运行时动态调度会带来很大的代价:有时它比用 C 或 Rust 等系统语言编写的等效代码慢 10 倍。

将代码迁移到一种全新的语言可能会在成本和可靠性方面付出巨大代价:所有的手工重写工作都将不可避免地引入错误。我们可以两者兼得么?

为了练习一下优化,我们需要一些慢代码。有什么比斐波那契数列的意外指数实现更慢?

def fib(n):
  if n < 2:
    return 1
  return fib(n-1) + fib(n-2)

由于对 fib 的调用会导致两次再次调用,因此这种效率极低的算法需要很长时间才能执行。例如,在我的新笔记本电脑上,fib(36) 需要大约 4.5 秒。这个 4.5 秒会成为我们探索 Python 的 Cython 扩展能提供的帮助的基准。

使用 Cython 的正确方法是将其集成到 setup.py 中。然而,使用 pyximport 可以快速地进行尝试。让我们将 fib 代码放在 fib.pyx 中并使用 Cython 运行它。

>>> import pyximport; pyximport.install()
>>> import fib
>>> fib.fib(36)

只使用 Cython 而不修改代码,这个算法在我笔记本上花费的时间减少到大约 2.5 秒。几乎无需任何努力,这几乎减少了 50% 的运行时间。当然,得到了一个不错的成果。

加把劲,我们可以让它变得更快。

cpdef int fib(int n):
  if n < 2:
    return 1
  return fib(n - 1) + fib(n - 2)

我们将 fib 中的代码变成用 cpdef 定义的函数,并添加了两个类型注释:它接受一个整数并返回一个整数。

这个变得快了,大约只用了 0.05 秒。它是如此之快,以至于我可能开始怀疑我的测量方法包含噪声:之前,这种噪声在信号中丢失了。

当下次你的 Python 代码花费太多 CPU 时间时,也许会导致风扇狂转,为何不看看 Cython 是否可以解决问题呢?

在本系列的下一篇文章中,我们将看一下 Black,一个自动纠正代码格式错误的项目。

(题图:Subgrafik San


via: https://opensource.com/article/19/5/python-cython

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出