shi0rik0 的博客shi0rik0 的博客
主页
所有文章
按类别浏览
按标签浏览
主页
所有文章
按类别浏览
按标签浏览
关于 VS Code Remote 的内存占用

Date: 12/7/2025Category: Tag:

最近尝试用 VS Code 在一台 512 MB 内存的远程服务器上进行开发,结果发现怎么也连接不上,起初以为是网络问题,后来才发现是因为 VS Code 的远程功能需要在服务器上运行一个后端进程,而这个进程很吃内存,导致服务器内存不足。

后来我换成了一台 2 GB 内存的服务器,VS Code 就能顺利连接了。我还什么都没做,此时服务器的可用内存就已经只剩 900 MB 左右了,这说明如果想用 VS Code Remote,至少需要 2 GB 的内存。

TL;DR

如果想用 VS Code Remote 功能,远程服务器需要至少 2 GB 内存。

PostgreSQL 对于大小写处理的一个陷阱

Date: 12/6/2025Category: Tag:

最近用 Prisma 创建了一个 PostgreSQL 数据库,由于 JavaScript 的习惯,在定义 model 的时候字段名都使用了 camelCase 命名法。后来,我在用 SQL 语句查询数据的时候,却发现总是报错,提示找不到对应的 table 或 column。查询之后才得知,原来 PostgreSQL 是大小写敏感的,但是在处理 SQL 语句的时候,会将没有用双引号括起来的标识符全部转换为小写。比如说,SELECT * FROM UserProfile; 实际上会被解析为 SELECT * FROM userprofile;,正确的写法应该是 SELECT * FROM "UserProfile";。由此可见,PostgreSQL 是提倡使用 snake_case 命名法的,所以用 Prisma 的时候,最好用 @map 和 @@map 来给字段和表起别名。

Axios vs Fetch

Date: 12/4/2025Category: Tag:

Fetch 是浏览器内置的原生网络请求 API,而 axios 则是一个封装后的库。总的来说,axios 的语法更加方便,而它唯一的缺点可能就是会略微增大一点打包体积,不过这在大多数场景下并不重要。

Fetch 示例

const response = await fetch("https://example.com/api", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ key: "value" }),
})

console.log(await response.json())
OpenAI Python SDK 使用指南

Date: 12/4/2025Category: Tag:

OpenAI 的 Chat Completions API 是 LM 事实上的标准接口。本文介绍如何使用 OpenAI 提供的 Python SDK 来调用该 API。

安装 OpenAI Python SDK

pip install openai
Python 环境管理工具 uv 教程

Date: 11/28/2025Category: Tag:

安装 uv

可以用官方脚本来安装 uv:

curl -LsSf https://astral.sh/uv/install.sh | sh
DSPy 的 MIPROv2 算法简介

Date: 11/27/2025Category: Tag:

最近看到有同事在用 DSPy 的 MIPROv2 来做 prompt engineering,便去学习了一下。结果发现官网的介绍很不清楚,下面我就用简单的语言来介绍一下 MIPROv2。

什么是 MIPROv2?

DSPy 把 MIPROv2 叫做 optimizer,但是我觉得这是严重的术语滥用,因为一般来说 optimizer 应该指的是调整模型参数的算法,而 MIPROv2 实际上只是一个利用 LLM 来生成 prompts 的算法。

MIRPOv2 的输入和输出

MIPROv2 的输入包括以下几个部分:

  1. 一个 DSPy program。(如果你不明白什么是 DSPy program 的话,这里举一个简单的例子:一个最简单的 DSPy program 就是 question -> answer,也就是直接用原本的问题和一次 LLM 调用来得到答案。)
  2. 一个 metric,用来评估 program 的输出和标准答案之间的差距。
  3. 一些示例数据,可以是完整的(包括所有输入、输出和中间结果),也可以是不完整的,但是一定要有输入。
Linux 实用工具

Date: 11/27/2025Category: Tag:

这篇文章会总结一些我平时常用的 Linux 命令。

磁盘占用

整体磁盘占用

用 df -h 可以查看磁盘分区的使用情况,-h 参数表示以人类可读的格式显示(比如 GB、MB)。

某个目录的磁盘占用

可以用 ncdu 来查看某个目录的磁盘占用情况。默认情况下 ncdu 会扫描当前目录,也可以手动指定目录:

ncdu /path/to/directory
VS Code 实用插件:在 SSH 环境下以 Root 权限保存文件

Date: 11/24/2025Category: Tag:

在 SSH 环境下,很多时候我们无法以 root 用户身份直接登入(本身这也不是一种推荐的做法),而是只能通过 sudo 命令来临时获得 root 权限。这种情况下用 VS Code 编辑文件的时候就经常会遇到权限不足的问题。有一个叫做 Save as Root in Remote - SSH 的插件可以解决这个问题。

安装这个插件之后,在 command palette (Ctrl+Shift+P) 里搜索 Save as Root,然后执行这个命令,就可以用 root 权限保存当前文件了。也可以用 Save as Specified User... 命令来以其他用户身份保存文件。

关于 Qwen 视频时间戳不准确的问题

Date: 11/21/2025Category: Tag:

最近做的一个项目需要用大模型来分析视频,在实验过程中发现,Qwen3-omni 系列的模型生成的视频时间戳并不准确。比如说,我问模型某个视频的时长是多少秒,结果模型无法给出正确答案。更复杂的问题就更加答不对了。

经过测试,以下模型均存在该问题:

  • Qwen3-omni(开源版)
  • Qwen2.5-VL
  • Qwen2.5-omni

而以下模型则不存在该问题:

  • Qwen3-VL
  • Qwen3-omni-flash(闭源)

我目前认为,Qwen3-VL 之所以能够得到正确的时间戳,是因为它的输入序列中有绝对时间信息的 token,而 Qwen3-omni 则没有。尽管 Qwen3-omni 采用的 M-RoPE 编码能够让模型明白视频帧的时间相对顺序,但是模型无法得知绝对时间。

LeetCode题解:数字流的秩

Date: 6/6/2025Category: Tag: 算法八股文, 树状数组

题目链接:https://leetcode.cn/problems/rank-from-stream-lcci/

解题思路

这道题目的官方提示是使用一个携带额外信息的二叉搜索树来解决问题,用二叉搜索树的优点是对于输入数据的范围没有限制,但是缺点在于:如果使用朴素的二叉搜索树,最坏时间复杂度会很差,而平衡二叉搜索树的实现会比较复杂。考虑到题目中x的范围是[0, 5e4],我们也可以使用树状数组来解决问题。

树状数组

树状数组(Binary Indexed Tree),也叫Fenwick Tree,是一种数据结构,可以用来高效地维护一个固定长度数组(设长度为N)的区间和。它支持两种操作:

关于Python中负数的位运算

Date: 6/5/2025Category: Tag:

引言

最近做了一道LeetCode题目,题目本身不难,只需要将两个整数进行按位XOR操作,然后计算结果的二进制表示中1的个数即可。下面是我最初的实现:

a = int(input())
b = int(input())
c = a ^ b
n = 0
while c:
    n += c & 1
    c >>= 1
print(n)
LeetCode题解:第k个数

Date: 6/4/2025Category: Tag: 算法八股文

题目链接:https://leetcode.cn/problems/get-kth-magic-number-lcci/

解题思路

这道题有时间复杂度为O(k)的解法,但是这里介绍一个更通用且更容易理解的算法,其时间复杂度为O(k log k)。

给出集合${f(a, b, c) | a, b, c \in \mathbb{N}^+}$,其中$f(a, b, c) = 3^a \cdot 5^b \cdot 7^c$。我们需要找到这个集合中的第k个最小值。注意到$f(a, b, c)$是单调递增的,也就是说如果$a_1 \leq a_2$,$b_1 \leq b_2$,$c_1 \leq c_2$,那么$f(a_1, b_1, c_1) \leq f(a_2, b_2, c_2)$。因此,假如我们把这个问题看作一个按照大小顺序进行搜索的问题,那么下一个要搜索的(a, b, c)一定会和已搜索过的(a, b, c)相邻,所以我们只需要用一个优先队列来存储已搜索过的(a, b, c)的邻居,从中获取下一个要搜索的(a, b, c)即可。我们还需要一个哈希表来记录已搜索过的(a, b, c),以避免重复搜索。

LeetCode题解:字母与数字

Date: 6/3/2025Category: Tag: 算法八股文, 前缀和

题目链接:https://leetcode.cn/problems/find-longest-subarray-lcci/

解题思路

这道题可以转化为一个经典的问题:在一个数组中找到和为k的最长子数组。我们只要将数组中的字母替换成1,数字替换成-1,那么原题就变成了在一个数组中找到和为0的最长子数组。

