Linux中國

使用 Jupyter 改善你的時間管理

Python 在探索數據方面具有令人難以置信的可擴展性。利用 PandasDask,你可以將 Jupyter 擴展到大數據領域。但是小數據、個人資料、私人數據呢?

JupyterLab 和 Jupyter Notebook 為我提供了一個絕佳的環境,可以讓我審視我的筆記本電腦生活。

我的探索是基於以下事實:我使用的幾乎每個服務都有一個 Web API。我使用了諸多此類服務:待辦事項列表、時間跟蹤器、習慣跟蹤器等。還有一個幾乎每個人都會使用到:日曆。相同的思路也可以應用於其他服務,但是日曆具有一個很酷的功能:幾乎所有 Web 日曆都支持的開放標準 —— CalDAV。

在 Jupyter 中使用 Python 解析日曆

大多數日曆提供了導出為 CalDAV 格式的方法。你可能需要某種身份驗證才能訪問這些私有數據。按照你的服務說明進行操作即可。如何獲得憑據取決於你的服務,但是最終,你應該能夠將這些憑據存儲在文件中。我將我的憑據存儲在根目錄下的一個名為 .caldav 的文件中:

import os
with open(os.path.expanduser("~/.caldav")) as fpin:
    username, password = fpin.read().split()

切勿將用戶名和密碼直接放在 Jupyter Notebook 的筆記本中!它們可能會很容易因 git push 的錯誤而導致泄漏。

下一步是使用方便的 PyPI caldav 庫。我找到了我的電子郵件服務的 CalDAV 伺服器(你可能有所不同):

import caldav
client = caldav.DAVClient(url="https://caldav.fastmail.com/dav/", username=username, password=password)

CalDAV 有一個稱為 principal(主鍵)的概念。它是什麼並不重要,只要知道它是你用來訪問日曆的東西就行了:

principal = client.principal()
calendars = principal.calendars()

從字面上講,日曆就是關於時間的。訪問事件之前,你需要確定一個時間範圍。默認一星期就好:

from dateutil import tz
import datetime
now = datetime.datetime.now(tz.tzutc())
since = now - datetime.timedelta(days=7)

大多數人使用的日曆不止一個,並且希望所有事件都在一起出現。itertools.chain.from_iterable 方法使這一過程變得簡單:

import itertools

raw_events = list(
    itertools.chain.from_iterable(
        calendar.date_search(start=since, end=now, expand=True)
        for calendar in calendars
    )
)

將所有事件讀入內存很重要,以 API 原始的本地格式進行操作是重要的實踐。這意味著在調整解析、分析和顯示代碼時,無需返回到 API 服務刷新數據。

但 「原始」 真的是原始,事件是以特定格式的字元串出現的:

print(raw_events[12].data)
    BEGIN:VCALENDAR
    VERSION:2.0
    PRODID:-//CyrusIMAP.org/Cyrus
     3.3.0-232-g4bdb081-fm-20200825.002-g4bdb081a//EN
    BEGIN:VEVENT
    DTEND:20200825T230000Z
    DTSTAMP:20200825T181915Z
    DTSTART:20200825T220000Z
    SUMMARY:Busy
    UID:
     1302728i-040000008200E00074C5B7101A82E00800000000D939773EA578D601000000000
     000000010000000CD71CC3393651B419E9458134FE840F5
    END:VEVENT
    END:VCALENDAR

幸運的是,PyPI 可以再次使用另一個輔助庫 vobject 解圍:

import io
import vobject

def parse_event(raw_event):
    data = raw_event.data
    parsed = vobject.readOne(io.StringIO(data))
    contents = parsed.vevent.contents
    return contents
parse_event(raw_events[12])
    {&apos;dtend&apos;: [<DTEND{}2020-08-25 23:00:00+00:00>],
     &apos;dtstamp&apos;: [<DTSTAMP{}2020-08-25 18:19:15+00:00>],
     &apos;dtstart&apos;: [<DTSTART{}2020-08-25 22:00:00+00:00>],
     &apos;summary&apos;: [<SUMMARY{}Busy>],
     &apos;uid&apos;: [<UID{}1302728i-040000008200E00074C5B7101A82E00800000000D939773EA578D601000000000000000010000000CD71CC3393651B419E9458134FE840F5>]}

好吧,至少好一點了。

仍有一些工作要做,將其轉換為合理的 Python 對象。第一步是 擁有 一個合理的 Python 對象。attrs 庫提供了一個不錯的開始:

import attr
from __future__ import annotations
@attr.s(auto_attribs=True, frozen=True)
class Event:
    start: datetime.datetime
    end: datetime.datetime
    timezone: Any
    summary: str

是時候編寫轉換代碼了!

第一個抽象從解析後的字典中獲取值,不需要所有的裝飾:

def get_piece(contents, name):
    return contents[name][0].value
