《R的极客理想—工具篇》—— 1.4 formatR 代码自动化排版

简介:

本节书摘来自华章出版社《R的极客理想—工具篇》一 书中的第1章,第1.4节,作者:张丹,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

1.4 formatR 代码自动化排版

问题
如何写出让别人看得懂,且符合规范的代码呢?
screenshot

引言
新手写的代码,大都不注重代码规范,以为实现功能就算完成了。这种代码不仅别人不愿意读,过几个月后再看自己都会觉得很烂。不仅仅新手如此,大多数程序员写的代码都没有考虑如何让别人看着更方便。程序员最痛苦的事情,不是每天加班写程序,而是每天加班读懂别人写的程序。最后,有人实在忍受不了其他人的丑陋代码,便开始制定代码编程规范,又有人去实现代码的自动化排版工具。formatR就是这样的一个R语言自动化排版的工具。

1.4.1 formatR介绍

formatR包是一个实用的包,提供了R代码格式化功能,可以自动设置空格、缩进、换行等代码格式,让代码看起来更友好。formatR包中的API中主要有下面5个函数。
tidy.source: 对代码进行格式化
tidy.eval: 输出格式化后的R代码和运行结果
usage: 格式化函数定义,并按指定宽度输出
tidy.gui: 一个GUI工具,支持编辑并格式化R代码
tidy.dir: 对某个目录下,所有R脚本进行格式化

1.4.2 formatR安装

本节使用的系统环境是:
Win7 64bit
R: 3.0.1 x86_64-w64-mingw32/x64 b4bit
注 formatR同时支持Windows 7环境和Linux环境。
formatR的安装过程如下:

~ R  # 启动R程序
> install.packages("formatR")  # 安装formatR包
library(formatR)  # formatR加载

1.4.3 formatR的使用

  1. 字符串格式化
tidy.source()函数,以字符串作为输入参数,对代码格式化。
> tidy.source(text = c("{if(TRUE)1 else 2; if(FALSE){1+1", "## comments", "} else 2}"))
{
    if (TRUE)
        1 else 2
    if (FALSE) {
        1 + 1
        ## comments
    } else 2
}

通过执行tidy.source()函数,把代码进行了重新格式化,让我们一眼就可以看得懂。

  1. 文件格式化
    messy.R是一个不太规范的R程序文件。我们读入这个文件,然后通过tidy.source()函数,以文件对象作为输入参数,进行代码格式化。
> messy = system.file("format", "messy.R", package = "formatR")
> messy
[1] "C:/Program Files/R/R-3.0.1/library/formatR/format/messy.R"
messy.R 的原始代码输出:
> src = readLines(messy)
> cat(src,sep="\n")
    # a single line of comments is preserved
1+1

if(TRUE){
x=1  # inline comments
}else{
x=2;print('Oh no... ask the right bracket to go away!')}
1*3  # one space before this comment will become two!
2+2+2    # 'short comments'

lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)))  
### only'single quotes' are allowed in comments
1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1  ## comments after a long line
'a character string with \t in it'

## here is a long long long long long long long long long long long long long
 long long long long long long long comment

格式化后的代码输出:

> tidy.source(messy)
# a single line of comments is preserved
1 + 1
if (TRUE) {
    x = 1  # inline comments
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3  # one space before this comment will become two!
2 + 2 + 2  # 'short comments'

lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100), x2 =
 rnorm(100)))  ### only 'single quotes' are allowed in comments
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
1 + 1 + 1  ## comments after a long line
"a character string with \t in it"
## here is a long long long long long long long long long long long long long
long long long
##long long long long comment

可以看出,格式化后的输出,经过了空格、缩进、换行、注释等处理,代码可读性就增强了。

  1. 格式化并输出R脚本文件
    新建R脚本文件demo.r。
~ vi demo.r
a<-1+1;a;matrix(rnorm(10),5);
if(a>2) { b=c('11',832);"#a>2";} else print('a is invalid!!')
格式化demo.r。
> x = "demo.r"
> tidy.source(x)
a <- 1 + 1
a
matrix(rnorm(10), 5)
if (a > 2) {
    b = c("11", 832)
    "#a>2"
} else print("a is invalid!!")