要解决这个问题,我们需要用到一个叫做“前缀和”的技巧。我们用sum(i, j)表示数组在区间[i, j)上的和(sum(i, i) = 0),如果令数组prefix[i] = sum(0, i),那么就可以快速计算出sum(i, j) = prefix[j] - prefix[i]。

LeetCode题解:多次搜索

Date: 6/2/2025Category: Tag: 算法八股文, 前缀树

题目网址:https://leetcode.cn/problems/multi-search-lcci/

解题思路

这题是前缀树(也叫字典树、Trie)的一个典型应用。我们可以将所有需要查询的单词插入到前缀树中,然后对字符串的每个字符进行遍历,查找以该字符为起点的所有单词。

参考实现

class Node:
    def __init__(self, idx=-1, children=None):
        self.idx = idx
        self.children = {} if children is None else children

class Solution:
    def multiSearch(self, big: str, smalls: List[str]) -> List[List[int]]:
        # 构建前缀树
        root = Node()
        for i, s in enumerate(smalls):
            node = root
            for j, ch in enumerate(s):
                if ch not in node.children:
                    node.children[ch] = Node()
                node = node.children[ch]
                if j == len(s) - 1:
                    node.idx = i

        # 遍历大字符串,查找所有小字符串
        ans = [[] for _ in range(len(smalls))]
        for i in range(len(big)):
            node = root
            j = i
            while j < len(big) and big[j] in node.children:
                node = node.children[big[j]]
                j += 1
                if node.idx != -1:
                    ans[node.idx].append(i)
        return ans
LeetCode题解:环路检测

Date: 5/30/2025Category: Tag: 算法八股文

原题网址:https://leetcode.cn/problems/linked-list-cycle-lcci/

解题思路

这道题目就是经典的Floyd龟兔赛跑算法。这个算法主要分为两个部分,第一个部分就是如何判定是否有环,第二个部分就是如何找到环的起点。第一个部分比较简单,很容易理解,因此也很脍炙人口。但是第二个部分就比较少人知道了,因为其推导过程相对复杂一些,具体可以参考LeetCode官方的题解。

LeetCode题解:最短超串

Date: 5/30/2025Category: Tag: 算法八股文, 滑动窗口

题目链接:https://leetcode.cn/problems/shortest-supersequence-lcci/

解题思路

这个题目属于一类很典型的问题:给定一个数组,要找到满足某个性质的最短子数组。这类问题往往可以用滑动窗口的方式来解决。

下面是滑动窗口的模板代码:

arr = get_input_arr()
status = init_status()
left = 0
right = 0
min_len = float('inf')
ans = []
while True:
    if right < len(arr) and not is_condition_met(status):
        right += 1
        status.update(left, right)
    else:
        if left == len(arr):
            break
        # 如果有多个相同长度的最短子数组,会保留左侧索引最小的
        if is_condition_met(status) and right - left < min_len:
            min_len = right - left
            ans = [left, right - 1]
        left += 1
        status.update(left, right)
return ans
解决Matplotlib中文乱码问题

Date: 5/30/2025Category: Tag:

Python的Matplotlib库默认是无法显示中文的,因为Matplotlib默认使用的字体不支持中文字符。为了在Matplotlib中显示中文,我们需要手动配置字体。

首先可以用以下代码查看当前系统可用的字体

import matplotlib.font_manager as fm

print(fm.get_font_names())
关于A*算法:我们真的需要Decrease-Key操作吗?

Date: 5/29/2025Category: Tag:

我们知道,A*算法的伪代码如下:

start_point = Point(pos=start_pos, g=0, h=heuristic(start_pos, end_pos))
open_list = {start_point}
closed_list = {}

while not open_list.is_empty():
    # 从open_list中取出f值最小的点
    current = open_list.pop_point_with_lowest_f()
    # 如果当前点是目标点,说明找到最短路径了
    if current == end_point:
        return reconstruct_path(current)
    # 否则,处理当前点的邻居
    for neighbor in get_neighbors(current):
        # 已经在closed_list中的点无需处理
        if neighbor in closed_list:
            continue
        tentative_g = current.g + cost(current, neighbor)
        tentative_h = heuristic(neighbor, end_pos)
        tentative_neighbor = Point(pos=neighbor, g=tentative_g, h=tentative_h)
        # 如果邻居点是新发现的点,直接加入open_list
        if neighbor not in open_list:
            open_list.add(tentative_neighbor)
        # 否则,检查能否更新邻居点的g值
        elif tentative_neighbor.g < open_list.get(neighbor).g:
            open_list.update(tentative_neighbor)
    # 当前点处理完毕,加入closed_list
    closed_list.add(current)
LeetCode题解:LRU缓存

Date: 5/29/2025Category: Tag: 算法八股文

原题网址:https://leetcode.com/problems/lru-cache/description/

实现思路

很显然,一个LRU缓存需要支持以下操作:

  1. 将尾部的元素删除。(即淘汰最久未使用的元素)
  2. 将其中一个元素移动到头部。(即将最近使用的元素标记为最新)
  3. 将一个新元素插入到头部。
  4. 给定key,查询对应的元素位置。

双向链表能够高效地实现操作1-3,而hash表能够高效地实现操作4,所以我们考虑同时使用双向链表和hash表来实现LRU缓存。

在Ubuntu 24.04上安装Unity Hub

Date: 5/21/2025Category: Tag:

最近在Ubuntu 24.04上安装Unity Hub的时候遇到了很多问题,下面记录一下解决的过程。

首先在官网上下载Unity Hub的deb包,安装之后就可以通过unityhub命令来启动Unity Hub,但是直接启动会报错,提示为找不到sandbox。

这个问题的解决办法是安装Chrome,然后将sandbox的文件复制过去:

sudo cp /opt/google/chrome/chrome-sandbox /opt/unityhub
sudo chown root:root /opt/unityhub/chrome-sandbox
sudo chmod 4755 /opt/unityhub/chrome-sandbox
用支付宝的未公开API获取银行卡发卡行

Date: 5/20/2025Category: Tag:

最近发现GitHub上有个项目(zhuzhichao/bank-card-info),提供了可以根据银行卡号获取发卡行的API。看了下源码,发现它是通过调用支付宝的一个未公开的API实现的。虽然不知道作者是怎么发现这个API的,不过这个API目前还可以使用,并且不需要任何认证。

下面是一个简单的Python示例代码,展示了如何用这个API来获取发卡行信息:

import requests

CARD_NUMBER = 6259960000114514

url = "https://ccdcapi.alipay.com/validateAndCacheCardInfo.json"
params = {"cardNo": CARD_NUMBER, "_input_charset": "utf-8", "cardBinCheck": "true"}
response = requests.get(url, params=params).json()
card_types = {"CC": "信用卡", "DC": "借记卡"}
print("银行卡类型:", card_types.get(response["cardType"], "未知"), sep="")
print("发卡行编码:", response["bank"], sep="")
深入Python的asyncio:Future对象

Date: 5/19/2025Category: Tag:

假设我们有这么一个异步API,它接受一个callback参数,当任务完成时会调用这个callback函数来返回结果:

import threading
import time


def async_func(callback):
    def thread_func():
        time.sleep(2)
        callback(123)

    thread = threading.Thread(target=thread_func)
    thread.start()


def callback(result):
    print(f"Callback executed with result: {result}")


async_func(callback)
Python中的__subclasshook__方法

Date: 5/13/2025Category: Tag:

最近在查阅Python标准库collections.abc的源代码时,发现一个有趣的“魔法方法”__subclasshook__。这个方法其实是一个假的魔法方法,因为它并没有任何特殊性。如果一个类继承了abc.ABC,那么对这个类调用issubclass,或者对这个类的对象调用isinstance时,Python就会自动调用这个类的__subclasshook__方法。

举个例子,下面的类Addable用来描述可以进行加法运算的接口。

在Python中实现一个简易的协程

Date: 5/12/2025Category: Tag:

基本概念

在Python的协程体系中有两个基本概念:coroutine和coroutine function。

coroutine对象拥有一个__await__函数,调用这个函数会返回一个生成器。而调用一个coroutine function则会返回一个coroutine对象。

import asyncio
from collections.abc import Generator

async def f():
    pass

# 都会返回 True
print(asyncio.iscoroutinefunction(f))
print(asyncio.iscoroutine(f()))
print(isinstance(f().__await__(), Generator))
Python配置库Hydra的教程

Date: 4/30/2025Category: Tag:

Hydra是Meta团队开发的一个Python库,主要是用来管理配置的。虽然我个人比较反感这种框架,因为我觉得配置本身是一个很简单的需求,没必要搞得这么复杂,但是Habitat仿真器的配置就是用Hydra加载的,所以还是不得不稍微了解一下。

