uv安装使用

uv的功能

uv 是一个用 Rust 编写的、速度极快的 Python 包和项目管理器。 它的目标是提供一个统一、高效的开发工作流程。

它可以方便的把所有的源都放在一个项目目录下,可以方便的进行包的管理。速度比conda快不知多少倍。

uv安装与换源

安装很方便,有两种方法,一种是用pip装,另一种是用命令脚本装。

pip装uv

pip install uv 执行这句就可以了。

命令脚本装uv

macOS / Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

装完后在命令窗口执行uv --version,如果显示版本号了,就表示安装完成了。

换源

因为uv是用了github的一些资源,而且国外的包资源总是比较慢,体现不出uv的快速。所以要换源。

比较推荐的方法是在用户目录下创建 uv.toml 文件(路径参考):

1
2
Linux/macOS: ~/.config/uv/uv.toml
Windows: %APPDATA%\uv\uv.toml

编辑文件内容如下:

1
2
3
4
5
[[index]]
url = "https://mirrors.aliyun.com/pypi/simple/"
default = true
# 或使用清华源
# url = "https://pypi.tuna.tsinghua.edu.cn/simple/"

uv的基础用法

uv创建项目文件夹

在命令行中打开某个文件夹,在这个目录下执行uv init <project dir>命令,稍等,就可以在这个文件夹下生成一个目录。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd D:\projects\python
$ uv init myproject
Initialized project `myproject` at `D:\projects\python\myproject`

$ cd .\myproject\

$ ls


目录: D:\projects\python\myproject


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/12/27 12:06:08 109 .gitignore
-a---- 2024/12/27 12:06:08 5 .python-version
-a---- 2024/12/27 12:06:08 87 hello.py
-a---- 2024/12/27 12:06:08 155 pyproject.toml
-a---- 2024/12/27 12:06:08 0 README.md

安装python包

比如安装numpy,可以在这个目录下输入命令uv add numpy,它会自动分析依赖,下载对应的包,而且,速度超快。

安装python

1
2
3
4
5
# 安装 Python 3.12
uv python install 3.12

# 查看所有可用的 Python 版本
uv python list

vscode中使用uv

在vscode中搜索扩展UV Toolkit,安装。

用的时候按ctrl+shift+p,输入uv,就可以看到一些uv相关的用法。

如果是用vscode打开的项目文件夹,是不用额外操作的,直接点运行,它就可以识别这个文件夹中安装的包。

如果这个文件夹中没有uv创建的环境,就需要通过按ctrl+shift+p,输入uv来选择uv的环境了。

华为触屏笔记本经常触屏失灵的一种解决方法

现象

前些年做项目,原来的thinkpad X240不够用了,就用赚的钱换了个华为的14寸的触屏笔记本,开始时一直没啥问题,去年下半年发现,触屏经常不好用,重启就好,但是我个人不喜欢重启电脑。后面发现用华为电脑管家修复问题,有机率修复好,但最近又不行了。

近期发现,当触屏不灵的时候,设备管理器中有一个I2C的设备显示错误,并且,设备管理器中没有触屏这个设备,而我禁用这个有问题的I2C设备,再启用,它就好了,触屏设备也出现了。

分析原因

触屏是通过I2C总线连接到cpu上的,但是当电脑休眠再唤醒后,这个设备不能正常加载,导致触屏失灵。既然可以通过设备管理器来禁用再启用就解决,那是不是可以通过命令行来实现自动化一键完成呢?

解决办法

有了上面的分析,就在豆包上问了几轮。

第一轮,我问在win平台上有没有办法用命令行来禁用设备,它给了三种方法,一种是用sc config,这个是对驱动是以服务形式加载的可以用,而I2C设备不有对应的服务。第二种是用Powershell命令,我想试一下,但是发现我不知道设备的标识符要怎么看,然后就问豆包。第三种是用一个devcon的工具,我没有安装,就暂时不考虑了。

第二轮,怎么在win平台上查看设备的InstanceId ,它给出了方法,我照着方法果然找到了。下面是它给的方法

1
Get-PnpDevice | Where-Object {$_.FriendlyName -like "*I2C*"}

找到这个ID后,就测试了下Disable-PnpDevice -InstanceId "设备的InstanceId" -Confirm:$false命令,果然有效。又试了下Enable-PnpDevice -InstanceId "设备的InstanceId" -Confirm:$false,也实现了目的。

第三轮,怎么把Disable-PnpDevice -InstanceId "设备的InstanceId" -Confirm:$false完成后延时5s再enable这个设备,写成一个可以执行的脚本。豆包正确的识别了我的意图,输出了三行powershell的命令,然后我把命令保存为ps1文件,再在powershell的终端界面运行,果然可行。

然后就是想办法让它可以在系统解锁的时候自动执行。

还是用豆包,查到了怎么把写的ps1文件填加到计划任务中去。

在 “程序或脚本” 文本框中输入 “powershell.exe”,在 “添加参数” 文本框中输入要执行的.ps1 脚本的路径及文件名,例如 “-File C:\Scripts\yourscript.ps1”。

GNOME-内存管理

以下内容译自:https://developer.gnome.org/documentation/guidelines/programming/memory-management.html 加入了个人的一些理解。

GNOME是的堆栈主是是用C编写的,所以动态的内存分配都要手动管理。可以使用Glib中提供的API来实现,但在编代码时要时刻记得内存。

这里假定读者都知道内存使用、堆分配,Glib中与malloc(), free(),相类似的是 g_malloc(), g_free().

前提知识-程序内存分配

摘自 https://blog.csdn.net/yingms/article/details/53188974

一个由C/C++编译的程序占用的内存分为以下几个部分

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放

4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区—存放函数体的二进制代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main.c
int a = 0; //全局初始化区
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来的10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}

栈 stack:

由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

堆 heap:

需要程序员自己申请,并指明大小,在C中malloc函数

如p1 = (char *)malloc(10);

在C++中用new运算符

如p2 = (char *)malloc(10);

但是注意p1、p2本身是在栈中的。

内存管理原则

这部分原文写的比较绕,简单的说,就是GNOME提供了一种内存所有权的机制来管理内存。每块分配的内存,只有一个所有者,所有权可以转移,只有所有者有权释放内存。

这里有一个重要的限制:变量绝不能从 owned 更改 更改为 unowned(反之亦然)。此限制是简化的关键。

1
2
3
char *  generate_string (const char *template);
/*这里定义的参数是用const修饰的,表示函数在调用的时候,不会把参数的所有权转进来*/
void print_string (const char *str);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//从修饰符可以看出变量具不具有内存所有权
char *my_str = NULL; /* owned 表示在栈里,退出时需要释放 char*修饰的 */
const char *template; /* unowned 表示不在栈里,因为这是const修饰的*/
GValue value = G_VALUE_INIT; /* owned GValue修饰的 */
g_value_init (&value, G_TYPE_STRING);

/* Transfers ownership of a string from the function to the variable. */
template = "XXXXXX";
my_str = generate_string (template); //template的所有权从系统管理到my_str了,因为template原本就没有所有权
/* template是用const修饰的,generate_string的参数也是const修饰的,他们本身都没有内存的所有权 */

/* No ownership transfer. */
print_string (my_str); //没有发生所有权转移

/* Transfer ownership. We no longer have to free @my_str. */
//这里发生所有权转移,猜测my_str的形参类型定义的是gchar,没有const修饰
g_value_take_string (&value, my_str);