输出格式化结果到文件demo2.r,如图1-3所示。

> f="demo2.r"
> tidy.source(x, keep.blank.line = TRUE, file = f)
> file.show(f)

screenshot

  1. 输出格式化代码和运行结果
    使用tidy.eval()函数,以字符串形式,执行R脚本:
> tidy.eval(text = c("a<-1+1;a", "matrix(rnorm(10),5)"))
a <- 1 + 1
a
## [1] 2
matrix(rnorm(10), 5)
## [,1] [,2]
## [1,] 0.65050729 0.1725221
## [2,] 0.05174598 0.3434398
## [3,] -0.91056310 0.1138733
## [4,] 0.18131010 -0.7286614
## [5,] 0.40811952 1.8288346

这样直接在当前的运行环境中,就输出了代码和运行结果。

  1. 格式化函数定义
    通过usage()函数可以只打印出函数定义,跳过函数细节。以var()函数为例,输入var,默认会打印出一个函数细节。
> var
function (x, y = NULL, na.rm = FALSE, use)
{
if (missing(use))
use <- if (na.rm)
"na.or.complete"
else "everything"
na.method <- pmatch(use, c("all.obs", "complete.obs", "pairwise.complete.obs",
"everything", "na.or.complete"))
if (is.na(na.method))
stop("invalid 'use' argument")
if (is.data.frame(x))
x <- as.matrix(x)
else stopifnot(is.atomic(x))
if (is.data.frame(y))
y <- as.matrix(y)
else stopifnot(is.atomic(y))
.Call(C_cov, x, y, na.method, FALSE)
}
<bytecode: 0x0000000008fad030>
<environment: namespace:stats>

> usage(var)  # 通过usage,只打印函数定义
var(x, y = NULL, na.rm = FALSE, use)

有时候函数定义也很长,比如lm()函数,通过usage的width参数可以控制函数的显示宽度。

> usage(lm)
lm(formula, data, subset, weights, na.action, method = "qr", model = TRUE, x =
FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, contrasts = NULL, offset, ...)

> usage(lm,width=30)  # usage的width参数,控制函数的显示宽度
lm(formula, data, subset, weights,
    na.action, method = "qr", model = TRUE,
    x = FALSE, y = FALSE, qr = TRUE,
    singular.ok = TRUE, contrasts = NULL,
    offset, ...)
  1. GUI工具
    tidy.gui()函数是一个GUI的工具,可以在界面上编辑并格式化R代码。首先安装gWidgetsRGtk2库:
> install.packages("gWidgetsRGtk2")
also installing the dependencies 'RGtk2', 'gWidgets'
打开GUI控制台:
> library("gWidgetsRGtk2")
> g = tidy.gui()

我们输入一段不太好看的代码,如图1-4所示。

screenshot

点击“转换”,结果如图1-5所示,可以看到,在GUI的编辑器中,R语言的代码被格式化了!

图1-5 格式化后的代码

  1. 格式化目录中的文件
    tidy.dir()函数可以批量格式化文件,对目录中的所有文件进行格式化操作。下面新建目录:dir,并在目录dir中新建两个R脚本文件:dir.r, dir2.r。
~ mkdir dir  # 新建目录 dir
~ cd dir
~ vi dir.r  # 用vi新建文件dir.r
a<-1+1;a;matrix(rnorm(10),5);

~ vi dir2.r
if(a>2) { b=c('11',832);"#a>2";} else print('a is invalid!!')
执行tidy.dir:
> tidy.dir(path="dir")
tidying dir/dir.r
tidying dir/dir2.r
分别查看dir.r和dir2.r:
~ vi dir.r
a <- 1 + 1
a
matrix(rnorm(10), 5)

~ vi dir2.r
if (a > 2) {
    b = c("11", 832)
    "#a>2"
} else print("a is invalid!!")

我们发现不规则的代码,已经被格式化了!

1.4.4 formatR的源代码解析

通过上面的使用,我们不难发现,formatR包的核心函数就是tidy.source()函数,从Github上面找到源代码:github.com/yihui/formatR/blob/master/R/tidy.R。我将在代码中增加注释:

