从前,我编写了一个编解码器,以将IPython Notebooks(.ipynb
)转换为“装饰的” Python代码,反之亦然。实践证明,这对于处理与笔记本相对应的源代码非常有用。在我的团队中,我们使用它来将经过修饰的Python源存储在我们的git repos中(而不是有时巨大的.ipynb
文件中)。我们可以比较笔记本(与其他笔记本或沿版本图),在我们喜欢的IDE中对其进行重构,等等。我们通常在代码库中有一些示例笔记本作为示例和部分文档(它们在构建时就已混合并运行)文档)。
语法是:
nb2py example.ipynb -o example.py
和
py2nb example.py -o example.ipynb
现在,我想设置一个Makefile
以更新所有.py
和.ipynb
文件,以使最后更改文件形式的方式。
例如,当我修改foo.py
时,foo.ipynb
将被更新以反映该更改。但是,如果我改为编辑foo.ipynb
(大概是通过Jupyter),那么foo.py
将被更新以反映该更改。
我有一个简单的Makefile
,它几乎可以满足我的要求:
.PHONY: clean update nb py
# py --> nb
PY_SRC = $(wildcard *.py)
NB_DST = $(PY_SRC:.py=.ipynb)
# nb --> py
NB_SRC = $(wildcard *.ipynb)
PY_DST = $(NB_SRC:.ipynb=.py)
%.ipynb: %.py
py2nb $< -o $@ && touch -r $< $@
%.py: %.ipynb
nb2py $< -o $@ && touch -r $< $@
clean:
tbd
update: nb py
nb: $(NB_DST)
py: $(PY_DST)
touch -r
命令将目标文件设置为与源文件具有完全相同的时间戳,这样就不会在第二make
内再次对其进行更新,并且也不会更新回来源。
我可以做:make nb
,它会更新所有早于相应.ipynb
的陈旧.py
文件。我可以make py
进行其他选择。 make
有很多喘不过气,但是警告我有关整个设置的明显循环性。但这是对的。
update
目标无法完全正常工作。 py --> nb
方向有效,反之则不然。
最后,我不知道如何实现clean
。举例来说,在我的上下文中,我希望clean
删除将不更新相应的.ipynb
的任何.py
(因为{{1 }}不存在,或者是因为它较旧。)
答案 0 :(得分:2)
Make不是为此类事情设计的。它不喜欢循环依赖。尝试使用纯make解决该问题可能会创建一个难以维护的makefile。我将尝试创建一个工件文件,并使用Shell逻辑按如下所示进行复制:
.%.updated: %.py %.ipynb
@if [ $*.py -nt $*.ipynb ]; then \
py2nb $*.py -o $*.ipynb; \
else \
nb2py $*.ipynb-o $*.py;
touch $@
该规则完成运行后,将创建.foo.updated
,它将比.py
或.ipynb
文件新,只要没有人修改这两个文件中的任何一个即可。有人进行修改后,它将立即按照正确的方向进行复制。
这假设.py
和.ipynb
文件都存在。如果不是这种情况,则可以添加一些仅订购的依赖项来创建它们:
%.py : | %.ipynb
touch -d "1 second ago" $@; touch $^
%.ipynb: | %.py
touch -d "1 second ago" $@; touch $^
这些将使依赖关系比目标更新,然后落入.%.updated
规则,该规则将以正确的方向运行副本。
答案 1 :(得分:2)
您可以考虑将其拆分为单独的Makefile,以隐藏make
的基本圆度:
制作文件
update:
$(MAKE) -f Makefile_py2nb
$(MAKE) -f Makefile_nb2py
clean:
$(MAKE) -f Makefile_py2nb clean
$(MAKE) -f Makefile_nb2py clean
.PHONY: clean update
Makefile_nb2py
NB_SRC = $(wildcard *.ipynb)
PY_DST = $(NB_SRC:.ipynb=.py)
py: $(PY_DST)
clean:
:
%.py: %.ipynb
nb2py $< -o $@ && touch -r $< $@
.PHONY: py clean
Makefile_py2nb
PY_SRC = $(wildcard *.py)
NB_DST = $(PY_SRC:.py=.ipynb)
nb: $(NB_DST)
clean:
for nb in $(NB_DST); do test -e $${nb} && test ! $${nb} -nt $${nb%%.*}.py && rm $${nb}; done
%.ipynb: %.py
py2nb $< -o $@ && touch -r $< $@
.PHONY: nb clean
这也可以按照您描述的方式实现clean
。请注意,对于您描述的清除模式,自然会(仅)在makefile中用于从.ipynb
文件构建.py
文件,因为这是前者是构建目标的一种,在这种情况下有意义以便对其进行清洁。该文件的结构排除了在该处处理的任何.ipynb文件,它们没有应有的相应.py文件。
您通常运行make
,它会选择标准的Makefile
。它将与其他每个makefile一起执行子make,每个makefile只能看到单向依赖项。