Hydra配置文件都是用YAML格式编写的,下面是一个例子:

main.yaml:

defaults:
  - _self_
  - db/engine: mysql
  - network

db:
  engine:
    name: postgresql
PyTorch的BatchSampler的一个优化技巧

Date: 4/29/2025Category: Tag:

最近在阅读PyTorch的源码的时候,发现torch.utils.data.sampler.BatchSampler的实现非常有意思:

    def __iter__(self) -> Iterator[List[int]]:
        # Implemented based on the benchmarking in https://github.com/pytorch/pytorch/pull/76951
        sampler_iter = iter(self.sampler)
        if self.drop_last:
            # Create multiple references to the same iterator
            args = [sampler_iter] * self.batch_size
            for batch_droplast in zip(*args):
                yield [*batch_droplast]
        else:
            batch = [*itertools.islice(sampler_iter, self.batch_size)]
            while batch:
                yield batch
                batch = [*itertools.islice(sampler_iter, self.batch_size)]
常见编程语言的GC机制对比

Date: 4/2/2025Category: Tag:

Java & C#

Java和C#都采用了“分代”的思想,将对象分为不同的“代”,对于不同的“代”采用不同的GC策略。具体采用的GC策略包括:复制、标记-清除、标记-整理等。这种方式的优点是吞吐量高,缺点则是STW(Stop The World)时间较长。

Go

Go使用了三色标记法,这种方式的GC中断次数较多,但是每次的中断时间较短,可以说是一种牺牲吞吐量来换取较低STW时间的GC策略。

参考资料:

  • https://www.zhihu.com/question/326191221
  • https://medium.com/servicetitan-engineering/go-vs-c-part-2-garbage-collection-9384677f86f1
推荐一个免费的SMTP送信服务:SMTP2GO

Date: 3/31/2025Category: Tag:

最近我想实现这么一个功能:编写一个程序监控股价,当剧烈波动时,就给我发个推送通知。仔细想了想,发现成本最低、实现最简单的推送方法是就是给我的邮箱发个邮件。但很可惜的是,现在主流的邮箱服务商都启用了2FA,因此不能直接通过用户名和密码来发送邮件了。最后我找到了一个名为SMTP2GO的服务商,它们提供的SMTP服务可以直接使用用户名和密码来发送邮件。下面是利用Python和SMTP2GO来发送邮件的教程。

首先要注册一个SMTP2GO的账户。注意:SMTP2GO不支持用public domain的邮箱(例如outlook.com等)注册,必须通过企业、学校邮箱之类的注册。

我最喜欢的clang-format配置

Date: 3/27/2025Category: Tag:

我最喜欢的clang-format配置是:

BasedOnStyle: WebKit
VS Code + CMake + Windows配置指南

Date: 3/27/2025Category: Tag:

这篇指南会教你如何在Windows下给VS Code配置一个舒服的CMake开发环境。

首先要安装VS Build Tools,可以在VS官网下载。

然后给VS Code安装CMake Tools和clangd扩展。

然后在VS Code的配置文件里加入以下设置:

"cmake.generator": "Ninja"
判断Windows的lib文件是静态库还是动态库的方法

Date: 3/27/2025Category: Tag:

众所周知,在Linux下,动态库的后缀名是.so,而静态库的后缀名则是.a。但是在Windows下,静态库的后缀名是.lib,而动态库则包含两个文件,一个是.dll文件,另一个是.lib文件。下面的表格总结了Linux和Windows的差异:

类型 Linux Windows
静态库 .a .lib
动态库 .so .dll + .lib
在Python中用进程池异步执行任务

Date: 3/25/2025Category: Tag:

废话不多说,先直接上代码:

import asyncio
from concurrent.futures import ProcessPoolExecutor
import multiprocessing
import os

var_per_process = None


def initialize_process():
    global var_per_process
    var_per_process = os.getpid()


def add(arg1, arg2):
    print(f"Process {var_per_process} received {arg1} and {arg2}")
    return arg1 + arg2


async def main():
    loop = asyncio.get_event_loop()
    with ProcessPoolExecutor(
        max_workers=multiprocessing.cpu_count(), initializer=initialize_process
    ) as executor:
        inputs = [(1, 2), (3, 4), (5, 6)]
        results = await asyncio.gather(
            *[loop.run_in_executor(executor, add, i, j) for i, j in inputs]
        )
        print(results)


if __name__ == "__main__":
    asyncio.run(main())
Self-Instruct: 让LLM自己生成指令微调数据集

Date: 3/24/2025Category: Tag:

最近看到一篇比较有意思的论文:Self-Instruct: Aligning Language Models with Self-Generated Instructions。这篇论文的思路就是让LLM自己生成指令微调数据集,然后用它反过来训练自己。这么做有点左脚踩右脚的感觉。基于这个思路,斯坦福大学的团队训练了一个名为Alpaca的模型,模型的权重、数据集和代码都在GitHub上开源了:tatsu-lab/stanford_alpaca。下面就简单介绍一下这个方法。

Python性能分析教程

Date: 3/20/2025Category: Tag:

Python标准库里面有两个可以用于性能分析的模块:cProfile 和 profile。cProfile是用C编写的,而profile是用纯Python实现的。它们的接口类似,但是并不完全相同,比如说它们的输出文件格式不同。有一个知名的库snakeviz可以用来可视化cProfile的输出文件,但是它不支持profile的输出文件。cProfile不仅更快,还被snakeviz所支持,所以大多数时候还是推荐使用cProfile。

Python标记deprecated的装饰器

Date: 3/19/2025Category: Tag:

从Python 3.13开始,warnings模块里新增了一个@deprecated装饰器,用于标记函数等已经被启用。具体可以参考Python文档和PEP 702。

如何获取公网IP地址

Date: 3/18/2025Category: Tag:

如果机器的网卡直接可以通过外网访问,那么可以直接通过以下命令获取网卡的IP地址:

hostname -I
Ubuntu安装Redis

Date: 3/17/2025Category: Tag:

安装Redis

在Ubuntu里,可以直接通过apt安装Redis:

sudo apt-get install lsb-release curl gpg
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install redis
主机与WSL虚拟机的网络通信

Date: 3/16/2025Category: Tag: WSL

主机到WSL

在WSL中,执行以下命令可以获得WSL的IP地址:

hostname -I
一个最简单的Python包示例

Date: 3/11/2025Category: Tag:

我写了一个最简单的Python包示例,并详细介绍了各个文件的作用。此外还介绍了如何在开发环境下本地安装包、如何构建包以及如何将包发布到PyPI。

相关内容已经上传到GitHub:shi0rik0/minimal-python-pkg。如果觉得有帮助的话,欢迎给个Star😀。

Linux下禁用IOMMU的方法

Date: 3/10/2025Category: Tag:

最近在安装Isaac Sim的时候,遇到了提醒我关闭IOMMU的警告。下面总结一下在Linux系统中关闭IOMMU的方法。

要关闭IOMMU,最简单的办法是修改内核的启动参数。首先用文本编辑器打开/etc/default/grub,找到GRUB_CMDLINE_LINUX_DEFAULT="..."这一行(如果没有就添加这一行),然后在引号中添加以下内容:

GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=off amd_iommu=off"
一个无聊的问题:第一个版本号究竟是什么?

Date: 3/10/2025Category: Tag:

最近强迫症发作,突然想到一个很无聊的问题:软件的第一个版本应该是什么?0.0.1?0.1.0?还是0.0.0?

这种情况我一般会选择随大流或者相信权威。语义化版本(Semantic Versioning)是一个被广泛采用的标准。在Semantic Versioning 2.0.0中,有这样一段话:

How should I deal with revisions in the 0.y.z initial development phase?

screen命令常用操作

Date: 3/6/2025Category: Tag:

新建session

screen -S session_name
如何将包发布到npm

Date: 3/5/2025Category: Tag: npm

首先要在npmjs.com上注册一个账号。

在发布包之前,要检查package.json中的name字段是否正确,这个字段就决定了在npm上的包名。

如果没问题,就可以发布了,首先要登录npm账号:

npm login
使用GitHub Pages免费部署静态网站

Date: 3/3/2025Category: Tag:

要使用GitHub Pages服务部署静态网站,首先要创建一个GitHub仓库。默认情况下,如果你的仓库名字是<username>.github.io,那么你的网站将会被部署到https://<username>.github.io。否则,你的网站将会被部署到https://<username>.github.io/<repository-name>。举个例子,假如你的GitHub用户名是john,那么仓库john.github.io将会被部署到https://john.github.io,而仓库my-website将会被部署到https://john.github.io/my-website。当然,你也可以在设置中使用自定义域名。

用PyTorch进行混合精度训练/推理

Date: 2/28/2025Category: Tag: 神经网络