/* We still have ownership of @value, so free it before it goes out of scope. */
g_value_unset (&value);

有一些库中用一些词来表示内存所有权的转移,比如函数名中用take表示完全转移,例如g_value_take_string()

理想情况是,所有的函数都注释了所有相关的参数和返回值是否有转移所有权,下面有几个方法可以确定是否有所有权转移。

  1. 如果这个类型有注释,认真看注释里怎么说的
  2. 否则,如果是const修饰的类型,没有转移
  3. 否则,如果函数文档明确的说明了返回值必须要释放,这里有或全部转移了
  4. 否则,如果函数名中有dup,take,steal,这里有或全部转移了
  5. 否则,如果函数名中有peek,没有转移
  6. 否则,你需要看一下函数的代码来确认是否发生转移

有了这些方法,就可以方便的判断内存所有权和转移情况了。注意,copy()函数必须使用正确的数据类型,比如g_strdup()是针对string的,g_object_ref()是针对GObject的。

论起内存所有权转移,malloc()/free() 与 引用计数是等价的。前者,一个新分配的堆内存转移,后者,一个新的引用(转移)。

函数文档

正确的在文档中记录每个函数和参数以及返回值的所有权转移是很重要的。最好的记录所有权转移的方法,是用(transfer) 记录,这是在gobject-introspection里介绍的方法。在API文档中包含了这个注释后,自检工具就可以读到了。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* g_value_take_string:
* @value: (transfer none): an initialized #GValue
* @str: (transfer full): string to set it to
*
* Function documentation goes here.
*/

/**
* generate_string:
* @template: (transfer none): a template to follow when generating the string
*
* Function documentation goes here.
*
* Returns: (transfer full): a newly generated string
*/

变量的内存所有权可以在代码中通过行内注释来记录。这个不是标准化的,任何工具也都读不到, 用好了,形成一个规范,也很方便。

1
2
GObject *some_owned_object = NULL;  /* owned */
GObject *some_unowned_object; /* unowned */

在文档中记录容器类型也是一种规范,它包括了所包含元素的类型。如

1
2
3
4
5
6
7
8
/* PtrArray<owned char*> */
GPtrArray *some_unowned_string_array; /* unowned */

/* PtrArray<owned char*> */
GPtrArray *some_owned_string_array = NULL; /* owned */

/* PtrArray<owned GObject*> */
GPtrArray *some_owned_object_array = NULL; /* owned */

需要注意,有所有权的变量,都应该被初始化,这样释放的时候会更方便。

引用计数

GLib库除了提供类似malloc()/free()这样的常规内存管理类型外,还包含多种引用计数类型的实现,其中GObject是一个典型的例子。所有权和转让的概念同样适用于引用计数类型,正如它们适用于已分配的类型一样。当一个作用域通过调用g_object_ref()持有对实例的强引用时,即表示该作用域拥有这个引用计数类型。通过再次调用g_object_ref()可以实现实例的“复制”(增加引用计数)。而释放所有权则是通过调用g_object_unref()来完成的——尽管这一步可能并不会立即导致实例被销毁,但它确实释放了当前作用域对该实例的所有权。

在GLib中,还有其他支持引用计数的数据类型,例如 GHashTable(使用 g_hash_table_ref() 和 g_hash_table_unref())或 GVariant(使用 g_variant_ref() 和 g_variant_unref())。由于历史原因,一些类型,如 GHashTable,既支持引用计数也支持显式终结。应始终优先使用引用计数,因为它允许实例在多个作用域之间轻松共享(每个作用域持有自己的引用),而无需为实例分配多个副本。这样可以节省内存。

浮动引用

ssh免密登陆

ssh免密登陆并不是什么复杂的事,但是配置了以后,会很方便。为了实现这个,在网上找了一些教程,也不知是服务器的默认配置的问题,还是什么原因,一直没有成功。

今天又试了下,在大语言模型智谱清言的帮助下,实现了免密登陆。记录一下。

客户端生成密钥

命令行中输入ssh-keygen -t rsa,然后一路回车,可以在当前用户的文件夹下的.ssh目录里生成两个文件,id_rsa.pub和 id_rsa。id_rsa.pub是公钥,可以给别人的,另一个是私钥,只能在自己电脑上的。

客户端复制公钥到服务端

如果是windows系统,这时就最好是用winscp一类的工具软件连接服务器,linux系统,可以用ssh-cp-id命令。

我是用的windows,下面说下怎么操作。

首先ssh登陆远程服务器,然后cd到当前用户下的.ssh目录里,ls查下有无authorized_keys这个文件,如果没有,使用touch authorized_keys创建这个文件。

然后,nano authorized_keys打开这个文件,也可以用vi。

第三步,用记事本打开客户端用户文件夹下.ssh目录中的id_rsa.pub文件。

第四步,复制id_rsa.pub中的全部内容。

第五步,到nano authorized_keys的编辑界面,右键,自动会把刚刚复制的内容粘贴到编辑界面中。

第六步,ctrl+o,再回车,保存文件。

第七步,ctrl+x,退出编辑。

修改服务端文件权限

服务器上~/.ssh/authorized_keys文件的权限是600,使用chmod 600 ~/.ssh/authorized_keys修改。

修改服务端ssh配置文件

这步可以在客户端的ssh界面操作。

配置文件通常是/etc/ssh/sshd_config,确保下面的配置是这样的。

1
2
3
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

重启sshd,service sshd restart

非22端口的操作

对于22端口的,上面的操作基本就完成了,再用ssh连的时候,ssh user@ip,就可以登陆了,对于非22端口的,再记端口,麻烦,可以用config文件来搞定。

在客户端的.ssh目录下,新建一个config文件,不要后缀。

在里面写上下面这些行

1
2
3
4
5
Host ssh连接的名称
User 用户名
Port 端口号
HostName 服务端的域名或ip地址
IdentityFile id_rsa

大功告成。

编程基础-变量、地址与指针

地址

前面咱们说了,数据在存储过程中,为了方便,将存储介质分成了不同的区域和格子。为了方便的找到数据,又给每个存储的格子,也就是存储单元,分配了地址。但是这些地址是一长串数,不好记,怎么办?起个名字吧,这个名字,就是变量名。为什么叫变量名呢?因为这个地址里的数据,大多数时候,是可以修改的,也就是可以变化的,所以叫变量名。

变量

说是变量,其实有些数据在运行的时候是不变的,那些就叫常量,常量可以说是变量的一种特例吧。变量其实就是内存中数据的一个别名,方便是记忆和使用的。知道了这个名字,就知道这个数据在哪里(地址),也就能找到数据值了。注意,这里的数据不指定具体的类型。

变量类型

但是,就像数学中,有整数,小数,实数,复数等等类型一样,计算机中,数据也是有类型的。但是,前面不是说了么,计算机中数据存的时候只是0或1这样存的,怎么表示不同的类型呢?这其实中间要有一个翻译的过程。比如0b 0010,如果说它是整数类型,也就整型,那它就表示3,为什么是3呢?因为0010是二进制,转成十进制,就是3。那如果说它是字符型,它是什么字符呢?可以查下ascii码表,查表可知,它表示ETX这个特殊符号。

所以,变量类型存在的意义,就是把存储单元中存储的二进制数据转变成实际数据时用到的一个翻译选项,不同的变量类型,在翻译的时候要用不同的翻译方法。

指针