tidy.source = function(
  source = 'clipboard', keep.comment = getOption('keep.comment', TRUE),
  keep.blank.line = getOption('keep.blank.line', TRUE),
  replace.assign = getOption('replace.assign', FALSE),
  left.brace.newline = getOption('left.brace.newline', FALSE),
  reindent.spaces = getOption('reindent.spaces', 4),
  output = TRUE, text = NULL,
  width.cutoff = getOption('width'), ...
) {

   if (is.null(text)) {   # 判断输入来源为剪贴板
    if (source == 'clipboard' && Sys.info()['sysname'] == 'Darwin') {
      source = pipe('pbpaste')
    }
  } else {  # 判断输入来源为字符串
    source = textConnection(text); on.exit(close(source))
  }

 text = readLines(source, warn = FALSE)  # 按行读取来源数据
   if (length(text) == 0L || all(grepl('^\\s*$', text))) {   # 文件行数判断
    if (output) cat('\n', ...)
    return(list(text.tidy = text, text.mask = text))
  }

    if (keep.blank.line && R3) {  # 空行处理
    one = paste(text, collapse = '\n') # record how many line breaks before/after
    n1 = attr(regexpr('^\n*', one), 'match.length')
    n2 = attr(regexpr('\n*$', one), 'match.length')
  }
 
  if (keep.comment) text = mask_comments(text, width.cutoff, keep.blank.line)
  # 注释处理
  text.mask = tidy_block(text, width.cutoff, replace.assign && length(grep('=', text)))
  # 把输入的R代码,先转成表达式,再转回字符串。用来实现对每个语句的截取
  text.tidy = if (keep.comment) unmask.source(text.mask) else text.mask
  # 对注释排版
  text.tidy = reindent_lines(text.tidy, reindent.spaces)  # 重新定位缩进
  if (left.brace.newline) text.tidy = move_leftbrace(text.tidy)    # 扩号换行
  if (keep.blank.line && R3) text.tidy = c(rep('', n1), text.tidy, rep('', n2))
  # 增加首尾空行
  if (output) cat(paste(text.tidy, collapse = '\n'), '\n', ...)
  # 在console打印格式化后的结果
  invisible(list(text.tidy = text.tidy, text.mask = text.mask))
  # 返回,但不打印结果
}

1.4.5 源代码中的Bug

在读源代码的过程中,我发现有一个小问题,即在R 3.0.1版本,没有对向右赋值操作(->)进行处理。我已经就这个问题给作者提Bug了,参见github.com/yihui/formatR/issues/31。Bug测试代码如下:

> c('11',832)->x2
> x2
[1] "11"  "832"

> tidy.source(text="c('11',832)->x2")   # 格式化代码
c("11", 832) <- x2

> tidy.eval(text="c('11',832)->x2")
c("11", 832) <- x2
Error in eval(expr, envir, enclos) : object 'x2' not found
Bug已修复。作者回复:“这个问题已经在R 3.0.2中修正了。”
> formatR::tidy.source(text="c('11',832)->x2") # 格式化代码
x2 <- c("11", 832)
> sessionInfo()
R version 3.0.2 (2013-09-25)
Platform: x86_64-pc-linux-gnu (64-bit)
locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
 [9] LC_ADDRESS=C               LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base
loaded via a namespace (and not attached):
[1] formatR_0.10.3

formatR包提供的功能非常实用,特别是读别人写的不规范的代码的时候。建议各IDE厂商能把formatR作为标准的格式化工具直接嵌入编辑器的工具中。让我们把阅读别人的代码,也变成一件快乐的事情吧。