最近尝试了用混合精度的方法来加速模型训练。要用PyTorch进行混合精度训练非常简单,因为PyTorch已经封装好了这个功能,API也很便捷,要import的东西只有下面这些:

from torch.amp import autocast, GradScaler
杠杆ETF中隐藏的陷阱

Date: 2/24/2025Category: Tag: 理财

很多人会用杠杆ETF来投资,这样可以用更少的本金博取更多的收益,但是,杠杆ETF存在一个很大的陷阱,那就是它的高损耗率。这里的损耗率不是来自于基金的管理费或者折价/溢价率,而是来自于杠杆ETF的内在机制。

一般的杠杆ETF逻辑是这样的:假设是一个2倍杠杆的ETF,那么这个ETF的净值会保证以下的性质:假设某天其标的资产上涨了1%,那么这个ETF的净值会上涨2%;反之,如果标的资产下跌了1%,那么这个ETF的净值会下跌2%。乍一看,这似乎毫无问题。花10000元投资无杠杆的ETF和花5000元投资2倍杠杆的ETF,应该是等价的。但是,仔细计算一下就会发现问题。

一个例子

Transformer decoder推理时是否应该设置causal mask

Date: 2/19/2025Category: Tag: transformer

最近在扣关于Transformer的细节,结果发现了一个问题:众所周知,在训练Transformer的过程中,decode的时候要使用causal mask避免泄漏还未生成的信息。在推理的时候,由于我们是逐个生成token的,所以不会出现泄漏的问题,那么是不是就不需要causal mask了呢?后来我看到StackExchange上有个人和我有相同的问题:Is the Mask Needed for Masked Self-Attention During Inference with GPT-2。目前我对这个问题的理解是这样的:答案是依然需要。

在Electron中读取并显示本地图片

Date: 2/12/2025Category: Tag: electron

最近需要在Electron应用程序中实现读取并显示本地图片的功能。这个功能看起来简单,但实现起来还是会遇到很多坑。这里就总结一下我遇到的问题,并给出一个完整的解决方案。

两种方案

首先,Electron是不支持在渲染进程中直接读取本地文件的,所以必须要通过主进程来读取文件。这里有两种方案:

  1. 利用Electron的Protocol API,将本地文件映射到一个自定义的协议,然后在渲染进程中通过这个协议来读取文件。
  2. 直接在主进程中提供一个读取文件的接口,然后在渲染进程中调用这个接口来读取文件。
Electron IPC教程

Date: 2/10/2025Category: Tag: electron

Electron的进程模型

在Electron中,有两种进程:主进程和渲染进程。渲染进程本质上就是一个Chromium的进程,它只负责渲染页面,不能访问Node.js提供的操作系统相关的API。主进程是Node.js的进程,可以访问操作系统的API。主进程和渲染进程之间通过IPC(Inter-Process Communication)进行通信。

这种模型的好处就在于:

  1. 不需要大幅修改Chromium的源码。
  2. 保证了浏览器环境的安全性。

如何使用IPC

假设我们希望在渲染进程中读取本地文件。

日本ACGN资讯网站

Date: 1/23/2025Category: Tag: ACGN

制定去日本的旅行计划的时候,不妨参考一下这些网站。

联动

コラボカフェ

演出等活动

アニメ・声優イベントカレンダー - アニメハック

同人展

同人イベントNAVI

Git取消追踪文件

Date: 1/19/2025Category: Tag:

Git 可以用.gitignore来忽略一些文件,但是如果是已经被追踪(track)的文件,将其添加到.gitignore并不会取消追踪。这时候就需要用到git rm --cached命令来手动取消追踪。但是有时候,误追踪的文件自己都不知道,有没有办法可以自动找出所有被追踪,但是原本应该被忽略的文件呢?

查阅 Git 文档之后,发现有两个命令可以实现这个功能:

  1. git ls-files,这个命令可以列出所有被追踪的文件。
  2. git check-ignore --no-index,这个命令可以用来判断某个文件是否被.gitignore忽略。这里必须加上--no-index参数,否则将不会输出已经被追踪的文件。
PowerShell脚本对于双横线的特殊处理

Date: 1/15/2025Category: Tag:

最近在运行npm create vue@latest -- --help的时候,发现一个有趣的现象:如果用 PowerShell 运行这个命令,会输出npm create的帮助信息,而用 cmd 运行就没有问题。后面经过调查,发现是 PowerShell 脚本对于双横线的特殊处理导致的。

npm 针对 cmd 和 PowerShell 提供了两个不同的脚本,分别是npm.cmd和npm.ps1。在 PowerShell 中运行npm的时候,实际上会执行npm.ps1。在 PowerShell 脚本中,--有特殊含义,因此执行npm create vue@latest -- --help就近似于执行npm create vue@latest --help,因此会输出npm create的帮助信息。

Python的坑:隐式字符串连接

Date: 1/13/2025Category: Tag:

最近写 Python 代码的时候遇到了一个坑:Python 和 C 语言一样,支持隐式字符串连接。所谓隐式字符串连接,就是当两个字符串字面量相邻的时候,会自动连接成一个字符串。比如下面这段代码:

s = 'hello' 'world'
assert s == 'helloworld'
用setuptools打包资源文件

Date: 1/13/2025Category: Tag:

默认情况下,setuptools 只会打包 Python 源代码文件,但有时候我们还需要打包一些资源文件,比如配置文件、模板文件、图片等等。这篇文章将会介绍如何用 setuptools 打包资源文件,并在库执行的时候访问这些文件。

我们假设项目的目录结构如下:

myproject/
├── mypkg/
│   ├── __init__.py
│   └── data/
│       ├── template.txt
└── setup.py
Pinia快速入门

Date: 1/12/2025Category: Tag: pinia

Pinia 是 Vue 3 官方的状态管理库,通俗点说就是可以给 Vue 应用程序提供一个“全局变量”。Pinia 的基本使用方法非常简单。

导入 Pinia

首先,我们需要安装 Pinia:

npm install pinia
在flex布局中实现按比例分配宽度或高度

Date: 1/8/2025Category: Tag:

需求

假设我们有这样的布局:

<div class="flex flex-col w-[400px] h-[400px]">
  <div>div 1</div>
  <div>div 2</div>
  <div>div 3</div>
</div>
代理服务器设置方法

Date: 1/6/2025Category: Tag:

由于 GFW 的存在,在中国境内访问境外的网站有时候会非常的慢,这时候就只能使用代理,但是有些程序没有使用系统全局的代理设置,需要自己手动配置代理。本文总结了一些需要手动配置的程序的配置方法。

git

Git 没有专门针对 HTTPS 协议的代理设置,不管是 HTTP 还是 HTTPS,都是使用 http.proxy 来设置代理。

git config --global http.proxy # 查看代理设置
git config --global http.proxy http://example.com:8000
git config --global --unset http.proxy
在Python中更优雅地创建抽象类及接口

Date: 1/5/2025Category: Tag:

以前我创建抽象类或者接口的时候,通常会这样写:

class AbstractClass:
    def method(self, arg: int) -> int:
        raise NotImplementedError()
理清Python的包管理系统

Date: 1/5/2025Category: Tag:

现代的编程语言都会提供官方的包管理系统,例如 Rust 的 Cargo、Node.js 的 npm、Go 的 go mod 等等。Python 也不例外,但是 Python 的包管理系统非常的混乱,这里主要介绍一下 pip 和 setuptools 这两个工具。

pip 和 setuptools 的关系

setuptools 是 Python 的构建和打包工具,它负责构建 Python 项目,并将其打包成一个 wheel 文件,而 pip 是 Python 的包管理工具,它可以直接安装 wheel 文件,也可以调用 setuptools 来打包项目并安装。pip 还负责管理包的依赖关系,它会在安装包的时候自动安装依赖。

Windows的程序文件路径

Date: 1/1/2025Category: Tag:

在 Windows 系统中,全局的(对于所有用户的)程序文件一般存放在C:\Program Files目录(64 位程序)或者C:\Program Files (x86)目录(32 位程序)下。这两个目录也有对应的环境变量,分别是%PROGRAMFILES%和%PROGRAMFILES(X86)%。

这两个目录下的文件受到系统保护,除非有管理员权限,否则只能读取,不能修改。这就是为什么软件在全局安装的时候一定会请求管理员权限。

程序运行过程中,有时候会需要创建或修改一些文件,比如配置文件或者临时文件等等,这种情况下,如果将这些文件放在%PROGRAMFILES%下就会很不方便。用户肯定不希望每次运行程序都要授予管理员权限。所以这些文件一般会放在用户目录下,比如C:\Users\<username>\AppData\Local目录。这个目录也有对应的环境变量,是%LOCALAPPDATA%。