那指针,又是什么东西呢?指针,其实就是地址。它,可以是任意类型数据的地址。这就厉害了,任意类型的数据都有指针,所以,指针也有指针,或者说,地址也有地址,就好像你家有一个地址,但是这个地址在物业那里有一个通讯录,通讯录里有你家的地址,你家的地址在通讯录的某页某行处。你家,可以看成是一个存储单元,你家的地址是你家的指针,假设你叫张三,那“张三家”,就是你家的别名,也就是你家的变量名,你家地址在物业通讯录里的那条记录,就是你家地址的指针,也就是你家指针的指针。

理论上,指针可以有很多级,或是很多层,但实际应用中,别太复杂,不然就分不清了。

编程基础-二进制与存储单元

前言

这是本系列的第一篇,目标受众是0基础的,想学编程的人。

二进制

说起二进制,首先想到的是十进制。平时我们数数,就是用的十进制,为什么叫十进制呢?因为数的过程中,从0开始,每次+1,都会让数增大,但位数不变,都是1个数字,直到9+1=10,这时,1个数字不够用了,用了2个数字,这两个数字组合成一个新的数。这就叫进1。所谓的进1,也就是个数,准确的说,是位数,增加1个。十进制,就是逢十进一。个位进一位到十位,十位进一位到百位。

类比十进制,二进制,就是逢二进一,这时就不能叫个位,十位,百位了,叫第0位,第1位,第2位……。

第0位,逢二进一,那数的时候,0,表示0,1,表示1,10呢?表示的是2,11呢?表示的是3。但是我们也发现了,单独写10,看不出是十进制的十,还是二进制的10,为了区分,就需要用一个标识。计算机里用的是0b前缀表示二进制,而十进制,不用加前缀。

下面是二进制数对0-10的数据的表达。

二进制 十进制
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 10

为什么用二进制

明明大家都是用的十进制,为什么要发明二进制呢?因为二进制方便计算机计算。为什么计算机方便用二进制计算呢?

二进制的基本数字,就是0,1,而十进制的基本数字是0-9,你能想到什么样的电路可以实现0-9的表达么?在电学里,电信号,一般就是电压、电流、电容、电感、电阻。要想找到不的数字的表达,这些表达就需要分成不同的等级,类似楼梯一样。如果是用电压来表示0-9的数字,按前面分析的,需要找到10个不同的等级来分别表示0-9,而二进制呢,只需要用2个不同的等级来表示0,1,很明显,二进制更简单一些。而十进制要复杂的多。所以,在电子计算机里,二进制得到了广泛的应用。

题外话:在以前,计算机也并不全是二进制的,苏联曾经开发出三进制的计算机,后面应用范围远不及二进制的,也就慢慢消失了。

其他进制

事实上,我们生活中也不只是有十进制,还有十二进制,就是时间,上午1点到12点,下午又是1点到12点。

在计算机领域中,除了二进制、十进制,还有十六进制、八进制。实际用的多的,就是二进制、十六进制和十进制。

数据的存储

可以存数据的东西,有光盘、磁带、硬盘等等,以上这些,存储的原理多少有些不同,但是本质是一样的。它们存的都是0或1,像光盘,是用激光在盘面上烧刻出不同长度的沟,短的是0,长的是1,磁带,是用强磁让磁带一段一段的磁化,留下不同长度的磁条,长的磁条是1,短的磁条是0。磁盘与磁带相似。

也就是说,他们都是存的0或1,用留下的记号的不同来标识。

扯个题外话。0或1这样的数据,本身是不连续的,因为在0和1之间还有无穷多个小数。这些不连续的数据,也被称为离散的数据,那计算机中有连续的数据么?很不幸,没有。计算机中不能直接处理和存储连续的数据。比如一段音乐,如果是用胶片存,胶片存的就是连续的,而CD盘,就不是连续的。那离散的为什么听不出来?同一段音乐,为什么又有胶片存的,又有CD存的,还有MP3文件存的?这些就涉及到了一些模数转换和数模转换了。简写为A/D转换(模数转换),D/A转换(数模转换)。

回到正题。从前面知道,数据计算机中存的是0,1这样的数。但是这些数要想好记,就需要给他们分个段,就像背电话号码,大家都用335或344的方式来分段记忆。同样,为了方便对二进制进行管理,在存储的时候,也对二进制进行了分段。

对于单一1个0或1,叫做一个bit,或比特,翻译过来,就是位。8个bit,就像前面写0-10那样,8个0/1组合,叫做一个字节,byte,为什么是8bit一个字节呢?因为最早计算机是美国搞的,他们把常用的字符编了一个表,用不同的数字表示不同的字符,刚好是127个,也就是7位,7这个数不利于分段,就搞了8位,4位一段。刚好8是2的3次方,8位最多可以表示2的8次方(255)个数。

数据在存储的时候,也是遵循这样的规律,最小的单位是1bit,但是这个单位太小了,就像分是中国货币的最小单位,但是现在很少有人直接说多少分,最少也是多少元。数据在存的时候也是,bit这个单位太小了。比bit大一级的,就是字节,byte,这个用的多一些。也是实际使用中,存储的最小单位。

计算机的数据存储,可以想像成一个大箱子,为了方便收纳,大箱子里又分了很多的区域,每个区域里又分了很多的格子。一个格子里只能存一个字节的数据,这一个字,就是最小的一个存储单元。在硬盘中,这个箱子,就对应着整块硬盘,不同的区域,就是不同的扇形区域,简称扇区,每个扇区下,又包括许多的字节。

数据索引

有过找东西经验的都会发现一个问题。东西是存起来了,但是想找的时候,如果没有提前记录存储位置,找起来就要一个格子一个格子的翻找,效率很低。怎么解决呢?一个很容易想到的方法是提前做一个记录,一条一条的记录着每个格子里存的是什么。这个记录表,不能离开这个箱子,他也要存在箱子里。当你有很多箱子的时候,为了方便找这个记录表,最好的方法是把这个表放在一个固定位置。硬盘里也是这样,在开始的位置,有一个文件记录表,记录表里记录着很个格子里都有什么内容。想要找某个东西的时候,先到这里找到记录表,再在记录表中找有没有想要找的东西。

这里忽略了一个问题,就是格子很多的时候,怎么区分不同的格子呢?聪明如你,给格子编上号就好了。按照一个固定的顺序,把每个格子都编上号码。这样,在找到要找的东西时,就知道了东西所在格子的编号。这像极了门牌号,所以,这个编号,又可以叫它 “地址”。当你开始在记录表中找要找的东西到你找到它的这段时间,在计算机里,叫 寻址

GNOME C规范

以下内容来自GNOME的开发文档,https://developer.gnome.org/documentation/guidelines/programming/coding-style.html

缩进

Linux 内核风格是一个tab缩进8个字符,GNU风格是一个tab 2个字符,这两种都可以,但要统一。

括号

if else,如果只有一条语句,可以不写大括号,如下:

1
2
3
4
5
/* valid */
if (condition)
single_statement ();
else
another_single_statement (arg1);

但也有例外。

第一种,if语句有大括号,else语句也要有大括号。

1
2
3
4
5
6
7
8
9
10
/* valid */
if (condition)
{
foo ();
bar ();
}
else
{
baz ();
}

第二种,单个语句写了多行,也要用大括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* valid Linux kernel style */
if (condition) {
a_single_statement_with_many_arguments (some_lengthy_argument,
another_lengthy_argument,
and_another_one,
plus_one);
} else
another_single_statement (arg1, arg2);