相关文章
|
5月前
|
运维 Prometheus 监控
3 年部署经验总结:用自动化工具轻松管理 300+ 服务器开源软件
三年前接手公司IT部门时,我满怀信心,却发现部署效率低下。尽管使用了GitLab、Jenkins、Zabbix等100+开源工具,部署仍耗时费力。文档厚重如百科,却难解实际困境。一次凌晨三点的加班让我下定决心改变现状。偶然看到一篇国外博客,介绍了自动化部署的高效方式,我深受启发。
233 0
|
4月前
|
机器学习/深度学习 人工智能 测试技术
EdgeMark:嵌入式人工智能工具的自动化与基准测试系统——论文阅读
EdgeMark是一个面向嵌入式AI的自动化部署与基准测试系统,支持TensorFlow Lite Micro、Edge Impulse等主流工具,通过模块化架构实现模型生成、优化、转换与部署全流程自动化,并提供跨平台性能对比,助力开发者在资源受限设备上高效选择与部署AI模型。
424 9
EdgeMark:嵌入式人工智能工具的自动化与基准测试系统——论文阅读
|
3月前
|
人工智能 搜索推荐 UED
一个牛逼的国产AI自动化工具,开源了 !
AiPy是国产开源AI工具,结合大语言模型与Python,支持本地部署。用户只需用自然语言描述需求,即可自动生成并执行代码,轻松实现数据分析、清洗、可视化等任务,零基础也能玩转编程,被誉为程序员的智能助手。
|
4月前
|
Java 测试技术 API
自动化测试工具集成及实践
自动化测试用例的覆盖度及关键点最佳实践、自动化测试工具、集成方法、自动化脚本编写等(兼容多语言(Java、Python、Go、C++、C#等)、多框架(Spring、React、Vue等))
207 6
|
5月前
|
人工智能 数据可视化 数据挖掘
团队日报不用愁!自动化生成工具亲测:任务进度实时同步
本文深入分析了传统手动日报在数据孤岛、格式混乱和时效性差三大痛点,并探讨了自动化日报带来的效率提升、决策优化等四大核心价值。通过对板栗看板、SmartBrief、n8n 等六款主流工具的功能、适用场景及实战效果进行测评,为企业提供科学的选型建议与实施路径。
团队日报不用愁!自动化生成工具亲测:任务进度实时同步
|
3月前
|
人工智能 缓存 自然语言处理
86_自动化提示:AutoPrompt工具
在当今人工智能领域,提示工程(Prompt Engineering)已成为释放大语言模型(LLM)潜能的关键技术。随着LLM规模和能力的不断增长,如何设计高效、精确的提示词成为研究和应用的焦点。然而,传统的手工提示工程面临着巨大挑战
|
5月前
|
人工智能 供应链 数据可视化
工作流梳理工具实战教程:手把手教你绘制第一张自动化流程图
本文剖析了团队因流程混乱导致重复劳动和效率低下的问题,提出通过工作流梳理提升协作效率的解决方案。总结了流程梳理的六大核心需求,并深度测评了6款主流工具,国内有板栗看板那,国外有kiss flow结合团队规模与需求提供选型建议,助力企业高效落地流程优化。
|
4月前
|
安全 搜索推荐 机器人
风险规则引擎-RPA 作为自动化依赖业务决策流程的强大工具
机器人流程自动化(RPA)是一种通过软件“机器人”自动执行重复性任务的技术,能大幅提升工作效率。它适用于财务、电商等领域的标准化流程,如账单处理和退货管理。然而,RPA在复杂决策场景中存在局限,需结合决策模型(DMN)和业务规则管理系统(BRMS)实现流程与决策的协同自动化,从而增强灵活性与业务价值。
|
6月前
|
数据采集 人工智能 API
推荐一款Python开源的AI自动化工具:Browser Use
Browser Use 是一款基于 Python 的开源 AI 自动化工具,融合大型语言模型与浏览器自动化技术,支持网页导航、数据抓取、智能决策等操作,适用于测试、爬虫、信息提取等多种场景。
845 4
推荐一款Python开源的AI自动化工具:Browser Use
|
6月前
|
运维 监控 持续交付
还在为部署开源工具烦恼?自动化部署工具 Websoft9一键部署 300+ 开源应用
在数字化时代,开源工具因免费、灵活、可定制等特性广受欢迎,但其部署过程却常因环境配置复杂、依赖繁琐、耗时长等问题令人头疼。本文介绍了传统部署的三大难点,并提出两种解决方案:传统手动部署与集成化控制台部署。
还在为部署开源工具烦恼?自动化部署工具 Websoft9一键部署 300+ 开源应用

热门文章

最新文章