听说你要用 Django 自己搭实验平台？（2——数据的发送和存储）
本文是 Django 实验平台系列教程的第三篇。阅读本文前需要你先阅读前两篇文章。[post cid="382" cover="" size=""/][post cid="392" cover="" size=""/]完成前两篇文章的步骤之后，你的手上应该有一个可以 Hello World 的 onlineExperimentTutorial。我们在这个基础上研究下面的流程。0 基本流程和概念在上一篇文章的末尾，其实我们已经把这个系统需要的核心功能梳理出来了：如何让实验程序发出数据？如何建立并管理 Django 模型？Django 视图如何接收实验程序的数据？如何处理这些数据？如何储存这些数据？到这里，需要引入一些基本的概念，我们来一条一条解决。0.1 网站如何和你的客户端交换数据？隆重介绍：HTTP 协议。没错，就是你在浏览器里看到的这个 HTTP。它是你和网站交互时沟通信息的指定方法。它的全称叫做“超文本传输协议（Hyper Text Transfer Protocol）”。它所传输的内容，也就是“超文本”——基本上仍然是文本，但不只是文本。如果拥有 jsPsych 的基础的话，你已经知道网页实验程序的本质是什么了——一大段一大段的文本、代码。这些代码通过 HTTP 协议发送到你的浏览器里，浏览器解析这些文本，并加载对应的内容，就变成了你所看到的网页。想要更进一步了解 HTTP 的相关内容的话，可以移步HTTP 教程 | 菜鸟教程。我们稍微快进一点。我们的目标是“让实验程序发出数据”。发出数据应该怎么做呢？HTTP 协议规定了五种标准的方法（method），分别如下：GET：请求从服务器获取指定资源。这是最常用的方法，用于访问页面。POST：请求服务器接受并处理请求体中的数据，通常用于表单提交。PUT：请求服务器存储一个资源，并用请求体中的内容替换目标资源的所有内容。DELETE：请求服务器删除指定的资源。HEAD：与 GET 类似，但不获取资源的内容，只获取响应头信息。通常访问网页时，我们的浏览器发出的是 GET 请求。那么上传一段数据应该用什么请求呢？答案是 POST 请求。注意，并不是 PUT，PUT 的作用是上传一个文件。虽然大家眼中的实验数据确实是以文件形式储存的，但我们在后续的操作中并不会手动去上传一个文件——我们会让实验程序直接把数据发送到服务端，再让服务端对数据做处理。（而且在现在实际的应用中，PUT 请求真的已经很少用了。）0.2 Django 如何管理数据？上一篇文章提过，Django 的架构是这样的：所以，很自然地，我们需要去模型里面处理数据。我们先试试如何建立一个模型。0.2.1 如何正确地创建一个模型？首先，我们要在 Django 项目里新建一个应用（app）。进入这个项目对应的终端（请回顾前面的教程），然后运行以下命令：django-admin startapp myapp应用是 Django 里的一种功能单元，你可以把不同的功能写在不同的 app 里，这对于日后维护代码有很大的帮助。创建成功后，你会在目录里看到这样的一个东西：注意，models 必须在 app 内调用，不能在项目的根目录调用。打开 models.py。让我们来随便写点什么：from django.db import models # Create your models here. class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) 我们创建了一个叫做 Person 的模型，并指定了两个变量：first_name 和 last_name。0.2.2 注册并执行迁移接下来，我们要怎么让 Django 知道这个模型的存在呢？为了在 Django 中注册这个模型，我们需要首先注册我们刚刚生成的应用。回到 \onlineExperimentTutorial\ 下面，找到这个叫 settings.py 的文件。这个文件管理的是整个项目的设置。打开它，看到大约 30 行左右的位置，找到 INSTALLED_APPS 这一项：接下来，我们还要执行一个步骤：迁移。回到终端，运行以下命令：python manage.py makemigrations python manage.py migrate这样的话，Django 就知道了这一个模型的存在，并在系统里注册好了这个模型。之后每次创建新的 app 或新的 model 时，你都需要执行一遍这个流程。这里究竟发生了什么？如果你想要知道这个问题的答案的话，这是一个更深层次的解释，但你也可以跳过这一段。Django 本体程序其实并不能单独存在，其一定需要一个数据库后端来支撑各项功能（它自己本身也是有很多数据需要储存的！不只是我们让它储存的那些）。只不过当你新建一个 Django 项目时，它会自动创建一个 SQLite 数据库，也就是你会看见的 db.sqlite3 的那个文件。这个文件由 Django 自己来驱动，不需要额外的应用程序。当你新建一个模型之后，Django 需要告诉数据库：我们新增了一个数据模型（在数据库的术语中，被称作“表”），随后让数据库新建用于储存这个模型的数据表。这也就是迁移的目的——让 Django 把我们对代码的最新改动反映到数据库里。这个过程通常不需要我们自己操心，Django 可以自行完成。在互联网领域，有各种各样的数据库驱动程序（比如 MySQL 等等）。你完全可以切换各种各样的数据库（只要 Django 支持），它们在性能上可能会有差别，但在功能上都是一样的，以便于管理和储存的方式来保存数据。0.2.3 如何管理这个模型？模型是确实创建好了，但它目前对我们是看不见摸不着的。我们要怎么管理这个模型呢？我们需要用到 Django 自带的 Admin 系统。这是 Django 网站的后台组件，默认预装在了你的项目里。在刚刚我们新建的 \myapp\下面，有一个 admin.py 的文件。我们把刚刚注册好的模型放进去：from django.contrib import admin from .models import Person # First import our model # Register your models here. admin.site.register(Person) # Then register it in the admin panel 接下来，我们运行这个项目（终端执行 python manage.py runserver），然后访问 127.0.0.1:8000/admin，打开后台面板哦等下，我们还没注册管理员账户。回到终端（你可能需要先退出 Django 的运行），输入这个指令创建一个超级用户。python manage.py createsuperuser指定好必要的信息（用户名、密码等等）之后，你就可以登录了。你可以在管理面板里看到我们刚刚新建的 myapp 和它下面的 Persons ……哎等下，怎么多了个复数？嗯，Django 是这样的，在这个面板里命名都会加一个 s，正常现象。你也可以在模型的 Meta 属性中指定它的名字，相关说明请参考官方教程。总之你已经看到这个数据的储存位置了！你也可以点开看看，往里面新增或者修改一些数据，试试这个管理面板的功能。1 新增 Django 视图和模型铺垫到此结束，让我们接下来看看怎么开始整合实验程序和 Django。在 Django 部分，我们需要一个视图用来接收数据、新建一个模型用来储存数据，并在视图里编写代码把数据放到数据库里。1.1 编写视图和模型我们先来新建一个 app，用来承载我们的视图和模型。django-admin startapp simpleExperimentData分别编辑 views.py、models.py、admin.py 如下：1.1.1 views.pyfrom django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from django.http import JsonResponse import json from .models import JsonData @csrf_exempt def handle_json_upload(request): if request.method == 'POST': # 规定方式为 POST try: json_data = json.loads(request.body) # 直接解析JSON数据 JsonData.objects.create(json_data=json_data) # 将JSON数据存储到模型中 return JsonResponse({'status': 'success'}) except json.JSONDecodeError: return JsonResponse({'status': 'error', 'message': 'Invalid JSON data'}) # 无法解析时报错 except Exception as e: return JsonResponse({'status': 'error', 'message': str(e)}) # 其他错误 else: return JsonResponse({'status': 'error', 'message': 'Invalid request method'}) # 报错，不接受其他方式传入 这是一个简单的视图，规定了只接受方法为 POST 的请求，并将请求体中的数据解析后保存到 JsonData 模型中。为了方便调试，我们还加入了一点点简单的报错。我们在这里指定的数据类型为 JSON，因此后续发送数据时应当发送 JSON 格式的数据。注意到我们在编写这个视图时使用了 @csrf_exempt 这一装饰器。这个装饰器表示下面的视图不进行任何的跨站攻击保护。如果只用于开发环境，或者短时间的数据收集，这样或许没有太大的风险。但如果要长期运转网站的话，你需要学习 Django 中的防跨站攻击保护（CSRF）相关的内容。这一部分我们会在下一篇教程中有所涉及。1.1.2 models.pyfrom django.db import models class JsonData(models.Model): json_data = models.JSONField() class Meta: verbose_name = "JSON Data TS1.1" verbose_name_plural = "JSON Data TS1.1" 我们创建了一个与之对应的 model，JsonData，并储存了一个名为 json_data 的字段。同时我们还指定了它在面板中显示的名字。1.1.3 admin.pyfrom django.contrib import admin from .models import JsonData admin.site.register(JsonData) 再在管理面板中注册相关模型即可。1.1.4 注册与迁移和前面一样，前往项目的 settings.py，添加 app：INSTALLED_APPS = [ "django.contrib.admin", # ... "myapp", "simpleExperimentData" # <<<< ]接下来进行迁移：python manage.py makemigrations，python manage.py migrate。1.2 添加路由为了让我们能访问这个视图，我们需要添加一个指向这个视图的 URL（还记得教程 1 里的 Hello World 吗？）。在我们刚刚新建的 \simpleExperimentData\ 下新建一个 urls.py：from django.urls import path from .views import handle_json_upload urlpatterns = [ path('data_upload/', handle_json_upload, name='handle_csv_upload'), ] 然后，修改该项目的主路由文件 urls.py：from django.contrib import admin from django.urls import include, path # <<<< import include from . import views urlpatterns = [ path("admin/", admin.site.urls), path("hello/", views.hello, name="hello"), path("simpleExp/", include('simpleExperimentData.urls')) # <<<< 加上我们刚刚的 url 配置。 ]接下来，保存，运行一下试试？你可以访问一下127.0.0.1:8000/simpleExp/data_upload/，看看会发生什么？对的！报错了，但报的是我们刚刚在视图里定义的错误！这说明我们的视图已经正常运转起来了！2 编辑实验文件假设你已经拥有了一个 jsPsych 的实验程序，并且你已经本地测试过它能正常运行了。如果你使用的是 PsychoPy 转写的 PsychoJS 的话……我确实好长时间没接触过了，你可能需要自己稍微研究一下，变通一下相关的代码，但基本的思路是一致的。2.0 实验的数据在哪？在浏览器里。准确地说，在浏览器的内存里。我们可以从内存里将数据导出来。如果你还不太清楚数据储存在什么位置，先打开一个你手边有的 jsPsych 程序，做一会，然后打开控制台，输入下面的命令：jspsych.data.get()可以看到，这个数据是以 JavaScript Object 的形式保存在浏览器中。我们可以把它变换成其他易于存取的格式：jspsych.data.get().json() // 转化为 JSON jspsych.data.get().csv() // 转化为 CSV2.1 将实验文件放进 Django 里面我们的实验文件：.html、.css、.js，以及其他你可能会用到的资源（图片、音频、视频等等），他们都是网站上的静态资源。要让 Django 服务器正确地分发静态资源文件，你就需要正确地配置相关设置。注意，以下方法适合快速部署，但不适合成规模地部署实验。正常情况下，我们应当考虑让 Django 的视图和模板来提供网页，然后加载 .js 和 .css 文件，这样的话整个流程都在 Django 可以管理的范围内。但，作为一个简易快速教程，我们将把实验程序用到的 .html 资源作为完全静态的资源来托管，这也就意味着 Django 无法管理这个文件，无法在这个文件里加入 Django 特有的功能。如果你需要成规模地部署网页实验，请考虑使用视图和相应的模板来正确地部署 .html 文件。2.1.1 修改 settings.py在 settings.py 的大约 110 行左右，你会看到一个设置：# Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ STATIC_URL = "static/"这表示了所有静态文件的 URL 路径，指示 Django 在这里去找对应的文件。接下来，我们去创建一下实验文件。在刚刚的 \simpleExperimentData\ 下，让我们创建一个 static 文件夹，再把实验文件整体放进去。2.1.2 修改 urls.py接下来，我们要让 Django 提供这些文件。我们需要修改项目的 urls.py：from django.conf import settings from django.conf.urls.static import static # <<<< 在头部新增两个 include urlpatterns = [ # ... the rest of your URLconf goes here ... ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # <<<< 通过这种方式提供静态文件的 URL重启一下服务端，访问 127.0.0.1:8000/static/simple_experiment/index.html，你应该就成功打开了你的实验文件了。注意，这种提供文件的方式仅适用于开发环境，Django 标记此种方式为不安全的文件提供方式。如果你要把实验部署在服务器上，你应当正确配置一个反代服务器。我们会在下一篇教程中提到相关的问题。2.2 使用 POST 请求发送数据接下来我们开始上手编辑实验文件。前面我们设置了在 127.0.0.1:8000/simpleExp/data_upload/ 这个地址接收传入的数据。从思路上来说，在不考虑特殊状况的情况下，我们有两种路径在实验结束时发送数据：通过 call-function 插件，在实验末尾增加一个特殊的 trial，发送数据；在初始化 jsPsych 对象时，使用 on_finish 参数，编辑自定义函数发送数据。本文通过第二种路径来实现相关功能。在实验文件（.js）中，找到 jsPsych 初始化的部分，写入以下函数：// 自定义数据保存函数 function saveData() { var xhr = new XMLHttpRequest(); // 通过 XHR 构造请求 xhr.open('POST', '/simpleExp/data_upload/'); // 向指定接口发送 POST 请求，接口在前面编辑视图时定义 xhr.setRequestHeader('Content-Type', 'application/json'); // 指定请求标头，告诉接口发送的数据类型 xhr.onload = function() { if(xhr.status == 200){ var response = JSON.parse(xhr.responseText); // 如果接口有响应，则发送数据 console.log(response.status); // 接受返回值并输出 } }; xhr.send(jsPsych.data.get().json()); // 将实验产生的 JSON 格式数据发出 } //初始化jsPsych，本文以 jsPsych 7.x 为例 let jsPsych = initJsPsych({ on_finish: function () { // 添加 on_finish 参数，在实验结束时执行 saveData() saveData() } });保存，运行实验并把实验做完（记得在运行实验的同时打开 F12）。如果数据发送成功了，那会有这样一条提示：我们还可以进后台看看数据有没有正常保存下来：2.3 常见问题排查考虑到网站开发是个复杂到要命的东西，这里简要讲述本文里操作时会遇到的几种常见问题。2.3.1 JS 里写好了保存数据的代码，但是打开网页之后没有发送数据检查 F12 中的 Source（源代码/来源），找到你的实验文件，看看和你刚刚保存的一不一样。不一样的话是有缓存，使用 Ctrl + Shift + R 强制刷新。下一篇文章会简单介绍如何要求客户端不保留缓存，强制加载最新版网页。2.3.2 Django 找不到静态文件检查 STATIC_URL 配置是否正确，urls.py 是否添加了检测静态文件的部分。2.3.3 保存数据时出现了 CSRF 相关的东西并报错，导致保存失败出于开发目的，在本文的视角下，请保证视图装饰器为 @csrf_exempt。我们会在下文中处理这个问题，使用标准的安全保护方式来加载文件。3 接下来还要做什么？到这里，你已经成功搭好了一个可以在本地运行，收集数据并保存到数据库的程序了！我们在前面所设定的目标已经全部完成了，恭喜你！那么我们下面还要做什么呢？或者说，你想要试试上服务器运行吗？下一篇教程会介绍如何将我们的这个项目以一个正确的方式部署到云服务器上，并向网络开放，收集数据。
听说你要用 Django 自己搭实验平台？（0——事前准备篇）
俗话说得好，自己动手丰衣足食，虽然我不知道为什么你会需要自己动手，但总之你这样做了（这个系列的教程会教你自己搭建一个能用的实验平台——可以在线收集数据，可以进行数据库管理的那种（而不需要手动下载文件再让被试发给你）。不过实话实说这个东西会比较糙，会有很多可以再优化的地方。0 首先，什么是 Django？Django 是一个用于构建 Web 应用程序的高级 Python Web 框架。Django 提供了一套强大的工具和约定，使得开发者能够快速构建功能齐全且易于维护的网站。——Django 教程 | 菜鸟教程Django 使用了非常先进的 MVC 模型框架，做到了数据和调用分离，因此有着相当强大且易于维护的功能…………但你需要完全学会并且完全掌握 Django 吗？好像不需要。如果只是收数据的话，你甚至不需要给这个网站做一个能看的首页，你只要把你的实验程序放上来，然后做好数据交互就可以了。（当然，实验程序得是网页版的——或许你在用 PsychoPy 转写的 PsychoJS？或许你在用 jsPsych？但总之我们在这个系列的教程中假设你已经拥有了适用于浏览器的实验程序。）所以我们的目标其实只是想个办法构造一个东西，这个东西能够（1）从我们的实验程序中接收一串数据，（2）把接收到的这串数据保存到一个数据库里（当然，现阶段不考虑受攻击之类的安全问题之类的，只考虑能用）。怎么样，是不是轻松了很多？1 装环境然后，写完上一句话之后，我意识到一个问题——最痛苦的不是写代码，是装环境。然后我意识到我需要教大家装环境。环境（Environment）在这里指的是一个程序跑起来所依赖的所有东西——比如有的程序需要一些前置的工具包（学过 C++ 都知道需要先 #include，差不多类似这么个概念）。但是，众所周知装环境是个玄学问题。同样的代码有的人能跑，有的人就跑不了；有的人装环境一次就成，有的人就死活装不上。用过 E-Prime 的人都说好。Python 的环境虽然没有 E-Prime 那么玄学，但是也很痛苦。说这么多，总之想告诉大家一个核心是：报错很正常，有的时候多跑几次就好了。所以，出于方便本地开发和调试的原因，我推荐所有人都装一个 Anaconda。Anaconda 是一款主要面向科学计算的 Python 版本与环境管理器，能够自动帮你配置虚拟环境（让不同的程序调用不同的环境，而不用直接跑在最底层上），避免你一不小心把电脑上的整个 Python 全部炸掉（而我真干过这事）。1.1 安装 Anaconda考虑到网络原因，这里并不推荐大家去官网下载 Anaconda。请访问清华大学开源软件镜像站，下载并安装最新版的 Anaconda。在此由衷感谢清华大学 TUNA 协会。下载，选个目录安装，注意整个目录必须是全英文的（不能有任何非英文字符，最好也不要有空格，否则会炸）。此外大致遵循 Anaconda 中文文档的说明就可以了。安装好之后，打开 Anaconda（会比较慢，多等一下），然后让它该更新更新，该重启重启，然后就可以看到它的主界面了：1.2 创建新环境在 Anaconda 主界面左边的工具栏中切换到 Environments 一栏，然后点击下方的 Create，创建一个新的环境。设置好环境名（这里输入的是 django_tutorial ，然后勾上 Python 选一个比较新的版本（我这里是 3.11，最新版 Django 要求至少 3.10）。[scode type="blue" size=""]如果你遇到了和我一样，在界面里加载不出 Python 版本的问题，那么请点击 base (root) 旁边的箭头，选择 Open Terminal，输入这样一行命令手动创建一个新的环境：conda create --name django_tutorial python=3.11[/scode]1.3 安装 Django切换到我们刚刚创建好的 django_tutorial 环境（会加载一会），点击旁边的绿色小箭头，选择 Open Terminal 运行这个环境：这里会弹出一个命令行窗口，输入如下指令安装 Django：pip install django然后去泡杯奶茶吧。装起来会需要一点时间的。1.3.1 装不上怎么办——使用清华大学 TUNA 镜像因为网络原因，不出意外的话大部分同学是装不上的。所以这里需要大家配置一下，这里仍然使用清华大学 TUNA 镜像。你可以根据 TUNA 协会的这篇指南更换镜像源，但如果你看不太明白里面在说什么的话，下面是一个更通俗的逐步教程。打开 base (root) 环境，输入命令 conda config --set show_channel_urls yes 并执行。前往你的电脑用户文件夹。你应该会看到用户文件夹下有一个 .condarc 文件，使用记事本打开它。复制以下内容，粘贴到这个文件里并保存。channels: - defaults show_channel_urls: true default_channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2 custom_channels: conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud deepmodeling: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/回到刚刚的环境中，运行 conda clean -i，正常情况下一切就绪了如果你在 1.2 步或 1.3 步中遭遇了各种网络问题的话，先按照这里的思路操作一番，然后再重试上面的指示即可。不出意外的话，现在你就能成功安装 Django 了。2 装编辑器好吧我摊牌了，我用的 VSCode。如果你折腾过 Python，会一些编程的话，你应该知道大家推荐的针对 Python 的编辑器其实是 PyCharm，我也推荐大家装 PyCharm。但后续我的教程中用的都是 VSCode，大同小异，问题不算特别大。在这里下载 PyCharm，下载好安装即可，这里就不啰嗦了。3 创建一个 Django 项目来都来了，为了验证你的 Django 是不是真的装好了，我们来创建一个项目试试吧。首先我们先在某个地方新建一个空的文件夹。最好选一个你能找到的地方。然后还是在我们的 django_tutorial 环境中，使用 cd 命令打开这个文件夹（可以 Bing 搜索一下 cd 命令的用法）。接下来，使用这个命令创建一个 Django 项目：django-admin startproject onlineExperimentTutorial如果你的 Python 和你的 Django 都安装成功了，你会看到文件夹里多出了一些东西。给自己鼓鼓掌，恭喜自己迈出了第一步吧！
[hplayer][Music server="netease" id="29774186" type="song"/][/hplayer]开始之前[post cid="216" cover="" size=""/]建议在基本掌握 PsychoPy 操作后阅读本文。可参考前述的入门篇指南。前言前略，这一篇主要是作为 PsychoPy 入门后的一些小技巧讲解，大多以我自己的经验出发，篇幅应该会短很多，也不会像入门篇那样讲解得非常细致，整体结构也会比较松散一些。也因为是从自己的角度出发，所以我自己还没涉及过的东西（脑电啊、眼动啊）我肯定也不会做，还是那句话：[scode type="yellow" size=""]本文无意成为一篇全面详尽的 PsychoPy 指南，本文作者也并不具有这样的实力与能力。本文希望做到的是能让阅读者快速了解这一工具，可以在较短时间内掌握其使用方法。如在开发、使用过程中遇到问题，官方文档与搜索引擎是更好的资料来源。[/scode]0. 关于颜色的那些事PsychoPy 的默认背景颜色是非常中性的一种灰[128, 128, 128]，默认文字颜色是纯白色[255, 255, 255]。有的时候这种灰色可能不太好，我们希望使用黑色作为底色，这个可以在实验设置中修改。点击顶上的 ⚙️ Edit experiment settings 按钮，选择 Screen 选项卡：在 Color 一栏填入颜色代码或颜色字符串即可，具体可用的颜色可点击右边的按钮查看：[scode type="blue" size=""]版本较老的 PsychoPy（如2021.2.3，脑岛的推荐版本）在将 RGB 三色代码转换至 Javascript 时存在 bug，无法将其正确映射为数组形式，因此转换出来的程序无法运行。如果有相应的线上实验需要，请直接使用颜色词汇。[/scode]1. 时间随机化很多实验会要求对注视点或者空屏时间进行随机化，防止被试进行预测或形成习惯化，影响实验结果。较新版的 PsychoPy 里预设了随机注视点的 Routine，可以参照学习一下：如果使用的是旧版 PsychoPy 也没关系，我们可以仿照它的模式自己写一个：这里使用了一个random()函数，可以生成 0 到 1 范围内的随机数，因此这里的持续时间便是 1 到 2 秒内随机。如法炮制，如果我们想要实现 400-600 毫秒内随机呢？[scode type="blue" size=""]可能有会 Python 的同学这里就要问了，Python 里还有别的随机函数，而且写起来更简单，更简短。但是我的实验结果是，不行。因为不知道 PsychoPy 引入了一些什么库，把什么样的指令引入到了什么样的程度，我尝试的各种更加简单的随机化代码都运行不起来，反而是这种写法最稳定。当然你也可以在头部 import 一点别的什么东西，但是这些东西只会适用于 Python 环境，所以如果想要做线上实验的话还是……就这样吧。[/scode]2. 提供反馈大多数简单的实验应该是不需要这个操作的，但是这个操作的知识（比如变量赋值、变量传递等）可以为后面的东西打下基础。假设在 Stroop 任务中，我们需要在被试的练习阶段为被试提供正确与错误的反馈，被试做出正确反应则显示绿色的反馈词，错误反应则为红色，到这里，纯 GUI 的编辑功能已经满足不了我们了，我们需要用到 PsychoPy 里的代码组件了。[scode type="yellow" size=""]我的 Python 水平也非常有限，这些代码通常都是现学现卖现抄的，所以如果有其他需求的话，可以考虑先自学一下 Python。[/scode]先假设我们已经设置好了各种指导语和实验用的 routine，分别是Instruction practice trial endings （实际情况要比这个复杂，会有多个指导语，这里仅为举例进行简化），在trial中有一个叫做trial_key_resp的键盘组件作为反应接收组件，并且已经载入了正确的条件文件，可以判断被试输入的正确与错误。大致应该和下面的图一样：我们梳理一下流程：我们需要从practice中获取几个变量（在这里，我们需要被试的反应时、被试的反应是否正确），将其显示在反馈中。我们在practice后面插入一个新的 Routine，命名为feedback，作为我们提供反馈的部分。随后，我们在feedback里添加一个代码组件（Code）：可以看到，这里有六个选项卡，分别对应六种运行的时机：Before Experiment：在实验文件加载前就加载，即放在所有代码的最开头运行Begin Experiment：在实验文件初始化时加载，即和其他初始化代码同步运行Begin Routine：在当前 Routine 开始时（即程序实际执行到该 Routine 时）运行Each Frame：实验程序运行时的每一帧都运行一次End Routine：在当前 Routine 结束时运行End Experiment：在实验完全结束时，生成数据文件前运行[scode type="blue" size=""]如要进行线上实验需要注意，脑岛要求研究者不要在 Before Experiment 阶段引入任何代码。[/scode]我们可以先初始化几个变量，用来承载我们的反馈消息。在 Begin Experiment 阶段引入这样的代码：右边的代码区是什么？注意到顶上的 Code Type 了吗？Auto -> JS的模式下，左边填写的 Python 代码可以自动转换为 Javascript 代码，如果需要把程序转换为 JS 运行，操作得当的情况下可以省去重写代码的时间。在这里，我们在实验开始时初始化了两个变量：message和message_color，用以更新后面的文本。接下来，我们希望每一次运行时，这里的数据都会相应更新一遍。因此我们切换到 Begin Routine，依葫芦画瓢填写这样的代码：if trial_key_resp.corr: message = 'Correct! RT=%.3f' %(trial_key_resp.rt) message_color = 'green' else: message = 'Wrong! RT=%.3f' %(trial_key_resp.rt) message_color = 'red'别急，我知道你很懵，看这玩意跟看魔法似的，听我解释.jpg我们从上一个 Routine 的键盘组件trial_key_resp中获取了它的一个属性：corr，即正确与否。如果被试输入正确，则会返回 1，反之返回 0。如果 trial_key_resp.corr不为 0，则会执行第一段 Correct 的代码。我们在这里把另外一个属性：rt，即反应时载入到了 message变量中，然后将消息的颜色设置为绿色。同理，在被试反应错误的情况下，返回红色的消息。那么到这里你大概会有一个问题，一个我当时也有的问题：我从哪里知道的corr和rt呢？我怎么知道它有多少个属性，有哪些可以用的变量呢？说实话我翻官方教程也没翻到这个，我用了一个比较野的路子——还记得数据文件吗？每一个程序当中会用到的变量，都会在这里创建一个对应的列，而每列第一格就是它的名字。所以，通过阅读数据文件，我可以知道有哪些变量在实验过程中被创建，可以在代码组件中使用。接下来，保存代码组件，在面板中添加一个新的文本组件：这样，我们就有了一个给被试提供反馈的组件。完成之后，你的feedback应该长这样：[scode type="blue" size=""]在引入代码组件时，注意程序的初始化顺序。PsychoPy 的初始化顺序是从上到下，也就是靠近顶端的代码最先运行，靠近底端的代码最后运行。在刚刚的例子，如果把code放到了text底下，那么就无法正常显示刚刚设置好的消息——程序会先加载文本，再给文本赋值，而这个时候文本已经显示到屏幕上了（或者程序已经报错闪退了）。[/scode]事实上其实这一节讲的内容大多数人在大多数实验中都用不上，包括我自己。讲这个主要是为了把代码组件介绍一下，并且给下面的内容开个头——3. 休息设置很多实验动不动就会有成百上千个试次，中间不插休息不行。当然比较简单粗暴的一个方法是做成这种样子：当然这样做也不是不行，只是……PsychoPy 会卡。亲身经历，我的同一个实验里有大概二十来个 Routine 一字排开，随后我想要编辑任何一个东西都会卡上半分钟才能打开窗口。我怀疑和 PsychoPy 的缓存管理有关系，可能做得不是很好。PsychoPy 论坛和一些教程里推荐了这样的一种做法：当然，会用到代码组件，不可能做一个 trial 就休息一次。打开rest，在里面添加一个代码组件：if (trials.thisN+1) % 24 != 0: continueRoutine = False意思是，如果当前trial的计数对 24 取模，余数不为 0 时，则不执行这个 Routine。continueRoutine是一个内建的值，指示程序是否运行这个 Routine。所以上面这段代码的效果就是，每 24 个trial过去，就会执行一次rest。注意，thisN的计数从 0 开始，所以在这里计算时加一处理。4. 数据分析这里就没有太多实例了，主要是我把自己的经验打个包讲一讲。拿到一个真实的数据文件，你会看到它大概是长这样的：很乱，一瞬间是反应不过来的。之前在入门篇里提到了，数据文件的每一行表示一个 Routine，每一列表示一个变量。在同一个 Routine 里发生的事件，就会在同一行里出现。所以整体上，按照各个变量初始化的顺序，数据文件呈现从左上到右下的一个阶梯排列状。事实上，我们不会关心各个指导语、休息期间发生的事件。这个时候之前提到的，规范化命名就起作用了。比如说，我们在设计时就只给正式的试次命名为*_trial，在数据分析时我们就完全可以直接搜索trial的字段。同时，如果涉及到多个条件文件，请尽量为不同的条件文件设计不同的变量名。因为可以注意到一个问题：PsychoPy 里的同名变量视作同一个变量，被放到同一列。假设我们有一个实验，分为两个阶段，每个阶段都会用到数字作为刺激。如果我们在设置条件文件时都命名为numbers，那么最后两个实验阶段的numbers就会合并到同一列，在数据文件中就会出现数据与条件相隔甚远的情况，不利于数据分析。我在分析 PsychoPy 的数据时，会采用一个思路：先选择有效的列，再选择有效的行。大家也可以这样参考。5. 线上实验的那些事终于到这个阶段了，就是为了这碗醋包的这么多饺子（这里也是，不会涉及实例，主要是大致说一下我遇到的各种情况。线上实验部分，之前提到了，是把实验程序编译成 Javascript 脚本运行。随后，你可以在本地 debug 你的实验。进入 Runner 界面，选择 Run → Run JS for local debug：PsychoPy 会在本地创建完整的实验程序和网页文件，随后自动在浏览器里打开实验程序，供 debug 使用。如果要进行线上实验，那么有两个方法：第一，自建网站进行实验，所需技术过于繁杂，大多数人也用不上，所以这里就不讲了第二，依靠现有平台（如 Pavlovia.org，脑岛等）进行在线实验我的经验主要针对脑岛展开。首先是版本问题。前面提到了，脑岛只对几个特定的 PsychoPy 提供支持，而我第一次上传实验时用的是更新的版本，因此——脑岛的系统不认识我的实验。我只能降版本重编译。这里就引出了第一个血泪史：[scode type="red" size=""]跨版本重新编译程序时，请一定完整试运行一次，以免遇到奇奇怪怪的 bug。[/scode]我就遇到了两个非常要命的 bug：一个是旧版本的 PsychoPy 转译为 JS 后，把本应是数组（[0, 0, 0]）的颜色代码转换成了字符串（'0, 0, 0'），然后我程序就炸了。解决方法是我把所有涉及到颜色代码的部分全部改成了颜色代词。第二个是，旧版本的 PsychoJS 脚本，处理旋转的方向和 Python 是反的。我头疼了一个小时，最后用了很魔法的方法，直接修改原始代码解决了这个问题。所以，一定一定记得 debug，不要直接上传了事。第二个血泪史就和脑岛处理实验的流程有关了：[scode type="red" size=""]请一定注意，在脑岛上运行实验时，一个网页只能承载一个被试。如果在同一个网页内多次打开实验，靠后的数据会覆盖靠前的数据。[/scode]脑岛的实验页面是长这样的：请注意，在这里点击“开始”后，会进入正式的实验程序，而从这里的开始到整体实验结束，都只能是同一个被试，不能通过反复开始实验对多个被试施测，否则会丢失数据。一定要在实验完全结束后，再重新开启一个实验页面。因为我们是远程实验，与实际施测的主试朋友沟通不善，出现了重复使用同一个页面的情况，导致丢失了一部分实验数据，非常悲惨。后记我能说出来的东西估计其实也就差不多了，暂时是没有别的能提到的内容了。说实话，我也并没有开发出 PsychoPy 的全部能力，笔下所能表达出的东西不及其潜能的十分之一。工具是为人所用的，而使用它的人则决定了工具能发挥多大的作用。“学海无涯，忌自满。”加油，祝你好运！
[hplayer][Music server="netease" id="35470288" type="song"/][/hplayer]前言容我想想要从哪开始写啊……前一阵子因为各种机缘巧合，接触到了 PsychoPy 这个工具，而且真用它完整做完了一整套从设计到分析数据出结论的实验流程。用完之后的感受是：比 EPrime 高到不知道哪里去了.jpg在这之后，又因为一些其他的机缘巧合，就有了写这篇指南的想法，希望能帮助一些后来的朋友了解、使用 PsychoPy。所以，这篇文章意在使 0 基础的朋友快速了解、上手使用 PsychoPy，并掌握使用 PsychoPy 完整进行一次实验的能力。开始吧！0. 什么是 PsychoPy？能吃吗？怎么吃？好不好吃？当然在具体进入教程之前，首先要介绍一下这个工具本身。PsychoPy 是一款基于 Python 开发的心理学工具包，旨在提供一套足够强大、现代化的工具，帮助各界心理学者设计、开展实验。略去开发者啊，开发历程之类的东西，直接跳到“PsychoPy 的特殊之处”，也就是为什么我们会选择 PsychoPy 的问题上：它的能力足够强大：从简单的文本、图像到视频、量表；从静止刺激到动态刺激，从普通的按键反应到滑动条、输入框等交互式程序，甚至还支持脑电眼动仪等复杂实验仪器；它上手足够简单：对普通用户，提供现代化的图形设计界面，实验流程一目了然、简单明了；对高级开发者，也提供了代码编辑界面，可轻松修改程序；如果只有这两个优势的话，PsychoPy 只是一个大号的、现代化的 E-Prime （其实本来也是），真正让它不同于 E-Prime 的是下面三点：它具有良好的跨平台兼容性与可迁移性：底层架构基于 Python，可以在任何搭载了 Python 的平台上使用、运行，不会遇到奇奇怪怪的兼容性问题 （反观 E-Prime 程序，随便换台电脑就运行不起来了）；它拥有运行线上实验的能力：PsychoPy 可以将程序转译为 Javascript 脚本，从而可以在浏览器中在线运行实验程序；它是开源的自由程序：无需任何门槛与费用，任何人均可使用；如有需要，任何人均可直接修改其底层代码；与其他目前常用且流行的心理学实验开发工具进行比较的话，大概是这样：工具名上手难度可迁移性自由度在线实验MATLAB高（纯代码环境）中（需配置环境）高难以实现E-Prime中 （可视化编辑器+代码编辑器）低（换个机器就炸了）低几乎无法实现Inquisit高（纯代码环境）高（仅需浏览器插件）中完全支持jsPsych高（纯代码环境）高（基于网页开发）高完全支持PsychoPy中（可视化编辑器+代码编辑器）高（需配置环境）高完全支持当然，上面这张表取决于我的主观经验，可能有失偏颇，大致做个参考。总而言之，如果你满足以下的条件的话：想要使用相对简单的办法制作实验程序想要使用较为轻量的实验环境 （MATLAB 实在是太大了）想要实现跨平台的线上实验不希望与底层代码结构打太多交道我会建议你尝试 PsychoPy。1. 安装并配置 PsychoPy那么，这么好的东西，在哪里才能买得到呢？既然到了这里，可能得先说：[scode type="yellow" size=""]本文无意成为一篇全面详尽的 PsychoPy 指南，本文作者也并不具有这样的实力与能力。本文希望做到的是能让阅读者快速了解这一工具，可以在较短时间内掌握其使用方法。如在开发、使用过程中遇到问题，官方文档与搜索引擎是更好的资料来源。[/scode]在最开始，先介绍几个 PsychoPy 开发过程中最重要的网站：PsychoPy 官网：https://www.psychopy.org/PsychoPy GitHub 页面：https://github.com/psychopy/psychopyPsychoPy 论坛：https://discourse.psychopy.org/PsychoPy 问答：https://www.google.com/ （如无法访问，可使用 https://www.bing.com 代替）首先前往 PsychoPy 项目的 GitHub 发布页（https://github.com/psychopy/psychopy/releases），根据版本与系统下载对应的安装包（通常 400-700 MB 不等）。[scode type="blue" size=""]如果希望对接国内脑岛平台（https://naodao.com/）进行在线实验，推荐下载 2021.2.3 版本，该版本为脑岛支持的最高版本。如果无此类需求，仅在本地或自建网站开展实验，则推荐下载带有 latest标签的最新版（图上为 2022.2.2）。我反正两个版本都装了， 如果同时安装多个版本的 PsychoPy，请注意区分安装文件夹。[/scode]Windows 与 MacOS 环境下，PsychoPy 安装时会自动安装独立的 Python 环境，因此无需额外配置，安装完成后开箱即用。如果是 Linux 用户，请参考官方文档（https://www.psychopy.org/download.html）进行安装与配置。安装完毕，打开 PsychoPy，你会看到如下的界面：2. 认识 PsychoPy——界面篇这是啥？那是啥？我是啥？随着这个窗口出现的应该还有两个窗口。这三个窗口分别是：PsychoPy Builder：图形化编辑界面，之后要讲的大部分的操作都会在这里完成；PsychoPy Coder：代码编辑界面，有需要时，可以在这里加载代码，进行更加直接的修改；PsychoPy Runner：程序运行器，在本地运行程序时，会通过它记录程序中发生的所有事件，并输出日志。我们绝大多数操作都会在 Builder 里完成，所以我们先把目光看向 Builder。① 为操作栏，和其他软件里顶上的按钮类似，这些都是针对文件本身的操作，包括新建、保存、调整设置、运行等功能；② 为 Routines 面板，在这里会显示当前 Routine 的所有组件，在后面的章节中会详细介绍；③ 为 Components 面板，这里列出了 PsychoPy 里可用的所有组件，点击组件即可将其添加到左边的 Routines 面板；④ 为 Flow 面板，这里会显示当前实验的整个流程。整个界面非常简洁，相信很多人看到这里就已经知道 PsychoPy 大致的编程流程了，因为界面很直观嘛。从下面开始，就会涉及到 PsychoPy 编程的核心概念了。3. 认识 PsychoPy——Routine 篇道理我都懂，但是我还是不会啊。从界面上可以看出，PsychoPy 程序是一个“组块式”进行的结构。不同的组块按照时间顺序安排，并在某些条件下循环出现，以此构成整个实验的流程。其中，图上的每一个彩色小方块就是一个 Routine，而右边灰色的practice则代表了一处循环（Loop）。Routine 和 Loop 是构建 PsychoPy 程序的核心概念，所有的实验程序设计都围绕着安排 Routine 和 Loop 展开。先来认识一下 Routine。来做个小实验吧！有条件的话可以打开 PsychoPy，跟着一起做。实验一：简单认识 Routine[scode type="green" size=""]制作一个简单的测试程序。运行程序后，在第一个页面显示“Hello, world!”。按空格进入第二个页面，在第二个页面显示“Welcome to psychology!”。[/scode]程序已经默认提供了一个 Routine，叫做trial，我们可以就把它作为第一个页面。首先，向trial中添加一个文本组件。在右边的 Stimuli 菜单中找到 Text，点击它，会弹出一个设置面板：Name（变量名）：组件的名字，和其他编程工具一样，只能以字母开头，只能包含字母、数字、下划线，并且整个程序中不能重复；Start（开始时间）：该组件从什么时候开始加载，默认为 0（Routine 开始时加载）；Stop（结束时间）：该组件持续多少时间，默认为 1（刺激持续 1 秒），留空表示不结束；Text（文本内容）：文本组件的内容。另外，Expected start 和 Expected duration 表示你预估的开始时间和持续时间，填写后对程序运行没有影响，可以作为笔记使用。将 Name 设置为 hello，Stop 一栏留空（不填写任何内容），将 Text 中的内容改为Hello, world!，点击 OK，一个名叫 hello 的组件就会出现在你的界面上：接下来，我们添加第二个 routine。点击 Insert Routine 按钮，选择(new)，输入welcome作为名称，就新建了一个名为 welcome 的 routine。但是，现在它并没有出现在我们的 Flow 面板中。你还需要手动指定它的位置（也就是图上的灰点，会跟随你的鼠标移动）。在trial的后面单击它，它就被插入到了trial后面。和前面一样，我们设置一个文本组件，就不重复展示了。接下来，找个地方保存一下你的实验文件（控制栏里的 ? Save current experiment file 按钮），然后点击 ▶ Run experiment，程序跑起来了！……然后你发现不管怎么按，你都没法进入下一页。恭喜你，被我坑了（很简单，因为我们压根还没设置键盘反馈，程序不知道我们按了空格键。按 ESC 强制退出实验程序，回到 Builder，往trial里面新增一个 Keyboard 组件（在 Responses 分类里）。Name、Start、Stop：同理，略；Force end of Routine（强制 Routine 结束）：默认勾选，当接收到符合条件的反应时结束 routine，进入下一步；Allowed keys（允许的按键）：仅接收在此处定义的按键，其他按键会被忽略。[scode type="blue" size=""]在 Allowed keys 一栏，前面带有一个固定的$符号。在 PsychoPy 里，$符号代表其后续的内容为 Python 语法，程序将使用 Python 进行解析。可以看到，这里的按键定义是字符串的形式。后续还会用到它，此处仅作讲解。[/scode]同样，将 Stop 一栏清空，将 Allowed keys 改成只剩下'space'一项，然后单击 OK。你肯定猜到了，为了正常结束程序，在welcome里面也需要一个键盘组件，如法炮制即可。那么，再次运行程序，这次就应该一切正常了——按两下空格，程序就应该正常退出了。恭喜你，写完了第一个 PsychoPy 程序！4. 认识 PsychoPy——文件篇菜做好了，盘子呢？刚搞了这么多，先休息一下。不妨趁现在打开你保存实验程序的文件夹，看看里面都有什么：当然我忘了给实验文件取名字了，所以这里是 untitled（未命名）。目前，这个文件夹里有三个东西：untitled.psyexp 文件：PsychoPy 的程序文件，也就是你刚刚编辑的东西；untitled.lastrun.py 文件：刚刚运行实验时，PsychoPy 编译的 Python 脚本代码，每次运行都会更新一次；data 文件夹：储存了所有运行时的数据，包括我们刚刚试运行的那几次；点开 data 文件夹，会出现三类文件：.csv 文件：大家喜闻乐见的表格文件（或者更严谨地说，“逗号分隔符文件”），保存了实验过程中产生的各种变量和对应的值；.log 文件：日志文件，记录了程序运行期间发生的所有事件；.psydat 文件：PsychoPy 独有的数据文件，将实验程序和其中的内容直接作为 Python 对象保存下来，最大限度地保留了原始数据。当然，我这里调整过设置，各位可能会有其他文件出现，具体的设置可以在 Builder 窗口里点击 ⚙️ Edit experiment settings 按钮，选择 Data 查看：一般情况下，这三个文件就足够我们使用了。[scode type="blue" size=""]以上是在本地使用 Python 环境运行程序时会产生的数据文件。如果要在线上进行实验，情况会变得完全不同，线上实验环境下程序只会生成 .csv 文件，后续将会详细说明。同时，.psydat 是一个需要使用 Python 才能解析的文件。根据官方文档，一般在“常规数据文件没有保存所有数据（通常是忘了设置）”的情况下才会用到它。读取这个文件的方法请参考：https://www.psychopy.org/general/dataOutputs.html#psychopy-data-file-psydat。[/scode]打开最新的那个 .csv 文件，然后……在数据文件中，每一行对应了实验中的一个 Routine；这一行里的数据则表明了“在这个 Routine 里发生了什么”。这里的每一个变量都单独成列，比如hello.started的意思是名为hello的组件在什么时候加载成功，而key_resp.rt则是名为key_resp的组件记录下的反应时。大致知道就行了，在这里还不需要详细了解。而文件后面的participant session date expName psychoPyVersion frameRate这些变量就是实验的元数据，记录了被试、被试编号、实验日期、实验名、程序版本、显示器帧率等数据。还是挺直观的，不是吗？5. 认识 PsychoPy——Loop 篇门前大桥下，游过一群鸭，快来快来数一数，二四六七八?来做一点比较像真正的心理学实验的东西吧！实验二：简单认识 Loop[scode type="green" size=""]创建一个程序，包含一个指导语页面、十个试次、一个结束语页面。在每个试次里，屏幕上首先呈现 200 毫秒注视点，随后空屏 300 毫秒，再出现字母 A，被试需要在出现 A 之后按键盘上的 A 进入下一个试次。[/scode]相信大家也都知道我想介绍什么了，这里就讲得简略一点了。我们首先创建三个 Routine，分别是Intro、trial和Ending。随后，我们在 Intro和Ending中加入一些指导语，并设置好按键反馈。[scode type="blue" size=""]相信你也注意到了，因为变量名不能重复的关系，如果不加以控制，整个程序的变量名就会非常混乱。又因为变量名直接关系到我们进行数据分析时的质量，因此我建议起名时起一些易于分辨的名称。以我自己的方法举例，假如我的 routine 叫Intro，那么我就会把里面的文本称作Intro_text，把键盘组件称作Intro_key_resp，这样既可以知道组件是什么，也知道组件对应的 routine 是什么。[/scode]接下来我们看向trial，在里面设置注视点、空屏和刺激：注视点trial_fixation从trial开始时呈现，持续 0.2 秒；刺激条件trial_stimuli与按键反馈trial_key_resp都从 0.5 秒时开始，不限持续时间。那么接下来，点击Insert Loop插入循环：Name（变量名）：略；loopType（循环类型）：在循环中，刺激条件如何呈现，此处暂时不设置；Is trials（是试次）：这个循环表示的是正式的试次，而不是练习或其他用途，会在数据文件中输出对应的标签；nReps（重复次数）：循环内的内容重复的次数；Selected rows（选择行数）：从条件文件中选择一部分行载入，而不是全部载入，此处暂时不设置；random seed（随机种子）：输入种子，可以使每次程序运行时输出相同的顺序，即“伪随机”——看似随机，但每一次运行都是相同的；conditions（条件文件）：载入一个文件，将文件内容作为循环运行的条件，此处暂时不设置。在这里，我们只需要勾上 Is trials，然后将 nReps 设置为10即可。这样程序应该就能运行了。在这里，我们创建了一个名为trials的循环。大功告成！……是不是少了什么？6. 真正的心理学实验——条件文件……我好像不是来学 Excel 和 MATLAB 的？到这里，你已经明白了 PsychoPy 编程的大致流程，唯一没有解决的问题是：它不是个实验。真正的心理学实验，包括各种各样的条件、各种各样的随机化，不是简简单单的一个 A 就能代表的。实验三：注入灵魂——条件文件的载入[scode type="green" size=""]在实验二的基础上，修改实验。试次包含五个小写字母 a，五个大写字母 A，被试看到 a 时按下 F 键，看到 A 时按下 J 键。如果按错，则记录为错误反应，也进入下一个试次。[/scode]我想了很久要怎么不突兀地引入这个“条件文件”的存在，但是想来想去都觉得很突兀，所以直接还是直接说了（条件文件是 PsychoPy 主程序外的一个文件，可以被 PsychoPy 读取，用以创建实验内需要用到的各种条件。而条件文件支持的格式是——.csv 或 .xlsx。打开 Excel 吧。条件文件的第一行是变量名，后面的每行都代表一个条件。这里的条件文件相对简单，所以我们可以手写。我在这里写了三列：stimuli即刺激，correct即正确的答案，type即类型。除开第一行，一共有 10 行，也就是 10 个条件。[scode type="blue" size=""]添加额外的分类变量与标记变量对分析数据非常有帮助。PsychoPy 会将条件文件的每一列都作为一个变量读入，因此添加分类变量可以帮助你快速从数据文件中筛选数据。[/scode]把它保存为conditions.xlsx，和 PsychoPy 程序文件放在同一个文件夹底下。随后再次进入trials的编辑面板，单击它，在刚刚被我们忽略的 Conditions 一栏选中我们做好的刺激文件：可以看到，PsychoPy 读取到了 10 个条件，3 个变量。下一步是把试次里的刺激改成我们在条件文件里定义好的东西，点击trial，进入trial_stimuli的编辑界面：如图所示，先把文本修改成$stimuli。这里是前面提到的$代表变量的知识。这里告诉程序，每次把变量stimuli里的内容展示到屏幕上——也就是 a 或者 A。第二步——这次不坑人了，把右边方框里的 constant 改为 set every repeat，告诉程序“在每次重复时更新”。如果不这样设置，那么你输入的内容会被视作常量，trial 仅接收第一次的输入，后面的循环和第一次循环显示的内容完全一致。接下来，修改键盘输入的相关内容，点击trial_key_resp：首先修改 Allowed Keys，改为我们需要的：'f','j'。第二步，在 Data 中，勾选 Store correct 选项，告诉程序“保存正确结果”。同时在下面的 Correct Answer 里输入$correct，我们刚刚在条件文件中设置的正确答案。这样做之后，数据文件中会新增一列，以 0 或 1 的形式保存每次按键的正确与否，可以省去手动对比答案的时间。点击 OK，大功告成，运行实验试试吧？……好像还有哪不对？怎么做了十一次实验？后面怎么还有？相信你已经猜到了。nReps 实际表示的是，条件文件中每一个条件的重复次数。刚刚的意思是，一共载入了 10 个条件，每个条件重复 10 次，也就是 100 个试次。因此，把这里改成 1 就行了。不过都到这里了，你肯定也想到了……两个条件，每个条件重复五次，效果也是一样的。完全可以。[scode type="blue" size=""]其实不一样。取决于顶上 loopType 的设定。loopType 分为五种，假设我们现在有 [a, b, c] 三种条件，每种条件重复三次（nReps = 3），则有：random：随机，但在条件文件内不会重复会出现类似 [a b c, b c a, c a b] 的序列，在每组 3 个条件都呈现过一次之前不会重复sequential：序列化，会按照条件文件的顺序依次读入即 [a, b, c, a, b, c, a, b, c]full random：全随机序列，会打乱组间不重复的规则会出现类似 [a, a, b, c, c, b, b, a, c] 的序列，即完全将所有试次全部打乱staircase：阶梯化，会在每次循环中产出一个level变量，帮助有自适应需求的用户开发程序，使得程序可以动态调节interleaved staircases：交叉阶梯化，存在多个变量同时进行变化时使用对于一般的实验来说，从前两个随机化方法中选择即可。如果实验对试次的呈现顺序、呈现方式、同一刺激连续次数等方面有要求的话，推荐还是用 MATLAB 写一个完整的、随机化的、固定的条件文件，随后用 sequintial 方法读取。[/scode]到这里，你已经成功使用 PsychoPy 编写了一个看起来很心理学的实验程序 （虽然不知道能拿来干嘛）。可以为自己鼓鼓掌，花了半个小时一个小时两个小时看到这里也辛苦了（最后做点总结吧。7. 小结下课了！PsychoPy 编程建立于三个基础：Routine、Loop、条件文件。通过串联 Routine 和 Loop，可以把实验的流程勾画出来，安排各个试次、各个刺激的呈现顺序；而屏幕上具体呈现什么，从设备上接收什么，则可以通过外置的条件文件进行定义与管理。后记我先睡会.jpg怎么小结就两句话啊，总感觉得多写点，但是不知道要写什么了。作为一个入门篇的内容，这篇文章主要就是介绍最基本的“怎么用”的问题，如果说读到这里让你感觉“啊，我的脑子会了”，那其实这篇文章就成功了。当然，脑子会了不代表手会了，更多的东西需要在实践中逐渐摸索出来，所以如果想真正掌握这个工具——还得真用起来才知道。写到这里我的字数统计器告诉我我已经写了一万三千个字了，太长了，所以我们把其他的一些东西——随机时间啊、线上实验啊、数据分析啊，都安排到后面吧。[post cid="264" cover="" size=""/]
[scode type="yellow" size=""]出于管理需求，Midnight_Radio 不自动接受各项邀请，以防滥用。私聊使用暂不限制。如需在群聊中使用本 bot，请联系单项式并将他本人和 bot 一起邀请至你的群里。[/scode]2023/07/08由yasu处继承B站链接功能2023/04/07整体升级，新增数个功能2022/07/11对大量插件进行升级，新增部分娱乐功能新增由 DOxide 开发的 sparkle 功能[collapse status="false" title="过往日志"]2022/02/21一定程度上恢复了核心功能运作。调整部分功能，新增部分功能。2022/01/06成功让抽卡插件跑了起来。2022/01/04机器人尚处风控期间，长消息可能无法发送。添加 MC 服务器功能与撤回功能。测试性添加 TRPG 相关功能。添加点歌插件。添加部分管理功能。[/collapse]使用须知本 bot 所用功能均需在指令前@或呼唤别名才可触发。如 @Midnight_Radio /帮助 或 mdp /帮助。bot 的可用别名包括：mist, 子夜。bot 有一部分隐藏功能，在此不列出（查询帮助用法：/帮助, /help, /指南会把这个页面的链接给你。回音用法：/echo <文本>bot 会原样返回你所输入的文本，通常用于测试 bot 是否在线。HarukaBotHarukaBot 是一款追踪并自动推送 B 站直播、动态的插件。此插件为本 bot 的核心功能。项目网站：https://haruka-bot.sk415.icu/[collapse status="true" title="HarukaBot 指令列表"]在未征得机器人主人同意前，请勿使用@全体功能。/关注 <UID>——关注账号/取关 <UID>——取关账号/关注列表——查看关注列表/开启动态 <UID>——开启账号的动态推送/关闭动态 <UID>——关闭账号的动态推送/开启直播 <UID>——开启账号的直播推送/关闭直播 <UID>——关闭账号的直播推送/开启全体 <UID>——推送时@全体成员（需管理员权限）/关闭全体 <UID>——关闭@全体成员（需管理员权限）/关闭权限——允许任何人操作 bot/开启权限——仅允许群主、管理员、超级用户（机器人主人）操作 bot[/collapse]B站AV、BV号转链接本功能继承自yasubot@机器人并附上AV、BV号，自动转换为链接无法正常处理封面图片，因此比起原版功能而言略有不足作息记录早晚安插件，可以记录作息。用法/早安、/晚安每日晚 21 点至第二天 6 点时可晚安，上午 6 点至 12 点可早安/我的作息、/群友作息查看自己和其他人的打卡记录词云根据记录到的消息生成词云图。用法/今日词云、/昨日词云、/历史词云天气查询使用和风天气 API 查询天气。用法地点+天气或天气+地点因使用了模糊关键词匹配，该功能为关键词触发，不需要前置斜杠。一言抽取一句一言（https://hitokoto.cn/）用法/一言抽卡支持游戏：明日方舟、原神、赛马娘、坎公骑冠剑、公主链接、碧蓝航线、FGO、阴阳师用法大概就是……/<游戏名><类别><数字><抽>例如：/方舟10抽或者/原神武器10抽 这样的完整的指令列表在下面，是正则表达式[collapse status="true" title="抽卡指令列表"]' .* ?方舟1-9|一{0,2}[抽|井]'' .* ?原神(武器|角色)?池?1-9|一{0,2}[抽|井]'' .* ?马娘卡?1-9|一{0,2}[抽|井]'' .* ?坎公骑冠剑武?器?1-9|一{0,2}[抽|井]'' .* ?(pcr|公主连结|公主连接|公主链接|公主焊接)1-9|一{0,2}[抽|井]'' .* ?碧蓝航?线?(轻型|重型|特型)池?1-9|一{0,2}[抽]'' .* ?fgo1-9|一{0,2}[抽]'' .* ?阴阳师1-9|一{0,2}[抽]'[/collapse]落子棋对局支持黑白棋、围棋、五子棋（均为只有两种棋子的落子棋）用法/黑白棋、/五子棋、/围棋——快速开启棋局/chess --rule <go/gomoku/othello> [--size <size>]——自定义棋局/落子 <position>——落子坐标/悔棋、/跳过回合、/查看棋局非常刷屏且扰民的功能，没事别动.jpg中国象棋、国际象棋对局用法/象棋人机、/国际象棋人机——开启人机对战可在命令后指定 AI 强度，如/象棋人机lv5，最高8级/象棋对战、/国际象棋对战——进行人人对战移动棋子使用坐标格式（如“e2h5”）；另外，对于中国象棋，可使用传统记谱法（如“炮二平五”）进行移动/悔棋、结束下棋——棋局操作指令/查看棋盘——查看当前棋盘同样是非常扰民的功能，没事别用.jpg[collapse status="false" title="高级指令"]对于中国象棋，前缀为cchess，对国际象棋为chess。-e, --stop, --end: 停止下棋-v, --show, --view: 显示棋盘--repent: 悔棋--reload: 重新加载已停止的游戏--battle: 对战模式，默认为人机模式--black: 执黑，即后手-l <LEVEL>, --level <LEVEL>: 人机等级，可选 1~8，默认为 4[/collapse]记事本可以实现定时提醒的记事本功能指令较为复杂，请参考https://github.com/Passerby-D/nonebot_plugin_note使用该功能前请咨询单项式建议计划实现功能（又称 Flag）抽签算命（？）跑团骰子（依赖别人的轮子实现功能进度大概和我的 Python 自学水平有关，所以……（所以什么时候学 Python（我不知道 在学了在学了
作为文献大户专业，心理学需要阅读的文献量巨大。但众所周知，CAJViwer 这玩意的响应速度实在不行，而且处理文献十分不便。虽然其内置了导出 PDF 的功能，但是其导出的为没有大纲的纯图片 PDF。又不是不能用.jpg好在 Github 上有这样一个项目：caj2pdf。然而这并不是一个标准的 Python 包，我不能一个 pip install caj2pdf直接开跑，然后我又不会别的了。行吧，感谢 Eter 雪中送炭（所以，这是一篇傻瓜式指南，旨在给像我一样没有任何编程基础（其实还是有那么一点点）但是又需要用这玩意的同志提供帮助。0. 安装 Python这里就不讲了，参考：Python 3 环境搭建 | 菜鸟教程1. 下载 caj2pdf包在其 Github 项目页上下载其完整源码。该项目处于持续更新状态，所以其实可以考虑时不时回来更新一下。下载后解压，记住存放路径备用。2. 安装 mutool根据其指引文件，该程序需要 mutool 才能运行，因此前往下载：https://www.mupdf.com/downloads/index.html解压之后，记住存放路径备用。3. 安装 PyPDF2打开命令行，输入：pip3 install PyPDF2如果你 Python 装得没问题的话，这一步应该会成功。4. 设置环境变量根据指南，需要把 mutool 设置为 Python 运行的环境变量，以便程序调用。右键“此电脑”——属性——高级系统设置——环境变量，编辑如下键值：点击编辑，出现如下界面，点击新建，将 mutool 的路径填进去。5. 程序理论上能跑了打开命令行（CMD），cd 到你解压 caj2pdf的目录，然后运行：# 转换文件 python caj2pdf convert [CAJ文件的完整目录（包含输入文件名）] -o [你准备输出文件的目录（包含输出文件名）] # 从 CAJ 文件中提取大纲信息并添加至 PDF 文件 ## 遇到不支持的文件类型或 Bug 时，可用 CAJViewer 打印 PDF 文件，并用这条命令为其添加大纲 python caj2pdf outlines [CAJ文件的完整目录（包含输入文件名）] -o [你准备输出文件的目录（包含输出文件名）]拿我自己的举个例子：python caj2pdf convert D:\Documents\BNU\文献\中英字幕对中国英语专业大学生附带词汇习得影响的实证研究_马超.caj -o D:\Documents\BNU\文献\中英字幕对中国英语专业大学生附带词汇习得影响的实证研究_马超.pdf python caj2pdf outlines D:\Documents\BNU\文献\中英字幕对中国英语专业大学生附带词汇习得影响的实证研究_马超.caj -o D:\Documents\BNU\文献\中英字幕对中国英语专业大学生附带词汇习得影响的实证研究_马超.pdf一切正常的话，你的文件已经输出了。6. 善后这个运行方式说实话非常不优雅，显然不是为外行设计的（原计划自己写个批处理，但是我 CMD 语法早八百年忘干净了，正在我重新捡起来的过程中 Eter 给我发来了一段代码：这段代码有 bug，我不会修，不是很好用，大家还是自己手动算了（@echo off for /f tokens^=* %%i in ('where .:*.caj') do python [替换成caj2pdf路径] convert ^%%~dpi^%%~nxi -o %%~dpi^converted^\^%%~ni^.pdf for /f tokens^=* %%i in ('where .:*.caj') do python [替换成caj2pdf路径] outlines ^%%~dpi^%%~nxi -o %%~dpi^converted^\^%%~ni^.pdf把路径填进去，保存成一个 .bat文件，然后把这个脚本放进你有 CAJ 文件的目录里运行，高效快捷全自动。就是其实也不是很优雅（但是我水平就到这了（后记原作者本人表示，该软件仍然无法处理全部的 CAJ 文件，但是应付日常应该是够用了。总之就是这样，也没什么技术含量也没什么别的，纯属帮帮像我这种小白（DDL 越多越想摸鱼，十篇文献摘要还没写，然后我花了半个小时写了个这个，溜了（