/* valid GNU style */
if (condition)
{
a_single_statement_with_many_arguments (some_lengthy_argument,
another_lengthy_argument,
and_another_one,
plus_one);
}
else
{
another_single_statement (arg1, arg2);
}

第三种,如果if的条件有多行,也需要用大括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* valid Linux kernel style */
if (condition1 ||
(condition2 && condition3) ||
condition4 ||
(condition5 && (condition6 || condition7))) {
a_single_statement ();
}

/* valid GNU style */
if (condition1 ||
(condition2 && condition3) ||
condition4 ||
(condition5 && (condition6 || condition7)))
{
a_single_statement ();
}

第四种情况,if嵌套,这时也需要用大括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* valid Linux kernel style */
if (condition) {
if (another_condition)
single_statement ();
else
another_single_statement ();
}

/* valid GNU style */
if (condition)
{
if (another_condition)
single_statement ();
else
another_single_statement ();
}

函数

函数返回值类型独占一行

1
2
3
4
5
void
my_function (void)
{
// ...
}

函数参数一行一个,参数名称左对齐

1
2
3
4
5
6
7
8
void
my_function (some_type_t type,
another_type_t *a_pointer,
double_ptr_t **double_pointer,
final_type_t another_type)
{
// ...
}

空格

在括号前加空格,不要在括号后加空格

1
2
3
4
5
if (condition)
do_my_things ();

switch (condition) {
}

定义结构体时,使用空行,将不同类型的数据隔开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct _GtkWrapBoxPrivate
{
GtkOrientation orientation;
GtkWrapAllocationMode mode;

GtkWrapBoxSpreading horizontal_spreading;
GtkWrapBoxSpreading vertical_spreading;

guint16 spacing[2];

guint16 minimum_line_children;
guint16 natural_line_children;

GList *children;
};

不要随便删除空行或空格,下面是错误的示范

1
2
/* invalid */
if (condition) foo (); else bar ();

switch

每个case的缩进级别应该相同,而且case的缩进应该与switch的大括号级别一致,一个case结束后,加一个空行,再开始下一个case。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* valid Linux kernel style */
switch (condition) {
case FOO:
do_foo ();
break;

case BAR:
do_bar ();
break;
}

/* valid GNU style */
switch (condition)
{
case FOO:
do_foo ();
break;

case BAR:
do_bar ();
break;

default:
do_default ();
}

头文件

头文件中函数定义要用三列。

1
2
3
return_type          function_name           (type   argument,
type argument,
type argument);

每列最大的宽度要以每列中最大的宽度为准。

1
2
3
4
void        gtk_type_set_property (GtkType     *type,
const char *value,
GError **error);
const char *gtk_type_get_property (GtkType *type);

如果要定义一个公共库,把小的头文件包括到单一的一个公共库头文件中,而不是在应用中导入多个小的头文件。比如,GTK库定义了一些头文件,但是能直接调用的,就是gtk.h,其他的库不能在应用中直接调用。

1
2
3
4
5
6
// The __GTK_H_INSIDE__ symbol is defined in the gtk.h header
// The GTK_COMPILATION symbol is defined only when compiling
// GTK itself
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif

对于库,所有的头文件都要有inclusion guards。下面这串代码演示了如何使用#ifndef和#endif来避免头文件被重复引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once
//Time Class definition
//member functions are declared in time.cpp

//prevent multiple inclusions of header
#ifndef TIME_H
#define TIME_H

//time class definition
class time
{
public:
Time();//constructor
void setTime(int, int, int);//set hour, minute and second
void printUniversal() const;//print time in universal-time format
void printStandard() const;//print time in standard-time format

private:
unsigned int hour;//0-23
unsigned int minute;//0-59
unsigned int second;//0-59

};

#endif // !TIME_H

GObject 类

类型声明应该放在文件最开始的位置

1
2
typedef struct _GtkBoxedStruct       GtkBoxedStruct;
typedef struct _GtkMoreBoxedStruct GtkMoreBoxedStruct;

下面的没看懂什么意思……

内存申请

在堆栈中动态申请内存,用g_new(), 公共的结构类型要填充0,或是使用g_new0()来申请内存。

宏Macros

尽量避免用私有宏,返回的时候,记得#undef它们,最好是使用inline函数。公共宏除非是返回一个常值,否则不要使用。

gtk gdb调试时不显示界面的一种情况

问题环境:win10,ucrt64,gtk4

上午的时候在vscode下成功调试了gtk的程序,但是晚上就不行了,能进到gdb,但是不显示窗口,网上查也没查到原因。后面回想了下当前和早上时候的不同,好像就是打开了一个glade的界面,而glade打开的时候,会占用ucrt64的一个控制台。

有了猜想就验证,关闭glade,再调试,一小会,界面就出来了。看来还真的是这个原因。后面又打开glade,再调试,还是不行,调试运行时,关闭glade,一小会儿,窗口就显示出来了。

gtk使用ui文件定义界面运行报错

今天在照着gtk的官方教程做示例的时候,遇到一个问题,就是下面的链接,https://docs.gtk.org/gtk4/getting_started.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <gtk/gtk.h>
#include <glib/gstdio.h>

static void
print_hello (GtkWidget *widget,
gpointer data)
{
g_print ("Hello World\n");
}

static void
quit_cb (GtkWindow *window)
{
gtk_window_close (window);
}

static void
activate (GtkApplication *app,
gpointer user_data)
{
/* Construct a GtkBuilder instance and load our UI description */
GtkBuilder *builder = gtk_builder_new ();
gtk_builder_add_from_file (builder, "builder.ui", NULL);

/* Connect signal handlers to the constructed widgets. */
GObject *window = gtk_builder_get_object (builder, "window");
gtk_window_set_application (GTK_WINDOW (window), app);

GObject *button = gtk_builder_get_object (builder, "button1");
g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

button = gtk_builder_get_object (builder, "button2");
g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

button = gtk_builder_get_object (builder, "quit");
g_signal_connect_swapped (button, "clicked", G_CALLBACK (quit_cb), window);

gtk_widget_set_visible (GTK_WIDGET (window), TRUE);

/* We do not need the builder any more */
g_object_unref (builder);
}

int
main (int argc,
char *argv[])
{
#ifdef GTK_SRCDIR
g_chdir (GTK_SRCDIR);
#endif

GtkApplication *app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

int status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

return status;
}

官方给的代码,没有说builder.ui这个文件放在哪里,我就放到了和main.c同级的子目录下,结果编译能通过,但是运行时报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(main.exe:53272): Gtk-CRITICAL **: 23:14:02.343: gtk_window_set_application: assertion 'GTK_IS_WINDOW (window)' failed

(main.exe:53272): GLib-GObject-CRITICAL **: 23:14:02.348: invalid (NULL) pointer instance

(main.exe:53272): GLib-GObject-CRITICAL **: 23:14:02.351: g_signal_connect_data: assertion 'G_TYPE_CHECK_INSTANCE (instance)' failed

(main.exe:53272): GLib-GObject-CRITICAL **: 23:14:02.356: invalid (NULL) pointer instance

(main.exe:53272): GLib-GObject-CRITICAL **: 23:14:02.360: g_signal_connect_data: assertion 'G_TYPE_CHECK_INSTANCE (instance)' failed

(main.exe:53272): GLib-GObject-CRITICAL **: 23:14:02.364: invalid (NULL) pointer instance

