Merge remote-tracking branch 'LCTT/master'

This commit is contained in:
Xingyu Wang 2020-06-28 07:27:31 +08:00
commit 27a934f1d8
3 changed files with 717 additions and 710 deletions

View File

@ -1,108 +1,123 @@
[#]: collector: (lujun9972)
[#]: translator: (MjSeven)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: url: (https://linux.cn/article-12356-1.html)
[#]: subject: (Simplify data visualization in Python with Plotly)
[#]: via: (https://opensource.com/article/20/5/plotly-python)
[#]: author: (Shaun Taylor-Morgan https://opensource.com/users/shaun-taylor-morgan)
使用 Plotly 来简化 Python 中的数据可视化
======
Plotly 是一个数据绘图库,具有整洁的接口,它旨在允许你构建自己的 API。
![Colorful sound wave graph][1]
> Plotly 是一个数据绘图库,具有整洁的接口,它旨在允许你构建自己的 API。
![](https://img.linux.net.cn/data/attachment/album/202006/27/215314y0rkrz0e9zw7wd2o.jpg)
Plotly 是一个绘图生态系统,可以让你在 [Python][2] 以及 JavaScript 和 R 中进行绘图。在本文中,我将重点介绍[使用 Python 库进行绘图][3]。
Plotly 有三种不同的 Python API你可以选择不同的方法来使用它
* 类似于 Matplotlib 的[面向对象的 API][4]
* [数据驱动的 API][5],通过构造类似 JSON 的数据结构来指定绘图
* 类似于 Matplotlib 的面向对象的 API
* 数据驱动的 API通过构造类似 JSON 的数据结构来定义绘图
* 类似于 Seaborn 的高级绘图接口,称为 “Plotly Express” API
* 类似于 Seaborn 的高级绘图接口,称为 ["Plotly Express" API][6]
我将通过使用每个 API 来绘制相同的图来探索它们:英国大选结果的分组柱状图。
我将探索使用每个 API 来绘制相同的图:英国大选结果的分组柱状图。
在我们进一步探讨之前,请注意,你可能需要调整你的 Python 环境来让这段代码运行,包括以下内容:
- 运行最新版本的Python[Linux][4]、[Mac][5] 和 [Windows][6] 的说明)
- 确认你运行的 Python 版本能与这些库一起工作
数据可在线获得,可以用 Pandas 导入。
```
import pandas as pd
df = pd.read_csv('https://anvil.works/blog/img/plotting-in-python/uk-election-results.csv')
```
现在我们可以继续进行了。
### 使用图对象来绘制图
Plotly 面向对象的 API 被称为图对象,它有点类似于 [Matplotlib 的面向对象 API][7]。
Plotly 面向对象的 API 被称为 `graph_objects`,它有点类似于 [Matplotlib 的面向对象 API][7]。
要创建一个柱状图,你可以构造一个包含四个柱状图的对象:
```
    # 导入 Plotly 和数据
    import plotly.graph_objects as go
    from votes import wide as df
# 导入 Plotly 和数据
import plotly.graph_objects as go
from votes import wide as df
    # 得到 x 列表
    years = df['year']
    x = list(range(len(years)))
# 得到 x 列表
years = df['year']
x = list(range(len(years)))
    # 指定绘图
    bar_plots = [
      go.Bar(x=x, y=df['conservative'], name='Conservative', marker=go.bar.Marker(color='#0343df')),
      go.Bar(x=x, y=df['labour'], name='Labour', marker=go.bar.Marker(color='#e50000')),
      go.Bar(x=x, y=df['liberal'], name='Liberal', marker=go.bar.Marker(color='#ffff14')),
      go.Bar(x=x, y=df['others'], name='Others', marker=go.bar.Marker(color='#929591')),
    ]
# 定义绘图
bar_plots = [
  go.Bar(x=x, y=df['conservative'], name='Conservative', marker=go.bar.Marker(color='#0343df')),
  go.Bar(x=x, y=df['labour'], name='Labour', marker=go.bar.Marker(color='#e50000')),
  go.Bar(x=x, y=df['liberal'], name='Liberal', marker=go.bar.Marker(color='#ffff14')),
  go.Bar(x=x, y=df['others'], name='Others', marker=go.bar.Marker(color='#929591')),
]
# 指定样式
layout = go.Layout(
  title=go.layout.Title(text="Election results", x=0.5),
  yaxis_title="Seats",
  xaxis_tickmode="array",
  xaxis_tickvals=list(range(27)),
  xaxis_ticktext=tuple(df['year'].values),
)
   
    # 指定样式
    layout = go.Layout(
      title=go.layout.Title(text="Election results", x=0.5),
      yaxis_title="Seats",
      xaxis_tickmode="array",
      xaxis_tickvals=list(range(27)),
      xaxis_ticktext=tuple(df['year'].values),
    )
       
    # 绘制柱状图
    fig = go.Figure(data=bar_plots, layout=layout)
# 绘制柱状图
fig = go.Figure(data=bar_plots, layout=layout)
    # 告诉 Plotly 去渲染
    fig.show()
# 告诉 Plotly 去渲染
fig.show()
```
与 Matplotlib 不同的是,你无需手动计算柱状图的 x 轴位置Plotly 会帮你适配。
与 Matplotlib 不同的是,你无需手动计算柱状图的 `x` 轴位置Plotly 会帮你适配。
最终结果图:
![A multi-bar plot made using Graph Objects][8]
A multi-bar plot made using Graph Objects (© 2019 [Anvil][9])
*A multi-bar plot made using Graph Objects (© 2019 [Anvil][9])*
### 使用 Python 数据结构来绘图
你还可以使用 Python 基本数据结构来定绘图,它与面对对象 API 具有相同的结构。这直接对应于 Plotly 的 JavaScript 实现的 JSON API。
你还可以使用 Python 基本数据结构来定绘图,它与面对对象 API 具有相同的结构。这直接对应于 Plotly 的 JavaScript 实现的 JSON API。
```
    # 指定绘图数据
    fig = {
        'data': [
            {'type': 'bar', 'x': x, 'y': df['conservative'], 'name': 'Conservative', 'marker': {'color': '#0343df'}},
            {'type': 'bar', 'x': x, 'y': df['labour'], 'name': 'Labour', 'marker': {'color': '#e50000'}},
            {'type': 'bar', 'x': x, 'y': df['liberal'], 'name': 'Liberal', 'marker': {'color': '#ffff14'}},
            {'type': 'bar', 'x': x, 'y': df['others'], 'name': 'Others', 'marker': {'color': '#929591'}},
        ],
        'layout': {
            'title': {'text': 'Election results', 'x': 0.5},
            'yaxis': {'title': 'Seats'},
            'xaxis': {
                'tickmode': 'array',
                'tickvals': list(range(27)),
                'ticktext': tuple(df['year'].values),
            }
# 定义绘图数据
fig = {
    'data': [
        {'type': 'bar', 'x': x, 'y': df['conservative'], 'name': 'Conservative', 'marker': {'color': '#0343df'}},
        {'type': 'bar', 'x': x, 'y': df['labour'], 'name': 'Labour', 'marker': {'color': '#e50000'}},
        {'type': 'bar', 'x': x, 'y': df['liberal'], 'name': 'Liberal', 'marker': {'color': '#ffff14'}},
        {'type': 'bar', 'x': x, 'y': df['others'], 'name': 'Others', 'marker': {'color': '#929591'}},
    ],
    'layout': {
        'title': {'text': 'Election results', 'x': 0.5},
        'yaxis': {'title': 'Seats'},
        'xaxis': {
            'tickmode': 'array',
            'tickvals': list(range(27)),
            'ticktext': tuple(df['year'].values),
        }
    }
}
    # 告诉 Plotly 去渲染它
    pio.show(fig)
# 告诉 Plotly 去渲染它
pio.show(fig)
```
最终结果与上次完全相同:
![A multi-bar plot made using JSON-like data structures][10]
A multi-bar plot made using JSON-like data structures (© 2019 [Anvil][9])
*A multi-bar plot made using JSON-like data structures (© 2019 [Anvil][9])*
#### 使用 Plotly Express 进行绘图
@ -111,23 +126,23 @@ A multi-bar plot made using JSON-like data structures (© 2019 [Anvil][9])
你可以使用一行代码来绘制柱状图:
```
    # 导入 Plotly 和数据
    import plotly.express as px
    from votes import long as df
# 导入 Plotly 和数据
import plotly.express as px
from votes import long as df
    # 定义颜色字典获得自定义栏颜色
    cmap = {
        'Conservative': '#0343df',
        'Labour': '#e50000',
        'Liberal': '#ffff14',
        'Others': '#929591',
    }
   
    # 生成图
    fig = px.bar(df, x="year", y="seats", color="party", barmode="group", color_discrete_map=cmap)
# 定义颜色字典获得自定义栏颜色
cmap = {
    'Conservative': '#0343df',
    'Labour': '#e50000',
    'Liberal': '#ffff14',
    'Others': '#929591',
}
# 生成图
fig = px.bar(df, x="year", y="seats", color="party", barmode="group", color_discrete_map=cmap)
```
这里使用了 [Long Form][12] 数据,也称为“整洁数据”。这些列代表年份、政党和席位,而不是按政党划分。这与在 [Seaborn][13] 中制作柱状图非常相似。
这里使用了<ruby>[长表][12]<rt>Long Form</rt></ruby> 数据,也称为“整洁数据”。这些列代表年份、政党和席位,而不是按政党划分。这与在 [Seaborn][13] 中制作柱状图非常相似。
```
>> print(long)
@ -147,38 +162,38 @@ A multi-bar plot made using JSON-like data structures (© 2019 [Anvil][9])
[108 rows x 3 columns]
```
你可以访问底层的图对象 API 进行详细调整。如添加标题和 y 轴标签:
你可以访问底层的图对象 API 进行详细调整。如添加标题和 `y` 轴标签:
```
    # 使用图对象 API 来调整绘图
    import plotly.graph_objects as go
    fig.layout = go.Layout(
        title=go.layout.Title(text="Election results", x=0.5),
        yaxis_title="Seats",
    )
# 使用图对象 API 来调整绘图
import plotly.graph_objects as go
fig.layout = go.Layout(
    title=go.layout.Title(text="Election results", x=0.5),
    yaxis_title="Seats",
)
```
最后,让 Plotly 渲染:
```
    fig.show()
fig.show()
```
这将在未使用的端口上运行一个临时 Web 服务器,并打开默认的 Web 浏览器来查看图像Web 服务器将会马上被关闭)。
不幸的是结果并不完美。x 轴被视为整数,因此两组之间的距离很远且很小,这使得我们很难看到趋势。
不幸的是,结果并不完美。`x` 轴被视为整数,因此两组之间的距离很远且很小,这使得我们很难看到趋势。
![使用 Plotly Express 制作的柱状图][14]
A multi-bar plot made using Plotly Express (© 2019 [Anvil][9])
*A multi-bar plot made using Plotly Express (© 2019 [Anvil][9])*
你可能会尝试通过将 x 值转换为字符串来使 Plotly Express 将其视为字符串,这样它就会以均匀的间隔和词法顺序来绘制。不幸的是,它们的间隔还是很大,设置 x 值不像在图对象中那样设置
你可能会尝试通过将 `x` 值转换为字符串来使 Plotly Express 将其视为字符串,这样它就会以均匀的间隔和词法顺序来绘制。不幸的是,它们的间隔还是很大,像在 `graph_objects`中那样设置 `xaxis_tickvals` 也不行
与 [Seaborn][13] 中的类似示例不同,在这种情况下,抽象似乎没有提供足够的[应急方案][15]来提供你想要的东西,但是也许你可以编写 _自己_ 的 API
与 [Seaborn][13] 中的类似示例不同,在这种情况下,抽象似乎没有提供足够的[应急方案][15]来提供你想要的东西,但是也许你可以编写*自己*的 API
### 构建自己的 Plotly API
对 Plotly 的操作方式不满意?构建自己的 Plotly API
对 Plotly 的操作方式不满意?那就构建自己的 Plotly API
Plotly 的核心是一个 JavaScript 库,它使用 [D3][16] 和 [stack.gl][17] 进行绘图。JavaScript 库的接口使用指定的 JSON 结构来绘图。因此,你只需要输出 JavaScript 库喜欢使用的 JSON 结构就好了。
@ -186,42 +201,42 @@ Anvil 这样做是为了创建一个完全在浏览器中工作的 Python Plotly
![Ployly 使用 JavaScript 库创建图形,由其它语言库通过 JSON 使用][18]
Plotly uses a JavaScript library to create plots, driven by libraries in other languages via JSON  2019 [Anvil][9])
*Plotly uses a JavaScript library to create plots, driven by libraries in other languages via JSON  2019 [Anvil][9])*
在 Anvil 版本中,你可以同时使用图对象 API 和上面介绍的 Python 数据结构方法。运行完全相同的命令,将数据和布局分配给 Anvil 应用程序中的 [Plot 组件][19]。
这是用 Anvil 的客户端 Python API 绘制的柱状图:
这是用 Anvil 的客户端 Python API 绘制的多列柱状图:
```
# Import Anvil libraries
# 导入 Anvil 库
from ._anvil_designer import EntrypointTemplate
from anvil import *
import anvil.server
# Import client-side Plotly
# 导入客户端 Plotly
import plotly.graph_objs as go
# This is an Anvil Form
# 这是一个 Anvil 表单
class Entrypoint(EntrypointTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    # Fetch the data from the server
    # 从服务器获取数据
    data = anvil.server.call('get_election_data')
   
    #  Get a convenient list of x-values
    # 获取一个方便的 x 值列表
    years = data['year']
    x = list(range(len(years)))
    # Specify the plots
    # 定义绘图
    bar_plots = [
      go.Bar(x=x, y=data['conservative'], name='Conservative', marker=go.Marker(color='#0343df')),
      go.Bar(x=x, y=data['labour'], name='Labour', marker=go.Marker(color='#e50000')),
      go.Bar(x=x, y=data['liberal'], name='Liberal', marker=go.Marker(color='#ffff14')),
      go.Bar(x=x, y=data['others'], name='Others', marker=go.Marker(color='#929591')),
    ]
    # Specify the layout
    # 规定布局
    layout = {
      'title': 'Election results',
      'yaxis': {'title': 'Seats'},
@ -232,7 +247,7 @@ class Entrypoint(EntrypointTemplate):
      },
    }
    # Make the multi-bar plot
    # 生成多列柱状图
    self.plot_1.data = bar_plots
    self.plot_1.layout = layout
```
@ -243,8 +258,7 @@ class Entrypoint(EntrypointTemplate):
![The election plot on the web using Anvil's client-side-Python Plotly library][20]
The election plot on the web using Anvil's [client-side-Python][21] Plotly library (© 2019 [Anvil][9])
使用 Anvil 的 [Python 客户端][21] Plotly 库在网络上进行选举的情节 (© 2019 [Anvil][9])
*The election plot on the web using Anvil's [client-side-Python][21] Plotly library (© 2019 [Anvil][9])*
你可以[复制此示例][22]作为一个 Anvil 应用程序注意Anvil 需要注册才能使用)。
@ -252,60 +266,53 @@ The election plot on the web using Anvil's [client-side-Python][21] Plotly libra
### 在 Plotly 中自定义交互
Plotly 绘图不仅是动态的,你可以自定义它们的互动行为。例如,你可以在每个条形图中使用 hovertemplate 自定义工具提示的格式:
Plotly 绘图不仅是动态的,你可以自定义它们的互动行为。例如,你可以在每个柱状图中使用 `hovertemplate` 自定义工具提示的格式:
```
    go.Bar(
      x=x,
      y=df['others'],
      name='others',
      marker=go.bar.Marker(color='#929591'),
      hovertemplate='Seats: &lt;b&gt;%{y}&lt;/b&gt;',
    ),
go.Bar(
x=x,
y=df['others'],
name='others',
marker=go.bar.Marker(color='#929591'),
hovertemplate='Seats: <b>%{y}</b>',
),
```
当你把这个应用到每个柱状图时,你会看到以下结果:
![A multi-bar plot with custom tool-tips][23]
A multi-bar plot with custom tool-tips (© 2019 [Anvil][9])
*A multi-bar plot with custom tool-tips (© 2019 [Anvil][9])*
这很有用,当你想要在某些事件发生时执行另一些事件时它会表现的更好(例如,当用户将鼠标悬停在栏上,你想要显示有关相关选择的信息框)。在 Anvil 的 Plotly 库中,你可以将事件处理程序绑定到诸如悬停之类的事件,这使得复杂的交互成为可能。
这很有用,当你想要在某些事件发生时执行任何你想要的代码就更好了(例如,当用户将鼠标悬停在栏上,你想要显示一个相关选举的信息框)。在 Anvil 的 Plotly 库中,你可以将事件处理程序绑定到诸如悬停之类的事件,这使得复杂的交互成为可能。
![A multi-bar plot with a hover event handler][24]
A multi-bar plot with a hover event handler (© 2019 [Anvil][9])
*A multi-bar plot with a hover event handler (© 2019 [Anvil][9])*
你可以通过将方法绑定到绘图的悬停事件来实现:
```
  def plot_1_hover(self, points, **event_args):
    """This method is called when a data point is hovered."""
    i = points[0]['point_number']
    self.label_year.text = self.data['year'][i]
    self.label_con.text = self.data['conservative'][i]
    self.label_lab.text = self.data['labour'][i]
    self.label_lib.text = self.data['liberal'][i]
    self.label_oth.text = self.data['others'][i]
    url = f"<https://en.wikipedia.org/wiki/{self.data\['year'\]\[i\]}\_United\_Kingdom\_general\_election>"
    self.link_more_info.text = url
    self.link_more_info.url = url
def plot_1_hover(self, points, **event_args):
"""This method is called when a data point is hovered."""
i = points[0]['point_number']
self.label_year.text = self.data['year'][i]
self.label_con.text = self.data['conservative'][i]
self.label_lab.text = self.data['labour'][i]
self.label_lib.text = self.data['liberal'][i]
self.label_oth.text = self.data['others'][i]
url = f"https://en.wikipedia.org/wiki/{self.data['year'][i]}_United_Kingdom_general_election"
self.link_more_info.text = url
self.link_more_info.url = url
```
这是一种相当极端的交互性,从开发人员的角度来看,也是一种极端的可定制性。这都要归功于 Plotly 的架构 - 它有一个整洁的接口,明确设计用于构建自己的 API。到处都可以看到这种出色的设计
这是一种相当极端的交互性,从开发人员的角度来看,也是一种极端的可定制性。这都要归功于 Plotly 的架构 —— 它有一个简洁的接口明确的设计是为了让你建立自己的API。如果到处都能看到这种伟大的设计那将会很有帮助
### 使用 Bokeh 进行自定义交互
现在你已经了解了 Plotly 如何使用 JavaScript 来创建动态图,并且可以使用 Anvil 的客户端编写 Python 代码在浏览器中实时编辑它们。
Bokeh 是另一个 Python 绘图库,它输出可嵌入 Web 应用程序的 HTML 文档,并获得与 Plotly 提供的功能类似的动态功能(如果你想知道如何发音,那就是 "BOE-kay")。
* * *
_本文基于 Anvil 博客中的 [如何使用 Plotly 来绘图][9]一文已允许使用。_
Bokeh 是另一个 Python 绘图库,它可以输出可嵌入 Web 应用程序的 HTML 文档,并获得与 Plotly 提供的功能类似的动态功能(如果你想知道如何发音,那就是 “BOE-kay”
--------------------------------------------------------------------------------
@ -314,7 +321,7 @@ via: https://opensource.com/article/20/5/plotly-python
作者:[Shaun Taylor-Morgan][a]
选题:[lujun9972][b]
译者:[MjSeven](https://github.com/MjSeven)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
@ -322,10 +329,10 @@ via: https://opensource.com/article/20/5/plotly-python
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/colorful_sound_wave.png?itok=jlUJG0bM (Colorful sound wave graph)
[2]: https://opensource.com/resources/python
[3]: https://opensource.com/article/20/4/plot-data-python
[4]: tmp.c4bQMTUIxx#GraphObjects
[5]: tmp.c4bQMTUIxx#DataDrivenAPI
[6]: tmp.c4bQMTUIxx#PlotlyExpress
[3]: https://linux.cn/article-12327-1.html
[4]: https://opensource.com/article/20/4/install-python-linux
[5]: thttps://opensource.com/article/19/5/python-3-default-mac
[6]: https://opensource.com/article/19/8/how-install-python-windows
[7]: https://opensource.com/article/20/5/matplotlib-python
[8]: https://opensource.com/sites/default/files/uploads/plotly.png (A multi-bar plot made using Graph Objects)
[9]: https://anvil.works/blog/plotting-in-plotly

View File

@ -1,576 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (lxbwolf)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Stop debugging Go with Println and use Delve instead)
[#]: via: (https://opensource.com/article/20/6/debug-go-delve)
[#]: author: (Gaurav Kamathe https://opensource.com/users/gkamathe)
Stop debugging Go with Println and use Delve instead
======
Delve is packed with functionality to make debugging a breeze.
![Bug tracking magnifying glass on computer screen][1]
When was the last time you tried to learn a new programming language? Do you stick with your tried and true, or are you one of the brave souls who tries out a new one as soon as it is announced? Either way, learning a new language can be extremely useful, and a lot of fun.
You try out with a simple "Hello, world!" then move onto writing some sample code and executing it, making minor changes along the way and then moving on from there. I am sure we have all gone through this experience no matter which technology we've worked on. Though if you do manage to stay with a language for some time, and you wish to become proficient in it, there are a few things that can help you along the way.
One of those things is a debugger. Some people prefer debugging using simple "print" statements in code, and they are fine for a few simple line programs; however, if you are working on a large project with multiple developers and thousands of lines of code, it makes sense to invest in a debugger.
I've recently started learning the Go programming language, and in this article, we will explore a debugger called Delve. Delve is a special utility made to debug Go programs, and we will cover some of its features using some sample Go code. Don't worry about the Go code examples presented here; they are understandable even if you have never programmed in Go before. One of the goals of Go is simplicity, so the code is consistent, which can be easier to understand and interpret.
### Introduction to Delve
Delve is an open source project hosted at [GitHub][2].
In its own words:
_Delve is a debugger for the Go programming language. The goal of the project is to provide a simple, full-featured debugging tool for Go. Delve should be easy to invoke and easy to use. Chances are if you're using a debugger, things aren't going your way. With that in mind, Delve should stay out of your way as much as possible._
Let's take a closer look.
My test system is a laptop running Fedora Linux along with the following Go compiler version:
```
$ cat /etc/fedora-release
Fedora release 30 (Thirty)
$
$ go version
go version go1.12.17 linux/amd64
$
```
### Golang installation
If you do not have Go installed, you can get it by simply running the following command to fetch it from your configured repositories.
```
`$ dnf install golang.x86_64`
```
Alternatively, you can visit the [install page][3] for other installation options suitable for your OS distribution.
Before we get started, please ensure that the following required PATHS are set, as required by Go tools. If these paths are not set, some of the examples might not work. These can be easily set as environment variables in your SHELL's RC file, like the `$HOME/bashrc` file in my case.
```
$ go env | grep GOPATH
GOPATH="/home/user/go"
$
$ go env | grep GOBIN
GOBIN="/home/user/go/gobin"
$
```
### Delve installation
You can install Delve by running a simple `go get` command, as shown below. `go get` is the Golang way of downloading and installing required packages from external sources. If you do face any issues with installation, please refer to the install instructions for Delve [here][4].
```
$ go get -u github.com/go-delve/delve/cmd/dlv
$
```
Running the above command downloads Delve to your $GOPATH location, which, in the default case, happens to be $HOME/go. It will be different if you have set $GOPATH to something else.
You can move to the go/ directory, within which you will see `dlv` under bin/ directory.
```
$ ls -l $HOME/go
total 8
drwxrwxr-x. 2 user user 4096 May 25 19:11 bin
drwxrwxr-x. 4 user user 4096 May 25 19:21 src
$
$ ls -l ~/go/bin/
total 19596
-rwxrwxr-x. 1 user user 20062654 May 25 19:17 dlv
$
```
Since you installed Delve under $GOPATH, it is also available as a regular shell command, so you do not have to move to the directory where it is installed every time. You can verify `dlv` is correctly installed by running it with the `version` option. The version it installed is 1.4.1.
```
$ which dlv
~/go/bin/dlv
$
$ dlv version
Delve Debugger
Version: 1.4.1
Build: $Id: bda606147ff48b58bde39e20b9e11378eaa4db46 $
$
```
Now, let's use Delve with some Go programs to understand its features and how to use them. As we do with all programs, let's start with a simple "Hello, world!" message, which, in Go, is called `hello.go`.
Remember, I am placing these sample programs within the $GOBIN directory.
```
$ pwd
/home/user/go/gobin
$
$ cat hello.go
package main
import "fmt"
func main() {
        fmt.Println("Hello, world!")
}
$
```
To build a Go program, you run the `build` command and provide it with the source file using the .go extention. If the program is free of any syntax issues, the Go compiler compiles it and puts out a binary or executables file. This file can then be executed directly, and we see the "Hello, world!" message displayed onscreen.
```
$ go build hello.go
$
$ ls -l hello
-rwxrwxr-x. 1 user user 1997284 May 26 12:13 hello
$
$ ./hello
Hello, world!
$
```
### Loading a program in Delve
There are two ways to load a program into the Delve debugger.
**Using the debug argument when source code is not yet compiled to binary.**
The first way is to use the **debug** command when you simply need to source files. Delve compiles a binary for you named **__debug_bin**, and loads it into the debugger.
In this example, move to the directory where **hello.go** is present and run the **dlv debug** command. If there are multiple Go source files within a directory and each has its own main function, then Delve might throw an error, expecting a single program or a single project to build a binary from. Should that occur, you're better off using the second option, presented below.
```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun  4 11:48 hello.go
$
$ dlv debug
Type 'help' for list of commands.
(dlv)
```
Now open another terminal and list the contents of the same directory. You see an additional **__debug_bin** binary that was compiled from the source code and loaded into the debugger. You can now move to the **dlv** prompt to continue using Delve further.
 
```
$ ls -l
total 2036
-rwxrwxr-x. 1 user user 2077085 Jun  4 11:48 __debug_bin
-rw-rw-r--. 1 user user      74 Jun  4 11:48 hello.go
$
```
**Using the exec argument**
The second method to load a program into Delve is useful when you have a pre-compiled Go binary, or one that you have already compiled using the **go build** command, and don't want Delve to compile it to a `__debug_bin` binary. In such cases, use the **exec** argument to load the binary directory into the Delve debugger.
```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun  4 11:48 hello.go
$
$ go build hello.go
$
$ ls -l
total 1956
-rwxrwxr-x. 1 user user 1997284 Jun  4 11:54 hello
-rw-rw-r--. 1 user user      74 Jun  4 11:48 hello.go
$
$ dlv exec ./hello
Type 'help' for list of commands.
(dlv)
```
### Getting help within Delve
At the **dlv** prompt, you can run `help` to have a look at the various help options available in Delve. The command list is quite extensive, and we will cover some of the important features here. Following is an overview of Delve's functionality.
```
(dlv) help
The following commands are available:
Running the program:
Manipulating breakpoints:
Viewing program variables and memory:
Listing and switching between threads and goroutines:
Viewing the call stack and selecting frames:
Other commands:
Type help followed by a command for full documentation.
(dlv)
```
#### Setting breakpoints
Now that we have loaded the hello.go program within the Delve debugger, let's set the breakpoint on our main function and then confirm it. Within Go, the main program starts with `main.main`, so you need to provide this name to the `break command`. Next, we will see if the breakpoint was set correctly using the `breakpoints` command.
Also, remember you can use shorthands for commands, so instead of using `break main.main`, you can also use `b main.main` to the same effect, or `bp` instead of `breakpoints`. To find the exact shorthand for a command, please refer to the help section by running the `help` command.
```
(dlv) break main.main
Breakpoint 1 set at 0x4a228f for main.main() ./hello.go:5
(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x42c410 for runtime.fatalthrow() /usr/lib/golang/src/runtime/panic.go:663 (0)
Breakpoint unrecovered-panic at 0x42c480 for runtime.fatalpanic() /usr/lib/golang/src/runtime/panic.go:690 (0)
        print runtime.curg._panic.arg
Breakpoint 1 at 0x4a228f for main.main() ./hello.go:5 (0)
(dlv)
```
#### Continue execution of the program
Now, let's continue running the program using "continue." It will run until it hits a breakpoint, which, in our case, is the `main.main` or the main function. From there, we can use the `next` command to execute the program line by line. Notice that, once we move ahead of `fmt.Println("Hello, world!")`, we can see that `Hello, world!` is printed to the screen while we are still within the debugger session.
```
(dlv) continue
&gt; main.main() ./hello.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a228f)
     1: package main
     2:
     3: import "fmt"
     4:
=&gt;   5:      func main() {
     6:         fmt.Println("Hello, world!")
     7: }
(dlv) next
&gt; main.main() ./hello.go:6 (PC: 0x4a229d)
     1: package main
     2:
     3: import "fmt"
     4:
     5: func main() {
=&gt;   6:              fmt.Println("Hello, world!")
     7: }
(dlv) next
Hello, world!
&gt; main.main() ./hello.go:7 (PC: 0x4a22ff)
     2:
     3: import "fmt"
     4:
     5: func main() {
     6:         fmt.Println("Hello, world!")
=&gt;   7:      }
(dlv)
```
#### Quitting Delve
If you wish to quit the debugger at any time, you can run the `quit` command, and you will be returned to the shell prompt. Pretty simple, right?
```
(dlv) quit
$
```
Let's use some other Go programs to explore some other Delve features. This time, we will pick a program from the [Golang tour][5]. If you are learning Go, the Golang tour should be your first stop.
The following program, `functions.go`, simply displays how functions are defined and called in a Go program. Here, we have a simple `add()` function that adds two numbers and returns their value. You can build the program and execute it, as shown below.
```
$ cat functions.go
package main
import "fmt"
func add(x int, y int) int {
        return x + y
}
func main() {
        fmt.Println(add(42, 13))
}
$
```
You can build the program and execute it as shown below.
```
$ go build functions.go  &amp;&amp; ./functions
55
$
```
#### Stepping into functions
As shown earlier, let's load the binary into the Delve debugger using one of the options mentioned earlier, again setting a breakpoint at `main.main` and continuing to run the program while we hit the breakpoint. Then hit `next` until you reach `fmt.Println(add(42, 13))`; here we make a call to the `add()` function. We can use the Delve `step` command to move from the `main` function to the `add()` function, as seen below.
```
$ dlv debug
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x4a22b3 for main.main() ./functions.go:9
(dlv) c
&gt; main.main() ./functions.go:9 (hits goroutine(1):1 total:1) (PC: 0x4a22b3)
     4:
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
=&gt;   9:      func main() {
    10:         fmt.Println(add(42, 13))
    11: }
(dlv) next
&gt; main.main() ./functions.go:10 (PC: 0x4a22c1)
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=&gt;  10:              fmt.Println(add(42, 13))
    11: }
(dlv) step
&gt; main.add() ./functions.go:5 (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=&gt;   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv)
```
#### Set breakpoint using filename: linenumber
Above, we went through `main` and then moved to the `add()` function, but you can also use the `filename:linenumber` combination to set a breakpoint directly where we want it. Following is another way of setting a breakpoint at the start of the `add()` function.
```
(dlv) break functions.go:5
Breakpoint 1 set at 0x4a2280 for main.add() ./functions.go:5
(dlv) continue
&gt; main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=&gt;   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv)
```
#### View current stack details
Now that we are at the `add()` function, we can view the current contents of the stack using the `stack` command in Delve. This shows the topmost function `add()` at 0 where we are, followed by `main.main` at 1 from where the `add()` function was called. Functions below `main.main` belong to the Go runtime, which is responsible for loading and executing the program.
```
(dlv) stack
0  0x00000000004a2280 in main.add
   at ./functions.go:5
1  0x00000000004a22d7 in main.main
   at ./functions.go:10
2  0x000000000042dd1f in runtime.main
   at /usr/lib/golang/src/runtime/proc.go:200
3  0x0000000000458171 in runtime.goexit
   at /usr/lib/golang/src/runtime/asm_amd64.s:1337
(dlv)
```
#### Move between frames
Using the `frame` command in Delve, we can switch between the above frames at will. In the example below, using `frame 1` switches us from within the `add()` frame to the `main.main` frame, and so on.
```
(dlv) frame 0
&gt; main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 0: ./functions.go:5 (PC: 4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=&gt;   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv) frame 1
&gt; main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 1: ./functions.go:10 (PC: 4a22d7)
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=&gt;  10:              fmt.Println(add(42, 13))
    11: }
(dlv)
```
**Print function arguments**
A function often accepts multiple arguments to work on. In the case of the `add()` function, it accepts two integers. Delve has a handy command called `args`, which displays the command-line arguments passed to the function.
```
(dlv) args
x = 42
y = 13
~r2 = 824633786832
(dlv)
```
#### View disassembly
Since we are dealing with compiled binaries, it is extremely helpful to be able to see the assembly language instructions produced by the compiler. Delve provides a `disassemble` command to view these. In the example below, we use it to view the disassembly instructions for the `add()` function.
```
(dlv) step
&gt; main.add() ./functions.go:5 (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=&gt;   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv) disassemble
TEXT main.add(SB) /home/user/go/gobin/functions.go
=&gt;   functions.go:5  0x4a2280   48c744241800000000   mov qword ptr [rsp+0x18], 0x0
        functions.go:6  0x4a2289   488b442408           mov rax, qword ptr [rsp+0x8]
        functions.go:6  0x4a228e   4803442410           add rax, qword ptr [rsp+0x10]
        functions.go:6  0x4a2293   4889442418           mov qword ptr [rsp+0x18], rax
        functions.go:6  0x4a2298   c3                   ret
(dlv)
```
#### Stepout of functions
Another feature is `stepout`, which allows us to return back to where the function was called from. In our example, if we wish to return to the `main.main` function, we can simply run the `stepout` command and it will take us back. This can be quite a handy tool to help you move around in a large codebase.
```
(dlv) stepout
&gt; main.main() ./functions.go:10 (PC: 0x4a22d7)
Values returned:
        ~r2: 55
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=&gt;  10:              fmt.Println(add(42, 13))
    11: }
(dlv)
```
Let's use another sample program from the [Go tour][6] to see how Delve deals with variables in Go. The following sample program defines and initializes some variables of different types. You can build the program and execute it.
```
$ cat variables.go
package main
import "fmt"
var i, j int = 1, 2
func main() {
        var c, python, java = true, false, "no!"
        fmt.Println(i, j, c, python, java)
}
$
$ go build variables.go &amp;&amp; ./variables
1 2 true false no!
$
```
#### Print variable information
As stated earlier, use `delve debug` to load the program in the debugger. You can use the `print` command from within Delve along with the variable name to show their current values.
```
(dlv) print c
true
(dlv) print java
"no!"
(dlv)
```
Alternatively, you can use the `locals` command to print all local variables within a function.
```
(dlv) locals
python = false
c = true
java = "no!"
(dlv)
```
If you are not aware of the type of the variable, you can use the `whatis` command along with the variable name to print the type.
```
(dlv) whatis python
bool
(dlv) whatis c
bool
(dlv) whatis java
string
(dlv)
```
### Conclusion
So far, we have only scratched the surface of the features Delve offers. You can refer to the `help` section and try out various other commands. Some of the other useful features include attaching Delve to running Go programs (daemons!) or even using Delve to explore some of the internals of Golang libraries, provided you have the Go source code package installed. Keep exploring!
--------------------------------------------------------------------------------
via: https://opensource.com/article/20/6/debug-go-delve
作者:[Gaurav Kamathe][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/gkamathe
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/bug_software_issue_tracking_computer_screen.jpg?itok=6qfIHR5y (Bug tracking magnifying glass on computer screen)
[2]: https://github.com/go-delve/delve
[3]: https://golang.org/doc/install
[4]: https://github.com/go-delve/delve/blob/master/Documentation/installation/linux/install.md
[5]: https://tour.golang.org/basics/4
[6]: https://tour.golang.org/basics/9

View File

@ -0,0 +1,576 @@
[#]: collector: (lujun9972)
[#]: translator: (lxbwolf)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Stop debugging Go with Println and use Delve instead)
[#]: via: (https://opensource.com/article/20/6/debug-go-delve)
[#]: author: (Gaurav Kamathe https://opensource.com/users/gkamathe)
使用 Delve 代替 Println 来调试 Go 程序
======
Delve 是能让调试变成轻而易举的事的万能工具包。
![Bug tracking magnifying glass on computer screen][1]
你上次尝试去学习一种新的编程语言时什么时候?你有没有持之以恒,或者你是那些在新事物发布的第一时间就勇敢地去尝试的一员吗?不管怎样,学习一种新的语言也许非常有用,也会有很多乐趣。
你尝试着写简单的 “Hello, world!”,然后写一些实例代码,运行它,继续做一些小的修改,之后继续做一些改动。我敢保证我们都有过这个经历,不论我们使用哪种技术。假如你尝试一直用一种语言,并且你希望能称为技术专家,有一些事物能在你的进取之路上帮助你。
其中之一就是调试器。有些人喜欢在代码中用简单的 “print” 语句进行调试,这种方式很适合代码量少的简单程序;然而,如果你处理的是有多个开发者和几千行代码的大型项目,你应该使用调试器。
最近我开始学习 Go 编程语言了,在本文中,我们将探讨 Delve 调试器。Delve 是专门用来调试 Go 程序的工具,我们会借助一些 Go 示例代码来了解下它的一些功能。不要担心这里展示的 Go 示例代码;即使你之前没有写过 Go 代码也能看懂。Go 的目标之一是简单,因此代码是始终如一的,理解和解释起来都很容易。
### Delve 介绍
Delve 是托管在 [GitHub][2] 上的一个开源项目。
它自己的文档中写道:
*Delve 是 Go 编程语言的调试器。该项目的目标是为 Go 提供一个简单、全功能的调试工具。Delve 的引入和使用都很简单。当你使用调试器时,事情可能不会按你的思路运行。如果你这样想,那么你不适合用 Delve。*
让我们来近距离看一下。
我的测试系统是运行着 Fedora Linux 的笔记本电脑Go 编译器版本如下:
```
$ cat /etc/fedora-release
Fedora release 30 (Thirty)
$
$ go version
go version go1.12.17 linux/amd64
$
```
### Golang 安装
如果你没有安装 Go你可以运行下面的命令很轻松地就可以从配置的仓库中获取。
```
`$ dnf install golang.x86_64`
```
或者,你可以在[安装页面][3]找到适合你的操作系统的其他安装版本。
在开始之前,请先确认已经设置好了 Go tool 依赖的下列各个 PATH。如果这些路径没有设置有些示例可能不能正常运行。你可以在 SHELL 的 RC 文件中轻松设置这些环境变量,我的机器上是在 `$HOME/bashrc` 文件中设置的。
```
$ go env | grep GOPATH
GOPATH="/home/user/go"
$
$ go env | grep GOBIN
GOBIN="/home/user/go/gobin"
$
```
### Delve 安装
你可以像下面那样,通过运行一个简单的 `go get` 命令来安装 Delve。`go get` 是 Golang 从外部源下载和安装需要的包的方式。如果你安装过程中遇到了问题,可以查看[Delve 安装教程][4]
```
$ go get -u github.com/go-delve/delve/cmd/dlv
$
```
运行上面的命令,就会把 Delve 下载到你的 \$GOPATH 的位置,如果你没有把 \$GOPATH 设置成其他值,那么默认情况下 \$GOPATH 和 \$HOME/go 是同一个路径。
你可以进入 go/ 目录,你可以在 bin/ 目录下看到 `dlv`
```
$ ls -l $HOME/go
total 8
drwxrwxr-x. 2 user user 4096 May 25 19:11 bin
drwxrwxr-x. 4 user user 4096 May 25 19:21 src
$
$ ls -l ~/go/bin/
total 19596
-rwxrwxr-x. 1 user user 20062654 May 25 19:17 dlv
$
```
因为你把 Delve 安装到了 \$GOPATH所以你可以像运行普通的 shell 命令一样运行它,即每次运行时你不必先进入它所在的目录。你可以通过 `version` 选项来验证 `dlv` 是否正确安装。示例中安装的版本是 1.4.1。
```
$ which dlv
~/go/bin/dlv
$
$ dlv version
Delve Debugger
Version: 1.4.1
Build: $Id: bda606147ff48b58bde39e20b9e11378eaa4db46 $
$
```
现在,我们一起在 Go 程序中使用 Delve 来理解下它的功能以及如何使用它们。我们先来写一个 `hello.go`,简单地打印一条 "Hello, world!" 信息。
记着,我把这些示例程序放到了 \$GOBIN 目录下。
```
$ pwd
/home/user/go/gobin
$
$ cat hello.go
package main
import "fmt"
func main() {
        fmt.Println("Hello, world!")
}
$
```
运行 `build` 命令来编译一个 Go 程序,它的输入是 `.go` 后缀的文件。如果程序没有语法错误Go 编译器把它编译成一个二进制或可执行文件。这个文件可以被直接运行,运行后我们会在屏幕上看到 "Hello, world!" 信息。
```
$ go build hello.go
$
$ ls -l hello
-rwxrwxr-x. 1 user user 1997284 May 26 12:13 hello
$
$ ./hello
Hello, world!
$
```
### 在 Delve 中加载程序
把一个程序加载进 Delve 调试器有两种方式。
**在源码编译成二进制文件之前使用 debug 参数。**
第一种方式是在需要时对源码使用 **debug** 命令。Delve 会为你编译出一个名为 **_debug_bin** 的二进制文件,并把它加载进调试器。
在这个例子中,你可以进入 **hello.go** 所在的目录,然后运行 **dlv debug** 命令。如果目录中有多个源文件且每个文件都有自己的主函数Delve 则可能抛出错误,它期望的是单个程序或从单个项目构建成单个二进制文件。如果出现了这种错误,那么你就应该用下面展示的第二种方式。
```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun  4 11:48 hello.go
$
$ dlv debug
Type 'help' for list of commands.
(dlv)
```
现在打开另一个终端,列出目录下的文件。你可以看到一个多出的 **__debug_bin** 二进制文件,这个文件是由源码编译生成的,并会加载进调试器。你现在可以回到 **dlv** 提示框继续使用 Delve。
```
$ ls -l
total 2036
-rwxrwxr-x. 1 user user 2077085 Jun  4 11:48 __debug_bin
-rw-rw-r--. 1 user user      74 Jun  4 11:48 hello.go
$
```
**使用 exec 参数**
如果你已经有提前编译好的 Go 程序或者已经用 **go build** 命令编译完成了,不想再用 Delve 编译出 `__debug_bin` 二进制文件,那么第二种把程序加载进 Delve 的方法在这些情况下会很有用。在上述情况下,你可以使用 **exec** 参数来把整个目录加载进 Delve 调试器。
```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun  4 11:48 hello.go
$
$ go build hello.go
$
$ ls -l
total 1956
-rwxrwxr-x. 1 user user 1997284 Jun  4 11:54 hello
-rw-rw-r--. 1 user user      74 Jun  4 11:48 hello.go
$
$ dlv exec ./hello
Type 'help' for list of commands.
(dlv)
```
### 查看 delve 帮助信息
**dlv** 提示框中,你可以运行 help 来查看 Delve 提供的多种帮助选项。命令列表相当长,这里我们只列举一些重要的功能。下面是 Delve 的功能概览。
```
(dlv) help
The following commands are available:
Running the program:
Manipulating breakpoints:
Viewing program variables and memory:
Listing and switching between threads and goroutines:
Viewing the call stack and selecting frames:
Other commands:
Type help followed by a command for full documentation.
(dlv)
```
#### 设置断点
现在我们已经把 hello.go 程序加载进了 Delve 调试器,我们在主函数处设置断点,稍后来确认它。在 Go 中,主程序从 `main.main` 处开始执行,因此你需要把这个名字提供个 `break` 命令。之后,我们可以用 `breakpoints` 命令来检查断点是否正确设置了。
不要忘了你还可以用命令简写,因此你可以用 `b main.main` 来代替 `break main.main`,两者效果相同,`bp` 和 `breakpoints` 同理。你可以通过运行 `help` 命令查看帮助信息来找到你想要的命令简写。
```
(dlv) break main.main
Breakpoint 1 set at 0x4a228f for main.main() ./hello.go:5
(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x42c410 for runtime.fatalthrow() /usr/lib/golang/src/runtime/panic.go:663 (0)
Breakpoint unrecovered-panic at 0x42c480 for runtime.fatalpanic() /usr/lib/golang/src/runtime/panic.go:690 (0)
        print runtime.curg._panic.arg
Breakpoint 1 at 0x4a228f for main.main() ./hello.go:5 (0)
(dlv)
```
#### 程序继续执行
现在,我们用 `continue` 来继续运行程序。它会运行到断点处中止,在我们的例子中,会运行到主函数的 `main.main` 处中止。从这里开始,我们可以用 `next` 命令来逐行执行程序。请注意,当我们运行到 `fmt.Println("Hello, world!")` 处时,即使我们还在调试器里,我们也能看到打印到屏幕的 `Hello, world!`
```
(dlv) continue
> main.main() ./hello.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a228f)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func main() {
     6:         fmt.Println("Hello, world!")
     7: }
(dlv) next
> main.main() ./hello.go:6 (PC: 0x4a229d)
     1: package main
     2:
     3: import "fmt"
     4:
     5: func main() {
=>   6:              fmt.Println("Hello, world!")
     7: }
(dlv) next
Hello, world!
> main.main() ./hello.go:7 (PC: 0x4a22ff)
     2:
     3: import "fmt"
     4:
     5: func main() {
     6:         fmt.Println("Hello, world!")
=>   7:      }
(dlv)
```
#### 退出 Delve
你随时可以运行 `quit` 命令来退出调试器,退出之后你会回到 shell 提示框。相当简单,对吗?
```
(dlv) quit
$
```
我们用其他的 Go 程序来探索下 Delve 的其他功能。这次,我们从 [golang 教程][5] 中找了一个程序。如果你要学习 Go 语言,那么 Golang tour 应该是你的第一站。
下面的程序,`functions.go` 中简单展示了 Go 程序中是怎样定义和调用函数的。这里,我们有一个简单的把两数相加并返回和值的 `add()` 函数。你可以像下面那样构建程序并运行它。
```
$ cat functions.go
package main
import "fmt"
func add(x int, y int) int {
        return x + y
}
func main() {
        fmt.Println(add(42, 13))
}
$
```
你可以像下面那样构建和运行程序。
```
$ go build functions.go  && ./functions
55
$
```
#### 进入函数
跟前面展示的一样,我们用前面提到的一个选项来把二进制文件加载进 Delve 调试器,再一次在 `main.main` 处设置断点,继续运行程序直到断点处。然后执行 `next` 直到 `fmt.Println(add(42, 13))` 处;这里我们调用了 `add()` 函数。我们可以像下面展示的那样,用 Delve 的 `step` 命令从 `main` 函数进入 `add()` 函数。
```
$ dlv debug
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x4a22b3 for main.main() ./functions.go:9
(dlv) c
> main.main() ./functions.go:9 (hits goroutine(1):1 total:1) (PC: 0x4a22b3)
     4:
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
=>   9:      func main() {
    10:         fmt.Println(add(42, 13))
    11: }
(dlv) next
> main.main() ./functions.go:10 (PC: 0x4a22c1)
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=>  10:              fmt.Println(add(42, 13))
    11: }
(dlv) step
> main.add() ./functions.go:5 (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv)
```
#### 使用`文件名:行号`来设置断点
上面的例子中,我们经过 `main` 函数进入了 `add()` 函数,但是你也可以在你想加断点的地方直接使用`文件名:行号`的组合。下面是在 `add()` 函数开始处加断点的另一种方式。
```
(dlv) break functions.go:5
Breakpoint 1 set at 0x4a2280 for main.add() ./functions.go:5
(dlv) continue
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv)
```
#### 查看当前的栈信息
现在我们运行到了 `add()` 函数,我们可以在 Delve 中用 `stack` 命令查看当前栈的内容。这里在 0 位置展示了栈顶的函数 `add()` ,紧接着在 1 位置展示了调用 `add()` 函数的 `main.main`。在`main.main` 下面的函数属于 Go 运行时,是用来处理加载和执行该程序的。
```
(dlv) stack
0  0x00000000004a2280 in main.add
   at ./functions.go:5
1  0x00000000004a22d7 in main.main
   at ./functions.go:10
2  0x000000000042dd1f in runtime.main
   at /usr/lib/golang/src/runtime/proc.go:200
3  0x0000000000458171 in runtime.goexit
   at /usr/lib/golang/src/runtime/asm_amd64.s:1337
(dlv)
```
#### 在 frame 间跳转
在 Delve 中我们可以用 `frame` 命令实现 frame 间的跳转。在下面的例子中,我们用 `frame` 实现了从 `add()` frame 跳到 `main.main` frame以此类推。
```
(dlv) frame 0
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 0: ./functions.go:5 (PC: 4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv) frame 1
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 1: ./functions.go:10 (PC: 4a22d7)
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=>  10:              fmt.Println(add(42, 13))
    11: }
(dlv)
```
**打印函数参数**
一个函数通常会接收多个参数。在 `add()` 函数中它的入参是两个整型。Delve 有个便捷的 `args` 命令,它能打印出命令行传给函数的参数。
```
(dlv) args
x = 42
y = 13
~r2 = 824633786832
(dlv)
```
#### 查看反汇编码
由于我们是调试编译出的二进制文件因此如果我们能查看编译器生成的汇编语言指令将会非常有用。Delve 提供了一个 `disassemble` 命令来查看这些指令。在下面的例子中,我们用它来查看 `add()` 函数的汇编指令。
```
(dlv) step
> main.add() ./functions.go:5 (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv) disassemble
TEXT main.add(SB) /home/user/go/gobin/functions.go
=>   functions.go:5  0x4a2280   48c744241800000000   mov qword ptr [rsp+0x18], 0x0
        functions.go:6  0x4a2289   488b442408           mov rax, qword ptr [rsp+0x8]
        functions.go:6  0x4a228e   4803442410           add rax, qword ptr [rsp+0x10]
        functions.go:6  0x4a2293   4889442418           mov qword ptr [rsp+0x18], rax
        functions.go:6  0x4a2298   c3                   ret
(dlv)
```
#### 单步退出函数
另一个功能是 `stepout`,这个功能可以让我们跳回到函数被调用的地方。在我们的例子中,如果我们想回到 `main.main` 函数,我们只需要简单地运行 `stepout` 命令,它就会把我们带回去。在我们调试大型代码库时,这个功能会是一个非常便捷的工具。
```
(dlv) stepout
> main.main() ./functions.go:10 (PC: 0x4a22d7)
Values returned:
        ~r2: 55
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=>  10:              fmt.Println(add(42, 13))
    11: }
(dlv)
```
我们一起通过 [Go 教程][6] 的另一个示例程序来看下 Delve 是怎么处理 Go 中的变量的。下面的示例程序定义和初始化了一些不同类型的变量。你可以构建和运行程序。
```
$ cat variables.go
package main
import "fmt"
var i, j int = 1, 2
func main() {
        var c, python, java = true, false, "no!"
        fmt.Println(i, j, c, python, java)
}
$
$ go build variables.go &&; ./variables
1 2 true false no!
$
```
#### 打印变量信息
像前面说过的那样,用 `delve debug` 在调试器中加载程序。你可以在 Delve 中用 `print` 命令通过变量名来展示他们当前的值。
```
(dlv) print c
true
(dlv) print java
"no!"
(dlv)
```
或者,你还可以用 `locals` 命令来打印函数内所有的局部变量。
```
(dlv) locals
python = false
c = true
java = "no!"
(dlv)
```
如果你不知道变量的类型,你可以用 `whatis` 命令来通过变量名来打印它的类型。
```
(dlv) whatis python
bool
(dlv) whatis c
bool
(dlv) whatis java
string
(dlv)
```
### 总结
现在我们只是了解了 Delve 所有功能的皮毛。你可以自己去查看 `help` 内容,尝试下其他的命令。你还可以把 Delve 绑定到运行中的 Go 程序上(守护进程!),如果你安装了 Go 源码库,你甚至可以用 Delve 导出 Golang 库内部的信息。勇敢去探索吧!
--------------------------------------------------------------------------------
via: https://opensource.com/article/20/6/debug-go-delve
作者:[Gaurav Kamathe][a]
选题:[lujun9972][b]
译者:[lxbwolf](https://github.com/lxbwolf)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/gkamathe
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/bug_software_issue_tracking_computer_screen.jpg?itok=6qfIHR5y (Bug tracking magnifying glass on computer screen)
[2]: https://github.com/go-delve/delve
[3]: https://golang.org/doc/install
[4]: https://github.com/go-delve/delve/blob/master/Documentation/installation/linux/install.md
[5]: https://tour.golang.org/basics/4
[6]: https://tour.golang.org/basics/9