《Python极客项目编程 》——2.3 代码-阿里云开发者社区

开发者社区> 异步社区> 正文

《Python极客项目编程 》——2.3 代码

简介: 本节书摘来自异步社区《Python极客项目编程 》一书中的第2章,第2.3节,作者 [美] Mahesh Venkitachalam,王海鹏 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。 2.3 代码 首先,定义类Sipro,来绘制这些曲线。
+关注继续查看

本节书摘来自异步社区《Python极客项目编程 》一书中的第2章,第2.3节,作者 [美] Mahesh Venkitachalam,王海鹏 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.3 代码

首先,定义类Sipro,来绘制这些曲线。我们会用这个类一次画一条曲线(利用draw()方法),并利用一个定时器和update()方法,产生一组随机螺线的动画。为了绘制Spiro对象并产生动画,我们将使用SpiroAnimator类。

要查看完整的项目代码,请直接跳到2.4节。

2.3.1 Spiro构造函数

下面是Spiro构造函数:

  # a class that draws a Spirograph
  class Spiro:
      # constructor
      def __init__(self, xc, yc, col, R, r, l):

          # create the turtle object
1         self.t = turtle.Turtle()
          # set the cursor shape
2       self.t.shape('turtle')
          # set the step in degrees
3        self.step = 5
          # set the drawing complete flag
4         self.drawingComplete = False

          # set the parameters
5         self.setparams(xc, yc, col, R, r, l)

          # initialize the drawing
6         self.restart()