在pybind11中操作numpy数组

Date: 12/29/2024Category: Tag:

最近需要给 Python 写一个涉及 numpy 数组操作的 C++扩展。我用的是pybind11,用这个库写 Python C++拓展非常方便,并且这个库也提供了对于 numpy API 的封装,可惜官方文档写得比较晦涩,并且也不太全面。我结合官方文档和源代码,总结了一下 numpy 数组的操作方法。

头文件

关于C++编译器的警告开关

Date: 12/28/2024Category: Tag:

C++编译器(主要是 GCC 和 Clang)都会提供一些控制警告的编译选项,这些选项的格式通常是-W<name>或者-Wno-<name>,其中<name>是警告的名称。例如-Wuninitialized表示启用“未初始化变量”的警告,-Wno-unused-variable表示禁用“未使用变量”的警告。

由于警告有很多种类,逐一启用会非常麻烦,因此编译器也提供了一些选项来批量启动警报,其中一个很有误导性的选项是-Wall。很多人会误以为这个选项会启用所有警告,但实际上这只会启用一部分警报。如果想要启用更多警告,可以再添加-Wextra选项。但是就算使用-Wall -Wextra也仍然不会启用所有警告。

setuptools的setup()函数的常用参数

Date: 12/28/2024Category: Tag:

本文将介绍setuptools的setup()函数的常用参数,主要参考的是官方文档。

name

包的名称,要将下划线替换为短横线,例如my-package。

version

版本号,例如1.2.3。

在Vite项目中使用Tailwind CSS

Date: 12/19/2024Category: Tag:

本文介绍如何在现有的 Vite 项目中使用 Tailwind CSS。

首先要安装 Tailwind CSS:

npm install -D tailwindcss postcss autoprefixer
关于clangd的配置文件

Date: 12/18/2024Category: Tag:

clangd 是一个可以分析 C++代码,并帮助编辑器进行代码补全、跳转等功能的工具,但是 clangd 不会自动分析项目的 include 路径、编译选项等信息,这些信息需要通过配置文件来提供。clangd 支持两种配置文件,第一种是compile_commands.json,这个文件会给项目中的每一个源文件分别指定编译选项,通常这个文件是由程序自动生成的。第二种则是compile_flags.txt,这个文件会给项目中的所有文件指定相同的编译选项,这个文件往往是手动编写的。

配置文件的查找路径

clangd 的文档写得不是很清楚,根据我所了解的信息,clangd 查找配置文件的逻辑大概是这样的:

在脚本中使用相对路径的一种方法

Date: 12/11/2024Category: Tag:

在软件项目中,经常需要写一些脚本来自动化执行一些任务。这里就有一个问题,脚本中的相对路径应该以什么为基准?目前我见过的常用方法有两种:

  1. 以当前工作目录(CWD)为基准。这种方法是最简单直接的,但是缺点也很明显。首先是在终端执行脚本的时候,就必须在正确的目录下执行。其次就是当其他进程调用脚本时,也必须确保 CWD 设置正确。事实上,CWD 可以看作是操作系统提供的一个进程级别的全局变量。本身依赖于全局变量就不是一件非常优雅的事情。

  2. 以脚本文件所在目录为基准。这种方法克服了上一种方法的缺点,但是却带来了一个新的问题:如果脚本文件被移动,那么脚本中的相对路径也要随之修改。

关于sudo的secure_path设置

Date: 12/11/2024Category: Tag:

sudo 有一个名为secure_path的设置,从 sudo 1.9.16 开始,secure_path设置会默认启用。官方博客介绍了这个改动的原因。

当使用 sudo 执行命令的时候,用户当前的环境变量会被修改。其中PATH会被设置为secure_path的值。因此,经常会发生加了sudo之后就找不到命令的情况。解决方法也是非常的简单,要么修改secure_path的值,要么直接删除这个设置,这样 sudo 就不会修改PATH了。

用sudo命令切换至root用户的正确方式

Date: 12/10/2024Category: Tag:

最近遇到了一个问题:我想查看一个只有 root 用户才有权限访问的目录下有哪些文件,于是我尝试用sudo cd /path/to/dir命令切换当前目录,结果报错。后来才知道cd并不是一个真实存在的可执行文件,而是一个 Shell 内置的特殊命令。所以,要通过cd切换目录,就只能把当前 Shell 的用户切换成 root 才行。

那么,将当前 Shell 的用户切换成 root,应该怎么做呢?我在网上看到了许多方法,包括sudo su、sudo -s等等。这些命令都能奏效,那么到底应该用哪个呢?我看到 StackOverflow 上有一篇回答总结得很好:

用Python实现经典设计模式(更新中)

Date: 12/6/2024Category: Tag:

观察者模式(Observer Pattern)

所谓观察者模式,就是一个简单的消息通知机制。当一个对象想要发送消息时,订阅了这个消息的对象会收到通知。

假设我们有一个杂志社MagazinePublisher,它有很多订阅者Subscriber,当杂志社有新的杂志发布时,会通知所有的订阅者。

class Subscriber:
    def __init__(self, name):
        self.name = name

    def update(self, message):
        print(f'{self.name} received message: {message}')

class MagazinePublisher:
    def __init__(self):
        self.subscribers = []

    def add_subscriber(self, subscriber):
        self.subscribers.append(subscriber)

    def remove_subscriber(self, subscriber):
        self.subscribers.remove(subscriber)

    def notify_subscribers(self, message):
        for subscriber in self.subscribers:
            subscriber.update(message)

subscriber1 = Subscriber('Alice')
subscriber2 = Subscriber('Bob')
publisher = MagazinePublisher()
publisher.add_subscriber(subscriber1)
publisher.add_subscriber(subscriber2)
publisher.notify_subscribers('New magazine is published!')
Windows NTP指南

Date: 12/5/2024Category: Tag:

本文将介绍如何在 Windows 系统上启动 NTP 服务器,以及如何让 NTP 客户端同步时间。

注意:本文中的大部分操作都需要管理员权限。

防火墙配置

默认配置下,Windows 防火墙会阻止所有未经允许的入站连接。因此,我们需要允许 NTP 服务(使用 UDP 123 端口)的入站连接。

New-NetFirewallRule -DisplayName "Allow NTP" -Direction Inbound -Protocol UDP -LocalPort 123 -Action Allow
Windows环境下Python UTF-8编码的一些坑

Date: 11/30/2024Category: Tag:

众所周知,Python 3 相比 Python 2 最大的变化之一就是字符串强制使用 UTF-8 编码,但这并不意味着使用 Python 3 就不会有字符编码的问题。在 Python 的世界里,确实一切皆 UTF-8,但是一旦和操作系统交互,就可能会出现问题。

open()的默认编码

在 Unix 系统上,open()函数的默认编码是 UTF-8,但是在 Windows 系统上,open()函数的默认编码是 ANSI(也就是根据 Windows 的地区设置而定,在中文 Windows 上一般是 GBK 编码)。因此,如果在 Windows 系统上要让 open()函数使用 UTF-8 编码,就需要显式指定编码:

给VSCode Vue项目配置ESLint和Prettier

Date: 11/25/2024Category: Tag:

什么是 ESLint 和 Prettier

ESLint 是一个专门针对 JS 程序的 linter,但是它也可以用来格式化 JS 代码。而 Prettier 是一个支持多种语言(包括 JS)的代码 formatter。Linter 和 formatter 的区别在于,formatter 只关注代码格式好不好看,不关心具体逻辑,而 linter 更关注程序逻辑是否有问题。

所以说,对于 HTML 和 CSS 代码,只能用 Prettier 来格式化。而对于 JS 代码来说,ESLint 和 Prettier 的功能有重复的部分。比较好的做法是,禁用 ESLint 中与 Prettier 冲突的格式化规则,然后同时使用 ESLint 和 Prettier。

关于npm install的开发依赖

Date: 11/25/2024Category: Tag:

npm在管理依赖的时候,会将依赖分为生产依赖和开发依赖。生产依赖就是运行的时候用到的依赖,而开发依赖则是测试框架、linter 等开发时才会用到的辅助工具。

如果你用npm install {package},那么默认就是作为生产依赖去安装。要安装一个开发依赖,就要指定--save-dev或者-D:

npm install --save-dev {package}
# 或者
npm install -D {package}
Ubuntu安装PostgreSQL的教程

Date: 11/20/2024Category: Tag:

安装

使用包管理器就可以直接安装 PostgreSQL:

sudo apt install postgresql
给boto3增加type hints

Date: 11/15/2024Category: Tag:

最近使用boto3的时候,发现boto3.client()的返回值没有标注类型,导致开发的时候很不方便。我原先以为是boto3的开发者忘了添加,后来看了源代码才知道,boto3.client()返回的对象的 class 是在运行时动态生成的,所以很难进行 type hints。(这就是动态语言的坏处啊!)

