每天用 Jupyter 写 5 分钟的日记
Jupyter 日记

> 
> 用 Jupyter 和 Python 在你的日常写作背后实现一些自动化。
> 
> 
> 


![](/data/attachment/album/202012/05/131314woxpksatp2toe7tz.jpg)


有些人会遵循传统，制定一年的计划。不过，一年的时间很长，所以我以季节性的主题或轨迹来规划。每个季度，我都会坐下来，看看即将到来的三个月的季节，并决定在这段时间里我将努力做什么。


对于我最新的主题，我决定要每天写一篇日记。我喜欢有明确的承诺，所以我承诺每天写 5 分钟。我也喜欢有可观察的承诺，哪怕只是对我而言，所以我把我的记录放在 Git 里。


我决定在写日记的过程中实现一些自动化，于是我使用了我最喜欢的自动化工具：[Jupyter](https://jupyter.org/)。Jupyter 有一个有趣的功能 [ipywidgets](https://ipywidgets.readthedocs.io/en/latest/)，这是一套用于 Jupyter Notebooks、JupyterLab 和 IPython 内核的交互式 HTML 组件。


如果你想跟着本文的代码走，请注意，让你的 JupyterLab 实例支持组件可能有点复杂，请按照[这些说明](https://ipywidgets.readthedocs.io/en/latest/user_install.html)来进行设置。


### 导入 ipywidgets 模块


首先，你需要导入一堆东西，比如 ipywidgets 和 [Twisted](https://twistedmatrix.com/trac/)。Twisted 模块可以用来创建一个异步时间计数器：



```
import twisted.internet.asyncioreactor
twisted.internet.asyncioreactor.install()
from twisted.internet import reactor, task
import ipywidgets, datetime, subprocess, functools, os

```

### 设置定时条目


用 Twisted 实现时间计数器是利用了 `task.LoopingCall`。然而，结束循环调用的唯一方法是用一个异常。倒计时时钟总会停止，所以你需要一个自定义的异常来指示“一切正常；计数器结束”：



```
class DoneError(Exception):
    pass

```

现在你已经写好了异常，你可以写定时器了。第一步是创建一个 `ipywidgets.Label` 的文本标签组件。循环使用 `divmod` 计算出分和秒，然后设置标签的文本值：



```
def time_out_counter(reactor):
    label = ipywidgets.Label("Time left: 5:00")
    current_seconds = datetime.timedelta(minutes=5).total_seconds()
    def decrement(count):
        nonlocal current_seconds
        current_seconds -= count
        time_left = datetime.timedelta(seconds=max(current_seconds, 0))
        minutes, left = divmod(time_left, minute)
        seconds = int(left.total_seconds())
        label.value = f"Time left: {minutes}:{seconds:02}"
        if current_seconds < 0:
            raise DoneError("finished")
    minute = datetime.timedelta(minutes=1)
    call = task.LoopingCall.withCount(decrement)
    call.reactor = reactor
    d = call.start(1)
    d.addErrback(lambda f: f.trap(DoneError))
    return d, label

```

### 从 Jupyter 组件中保存文本


下一步是写一些东西，将你输入的文字保存到一个文件中，并提交到 Git。另外，由于你要写 5 分钟的日记，你需要一个能给你提供写字区域的组件（滚动肯定是可以的，但一次能看到更多的文字就更好了）。


这就用到了组件 `Textarea`，这是一个你可以书写的文本字段，而 `Output` 则是用来给出反馈的。这一点很重要，因为 `git push` 可能会花点时间或失败，这取决于网络。如果备份失败，用反馈提醒用户很重要：



```
def editor(fname):
    textarea = ipywidgets.Textarea(continuous_update=False)
    textarea.rows = 20
    output = ipywidgets.Output()
    runner = functools.partial(subprocess.run, capture_output=True, text=True, check=True)
    def save(_ignored):
        with output:
            with open(fname, "w") as fpout:
                fpout.write(textarea.value)
            print("Sending...", end='')
            try:
                runner(["git", "add", fname])
                runner(["git", "commit", "-m", f"updated {fname}"])
                runner(["git", "push"])
            except subprocess.CalledProcessError as exc:
                print("Could not send")
                print(exc.stdout)
                print(exc.stderr)
            else:
                 print("Done")
    textarea.observe(save, names="value")
    return textarea, output, save

```

`continuous_update=False` 是为了避免每个字符都保存一遍并发送至 Git。相反，只要脱离输入焦点，它就会保存。这个函数也返回 `save` 函数，所以可以明确地调用它。


### 创建一个布局


最后，你可以使用 `ipywidgets.VBox` 把这些东西放在一起。这是一个包含一些组件并垂直显示的东西。还有一些其他的方法来排列组件，但这足够简单：



```
def journal():
    date = str(datetime.date.today())
    title = f"Log: Startdate {date}"
    filename = os.path.join(f"{date}.txt")
    d, clock = time_out_counter(reactor)
    textarea, output, save = editor(filename)
    box = ipywidgets.VBox([
        ipywidgets.Label(title),
        textarea,
        clock,
        output
    ])
    d.addCallback(save)
    return box

```

biu！你已经定义了一个写日记的函数了，所以是时候试试了。



```
journal()

```

![Jupyter journal](/data/attachment/album/202012/05/131427th1adajyyyeqhrny.png "Jupyter journal")


你现在可以写 5 分钟了！




---


via: <https://opensource.com/article/20/11/daily-journal-jupyter>


作者：[Moshe Zadka](https://opensource.com/users/moshez) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Some people follow the tradition of creating New Year's resolutions. A year is a long time, though, so I plan with a seasonal theme or trajectory. Each quarter, I sit down and look at the upcoming three-month season and decide what I'll work on during that time.


For my latest theme, I decided I wanted to write a daily journal. I like having clear commitments, so I committed to writing for five minutes each day. I also like having observable commitments, even if it is just for me, so I put my entries in Git.

I decided I wanted some automation around my journaling and turned to my favorite automation tool: [Jupyter](https://jupyter.org/). One of Jupyter's interesting features is [ipywidgets](https://ipywidgets.readthedocs.io/en/latest/), a set of interactive HTML widgets for Jupyter Notebooks, JupyterLab, and the IPython kernel.

If you want to follow along with the code in this article, note that making your Jupyter lab instance support widgets can be a bit frustrating. Follow [these instructions](https://ipywidgets.readthedocs.io/en/latest/user_install.html) to set things up.

## Import ipywidgets modules

First, you need to import a bunch of things, such as ipywidgets and [Twisted](https://twistedmatrix.com/trac/). The Twisted module helps create an asynchronous time counter:

```
import twisted.internet.asyncioreactor
twisted.internet.asyncioreactor.install()
from twisted.internet import reactor, task
import ipywidgets, datetime, subprocess, functools, os
```


## Set up timed entries

Implementing a time counter with Twisted takes advantage of `task.LoopingCall`

. However, the only way to end a looping call is with an exception. A countdown clock will always stop, so you need a custom exception that indicates "all is well; the counter is done":

```
class DoneError(Exception):
pass
```


Now that you've written the exception, you can write the timer. The first step is to create an `ipywidgets.Label`

with a text label widget. The loop uses `divmod`

to figure out minutes and seconds and then sets the label's text value:

```
def time_out_counter(reactor):
label = ipywidgets.Label("Time left: 5:00")
current_seconds = datetime.timedelta(minutes=5).total_seconds()
def decrement(count):
nonlocal current_seconds
current_seconds -= count
time_left = datetime.timedelta(seconds=max(current_seconds, 0))
minutes, left = divmod(time_left, minute)
seconds = int(left.total_seconds())
label.value = f"Time left: {minutes}:{seconds:02}"
if current_seconds < 0:
raise DoneError("finished")
minute = datetime.timedelta(minutes=1)
call = task.LoopingCall.withCount(decrement)
call.reactor = reactor
d = call.start(1)
d.addErrback(lambda f: f.trap(DoneError))
return d, label
```


## Save text from a Jupyter widget

The next step is to write something that saves the text you type into your journal to a file and commits it to Git. Also, since you will be journaling for five minutes, you want a widget that gives you room to write (scrolling is always possible, but it's nice to see a bit more text at a time).

This uses the widgets `Textarea`

, which is a text field where you can write, and `Output`

to give feedback. This is important since `git push`

can take time or fail, depending on the network. If a backup fails, it's important to alert the user with feedback:

```
def editor(fname):
textarea = ipywidgets.Textarea(continuous_update=False)
textarea.rows = 20
output = ipywidgets.Output()
runner = functools.partial(subprocess.run, capture_output=True, text=True, check=True)
def save(_ignored):
with output:
with open(fname, "w") as fpout:
fpout.write(textarea.value)
print("Sending...", end='')
try:
runner(["git", "add", fname])
runner(["git", "commit", "-m", f"updated {fname}"])
runner(["git", "push"])
except subprocess.CalledProcessError as exc:
print("Could not send")
print(exc.stdout)
print(exc.stderr)
else:
print("Done")
textarea.observe(save, names="value")
return textarea, output, save
```


The `continuous_update=False`

is so that not every character is saved and sent to Git. Instead, it saves whenever you lose focus. The function also returns the `save`

function, so it can be called explicitly.

## Create a layout

Finally, you can put all of these together using `ipywidgets.VBox`

. This is something that contains a few widgets and displays them vertically. There are a few more ways to arrange widgets, but this is simple and good enough:

```
def journal():
date = str(datetime.date.today())
title = f"Log: Startdate {date}"
filename = os.path.join(f"{date}.txt")
d, clock = time_out_counter(reactor)
textarea, output, save = editor(filename)
box = ipywidgets.VBox([
ipywidgets.Label(title),
textarea,
clock,
output
])
d.addCallback(save)
return box
```


Phew! You've defined a function for journaling, so it's time to try it out.

`journal()`



![Jupyter journal Jupyter journal](https://opensource.com/sites/default/files/uploads/journaling_output_13_0.png)

(Moshe Zadka, CC BY-SA 4.0)

You have five minutes—start writing!

## Comments are closed.