在1行,Spiro构造函数创建一个新的turtle对象,这将有助于我们同时绘制多条螺线。在2行,将光标的形状设置为海龟(在https://docs.python.org/3.3/library/ turtle.html,你可以在turtle文档中找到其他选项)。在3行,将参数绘图角度的增量设置为5度,在4行,设置了一个标志,将在动画中使用它,它会产生一组螺线。

在5和6行,调用设置函数,接下来讨论该函数。

2.3.2 设置函数

现在让我们看看getParams()方法,它帮助初始化Spiro对象,如下所示:

      # set the parameters
      def setparams(self, xc, yc, col, R, r, l):
         # the Spirograph parameters
1         self.xc = xc
          self.yc = yc
2         self.R = int(R)
          self.r = int(r)
          self.l = l
          self.col = col
         # reduce r/R to its smallest form by dividing with the GCD
3         gcdVal = gcd(self.r, self.R)
4         self.nRot = self.r//gcdVal
         # get ratio of radii
         self.k = r/float(R)
         # set the color
         self.t.color(*col)
         # store the current angle
5         self.a = 0

在1行,保存曲线中心的坐标。然后在2行,将每个圆的半径(R和r)转换为整数并保存这些值。在3行,用Python模块fractions内置的gcd()方法来计算半径的GCD。我们将用这些信息来确定曲线的周期性,在4行将它保存为self.nRot。最后,在5行,保存当前的角度,我们将用它来创建动画。

2.3.3 restart()方法

接下来,restart()方法重置Spiro对象的绘制参数,让它准备好重画:

      # restart the drawing
      def restart(self):
          # set the flag
1        self.drawingComplete = False
          # show the turtle
2         self.t.showturtle()
          # go to the first point
3         self.t.up()
4         R, k, l = self.R, self.k, self.l
          a = 0.0
5         x = R*((1-k)*math.cos(a) + l*k*math.cos((1-k)*a/k))
          y = R*((1-k)*math.sin(a) - l*k*math.sin((1-k)*a/k))
6         self.t.setpos(self.xc + x, self.yc + y)
7         self.t.down()

这里用了布尔标志drawingComplete,来确定绘图是否已经完成,在1行初始化该标志。绘制多个Spiro对象时,这个标志是有用的,因为它可以追踪某个特定的螺线是否完成。在2行,显示海龟光标,以防它被隐藏。在3行提起笔,这样就可以在6行移动到第一个位置而不画线。在4行,使用了一些局部变量,以保持代码紧凑。然后,在5行,计算角度a设为0时的x和y坐标,以获得曲线的起点。最后,在7行,我们已完成,并落笔。Setpos()调用将绘制实际的线。

2.3.4 draw()方法

draw()方法用连续的线段绘制该曲线。

      # draw the whole thing
      def draw(self):
          # draw the rest of the points
          R, k, l = self.R, self.k, self.l
1         for i in range(0, 360*self.nRot + 1, self.step):
             a = math.radians(i)
2           x = R*((1-k)*math.cos(a) + l*k*math.cos((1-k)*a/k))
              y = R*((1-k)*math.sin(a) - l*k*math.sin((1-k)*a/k))
              self.t.setpos(self.xc + x, self.yc + y)
          # drawing is now done so hide the turtle cursor
3       self.t.hideturtle()

在1行,迭代遍历参数i的完整范围,它以度表示,是360乘以nRot。在2行,计算参数i的每个值对应的X和Y坐标。在3行,隐藏光标,因为我们已完成绘制。

2.3.5 创建动画

update()方法展示了一段一段绘制曲线来创建动画时所使用的绘图方法。

      # update by one step
      def update(self):
          # skip the rest of the steps if done
1       if self.drawingComplete:
              return
          # increment the angle
2         self.a += self.step
          # draw a step
          R, k, l = self.R, self.k, self.l
          # set the angle
3         a = math.radians(self.a)
          x= self.R*((1-k)*math.cos(a) + l*k*math.cos((1-k)*a/k))
          y = self.R*((1-k)*math.sin(a) - l*k*math.sin((1-k)*a/k))
          self.t.setpos(self.xc + x, self.yc + y)
          # if drawing is complete, set the flag
4         if self.a >= 360*self.nRot:
              self.drawingComplete = True
              # drawing is now done so hide the turtle cursor
              self.t.hideturtle()

在1行,update()方法检查drawingComplete标志是否设置。如果没有设置,则继续执行代码其余的部分。在2行,update()增加当前的角度。从3行开始,它计算当前角度对应的(X,Y)位置并将海龟移到那里,在这个过程中画出线段。

讨论万花尺方程时,我提到了曲线的周期性。在一定的角度后,万花尺的图案开始重复。在4行,检查角度是否达这条特定曲线计算的完整范围。如果是这样,就设置drawingComplete标志,因为绘图完成了。最后,隐藏海龟光标,你可以看到自己美丽的创作。

2.3.6 SpiroAnimator类

SpiroAnimator类让我们同时绘制随机的螺线。该类使用一个计时器,每次绘制曲线的一段。这种技术定期更新图像,并允许程序处理事件,如按键、鼠标点击,等等。但是,这种计时器技术需要对绘制代码进行一些调整。

  # a class for animating Spirographs
  class SpiroAnimator:
      # constructor
      def __init__(self, N):
          # set the timer value in milliseconds
1          self.deltaT = 10
          # get the window dimensions
2          self.width = turtle.window_width()
          self.height = turtle.window_height()
          # create the Spiro objects
3          self.spiros = []
          for i in range(N):
              # generate random parameters
4             rparams = self.genRandomParams()
              # set the spiro parameters
5              spiro = Spiro(*rparams)
              self.spiros.append(spiro)
              # call timer
6              turtle.ontimer(self.update, self.deltaT)

在1行,该SpiroAnimator构造函数将DeltaT设置为10,这是以毫秒为单位的时间间隔,将用于定时器。在2行,保存海龟窗口的尺寸。然后在3行创建一个空数组,其中将填入一些Spiro对象。这些封装的万花尺绘制,然后循环N次(N传入给构造函数SpiroAnimator),在5行创建一个新的Spiro对象,并将它添加到Spiro对象的列表中。这里的rparams是一个元组,需要传入到Spiro构造函数。但是,构造函数需要一个参数列表,所以用Python的*运算符将元组转换为参数列表。

最后,在6行,设置turtle.ontimer()方法每隔DeltaT毫秒调用update()。

请注意,在4行调用了一个辅助方法,名为genRandomParams()。接下来就看看这个方法。

2.3.7 genRandomParams()方法

我们用genRandomParams()方法来生成随机参数,在每个Spiro对象创建时发送给它,来生成各种曲线。

      # generate random parameters
      def genRandomParams(self):
         width, height = self.width, self.height
1         R = random.randint(50, min(width, height)//2)
2         r = random.randint(10, 9*R//10)
3         l = random.uniform(0.1, 0.9)
4        xc = random.randint(-width//2, width//2)
 5       yc = random.randint(-height//2, height//2)
6        col = (random.random(),
                 random.random(),
                 random.random())
7         return (xc, yc, col, R, r, l)

为了生成随机数,利用来自Python的random模块的两个方法:randint(),它返回指定范围内的随机整数,以及uniform(),它对浮点数做同样的事。在1行,将R设置为50至窗口短边一半长度的随机整数,在2行,将r设置为R的10%至90%之间。

然后,在3行,将l设置为0.1至0.9之间的随机小数。在4和5行,在屏幕边界内随机选择x和y坐标,选择屏幕上的一个随机点作为螺线的中心。在6行随机设置为红、绿和蓝颜色的成分,为曲线指定随机的颜色。最后,在7行,所有计算的参数作为一个元组返回。

2.3.8 重新启动程序

我们将用另一个restart()方法来重新启动程序。

# restart spiro drawing
    def restart(self):
        for spiro in self.spiros:
            # clear
            spiro.clear()
            # generate random parameters
            rparams = self.genRandomParams()
            # set the spiro parameters
            spiro.setparams(*rparams)
            # restart drawing
            spiro.restart()

它遍历所有的Spiro对象,清除以前绘制的每条螺线,分配新的螺线参数,然后重新启动程序。

2.3.9 update()方法

下面的代码展示了SproAnimator中的update()方法,它由定时器调用,以动画的形式更新所有的Spiro对象:

      def update(self):
         # update all spiros
1         nComplete = 0
         for spiro in self.spiros:
             # update
2             spiro.update()
             # count completed spiros
3             if spiro.drawingComplete:
                  nComplete += 1
         # restart if all spiros are complete
4         if nComplete == len(self.spiros):
              self.restart()
         # call the timer
5         turtle.ontimer(self.update, self.deltaT)

update()方法使用一个计数器nComplete来记录已画的Spiro对象的数目。在1行初始化后,它遍历Spiro对象的列表,在2行更新它们,如果一个Spiro完成,就在3行将计数器加1。

在循环外的4行,检查计数器,看看是否所有对象都已画完。如果已画完,调用restart()方法重新开始新的螺线动画。在5行restart()的末尾,调用计时器方法,它在DeltaT毫秒后再次调用update()。

2.3.10 显示或隐藏光标

最后,使用下面的方法来打开或关闭海龟光标。这可以让绘图更快。

    # toggle turtle cursor on and off
    def toggleTurtles(self):
        for spiro in self.spiros:
            if spiro.t.isvisible():
                spiro.t.hideturtle()
            else:
                spiro.t.showturtle()

2.3.11 保存曲线

使用saveDrawing()方法,将绘制保存为PNG图像文件。

  # save drawings as PNG files
  def saveDrawing():
      # hide the turtle cursor
1         turtle.hideturtle()
          # generate unique filenames
2         dateStr = (datetime.now()).strftime("%d%b%Y-%H%M%S")
          fileName = 'spiro-' + dateStr
          print('saving drawing to %s.eps/png' % fileName)
          # get the tkinter canvas
3         canvas = turtle.getcanvas()
          # save the drawing as a postscipt image
4         canvas.postscript(file = fileName + '.eps')
          # use the Pillow module to convert the postscript image file to PNG
5        img = Image.open(fileName + '.eps')
6         img.save(fileName + '.png', 'png')
          # show the turtle cursor
7        turtle.showturtle()

在1行,隐藏海龟光标,这样就不会在最后的图形中看到它。然后,在2行,使用datetime(),利用当前时间和日期(以“日—月—年—时—分—秒”的格式),以生成图像文件的唯一名称。将这个字符串加在spiro-后面,生成文件名。

turtle程序采用tkinter创建的用户界面(UI)窗口,在3和4行,利用tkinter的canvas对象,将窗口保存为嵌入式PostScript(EPS)文件格式。由于EPS是矢量格式,你可以用高分辨率打印它,但PNG用途更广,所以在5行用Pillow打开EPS文件,并在6行将它保存为PNG文件。最后,在7行,取消隐藏海龟光标。

2.3.12 解析命令行参数和初始化

像第1章中一样,在main()方法中用argparse来解析传入程序的命令行选项。

1     parser = argparse.ArgumentParser(description=descStr)

     # add expected arguments
2     parser.add_argument('--sparams', nargs=3, dest='sparams', required=False,
                          help="The three arguments in sparams: R, r, l.")

     # parse args
3     args = parser.parse_args()

在1行,创建参数解析器对象,在2行,向解析器添加--sparams可选参数。在3行,调用函数进行实际的解析。

接下来,代码设置了一些turtle参数。

     # set the width of the drawing window to 80 percent of the screen width
1     turtle.setup(width=0.8)

     # set the cursor shape to turtle
2     turtle.shape('turtle')

     # set the title to Spirographs!
3     turtle.title("Spirographs!")
     # add the key handler to save our drawings
4    turtle.onkey(saveDrawing, "s")
      # start listening
5     turtle.listen()

     # hide the main turtle cursor
6     turtle.hideturtle()

在1行,用setup()将绘图窗口的宽度设置为80%的屏幕宽度(你也可以给setup指定高度和原点参数)。在2行,设置光标形状为海龟,在3行,设置程序窗口的标题为Spirographs!,在4行,利用onkey()和saveDrawing,在按下S时保存图画。然后,在5行,调用listen()让窗口监听用户事件。最后,在6行,隐藏海龟光标。

命令行参数解析后,代码的其余部分进行如下:

      # check for any arguments sent to --sparams and draw the Spirograph
1    if args.sparams:
2        params = [float(x) for x in args.sparams]
          # draw the Spirograph with the given parameters
          col = (0.0, 0.0, 0.0)
3       spiro = Spiro(0, 0, col, *params)
4        spiro.draw()
      else:
          # create the animator object
5         spiroAnim = SpiroAnimator(4)
          # add a key handler to toggle the turtle cursor
6         turtle.onkey(spiroAnim.toggleTurtles, "t")
          # add a key handler to restart the animation
7        turtle.onkey(spiroAnim.restart, "space")

      # start the turtle main loop
8     turtle.mainloop()

在1行,首先检查是否有参数赋给--sparams。如果有,就从字符串中提取它们,用“列表解析”将它们转换成浮点数2(列表解析是一种Python结构,让你以紧凑而强大的方式创建一个列表,例如,a = [2*x for x in range(1, 5)]创建前4个偶数的列表)。

在3行,利用任何提取的参数来构造Spiro对象(利用Python的*运算符,它将列表转换为参数)。然后,在4行,调用draw(),绘制螺线。

现在,如果命令行上没有指定参数,就进入随机模式。在5行,创建一个SpiroAnimator对象,向它传入参数4,告诉它创建4幅图画。在6行,利用onkey()来捕捉按键T,这样就可以用它来切换海龟光标(toggleTurtles),在7行,处理空格键(space),这样就可以用它在任何时候重新启动动画。最后,在8行,调用mainloop()告诉tkinter窗口保持打开,监听事件。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
得到Go程序的汇编代码的方法
有多种方式可以获得Go程序的汇编代码, 尽管输出的格式有些不同,但是都是方便阅读的汇编代码,可以帮助我们更好的了解程序的底层运行方式。 我们看下面一段代码, 它是sync.Once的实现,去掉了不必要的注释,复制出来用来研究的一段小代码: once.
20911 0
【HEVC学习与研究】38、HEVC编码过程中的块分割结构
【本文主要分为前后两部分,前半部分基本是Vivienne Sze、Madhukar BudagaviGary和J. Sullivan所编著的《High Efficiency Video Coding (HEVC) ——Algorithms and Architectures》的第三章前半部分的笔记,后半部分是在HM-10.0中对Intra预测时块分割相应的代码研究。
1306 0
DL之RNN:人工智能为你写代码——基于TF利用RNN算法实现生成编程语言代码(C++语言)、训练&测试过程全记录(二)
DL之RNN:人工智能为你写代码——基于TF利用RNN算法实现生成编程语言代码(C++语言)、训练&测试过程全记录
29 0
numpy基础代码操练
In [20]: b[0,:,1] Out[20]: array([1, 5, 9]) In [21]: b[0,:,1] Out[21]: array([1, 5, 9]) In [22]: b[0,:,-1] Out[22]: array([ 3, 7, 11]) ...
929 0
05.Java网络编程(代码实践)
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路链接起来,在网络操作系统,网络管理软件及网络通信协议的协调下,实现资源贡献和信息传递的计算机系统 网络编程就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换 网...
786 0
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
12049
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载