好在有个开发者生成了boto3各种 client 的 stub,包名叫boto3-stubs。假如使用的是 S3 client,就输入:

将本地机器的SSH密钥添加到远程机器的方法

Date: 11/13/2024Category: Tag:

用密码进行 SSH 登入麻烦又不安全。这篇文章将会教你如何将本地机器的 SSH 密钥添加到远程机器,用密钥快速登入 SSH。

首先,如果是 Windows 系统,建议安装下 Git for Windows,哪怕你不用 Git。因为 Git for Windows 附带了 SSH 客户端工具,以及兼容 Linux 的 Git Bash。

如果你的电脑上还没有 SSH 密钥,就输入下面的命令生成一个:

ssh-keygen -t ed25519 -C "your_email@example.com"
Django外键的on_delete参数

Date: 11/11/2024Category: Tag:

在 Django 的 ORM 框架中,通过models.ForeignKey可以很方便地创建外键字段。请看下面的例子:

from django.db import models

class Manufacturer(models.Model):
    name = models.TextField()

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.PROTECT)
Python标准库的functools.wraps装饰器

Date: 11/4/2024Category: Tag:

最近写代码的时候,发现给一个函数加了装饰器后就会报错。后来发现是因为装饰器抹去了原函数的 type hints 信息,导致后续逻辑出错。Python 的函数对象有很多 metadata,比如函数名、docstring、type hints 等,functools.wraps装饰器的作用就是复制这些 metadata。

请看下面的示例:

from functools import wraps

def decorator(func):
    def new_func():
        func()
    return new_func

def decorator_with_wraps(func):
    @wraps(func)
    def new_func():
        func()
    return new_func

@decorator
def f():
    pass

@decorator_with_wraps
def g():
    pass

print(f.__name__)  # 输出:new_func
print(g.__name__)  # 输出:g
用CloudFlare Workers建立免费的Docker Hub镜像

Date: 10/31/2024Category: Tag:

2024 年 12 月 28 日更新:似乎有消息称 CloudFlare 更新了用户条款,禁止将其服务用于代理用途。因此这个方法可能有风险。

由于中国封锁了 Docker Hub,导致在国内用 Docker 的时候会遇到很多问题。一个最简单的办法就是使用镜像,然而很遗憾的是,很多镜像站也被封锁了,因此我不得不自己建立一个 Docker Hub 镜像。在 GitHub 上,我发现一个使用 CloudFlare Workers 建立 Docker Hub 镜像的项目。CloudFlare Workers 是有免费版的,这就意味着可以免费建立一个 Docker Hub 镜像。然而原项目的 README 文档写得实在是太简陋了,我摸索了很久才最终测试成功,因此就将一个更详细的步骤写在这里。

域名的 DNS 设置

Date: 10/30/2024Category: Tag:

当我们购买了一个域名之后,必须要做的一件事就是将域名绑定到服务器的 IP 地址上。这个过程是通过在域名注册商那里修改 DNS 设置实现的。当我们在域名注册商那里购买一个域名之后,我们就可以通过域名注册商的控制面板来管理域名的 DNS 设置。

指定 DNS 服务器

通常来说,域名注册商会有自己的 DNS 服务器。默认情况下,域名会使用域名注册商自己的 DNS 服务器,但我们也可以改为第三方的 DNS 服务器。下图是 GoDaddy 的配置页面,图中我选择使用 CloudFlare 的 DNS 服务器。

修改 DNS 记录

关于CSRF攻击及其防御

Date: 10/25/2024Category: Tag:

什么是CSRF攻击

CSRF 攻击是利用了浏览器的一个安全漏洞。假设用户在 a.com 上登入,浏览器获得了 cookie,接下来如果用户访问 b.com,而 b.com 访问了 a.com,则浏览器会将 cookie 附加在请求上,从而可能在用户不知情的情况下以登入权限进行了 a.com 的操作。尽管较新的浏览器修复了部分漏洞,但是由于要保持互联网的兼容性,因此并没有完全修复这个问题,并且我们不能假设用户使用的都是较新的浏览器,因此对 CSRF 攻击进行防御还是有必要的。

如何防御CSRF攻击

浏览器发起请求的两种分类

要防御 CSRF 攻击,首先要知道在什么情况下,访问 b.com 的时候浏览器会对 a.com 发起请求。主要有两种情况:

Windows下Docker对路径斜杠处理的一个bug

Date: 10/22/2024Category: Tag:

最近在 Windows 下使用 Docker 的时候遇到了一个 bug。起因是我需要将 host 上的一个文件挂载到 Docker 容器里,于是我就执行了以下命令:

docker run -v ./foo.txt:/tmp/foo.txt ubuntu
Docker Compose确保Postgres启动的方法

Date: 10/16/2024Category: Tag:

最近我编写了一个 Django + Postgres 的网站,其 docker-compose.yml 大概是下面这样的:

services:
  db:
    image: postgres

  web:
    image: django
    depends_on:
      - db
Dockerfile中CMD和ENTRYPOINT的区别

Date: 10/14/2024Category: Tag:

在 Dockerfile 中有两个命令CMD和ENTRYPOINT,它们都可以用来指定 Docker 镜像启动时的默认命令,那么它们究竟有什么区别呢?

我在网上看到一篇博客文章很好地解释了两者的区别。简而言之,最终运行docker run命令的时候,容器执行的命令会是 ENTRYPOINT + CMD,但是有以下几点需要注意:

Attention层的简单讲解

Date: 10/2/2024Category: Tag:

输入与输出的维度

Attention 层是用来处理序列输入的,所以它接受的输入是一个二维的$n \times N_i$的矩阵,而输出则是一个$n \times N_o$的矩阵。$n$是输入序列的 token 个数,$N_i$则是 embedding 向量的长度,$N_o$则是输出的状态向量的长度。理论上 Attention 层对于$n$没有限制,而$N_i$和$N_o$则是模型的超参数。

Attention 层的超参数

除了$N_i$和$N_o$以外,Attention 层还有其他超参数。

一个 Attention 层包含 3 个全连接层,分别叫做 Query、Key 和 Value,用符号$Q$、$K$和$V$表示。这 3 个全连接层的输入维度都是$N_i$,而输出维度都是超参数,分别为$D_q$、$D_k$和$D_v$。稍后会看到,Attention 层要求$D_q=D_k$以及$D_v=N_o$。

docker compose up的build选项

Date: 10/2/2024Category: Tag:

从本地的 Dockerfile 构建镜像启动 Docker Compose 的时候(比如像下面的配置),

services:
  service0:
    build:
      context: .
      dockerfile: Dockerfile
关于.dockerignore的陷阱

Date: 9/24/2024Category: Tag:

在 Docker 构建镜像的时候,默认会将 context 下的所有文件打包发送到 builder 进程。这是因为 Docker builder 采用了一种客户端/服务器的架构,这样就可以实现远程构建。但是如果 context 下面文件很大,就会影响构建速度。为此,Docker 提供了一个机制,可以通过在.dockerignore文件中指定要排除的文件。但是这个.dockerignore 的语法和.gitignore 有很大差异,因此很容易写错,其中一个陷阱就是,在.gitignore 中,foo等价于**/foo,而在.dockerignore 中,这等价于/foo。除此之外还有很多微妙的差异,比如这篇文章中指出的:

关于Docker的当前目录

Date: 9/20/2024Category: Tag:

最近被 Docker 的当前目录位置整得有些混乱,特地在此整理一下。

Docker Build

执行docker build命令的时候,需要传入一个 positional 参数,这个参数就是 context。举个例子,假如当前 shell 的目录是/root,我们执行以下命令:

docker build -f dockerfiles/a.Dockerfile dockerfiles
Vite配置开发/生产环境下的环境变量

Date: 9/20/2024Category: Tag:

在vite.config.js里面有一个envDir的选项,用来指定 Vite 加载的环境变量的路径,默认值是项目的根目录,可以改为其他值:

export default defineConfig({
  envDir: "env",
});
现代HTML的最简模板

Date: 9/17/2024Category: Tag:

下面是使用 Vite 创建的前端项目的默认index.html文件内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>

  <body></body>
</html>
关于SysVinit和systemd

Date: 9/14/2024Category: Tag:

Linux 管理服务的系统有两种:SysVinit 和 systemd。systemd 比 SysVinit 推出得更晚。SysVinit 本质上就是运行一些管理服务的 shell 脚本,轻量灵活但是简陋,而 systemd 则是一个封装得更加复杂的系统。现在大多数 Linux 发行版都已经使用 systemd 了,但是在 Docker 环境下,由于种种原因,大多数情况下还是会使用 SysVinit。因此,同时掌握这两种系统还是很有必要的。