(main.exe:53272): GLib-GObject-CRITICAL **: 23:14:02.367: g_signal_connect_data: assertion 'G_TYPE_CHECK_INSTANCE (instance)' failed

(main.exe:53272): Gtk-CRITICAL **: 23:14:02.372: gtk_widget_set_visible: assertion 'GTK_IS_WIDGET (widget)' failed

由于刚开始学,也不太会调试,完全没有头绪。但是编译能通过,觉得代码应该是没问题的。就猜,可能是ui文件路径不对。就试着改了两下,最后试出来了,gtk_builder_add_from_file (builder, "builder.ui", NULL); 这句中的builder.ui应该写这个项目中的相对路径,或是绝对路径。我放到了项目目录的src文件夹下,用相对路径写法,应该写成gtk_builder_add_from_file (builder, "src/builder.ui", NULL);,再次make run,就不再报错了。

如果这个以后想换个位置也能运行,这个ui文件应该放到另外的一个目录下,到时再研究怎么发布exe吧。

windows安装gtk4并配置VSCode进行编程

下载MSYS2

gtk的网站上是这样写的

There are various methods to install GTK on Windows development machines.

MSYS2

This method is based on the packages provided by MSYS2, which provides a UNIX-like environment for Windows. Both of these repositories also provide packages for a large number of other useful open source libraries.

gvsbuild

This method provides scripts to build the GTK stack from source and outputs libraries and tools that can be consumed by Visual Studio or Meson based projects.

意思是有两种方法在windows平台上安装gtk,一种是用MSYS2,一种是用gvsbuild。我选的是MSYS2这个路径。

https://www.msys2.org/ 从这个网址下载,一打开就可以看到下载链接,很显眼。跟着指导步骤一步一步做。
msys2下载

需要注意的是,按照这个网站上的安装方法,会安上 UCRT64 环境,这个环境是基于MinGW64的,很多教程里边,环境变量,include路径基本都是用的MinGW的路径。

安装GTK4和依赖

在第一步完成时,就打开了一个命令行窗口,如果不小心关了,可以从开始菜单找到MSYS2 UCRT64,点它,启动。运行pacman -S mingw-w64-ucrt-x86_64-gtk4,如果想安装gtk3,则运行pacman -S mingw-w64-ucrt-x86_64-gtk3

安装工具链(可选)

如果要在C、C++等语言下使用GTK,需要安装编译器,比如GCC和它的工具链。pacman -S mingw-w64-ucrt-x86_64-toolchain base-devel,如果是使用Python,需要安装Python包,pacman -S mingw-w64-ucrt-x86_64-python-gobject

配置VSCode

由于VSCode免费,支持多种语言,所以计算机上安了VSCode,不想再安其他的IDE,就配一下VSCode吧。结果遇到了很多坑。

设置环境变量

在系统环境变量的PATH中,填加MSYS2中的msys64\ucrt64\bin,当然,主要还是看哪个目录的bin文件夹下有内容。

设置完环境变量,打开cmd,输入gcc --version,可以看到回显数据。

安装VSCode插件

一个项目管理的Easy C++ project,一个是C/C++代码提示的Microsoft C/C++ 扩展

创建C++项目

vscode打开新建的文件夹,按下键盘F1输入easy cpp,选择Create new C++ proejct,然后选择[G++/GDB] Linux,Windows系统也是选这个,因为只有这个才是用mingw的。

获取include path

打开MSYS2 UCRT64 在命令窗口输入pkg-config --cflags gtk4 | sed 's/ /\n/g' | sed '1,3d;s/-I\(.*\)/\1/g' | sort | uniq,将输出的结果复制。注意,复制的时候,有几项是短横线-打头的,不是文件路径的项,不要复制这些。

设置include path

在vscode按下F1,选择C/C++:编辑配置(UI),这将自动生成c_cpp_properties.json文件并且进入图形界面进行修改。将前面复制的路径,贴到包含路径处。注意,图片只是示例,不是真实值。这里的路径,不太全,需要手动再填加两个,一个是include/gtk-4.0,一个是include/pango-1.0。如果不加这两个,VSCode的代码检查总是会提示有文件找不到,其实没啥事。

设置includePath

修改Makefile

下面是我用的Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CXX		  := g++
CXX_FLAGS := `pkg-config --cflags gtk4`

BIN := bin
SRC := src
INCLUDE := include
LIB := lib

LIBRARIES := `pkg-config --libs gtk4`
EXECUTABLE := main


all: $(BIN)/$(EXECUTABLE)

run: clean all
clear
./$(BIN)/$(EXECUTABLE)