get_piece(_, "dtstart")
    datetime.datetime(2020, 8, 25, 22, 0, tzinfo=tzutc())

日曆事件總有一個「開始」、有一個「結束」、有一個 「持續時間」。一些謹慎的解析邏輯可以將兩者協調為同一個 Python 對象:

def from_calendar_event_and_timezone(event, timezone):
    contents = parse_event(event)
    start = get_piece(contents, "dtstart")
    summary = get_piece(contents, "summary")
    try:
        end = get_piece(contents, "dtend")
    except KeyError:
        end = start + get_piece(contents, "duration")
    return Event(start=start, end=end, summary=summary, timezone=timezone)

將事件放在 本地 時區而不是 UTC 中很有用,因此使用本地時區:

my_timezone = tz.gettz()
from_calendar_event_and_timezone(raw_events[12], my_timezone)
    Event(start=datetime.datetime(2020, 8, 25, 22, 0, tzinfo=tzutc()), end=datetime.datetime(2020, 8, 25, 23, 0, tzinfo=tzutc()), timezone=tzfile(&apos;/etc/localtime&apos;), summary=&apos;Busy&apos;)

既然事件是真實的 Python 對象,那麼它們實際上應該具有附加信息。幸運的是,可以將方法添加到類中。

但是要弄清楚哪個事件發生在哪一天不是很直接。你需要在 本地 時區中選擇一天:

def day(self):
    offset = self.timezone.utcoffset(self.start)
    fixed = self.start + offset
    return fixed.date()
Event.day = property(day)
print(_.day)
    2020-08-25

事件在內部始終是以「開始」/「結束」的方式表示的,但是持續時間是有用的屬性。持續時間也可以添加到現有類中:

def duration(self):
    return self.end - self.start
Event.duration = property(duration)
print(_.duration)
    1:00:00

現在到了將所有事件轉換為有用的 Python 對象了:

all_events = [from_calendar_event_and_timezone(raw_event, my_timezone)
              for raw_event in raw_events]

全天事件是一種特例,可能對分析生活沒有多大用處。現在,你可以忽略它們:

# ignore all-day events
all_events = [event for event in all_events if not type(event.start) == datetime.date]

事件具有自然順序 —— 知道哪個事件最先發生可能有助於分析:

all_events.sort(key=lambda ev: ev.start)

現在,事件已排序,可以將它們載入到每天:

import collections
events_by_day = collections.defaultdict(list)
for event in all_events:
    events_by_day[event.day].append(event)

有了這些,你就有了作為 Python 對象的帶有日期、持續時間和序列的日曆事件。

用 Python 報到你的生活

現在是時候編寫報告代碼了!帶有適當的標題、列表、重要內容以粗體顯示等等,有醒目的格式是很意義。

這就是一些 HTML 和 HTML 模板。我喜歡使用 Chameleon

template_content = """
<html><body>
<div tal_repeat="item items">
<h2 tal_content="item[0]">Day</h2>
<ul>
    <li tal_repeat="event item[1]"><span tal_replace="event">Thing</span></li>
</ul>
</div>
</body></html>"""

Chameleon 的一個很酷的功能是使用它的 html 方法渲染對象。我將以兩種方式使用它:

  • 摘要將以粗體顯示
  • 對於大多數活動,我都會刪除摘要(因為這是我的個人信息)
def __html__(self):
    offset = my_timezone.utcoffset(self.start)
    fixed = self.start + offset
    start_str = str(fixed).split("+")[0]
    summary = self.summary
    if summary != "Busy":
        summary = "<REDACTED>"
    return f"<b>{summary[:30]}</b> -- {start_str} ({self.duration})"
Event.__html__ = __html__

為了簡潔起見,將該報告切成每天的:

import chameleon
from IPython.display import HTML
template = chameleon.PageTemplate(template_content)
html = template(items=itertools.islice(events_by_day.items(), 3, 4))
HTML(html)

渲染後,它將看起來像這樣:

2020-08-25

  • -- 2020-08-25 08:30:00 (0:45:00)
  • -- 2020-08-25 10:00:00 (1:00:00)
  • -- 2020-08-25 11:30:00 (0:30:00)
  • -- 2020-08-25 13:00:00 (0:25:00)
  • Busy -- 2020-08-25 15:00:00 (1:00:00)
  • -- 2020-08-25 15:00:00 (1:00:00)
  • -- 2020-08-25 19:00:00 (1:00:00)
  • -- 2020-08-25 19:00:12 (1:00:00)

Python 和 Jupyter 的無窮選擇

通過解析、分析和報告各種 Web 服務所擁有的數據,這只是你可以做的事情的表面。

為什麼不對你最喜歡的服務試試呢?

via: https://opensource.com/article/20/9/calendar-jupyter

作者:Moshe Zadka 選題:lujun9972 譯者:stevenzdg988 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國