用 systemd 管理服务

systemd 管理服务的命令是systemctl。

使用pypdf库提取PDF文件中的图片

Date: 9/13/2024Category: Tag:

最近我遇到了这么一个问题:我用扫描仪扫描出来的文件是 PDF 格式的,然而我希望得到的是图片格式。从理论上说,扫描仪生成的 PDF 文件应该也只是图片的集合,因此只需要解析 PDF 文件就可以提取了。有一个名为pypdf的 Python 库就可以解析 PDF 文件中的对象。

from pypdf import PdfReader

reader = PdfReader('test.pdf')

for p in reader.pages:
    for i in p.images:
        with open(i.name, 'wb') as f:
            f.write(i.data)
Nginx入门教程

Date: 9/5/2024Category: Tag:

启动、终止与重新加载

启动 nginx:

nginx -c conf/nginx.conf
Windows家庭版关闭BitLocker

Date: 7/20/2024Category: Tag:

现在很多笔记本出厂时会默认启用 BitLocker。通过运行manage-bde -status命令可以查看分区是否有启用 BitLocker(这个命令需要管理员权限)。

如果是家庭版的 Windows(可以通过运行winver确认),那么 BitLocker 可以用系统设置的“隐私和安全性”->“设备加密”进行设置。这个“设备加密”就是家庭版 Windows 自带的阉割版 BitLocker。关闭“设备加密”就可以解密了。解密完成后,可以通过运行manage-bde -status命令确认是否已经解密成功。

关于VSCode的launch.json

Date: 7/20/2024Category: Tag:

VSCode 工作区的.vscode/launch.json文件中存储着调试和运行的配置。

基本结构

launch.json的基本结构如下:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "foo",
      "type": "debugpy",
      "request": "launch",
      "program": "${workspaceFolder}/bar.py"
    }
    // ...
  ]
}
解决Pixel 8无法使用中国SIM卡接打电话的问题

Date: 7/19/2024Category: Tag:

最近购买了一台日版的 Pixel 8,发现使用国内的 SIM 卡的时候可以流量上网,但无法正常接打电话和收发短信。经过调查,发现问题的根源是这样的:

  1. 由于 Pixel 手机没有在国内上市,所以谷歌在 Pixel 上加了地区锁,导致国内 SIM 卡无法使用 5G,最多只能用到 4G。
  2. 国内的 4G 通信基本上都使用了一种名为 VoLTE 的技术。这种技术需要手机的配置文件里有运营商的信息。由于 Pixel 手机没有在国内上市,自然 Pixel 里面也没有写入国内运营商的信息,因此无法使用 VoLTE。

综上所述,解决办法有两种:如果是 5G SIM 卡,那么可以尝试 root 之后解开地区锁使用 5G。还有一种更通用的办法是修改配置文件,让 Pixel 能够使用 4G VoLTE。修改配置文件无需 root。

用Python的zipfile标准库解压非标准编码的zip压缩包

Date: 7/19/2024Category: Tag:

时至今日(2024 年),依然有很多日本的 zip 压缩包采用 Shift JIS 编码。使用 7-Zip 等软件解压这些压缩包的时候,会发现文件名都是乱码。WinRAR 支持指定压缩包的文本编码,但是我个人不怎么喜欢 WinRAR。因此,我决定使用 Python 的标准库来解压非标准编码的 zip 压缩包。

对于 3.11 及 3.11 之后的 Python 版本,ZipFile提供了一个metadata_encoding的参数,通过指定这个参数就可以设置编码:

import zipfile

with zipfile.ZipFile('foo.zip', metadata_encoding='cp932') as f:
    f.extractall()
关于在data warehouse中实例化view的优化问题

Date: 7/14/2024Category: Tag:

问题背景

假设我们有一个名为 SalesTable 的 SQL 表格,这个表格有 4 列:time_、kind、shop 和 sales,分别代表某个时间段内某个种类的商品在某家商店的销售额。

假如我们需要统计每个时间段内每个种类商品的销售总额,那么我们可以这么编写 SQL 语句:

SELECT time_, kind, SUM(sales) FROM SalesTable GROUP BY time_, kind;
数据分析中的association问题及其算法

Date: 7/7/2024Category: Tag:

问题定义

顾客的交易记录可以用一个$m \times n$的矩阵来表示,如果第$i$个顾客购买了第$j$个商品,矩阵的第$i$行第$j$列就为 1,否则为 0。

接下来定义 3 个新概念:association、support 和 confidence。

一个 association 可以用$X \to Y$来表示,其中$X$和$Y$都是商品的集合,$X \cap Y = \emptyset$。

一个商品集合的 support 是指交易记录中包含这些商品的交易的个数。一个 association 的 support 是指$X \cup Y$的 support。

3种决策树:ID3、C4.5和CART

Date: 7/7/2024Category: Tag:

假设有以下数据集,记录着一些人的种族、收入、是否有子女以及他们是否有购买保险:

Race Income Child Insurance
B H N Y
W H Y Y
W L Y Y
W L Y Y
B L N N
B L N N
B L N N
W L N N
Python标准库的简易HTTP服务器

Date: 6/30/2024Category: Tag:

Python 标准库有一个名为http.server的模块,提供了简易的 HTTP 服务器功能。下面的示例代码用这个模块启动了一个简单的 HTTP 服务器。这个服务器只支持 POST 方法,会打印 POST 请求的信息,并返回一个固定的字符串。

from http.server import BaseHTTPRequestHandler, HTTPServer


class MyHandler(BaseHTTPRequestHandler):

    def do_POST(self):
        print('method:', self.command)
        print('path:', self.path)
        print('HTTP version:', self.request_version)
        print('headers:', self.headers)
        content_length = int(self.headers['Content-Length'])
        body = self.rfile.read(content_length).decode()
        print('body:', body)

        response = b'Roger!'
        self.send_response(200)
        self.send_header('Content-Length', len(response))
        self.end_headers()
        self.wfile.write(response)


if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), MyHandler)
    server.serve_forever()
Ubuntu常用软件包

Date: 6/23/2024Category: Tag:

下面列出的软件包主要是命令名称和包名不一致的软件包。

包名 提供的命令
build-essential gcc, g++, make
iproute2 ip
iputils-ping ping
关于Pydantic model字段的别名

Date: 6/22/2024Category: Tag:

给 model 字段指定别名

Pydantic 的 model 字段名是通过 Python 的类型注解设定的。这样一来当 schema 的字段名不是合法的 Python 变量名的时候就会带来问题。这种情况下可以用 Field 对象的 alias 属性来给字段指定名称。例如下面的例子给family_name这个字段设定了别名family-name。

from pydantic import BaseModel, Field


class Person(BaseModel):
    family_name: str = Field(alias='family-name')


json_str = '{"family-name": "Smith"}'
person = Person.model_validate_json(json_str)
print(person.model_dump_json())
使用Docker搭建PySpark学习环境

Date: 6/20/2024Category: Tag:

首先安装 Docker,然后拉取 Spark 的 Docker 镜像:

docker pull apache/spark
Python的代码格式化程序对比

Date: 6/19/2024Category: Tag:

知名的 Python 代码格式化程序主要有 3 个:autopep8、black和yapf。其中 autopep8 是最古老的,它的开发已经不是很活跃了,因此不推荐使用。black 和 yapf 的区别主要在于 yapf 提供了更多的格式化选项,让程序员可以根据自己的喜好调节代码风格。而 black 的作者有比较严重的洁癖,主张所有的 Python 代码都应该是统一的风格,因此几乎没有提供自定义选项。black 甚至会强制要求字符串用双引号包裹。由于本人没有强迫症,因此还是更倾向于使用 yapf。

Python内置函数的key参数

Date: 6/19/2024Category: Tag:

Python 内置的 sort()、sorted()、max()和 min()都有一个可选的 key 参数。通过这个参数可以指定比较的基准。下面提供一些范例。

下面的代码会将 list 中的点按照 l2-norm 排序:

a = [(3, 4), (1, 6), (2, 5)]
print(sorted(a, key=lambda x: x[0] ** 2 + x[1] ** 2))
BT种子文件和磁力链的区别

Date: 6/16/2024Category: Tag:

在 BT 分享网站上,经常可以看见一个资源有两种下载方式:BT 种子文件或者磁力链。使用这两种方式有什么区别呢?先下结论:磁力链只存储了 BT 种子文件部分内容的 hash 值。使用磁力链下载文件的时候,要首先从拥有资源的节点上根据 hash 值获得 BT 种子文件,然后才能下载。因此可以说磁力链只是一个中间过程,BT 种子文件才是最终的结果。

