要在 IPython 启动时自动导入所需的模块,最可靠的方法是借助其启动目录(startup)。IPython 会在这个目录里查找并执行指定的Python(.py)或IPython(.ipy)脚本文件,从而在交互式会话开始前,就自动完成所有预先配置的工作。

环境准备:找到startup目录

操作的核心就是把你写好的自动导入脚本,放到正确的startup文件夹里

1. 找到配置文件目录:打开终端(命令行界面),输入ipython locate命令。这个命令会告诉你 IPython的配置文件目录在系统的哪个位置, 假设返回结果示例为/home/用户名/.ipython。实际路径可能因操作系统而异。

$ ipython locate
/home/blctrl/.ipython

2. 进入startup目录:根据上一步找到的路径进入startup文件夹。

$ cd /home/blctrl/.ipython/profile_default/startup

如果profile_default下没有 startup 文件夹,请用下面命令新建:

$ mkdir -p  /home/blctrl/.ipython/profile_default/startup

3. 使用启动目录脚本

1) 创建启动脚本:在startup文件夹下,创建一个Python文件。文件名可以随意,但最好有意义。推荐使用数字前缀来控制脚本的执行顺序.

(base) ~/.ipython/profile_default/startup$ ls
00_devices.py  01_writer.py  02_re.py  README

00_devices.py用于导入需要使用的设备:

from ophyd import EpicsMotor

tth = EpicsMotor("PMC16C:m1", name="tth")
tth.wait_for_connection()
th =  EpicsMotor("PMC16C:m2", name="th")
th.wait_for_connection()
chi = EpicsMotor("PMC16C:m3", name="chi")
chi.wait_for_connection()
phi = EpicsMotor("PMC16C:m4", name="phi")
phi.wait_for_connection()


from ophyd.scaler import EpicsScaler

scaler = EpicsScaler("NCT16:scaler1", name='scaler')
scaler.wait_for_connection()
scaler.channels.read_attrs = ['chan1', 'chan2', 'chan3']
scaler.channels.chan1.name = 'PCT'
scaler.channels.chan2.name = 'Mon'
scaler.channels.chan3.name = 'Det'
scaler.preset_time.set(1.0)

01_writer.py用于导入用于在本地指定路径下保存csv格式文件的python类:

import csv
import os
from datetime import datetime

class DataSaver:
    """Bluesky 回调:自动将扫描数据保存为 CSV 文件"""
    def __init__(self, data_dir="./data"):
        self.data_dir = data_dir
        os.makedirs(self.data_dir, exist_ok=True)
        self.file = None
        self.writer = None
        self.filename = None

    def __call__(self, name, doc):
        if name == 'start':
              # 生成文件名:scan_{scan_id}_{时间戳}.csv
            scan_id = doc.get('scan_id', 0)
            timestamp = datetime.fromtimestamp(doc['time']).strftime("%Y%m%d_%H%M%S")
            self.filename = os.path.join(self.data_dir, f"scan_{scan_id}_{timestamp}.csv")
            self.file = open(self.filename, 'w', newline='')
            self.writer = csv.writer(self.file)
            # 可写入元数据注释(可选)
            self.writer.writerow([f"# scan_id: {scan_id}"])
            self.writer.writerow([f"# uid: {doc.get('uid', '')}"])
            # 列名将在 descriptor 中确定,先留空
            self.headers_written = False

        elif name == 'descriptor':
            self.data_keys = list(doc.get('data_keys', {}).keys())
            if not getattr(self, 'headers_written', False):
                self.writer.writerow(['seq_num', 'time'] + self.data_keys)
                self.headers_written = True
                self.file.flush()

        elif name == 'event':
            seq_num = doc.get('seq_num', 0)
            t = doc.get('time', 0)
            data = doc.get('data', {})
            row = [seq_num, t] + [data.get(k, None) for k in self.data_keys]
            self.writer.writerow(row)
            self.file.flush()
        elif name == 'stop':
            if self.file:
                self.file.close()
                print(f"数据已保存到: {self.filename}")

02_re.py用于导入需要使用的计划, 运行引擎的运行参数等:

from bluesky.plans import scan, list_scan, grid_scan, rel_scan, count, rel_grid_scan, rel_grid_scan
from bluesky.plan_stubs import mv, mvr, read
from bluesky.callbacks.best_effort import BestEffortCallback
from databroker import Broker
from bluesky import RunEngine

ds = DataSaver(data_dir='/home/bl0202/data')
db = Broker.named('temp')
bec = BestEffortCallback()
RE = RunEngine()
RE.subscribe(db.insert)
RE.subscribe(bec)
RE.subscribe(ds)

启动IPython:

$ ipython
Python 3.11.15 | packaged by conda-forge | (main, Mar  5 2026, 16:45:40) [GCC 14.3.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.10.1 -- An enhanced Interactive Python. Type '?' for help.
Tip: You can use `files = !ls *.png`

In [1]: %matplotlib qt

使用count计划计划进行测试:

In [2]: RE(count([scaler], num=5, delay=1))

Transient Scan ID: 1     Time: 2026-05-14 16:18:56
Persistent Unique Scan ID: '4de04f99-40c3-4253-945d-ec8e7712da1f'
New stream: 'primary'
+-----------+------------+------------+------------+------------+
|   seq_num |       time |        PCT |        Mon |        Det |
+-----------+------------+------------+------------+------------+
|         1 | 16:18:57.2 |    1000000 |          0 |          0 |
|         2 | 16:18:58.2 |    1000000 |          0 |          0 |
|         3 | 16:18:59.3 |    1000000 |          0 |          0 |
|         4 | 16:19:00.4 |    1000000 |          0 |          0 |
|         5 | 16:19:01.4 |    1000000 |          0 |          0 |
数据已保存到: /home/bl0202/data/scan_1_20260514_161856.csv
+-----------+------------+------------+------------+------------+
generator count ['4de04f99'] (scan num: 1)

使用scan计划进行测试:

<pre><font color="#26A269">n [</font><font color="#33DA7A"><b>4</b></font><font color="#26A269">]: </font>RE(scan([scaler], tth, -<font color="#26A269">1</font>,<font color="#26A269">1</font>,<font color="#26A269">6</font>))

Transient Scan ID: 2     Time: 2026-05-14 16:19:34
Persistent Unique Scan ID: &apos;52d0f735-e45d-4090-a1da-36dd16a18e52&apos;
New stream: &apos;primary&apos;
+-----------+------------+------------+------------+------------+------------+
|   seq_num |       time |        tth |        PCT |        Mon |        Det |
+-----------+------------+------------+------------+------------+------------+
|         1 | 16:19:44.2 |    -1.0000 |    1000000 |          0 |          0 |
|         2 | 16:19:53.3 |    -0.6000 |    1000000 |          0 |          0 |
|         3 | 16:20:02.4 |    -0.2000 |    1000000 |          0 |          0 |
|         4 | 16:20:11.5 |     0.2000 |    1000000 |          0 |          0 |
|         5 | 16:20:20.6 |     0.6000 |    1000000 |          0 |          0 |
|         6 | 16:20:29.7 |     1.0000 |    1000000 |          0 |          0 |
数据已保存到: /home/bl0202/data/scan_2_20260514_161934.csv
+-----------+------------+------------+------------+------------+------------+
generator scan [&apos;52d0f735&apos;] (scan num: 2)

使用list_scan计划进行测试:

In [5]: RE(list_scan([scaler], tth, [-1,-0.5, 0, 0.5, 1]))

Transient Scan ID: 3     Time: 2026-05-14 16:22:08
Persistent Unique Scan ID: '1935608f-37af-4fa3-ad8f-ca9846df61ac'
New stream: 'primary'
+-----------+------------+------------+------------+------------+------------+
|   seq_num |       time |        tth |        PCT |        Mon |        Det |
+-----------+------------+------------+------------+------------+------------+
|         1 | 16:22:17.8 |    -1.0000 |    1000000 |          0 |          0 |
|         2 | 16:22:26.9 |    -0.5000 |    1000000 |          0 |          0 |
|         3 | 16:22:36.0 |     0.0000 |    1000000 |          0 |          0 |
|         4 | 16:22:45.1 |     0.5000 |    1000000 |          0 |          0 |
|         5 | 16:22:54.2 |     1.0000 |    1000000 |          0 |          0 |
数据已保存到: /home/bl0202/data/scan_3_20260514_162208.csv
+-----------+------------+------------+------------+------------+------------+
generator list_scan ['1935608f'] (scan num: 3)

使用grid_scan进行测试:

In [6]: RE(grid_scan([scaler], tth, -1,1,6, th,-2,2,6))


Transient Scan ID: 4     Time: 2026-05-14 16:23:26
Persistent Unique Scan ID: 'f1aabdbe-cd3b-4d75-81fe-9fa938195ccc'
New stream: 'primary'
+-----------+------------+------------+------------+------------+------------+------------+
|   seq_num |       time |        tth |         th |        PCT |        Mon |        Det |
+-----------+------------+------------+------------+------------+------------+------------+
|         1 | 16:23:45.7 |    -1.0000 |    -2.0000 |    1000000 |          0 |          0 |
|         2 | 16:23:54.8 |    -1.0000 |    -1.2000 |    1000000 |          0 |          0 |
...
|        35 | 16:29:46.8 |     1.0000 |     1.2000 |    1000000 |          0 |          0 |
|        36 | 16:29:55.9 |     1.0000 |     2.0000 |    1000000 |          0 |          0 |
数据已保存到: /home/bl0202/data/scan_4_20260514_162326.csv
+-----------+------------+------------+------------+------------+------------+------------+
generator grid_scan ['f1aabdbe'] (scan num: 4)

同时测试数据也在指定路径下进行了保存:

:~/data$ ls
scan_2_20260513_155455.csv  scan_2_20260514_161934.csv  scan_3_20260514_162208.csv  scan_4_20260514_162326.csv

使用 startup 目录是首选:它更灵活,可以组织多个脚本,维护也方便。

  • 命名决定顺序:文件会按字典序(即文件名顺序)执行。你可以用数字前缀来控制,比如 01_imports.py 先于 02_settings.py 运行。
  • .py vs .ipy:如果脚本里只有 Python 代码(如 import ...),用 .py 即可。如果包含 IPython 特有的魔术命令(如 %matplotlib),则需要使用 .ipy 后缀。
  • scripts目录扩展:你也可以把自定义的Python脚本整个放在profile_default/startup/ 下,当做一个可执行的库来自动导入,实现隐式导入。

更多推荐