$(BIN)/$(EXECUTABLE): $(SRC)/*.cpp
$(CXX) $(CXX_FLAGS) -I$(INCLUDE) -L$(LIB) $^ -o $@ $(LIBRARIES)

clean:
-rm $(BIN)/*

launch.json和task.json

做出上面的修改后,编译是可以了,但是要手动打命令,如果要点下面的build & run,则需要修改task.json,第一次运行时,会在右下角提示生成task.json。我改成下面这样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
"version": "2.0.0",
"tasks": [
{
"label": "Build C++ project",
"type": "shell",
"group": "build",
"command": "make"
},
{
"label": "Build & run C++ project",
"type": "shell",
"group": {
"kind": "test",
"isDefault": true
},
"command": "make",
"args": [
"run"
]
},
{
"type": "cppbuild",
"label": "C/C++: g++.exe 生成活动文件",
"command": "C:/msys64/ucrt64/bin/g++.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
"options": {
"cwd": "C:/msys64/ucrt64/bin"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "调试器生成的任务。"
}
]
}

调试的话,需要配置launch.json。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"version": "0.2.0",
"configurations": [
{
"name": "C++ Debug (gdb)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/main",
"preLaunchTask": "Build C++ project",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"miDebuggerPath": "gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

线规格,端子规格,压线钳规格

线规格

线规格现在主要说美标的,以AWG标识,数字越大,线越细。

线规格.png

图上展示了不同规格的线的直径、截面积、导流能力。

端子规格

端子形式有很多,但与线相连接的部分就三种形式。

柱状端子

把线穿进圆柱里,把圆柱夹线,把线固定,这种一般线不是很粗,线套上这个端子后,可以很方便的接到种子端子排上。

柱状端子

类似杜邦头端子

压线时两片压线芯,两片压线皮,需要用专用钳子压端子。

类杜邦头端子

大圆柱鼻状端子

有较大的线鼻,一般是U形口,或是扁口的端子上用,压的时候与第二种类似,但是一般是压中间一处,而不是两处。

大线鼻

压线钳规格

不论是何种端子,都有适应的线径,不是一个型号的端子可以压任意规格的线。而不同形式压接方式,可以选不同形式的钳子,但是钳子的规格,一般是以线径来说的。比如一个端子用的16AWG的线,那钳子应要用适配16AWG的钳子。由于线规格很多,所以钳子一般一个头上有多个槽位,可以压不同的线径,线径跨度再大点,还要换其它的钳子。大钳子压小线,压不紧,小钳子压大线,容易变形,甚至断线。

win10安装solidworks2016

win10离线安装SolidWorks2016的问题

安装过程很顺利,但是激活的时候,SSQ的激活程序一直运行,不提示“All done”的信息。上网检索了一圈,发现了问题的源头。

  1. 防火墙
  2. dotnet3.5

防火墙好办,dotnet3.5若是在线,很好安,但是离线安装不太方便。通过检索,找到了一个不错的解决方法。

解决方法

这个网址 找到对应的win10的版本,下载相应的dotnet3.5的包,然后照着命令执行就可以了。

测试验证

当dism显示dotnet3.5安装成功后,再执行SSQ的激活程序,很快就激活成功了。

jupyter notebook切换环境

在虚拟环境中安ipykernel

为了能被jupyter notebook识别,先要在虚拟环境中安装ipykernel,先进入虚拟环境,conda activate env_name, 再运行命令conda install ipykernel

在base环境里安装nb_conda_kernels

原理的详细用法可以看github上的说明https://github.com/anaconda/nb_conda_kernels 。操作方法,先回到base环境,conda activate,再运行conda install nb_conda_kernels。这个安装时会卡在solving environment那里几分钟。

另外看到有文章说,3.6版的python,需要安装的是conda install nb_conda,没有测试过。如果一直失败,不防试一试。

测试

安装完成后,从所有程序那里,打开jupyter notebook,新建一个notebook,然后选择kernel,这时如果可以看到多个不同的源,表示,安装好了。

安装keras

keras与Tensorflow

keras是一个用于快速构建模型的API库,但是他不处理实际的计算,所以它需要一个更底层的“服务”,这个服务负责把keras构的模型具体的实现。常用的有 TensorFlow, CNTK 或者 Theano作为它的后端服务,但是从Tensorflow2开始,Tensorflow中集成了Keras。目前Keras也是推荐使用Tensorflow作为它的后端。

安装keras

在anaconda的体系下,安装keras,实际上,只需要安装了Tensorflow就自然的装上了。如何安装Tensorflow可以看前面的博文。当然,这是安装CPU版的,GPU版的与CPU的类似,只是多了CUDA的安装部分。

测试

在jupyter notebook中写入下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import keras
from keras import models
from keras import layers
from keras.datasets import imdb
import numpy as np

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

def vectorize_sequences(sequences, dimension=10000):
# Create an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1. # set specific indices of results[i] to 1s
return results

# Our vectorized training data
x_train = vectorize_sequences(train_data)
# Our vectorized test data
x_test = vectorize_sequences(test_data)
# Our vectorized labels
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test)
print(result)

运行,如果过程中不报错,就表示都安好了。

安装tensorflow

安装前操作

anaconda的源可能下载比较慢,所以国内使用清华的源,速度会稳定些。

打开anaconda prompt,粘贴下面的命令,实现换源

1
2
3
4
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/

新建虚拟环境

tensorflow 支持的python版本,需要到官网上查看,https://tensorflow.google.cn/install?hl=zh-cn 。当前的是tensorflow2, 支持的python版本是3.6-3.9。所以创建虚拟环境时,需要指定python版本在这个范围里。这里使用新的3.9。

打开anaconda prompt,粘贴下面的命令
conda create -n tensorflow python=3.9

回车后,它会解析要安装的文件,根据网络和电脑性能,需要等一会,然后确认是否安装,输入 y 或直接回车,然后它就下载,安装环境。

安装tensorflow

先激活tensorflow的环境
conda activate tensorflow

这时命令行变成(tensorflow) C:\**\**\表示切换环境成功。

安装cpu版的tensorflow,输入命令conda install tensorflow,还是要等待一会,查找要安哪些包,输入y,然后它开始下载安装。

也有的教程里写用pip安装,pip安装的话,与conda安装有时会冲突,为了避免这种问题,优先使用conda安装。conda安不上的,再用pip试试。

测试

安完以后,在anaconda prompt里输入python,进入python环境,然后输入import tensorflow as tf,如果过程中不报错,大概率是安装成功了。

还可以继续输入tf.__version__)回车后,应该会回显tensorflow的版本号。此时安装tensorflow成功。

安装anaconda3

下载anaconda

打开网址https://www.anaconda.com/download/sucess 直接打开官网的话,它会让你注册,但下面有小字,skip,就不用注册了。

打开网页以后,根据操作系统,选择对应的版本,点下载,等着下载完成就可以了。

Anaconda的版本是不断的更新的,就像python的版本不断的更新一样。官网上下载的总是最新的。不用担心python的历史版本问题。因为它本身就是为了管理不同的python版本存在的。

安装anaconda

下载完成后,双击运行,依次点击 “next”,“I Agree”,这时会出现一个选择,是只给自己安装,还是所有用户都可以用。推荐的是只有当前用户可以用。主要的不同在于安装位置和一些包的文件存放位置不同。对于一般的用户来说,选“Just me”, 再点“next”。这时又会跳出一个安装路径的选择界面,这里我也建议不要更改,保留原始的状态。点“Next”,然后会出现一个选择界面,把Recommended的选项都勾上,NOT recommended的选项不要勾。点“Install”。

安装过程进行到Setting up the package cache时会很慢,如果有杀毒软件,会更慢,另外安装过程可能会下载一些包,所以安装过程需要保持联网。稍等,进度条跑完就安装完成了。如果一直卡住不动,可以试下重启安装程序。重启安装程序时,它会提示安装位置非空,让你换一个安装位置,可以去删了那个文件夹。

安完以后,默认安装程序会打开Navigator,这个打开很慢,等着就好了。

使用anaconda

点开始菜单,从所有程序列表里,找到Anaconda的文件夹,里面有一些安装完成的程序。

包括:Anaconda Navigator,Anaconda Powershell Prompt,Anaconda Prompt,Jupyter Notebook,Reset Spyder Settings,Spyder。

Anaconda Navigator是anaconda的管理软件,可以通过鼠标点点来完成管理和配置,包括虚拟环境的管理,包管理,界面的操作全部可以用命令行来实现,它还启动慢,适合不太习惯命令行的人使用。

Anaconda Powershell Prompt是基于Powershell的命令行工具,启动它时,自动加载了conda所需要的一些命令。

Anaconda Prompt是基于CMD的命令行工具。

Jupyter Notebook是一个交互式开发的IDE工具,适合探索式的写一些程序。也可以记录一些心得。

Spyder是一个类似MATLAB的IDE,可以比较方便的调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
conda --version #查看conda版本
conda config --show #查看conda的环境配置
#设置清华镜像
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
#设置bioconda
conda config --add channels bioconda
conda config --add channels conda-forge
#设置搜索时显示通道地址
conda config --set show_channel_urls yes
conda update conda #将conda自身更新到最新版本
conda update Anaconda #将整个Anaconda都更新到确保稳定性和兼容性的最新版本
conda create --help #查询create命令的帮助
#创建python版本为3.8、名字为env_name的虚拟环境
conda create -n env_name python=3.8
#查看有哪些虚拟环境
conda env list
conda info -e
conda info --envs
conda activate env_name #激活虚拟环境
#退出当前工作的虚拟环境
conda activate
conda deactivate
#将指定虚拟环境及其中所安装的包都删除
conda remove --name env_name --all
#只删除虚拟环境中的某个或者某些包则是
conda remove --name env_name package_name

#获得环境中的所有配置
conda env export --name myenv > myenv.yml
#重新还原环境
conda env create -f myenv.yml

conda list #查询看当前环境中安装了哪些包

#查询是否有安装某个包
conda list pkgname
conda list pkgname*

conda install package_name #安装一个包
conda update numpy #更新到它的最新版本

conda uninstall package_name #卸载包
conda clean -p # 删除没有用的包 --packages
conda clean -t # 删除tar打包 --tarballs
conda clean -y -all # 删除所有的安装包及cache(索引缓存、锁定文件、未使用过的包和tar包)

conda install 与pip install

只有在conda install搞不定时才使用pip intall。

使用nginx发布静态文件

起因

家中有个宽带有公网ip,动态的,为什么有公网ip还要买虚拟服务器呢?因为先买了个域名,大陆的ip地址绑到域名上无法正常解析。二级的免费域名呢,总是会招来一些攻击,导致宽带基本用不了。所以就取消了域名与ip绑定,但是这样一来,通过那个ip来访问的服务就不能用了。为什么不直接用虚拟服务器呢?带宽不如宽带的高,宽带的上行50,下行500,虚拟服务器上行10,下行100。

因为没有绑域名,导致很难记ip地址,导致跑在那上面的服务不好用。所以自己想着自己管理一个域名解析,又不想去搭dns,防止查水表,就只想写一个host文件来实现对宽带ip的域名绑定。

步骤

可以分为六步;

  1. 获取公网ip
  2. 生成host文件
  3. 更新到虚拟服务器
  4. 其他客户端从服务器下载host文件
  5. 将host文件应用到客户端
  6. 通过域名访问宽带服务

发布host文件到服务器

要想让客户端可以下载到这个host文件,需要在服务器上发布这个文件。由于服务器上已经跑了一个nginx,索性就用这个nginx来发布。

这里使用了智谱清言来帮我生成代码。我问他:

我有一个名为host的文件,这个文件在/var/www/html路径下,我想用nginx发布这个文件,让我通过domain.com/host获取文件内容

可以看到文字在快速的生成,生成了一堆代码和说明,比较有用的是这两行

1
2
alias /var/www/html/host;  # 指向你的 host 文件
try_files $uri$uri/ =404;

我开心的照着写了下,发现直接报404的错误,试着改了改文件权限,还是报错,后面在$uri$uri中间加了个空格,变成$uri $uri,再重载nginx,刷新页面,好了!

客户端使用host文件

对于windows系统,可以使用switchhost软件,linux桌面版也可以用,server不清楚。安卓系统可以用一个叫vitual host的APP,可以应用一个远程的host文件,然后通过域名访问服务。

为网站申请ssl以使用https

申请ssl的好处

安全,ssl是一种加密传输方式,有了它,密码传的过程,网页数据传的过程都是加密的,不易被破解。打开站点可以用https,而不是http,显的高级一些。

申请过程记录

先在网上查了一圈,免费申请的方法有一些,比如知乎上的这个文章,但是下面的评论有人说一些不好用,不能用了,也有人说有一个挺好的,比如acme,搜了一下,github上有项目,而且几个月前还在更新,算是比较新了,有中文的README,看了一下挺简单的,就照着做了一下。

下载acme.sh

1
2
bash
curl https://get.acme.sh | sh -s email=my@example.com

这里我输入了我用的一个邮箱,很快就安上了脚本

生成证书

acme.sh 实现了 acme 协议支持的所有验证协议.
一般有两种方式验证: http 和 dns 验证。 我用的是http认证。

http 方式需要在你的网站根目录下放置一个文件, 来验证你的域名所有权,完成验证. 然后就可以生成证书了.

1
acme.sh --issue -d mydomain.com -d www.mydomain.com --webroot /home/wwwroot/mydomain.com/

只需要指定域名, 并指定域名所在的网站根目录. acme.sh 会全自动的生成验证文件, 并放到网站的根目录, 然后自动完成验证. 最后会聪明的删除验证文件. 整个过程没有任何副作用.

更高级的用法请参考: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert

copy/安装 证书

前面证书生成以后, 接下来需要把证书 copy 到真正需要用它的地方.

注意, 默认生成的证书都放在安装目录下: ~/.acme.sh/, 请不要直接使用此目录下的文件, 例如: 不要直接让 nginx/apache 的配置文件使用这下面的文件. 这里面的文件都是内部使用, 而且目录结构可能会变化.

正确的使用方法是使用 --install-cert 命令,并指定目标位置, 然后证书文件会被copy到相应的位置,

1
2
3
4
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/in/nginx/key.pem \
--fullchain-file /path/to/fullchain/nginx/cert.pem \
--reloadcmd "service nginx force-reload"

(一个小提醒, 这里用的是 service nginx force-reload, 不是 service nginx reload, 据测试, reload 并不会重新加载证书, 所以用的 force-reload)

Nginx 的配置 ssl_certificate 使用 /etc/nginx/ssl/fullchain.cer ,而非 /etc/nginx/ssl/<domain>.cer ,否则 SSL Labs 的测试会报 Chain issues Incomplete 错误。

--install-cert命令可以携带很多参数, 来指定目标文件. 并且可以指定 reloadcmd, 当证书更新以后, reloadcmd会被自动调用,让服务器生效.

这里补充一下,我在做这里的时候不知道/path/to/keyfile/in/nginx/key.pem是应该写哪里,后面看到/etc/nginx/ssl/fullchain.cer,我就在nginx目录下创建了ssl文件夹,然后替换了/path/to/keyfile/in/nginx//etc/nginx/ssl/,其他保持不变,后面测试成功了。

详细参数请参考: https://github.com/Neilpang/acme.sh#3-install-the-issued-cert-to-apachenginx-etc

值得注意的是, 这里指定的所有参数都会被自动记录下来, 并在将来证书自动更新以后, 被再次自动调用.

查看已安装证书信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
acme.sh --info -d example.com
# 会输出如下内容:
DOMAIN_CONF=/root/.acme.sh/example.com/example.com.conf
Le_Domain=example.com
Le_Alt=no
Le_Webroot=dns_ali
Le_PreHook=
Le_PostHook=
Le_RenewHook=
Le_API=https://acme-v02.api.letsencrypt.org/directory
Le_Keylength=
Le_OrderFinalize=https://acme-v02.api.letsencrypt.org/acme/finalize/23xxxx150/781xxxx4310
Le_LinkOrder=https://acme-v02.api.letsencrypt.org/acme/order/233xxx150/781xxxx4310
Le_LinkCert=https://acme-v02.api.letsencrypt.org/acme/cert/04cbd28xxxxxx349ecaea8d07
Le_CertCreateTime=1649358725
Le_CertCreateTimeStr=Thu Apr 7 19:12:05 UTC 2022
Le_NextRenewTimeStr=Mon Jun 6 19:12:05 UTC 2022
Le_NextRenewTime=1654456325
Le_RealCertPath=
Le_RealCACertPath=
Le_RealKeyPath=/etc/acme/example.com/privkey.pem
Le_ReloadCmd=service nginx force-reload
Le_RealFullChainPath=/etc/acme/example.com/chain.pem

更新证书

目前证书在 60 天以后会自动更新, 你无需任何操作. 今后有可能会缩短这个时间, 不过都是自动的, 你不用关心.

请确保 cronjob 正确安装, 看起来是类似这样的:

1
2
3
crontab  -l

56 * * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

最后, 本文并非完全的使用说明, 还有很多高级的功能, 更高级的用法请参看其他 wiki 页面.

https://github.com/Neilpang/acme.sh/wiki

hexo的安装与部署到虚拟服务器

引子

网上有很多关于怎么把hexo部署到github上的教程,部署到虚拟服务器的也有一些。我是在腾讯的开发者社区看到了一个文章,照着他写的做的。在这里也记录一下。

hexo是一个基于markdown的个人博客工具,因为我觉得markdown挺好的,以前学过一点。以前也用过wordpress,觉得那东西做的是挺好看的,但是会花费不少的精力在找排版上面。而markdown就没有这个问题,主打一个记录,让记录回归记录的本质。所以选了hexo。当然可能有更优秀的工具,但我是新手,并不是很熟悉,有能用的就先用着。

安装前准备

  1. 虚拟服务器
  2. 安装必要的库
  3. 本地安装node和git
  4. 安装hexo包

以上是将hexo部署到虚拟服务器上的一些必备的东西。如果是github上,不需要虚拟服务器,但需要github的账号。

虚拟服务器

虚拟服务器的准备,如果要长期用,就要花钱了,短期是不用的,有一些体验的账号可以用,但是体验一般是一个月或三个月,很少有体验很久的。

要买服务器的话,其实有很多选择。国内的有腾讯云,阿里云,华为云,京东云等等,虽有一些差异,但总体来说,都那个样。不会的都不会。会的,都一样。我试用了一个月的腾讯云服务器,发现要想正常的访问,备案是必要的,因为试用的是国内的服务器,并没有香港的可以试用。

然后也我了解了一下备案的流程,服务器要租够满2个月还是3个月才能备案,备案还要有域名,备案前不需要建站,但需要明确建站的用途、域名、服务器。看了下个人备案的所需材料,还是挺麻烦的,我就放弃了。但是这时,我已经买了一个域名,不想浪费了,就又在别的地方买了一个香港的服务器。也不知道能用多久,先买了一年的。如果一年后觉得还可以,再续吧。

个人博客的话,流量不大,虚拟服务器的性能不用很好,带宽也是,2M其实就够用。内存也不用很大,这时一般选Linux系统,因为Windows server内存占用还是很大的。我因为以前用ubuntu用的我,所以选了ubuntu的系统。

安装必要的库

虚拟服务器(以后简称服务器)一般会给配上操作系统,但是软件包会比较少。在ubuntu里要做以下几个操作:(下面步骤比腾讯那里给的少了一些,可以结合我的说明去看原文)

  1. 创建git仓库
  2. 配置nginx
  3. 创建git钩子

创建git仓库

这里需要先安openssh,因为买的服务器一般都自带了这个组件,所以这步可以跳过。

创建一个博客用户,这是为了做隔离,防止黑客通过博客拿到系统权限。我不害怕这点,因为站小,也没啥东西,要是有技术能拿下博客,还能注入指令,那以我的技术根本防不住,所以就不给自己添麻烦了。所以我也跳过了这步,直接在root下面开干。

安装git,这是必需的,在终端中输入sudo apt install git-core,因为我是直接在root里干的,所以可以省掉sudo。

创建hexo_static裸仓库。这个是必需的。

1
2
3
4
5
6
sudo mkdir /var/repo/
sudo chown -R $USER:$USER /var/repo/
sudo chmod -R 755 /var/repo/

cd /var/repo/
git init --bare hexo_static.git

同样,sudo在root账户下可省略。执行完以后,目录里会出现下面的这些文件。

仓库内容

配置nginx

nginx可以管理多个站点,如果只想用一个hexo可以改端口,或在服务器上做端口映射。

安装ngixn,apt install nginx

创建/var/www/hexo目录,用于Nginx托管,修改目录所有权和权限。

1
2
3
sudo mkdir -p /var/www/hexo
sudo chown -R $USER:$USER /var/www/hexo
sudo chmod -R 755 /var/www/hexo

使用vim修改/etc/nginx/sites-enabled/default,使root指向hexo目录
nano /etc/nginx/sites-enabled/default

找到server部分,修改如下内容
root /var/www/hexo;

重启Nginx服务,使得改动生效
service nginx restart

创建Git钩子

在之前创建的hexo_static裸仓库下有一个hooks文件夹,在其中创建一个名为post-receive的钩子文件:
nano /var/repo/hexo_static.git/hooks/post-receive

在其中写入如下内容:

1
2
#!/bin/bash
git --work-tree=/var/www/hexo --git-dir=/var/repo/hexo_static.git checkout -f

保存后退出

让该文件变为可执行文件
chmod +x /var/repo/hexo_static.git/hooks/post-receive

本地安装git和node

git安装

git下载的时候有2个选项,安装版一免安装版。git for windows 下载
免安装版解压后是一个文件夹,里面有git-cmd.exe,可以直接运行。我个人喜欢这种免安的版本。

node.js安装

node.js下载链接
下载后一路下一步就可以了。

安装hexo包

在安完node以后,在git-cmd.exe里执行node -v,能看到回显的版本号,软件基础就都有了。
还是在git-cmd.exe里执行npm install -g hexo-cli,等待安装完成就行了。

安装成功后,可以通过
hexo -version
查看hexo的版本。

初始化hexo

先创建一个存博客文件的文件夹,比如C:\blog,在git-cmd.exe里cd C:\blog,执行命令hexo init

稍等一会儿,在blog目录里可以看到一堆文件。

这里如果再执行hexo s,在浏览器中输入 http://localhost:4000 就可以看到网站。

部署到服务器

安装部署工具

执行npm install hexo-deployer-git --save安装部署用的工具。

修改_config.yml

用vscode打开blog目录,在里面找到_config.yml这个文件,修改deploy参数

1
2
3
4
deploy:
type: git
repo: root@server_ip:/var/repo/hexo_static.git
branch: master

这里,看了几个介绍,都没有细说这块怎么写。通过查一些资料,发现了原理。
root@server_ip的root,就是用户名,前面创建的git钩子的所有者的用户名,
server_ip就是服务器的ip地址,域名也可以。这里默认是22端口的,如果是非22端口,
可以在本地计算机的.ssh目录里创建一个config文件,文件里写上下面的内容

1
2
3
4
5
Host hostname
User user
Port port
Hostname hostname
IdentityFile id_rsa

配了上面这些,就可以省略端口,就用上面的repo的写法,如果不配这个,resp那里就需要带上端口。
这些可以从ssh免密登陆查到相关的说明。

配置免密登陆

免密登陆服务器需要下列配置

    1. 服务器中开启允许用密钥登陆。这个查看/etc/ssh/sshd_config文件中PubkeyAuthentication yes是否存在。
    1. 本地电脑上用ssh-keygen -t rsa生成密钥,本地存私钥,公钥传到服务器。
    1. 在本地电脑的~/.ssh/config中的IdentityFile选项后面要配上本地私钥的路径。如"C:\Program Files\Custom SSH Keys\my_key"
    1. 公钥要放到服务器的~/.ssh/authorized_keys文件中,把公钥的全部内容复制粘贴到这个文件中。

以上做完了,就可以在本地测试,通过ssh 服务器地址,来登陆,这时服务器会把它的公钥传回来,填加到本地的known_hosts文件中,你只需要输入yes来允许这个操作就可以了。

部署到服务器

执行hexo deploy,git会自动把项目传到服务器,中间可能会要输入服务器ssh的用户密码,
这里和ssh一样,密码是不回显的,输入的时候注意一下。

写博文

使用cmd定位到hexo blog的目录,执行hexo new "文章名称",系统会自动创建一个文章名称.md 的文件,使用VS Code这样的编辑器可以直接编辑文件,然后cmd输入hexo g,hexo d就可以发布到服务器上了。