接下来将首先介绍使用 BT 协议下载文件的时候需要的信息,然后将分别介绍 BT 种子文件和磁力链分别存储了哪些信息。

BT 协议需要的信息

要让 BT 协议工作,需要两个最关键的信息。第一个是 tracker 服务器的地址。第二个是文件名及其 hash 值。Tracker 服务器的作用是让需要下载资源的节点能够发现拥有资源的节点。在这两个信息中,文件名及其 hash 值显然是更重要的,因为没有这个信息,我们就无法确定要下载的文件。至于 tracker 服务器地址这个信息其实并不是那么重要,因为 tracker 服务器并不是唯一且固定的,知名的 tracker 服务器列表在互联网上都可以查到,所以我们直接让 BT 客户端使用互联网上的 tracker 列表就行。

Python 3.12的一个破坏兼容性的更改:移除distutils

Date: 6/10/2024Category: Tag:

最近使用 Python 3.12 import keras 的时候,遇到了如下的错误:

ModuleNotFoundError: No module named 'distutils'
非日本居民能够使用的日本服务

Date: 6/8/2024Category: Tag:

本文列举了一些非日本居民也能够使用的日本服务(网购、预约系统等等)。非日本居民使用日本服务最大的障碍主要是手机号码。有些日本服务需要日本手机号码的短信验证才能够使用,而非日本居民是无法获得日本手机号码的。

网购

駿河屋:注册账号的时候需要填写日本电话号码和住址,不过乱填也可以。支付时可以用 PayPal 刷信用卡。

卡拉 OK

日本的卡拉 OK 不需要注册会员就可以用,但是会员往往会有优惠价,所以这里列举的是可以注册会员的店铺。

实用软件推荐

Date: 5/21/2024Category: Tag:

二进制编辑器、PE 文件解析器等

HexEd.it:一个在线二进制编辑器。

PE-bear:开源的 PE 文件解析器。

Resource Hacker:编辑、提取 PE 文件的资源(.rsrc section)。

关于Python的from future import annotations

Date: 5/20/2024Category: Tag:

在默认情况下,Python 的 type hints 的值是真正的对象。请看下面的例子:

def add(a: int, b: int) -> int:
    return a + b


print(add.__annotations__)

# output:
# {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
关于LaTeX的各种算法伪代码宏包

Date: 5/19/2024Category: Tag:

在用 LaTeX 描述算法的时候,经常会用到 algorithm、algorithmic、algorithmicx、algpseudocode、algcompatible、algorithm2e 等各种宏包。这些宏包关系比较复杂,并且用法也存在差异,经常会引发混淆。本文就对这些宏包的作用进行简要的介绍。

首先,这些宏包可以分为两大类,algorithm2e 单独算作一类,而其他的是另一类。任何算法都可以分为算法标题和算法主体两个部分。对于 algorithm2e 宏包而言,算法标题和算法主体都通过 algorithm 环境来实现。而对于其他的宏包来说,算法标题通过 algorithm 环境实现,算法主体通过 algorithmic 环境实现。也就是说,如果你用的是 algorithm2e 宏包,那么你的算法代码大概就是下面这样:

Python循环语句中的else子句

Date: 5/16/2024Category: Tag:

Python 的循环语句(while 和 for 语句)有一个特殊的语法,就是它们支持 else 子句。下面用一个例子来说明其作用。

for i in a:
    if find(i):
        do_something(i)
        break
else:
    not_found()
管理Windows“添加或删除程序”中的项目

Date: 5/15/2024Category: Tag:

在 Windows 的控制面板中点击“添加或删除程序”,便会看到当前电脑中安装的程序。这些程序的信息实际上是存储在注册表中的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall。这个注册表项下的每一个子项都代表了一个安装的程序。每个项记录的信息可以参见微软的官方文档。下面简要介绍关键的信息。下面提到的值类型都是字符串。

《基于启动和脑电波实验研究普通话和闽南语连续变调词的储存模式》书摘

Date: 5/15/2024Category: Tag:

最近粗略翻阅了一本学术著作《基于启动和脑电波实验研究普通话和闽南语连续变调词的储存模式》。书中第二章和第三章针对普通话和闽南话的变调规则做了一个实验。实验流程是这样的,以普通话的“3-3→2-3”规则(比如辅导 fu3dao3 实际上会念成 fu2dao3)为例。实验人员让被试者听一些词语,然后让被试者判断这些词语是不是真实存在的词语。在让被试者听一个词语之前,会播放这个词的第一字作为刺激。实验分为两组,第一组播放的刺激是变调前的发音,比如播放“辅导”之前会先播放“fu3”的声音。另一组则会提前播放变调后的发音,比如播放“辅导”之前会先播放“fu2”的声音。然后比较两组实验中被试者的反应时间和准确率。除了普通话的“3-3→2-3”规则,研究者还针对闽南语的“24→33”规则(例如“平等”)和“51→55”规则(例如“主持”)也做了相同的实验。

1932-1939年的台语歌曲

Date: 5/15/2024Category: Tag:

最近阅读了厦门大学出版社出版的《传播与流变: 海峡两岸闽南语歌曲研究》,发现 91 页有一个表格,列举了所有 1932-1939 年的台语歌曲。于是我便在 YouTube Music 上建立了一个歌单,收集了表格中的经典台语歌曲。由于 YouTube Music 音源有限,因此遗漏了一些歌曲。这些缺失的歌曲可以在台湾声音一百年里面找到。

允许UWP应用访问localhost

Date: 5/12/2024Category: Tag:

由于 Windows 的安全限制,UWP 应用默认是不能访问 localhost 的。这会导致 UWP 应用无法翻墙。要允许 UWP 应用访问 localhost,就必须修改注册表。GitHub 上有人编写了一个 GUI 程序,可以帮助我们修改注册表来解除限制。原作者的仓库地址在这里。可惜的是,原作者只提供了源代码,而没有提供编译好的可执行文件。不过好在 GitHub 上有人 fork 了仓库,提供了编译好的可执行文件。

使用date命令生成ISO 8601格式的当前时间

Date: 5/11/2024Category: Tag:

在很多场合可以看到形如 2024-05-11T17:27:12+08:00 的时间字符串。这是用 ISO 8601 格式表示的时间。字符串中的“T”是日期和时间和分隔符,而“+08:00”是时间的偏移量,也就是说时区是东八区。

在 Linux 下,可以使用 date 命令快速生成 ISO 8601 格式的当前时间:

date --iso-8601=seconds
在shebang中使用/usr/bin/env

Date: 5/11/2024Category: Tag:

假设我们要编写一个 bash 脚本,很多人会这么写 shebang:

#!/bin/bash
用Makefile编写生成多个目标文件的规则

Date: 4/27/2024Category: Tag:

假设现在有一个命令 cmd,它会读取文件 a,然后生成文件 x 和 y。

#include <stdio.h>
#include <stdlib.h>

int main() {
    puts("Reading a.");
    system("cat a");
    puts("Creating x and y.");
    system("touch x y");
    return 0;
}
使用screen命令在后台运行进程

Date: 4/26/2024Category: Tag:

谈到在后台运行进程,大多数人第一个想到的可能是直接在命令后面加一个&,这么做有一些缺点:

  1. 需要强行终止进程的时候,必须记住或者查询进程的名称、ID 等等。
  2. 有时候使用&似乎会导致一个奇怪的 bug,使得终端输出紊乱甚至无法使用(参见这个 StackOverflow 问题)。
Windows下用HTTP代理连接SSH

Date: 4/23/2024Category: Tag:

这里假设使用的是 Git 提供的 SSH。首先打开 Git Bash,输入命令

where connect
PowerShell脚本被禁止执行的解决方法

Date: 4/9/2024Category: Tag:

在 Windows 中执行 PowerShell 脚本的时候,默认情况下会得到下面的错误信息:

无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。

这是因为默认情况下,Windows 的 PowerShell 脚本执行策略是Restricted,也就是阻止运行任何脚本。要允许执行任意脚本,只需要将执行策略修改为Unrestricted即可:

主机与VirtualBox虚拟机的网络通信配置

Date: 3/15/2024Category: Tag:

虚拟机连接主机

VirtualBox 已经默认建立了一个网卡,可以通过这个网卡和主机通信。

首先点击菜单栏的“管理”->“工具”->“网络管理器”。

然后就可以在“仅主机(Host-Only)网络”里看到网卡的 IP 地址。图中所示的地址是 192.168.56.1。

主机连接虚拟机

VirtualBox 默认的 NAT 网络只能支持虚拟机与主机或外网通信,主机是无法与虚拟机通信的。有时候我们需要让主机与虚拟机通信,比如让主机连接虚拟机的 SSH 等等。这种情况下,我们需要手动配置 VirtualBox 的端口转发。