一些追忆

2025.11.07

这些笔记,类似于卡片盒笔记的东西,是我在上课的时候自我学习的产物,其实没维持下去,坚持了一个月?还是多久我已经不记得了,当时使用 x230,使用 arch 系统,觉得学习非常有意思,就一直写下去了,后面有事情就不再做了。

中断对于我而言是不可接受的事情,但是这件事情始终发生。

当然这种形式不是为了卡片盒笔记,而是为了我所学习到的 obsidian 配合 anki 制卡的形式,这个形式我最后只在语言学习上面使用了,并且还并不完美。

计算机方面没法使用,至少我目前还不觉得可以使用,最近看一个计算机大神的博客,也是曾经使用 anki 制计算机卡,最后也放弃了。

计算机重实操,记忆什么的可能就是我自己的做题家思维了。

使用Obsidian实现Anki快速制卡

Python

Q1

递归不要考虑复杂了,本身就是一个很简单的东西,我愿意称最简单的算法,没有之一,全天下所有的递归都是一个算法,只用找到n是n-1通过什么逻辑到达的即可。这个逻辑就是递归的逻辑。

Q2

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量。

总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。 没有什么private,什么protected了。

Q3

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

只要有所需的功能,那他就是。

Q4

有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!

python用的是@property,@属性名.setter,这些装饰器来实现对属性的封装。

python中如何继承

其实就是在class的小括号里面写入你想要继承的类名,多个就是多继承。

MixIn

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。

为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn

切片是深拷贝还是浅拷贝

是浅拷贝.

只要是切片就是浅拷贝.

定制类

定制类

枚举类

直接使用枚举,value属性则是自动赋给成员的int常量,默认从1开始计数。

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类

类的类型

类的类型是type,如int就是type类型,而8这个数字的类型是int类型。

其他创建类的方法

使用type()

以及metaclass

metaclass

class是定义类。

而metaclass是创建类。

metaclass是class的模板,所以必须从type中派生。

class A(type):
    pass

错误处理

try except finally

except可以捕获多个,也就是说可以有多个except,但是错误也是类型,所以上面的except要捕获小辈份的子类,错误类型。如果大的放前面,小的就不可能捕获到了。

记录错误

使用内置的logging模块。能打印错误的同时,让程序及需运行。

异常栈的解析

捕获与抛出错误

在bar()函数中,我们明明已经捕获了错误,但是,打印一个ValueError!后,又把错误通过raise语句抛出去了,这不有病么?

其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。好比一个员工处理不了一个问题时,就把问题抛给他的老板,如果他的老板也处理不了,就一直往上抛,最终会抛给CEO去处理。

断言

凡是用print()来辅助查看的地方,都可以用断言(assert)来替代

调试方法

  1. print直接输出
  2. assert断言来进行调试
  3. import logging来进行调试
  4. pdb来调试
    1. 也可以import pdb

虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。

单元测试

继承Unittest.TestCase

以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

文档注释

如果是异常的话,注释需要加上下面的:

Traceback是一定要加的,... 是省略了

Traceback (most recent call last):
    ...
ValueError

IO操作

在Python中,文件读写是通过open()函数打开的文件对象完成的。使用with语句操作文件IO是个好习惯。

序列化

其实就是把结构化的数据结构变为序列式的.

将类转为json时,需要先将class转为dict再转为json。

反序列化

就是把序列的数字,转成结构化的数据结构, 例如二叉树一类的.

dict

这个特殊属性并非是一个函数,不是callable也即可调用的,我们用的话是用实例.__dict__

Asicii

json.dumps(obj,ensure_ascii=True)

这里如果obj里面的属性字符串为中文,则会被换成utf8编码,如果是英文,则还是原来的。

{"name": "\u9a6c", "age": 20}
{"name": "ma", "age": 20}

进程与线程

一个任务就是一个进程。

一个任务可能有多个子任务,这个子任务就是线程。

所以说,一个进程至少有一个线程。

多任务的实现有三种方法:

  1. 多进程
  2. 多线程
  3. 多进程+多线程

线程是最小的执行单元。

进程知识

Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

python如何换行写代码

可以使用多行注释,或者使用 \

或者这样

print(f'child process is {os.getpid()}, parent process is {os.getppid()} '
        f'and my return pid is {pid}')

大量创建子进程

如果要启动大量的子进程,可以用进程池的方式批量创建子进程,进程池的默认上线是4,可以通过

p = Pool(数字)

来进行更改。不过其实是跟你的cpu核心有关的。

进程间通信

使用multiprocess中的Queue(),Pipes()来实现。

线程代码

import time, threading
 
# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)
 
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')  # 这里是把函数放入线程中,在这里才是真正创建了个线程。这个线程名字为LoopThread
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

进程与线程的区别

多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

进程相比于线程更稳定,因为进程直接的值是独立的,而线程之间的值是共享的,线程挂掉一个,就都完了;而进程挂掉一个,其他的不受影响。

给线程上锁

如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现

balance = 0
lock = threading.Lock()
 
def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()

但是上锁阻碍了多线程并发执行,降低了效率

GIL锁

所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。

不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。

异步IO

考虑到CPU和IO之间巨大的速度差异,一个任务在执行的过程中大部分时间都在等待IO操作,单进程单线程模型会导致别的任务无法并行执行,因此,我们才需要多进程模型或者多线程模型来支持多任务并发执行。

现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。

对应到Python语言,单线程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。我们会在后面讨论如何编写协程。

正则表达式

匹配数字

\d这个是匹配一个数字. \d{n}这个能够匹配n个数字. \d{n,m}这个能匹配n到m个数字,这个是包括两端的.也就是说包含N和M,例如说`\d{3,10},这个就是既能匹配到3个数字,也能匹配到10个数字.

匹配空格

\s这个其实指的是空白符,例如\n,\t什么的.

匹配字母或数字

\w这个只能匹配一个,如需多个后面加上{},用法域匹配数字那个一样.

匹配任意字符

.当然,这个也是只能匹配一个字符,用法与匹配数字那个一样.

匹配变长字符

    • 表示任意个字符,包含0个
    • 表示至少一个字符
  1. ? 表示0个或者1个字符
  2. {n}
  3. {n,m}

进阶的模式匹配

可以用[]来表示范围.

[0-9a-zA-Z_]可以匹配一个数字,字母或者下划线.

[a-zA-Z_]可以匹配一个字母或者下划线

或匹配

使用 |.

(P|p)ython可以匹配Python或者python.

匹配开头或结尾

^ 表示行的开头, ^d 必须以数字开头.

$表示行的结束,\d$必须以数字结尾.

r前缀

使用python中的r前缀,无需考虑转义问题.

s = r'abc\-ttt'

re的match方法

成功则返回match对象,失败返回None.

match方法也可以通过()来进行分组.

贪婪匹配

正则匹配默认是贪婪匹配,也就是尽可能多的匹配字符.

预编译正则表达式

使用re.compile(正则表达式)可以预编译.

常用内建模块

datatime

用于处理日期和时间的标准库.

timestamp

计算机存储当前时间是以timestamp表示的,timestamp一旦确定其UTC时间也就确定下来了. 与时区毫无关系.

timestamp与datatime可以互相转换.

namedtuple

就是给tuple一个名字.

使用collections import namedtuple.

我们可以用这个方便的定义一种数据类型,具有tuple的不变性,但是可以通过属性来引用.

属于是tuple的一个子类.

deque

也是collections中的,是一个双向列表.适用于队列和栈.适合大量数据时候的插入与删除.

四个方法:

  1. append
  2. pop
  3. appendleft
  4. popleft

Base64

是一种用64个字符表示任意二进制数据的方法.

适用于小段内容的编码,如数字证书签名,cookie的内容等.

struct

用来处理bytes和其他二进制数据类型的转换.

常用第三方模型

pillow

requets

chardet

psutil

海龟绘图

海龟绘图

这个挺好玩的.

网络编程

什么是网络通信

其实就是电脑上两个进程之间的通信

客户端与服务器的定义

在创建tcp连接时, 主动发起连接的叫客户端, 被动响应的是服务器.

如何进行客户端的tcp编程

建立套接字socket.

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功

建立连接

在创建完套接字后建立连接.

s.connect((a,b))

接受一个元组, a表示一个域名, 或者是ip地址, 不过本质是一样的, 因为域名可以被解析为ip地址.

b 表示端口号.

发送与接收

s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')

因为http协议规定了客户端必须先发请求给服务器, 服务器收到后才发数据给客户端.

所以这句话就是发信息的.

需要请求行.

GET / HTTP/1.1\r\n

get是http的请求方法.

/ 表示的是请求的路径.

HTTP/1.1表示的是使用的HTTP协议的版本.

请求头.

Host: www.sina.com.cn\r\n
Connection: close\r\n

Host: www.sina.com.cn:指定要请求的主机名。在 HTTP/1.1 中,Host 头是必需的,因为一台服务器可能托管多个域名(虚拟主机)。

Connection: close:告知服务器在完成请求后关闭连接。否则,服务器可能会保持连接处于开放状态,以便重用。

\r\n 在http协议中表示一行结束的换行符, 每一行都必须以这个结尾.

Rust

Q1

定义函数里面的行参列表里面的形参要有数据类型。

fn another_function(x: i32) {
    print!("x = {}\n", x);
}

不写是会报错的。

Q2

let y = 6 语句并不返回值,所以没有可以绑定到 x 上的值。这与其他语言不同,例如 C 和 Ruby,它们的赋值语句会返回所赋的值。在这些语言中,可以这么写 x = y = 6,这样 x 和 y 的值都是 6;Rust 中不能这样写。

Q3

使用 —release flag 在 release 模式中构建时,Rust 不会检测会导致 panic 的整型溢出。相反发生整型溢出时,Rust 会进行一种被称为二进制补码 wrapping(two’s complement wrapping)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 256 变成 0,值 257 变成 1,依此类推。程序不会 panic,不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。

Q4

首先,不允许对常量使用 mut。常量不光默认不可变,它总是不可变。声明常量使用 const 关键字而不是 let,并且必须注明值的类型。

const THIS_IS_A_CONST:int = x;

Q5

表达式与语句的区别rust里面。

表达式后面有分号,并且不会返回值。

函数的返回值得是表达式而非语句。

Q6

我们通常将一句话的注释写道这行代码的上面。

所有权

Q7

rust独有的概念,所有权。

这个概念让rust无需使用垃圾回收,即可保障内存安全。

什么是垃圾回收机制

在程序运行时有规律的寻找不再使用的内存。

内存管理

  1. 垃圾回收,这个是系统来管的。
  2. 程序员自己分配和释放内存
  3. 所有权

这是内存管理的三种方法。

Rust的堆与栈

不过在像 Rust 这样的系统编程语言中,值是位于栈上还是堆上在更大程度上影响了语言的行为以及为何必须做出这样的抉择。

入栈比在堆上分配内存要快,因为(入栈时)分配器无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为分配器必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。

二次释放

两次释放相同的内存,会导致内存污染,产生安全问题。

Rust不会自动建立深拷贝

另外,这里还隐含了一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何 自动 的复制都可以被认为是对运行时性能影响较小的。

拷贝在栈与堆的区别

栈上数据不存在深浅拷贝,而堆上数据存在深浅拷贝。

Copy与clone

管理权

定义一个变量,调用函数传入该变量,该变量的管理权在调用后立马没有管理权。

变量与返回值

变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。

多个返回值

我们可以使用元组来返回多个值

使用引用

使用引用而不是直接传值进函数,不会将所有权转到函数里面。就不用返回来返回去了。

rust也有解引用和引用

&为引用,*为解引用。

函数签名

fn calculate_length(s: &String) -> usize { // s 是 String 的引用
    s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
//所以什么也不会发生

变量 s 有效的作用域与函数参数的作用域一样,不过当 s 停止使用时并不丢弃引用指向的数据,因为 s 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。

我们将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。

借用

借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。

所以借用的值就无法改变。

可变引用

let mut s = String::from("hello");
change(&mut s);
 
fn change(x:&mut String)

借用有讲究

一次不能借两个。

一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能同时拥有。

难以两全

我们也不能在拥有不可变引用的同时拥有可变引用。

悬垂指针

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。

指针指向的空间里面什么也没有,或者不是想要的。

悬垂指针与空指针

悬垂指针指向已经释放的内存。

空指针不指向任何内存。

slice作为参数

在知道了能够获取字面值和 String 的 slice 后,我们对 first_word 做了改进,这是它的签名:

fn first_word(s: &String) -> &str {

而更有经验的 Rustacean 会编写出示例 4-9 中的签名,因为它使得可以对 &String 值和 &str 值使用相同的函数:

fn first_word(s: &str) -> &str {

在rust中&str是对slice的引用。&String是对String对象的引用。

这种灵活性利用了 deref coercions 的优势。

结构体

如何定义结构体

struct Student{
    age:u64,
    active:bool,
    name:String,
}
//里面的值是字段。
//跟定义类差不多,不过类出现的比结构体晚。

如何实例化

let user1 = User{
    age:10,
    active:true,
    name:String::from("Ma"),
 
};
 

rust的命名规范

结构体的实例话命名需要是snake_case也就是全小写加上下划线。

可变结构体实例

要么全可变要么就都不可变。

不能让一个字段可变一个字段不可变。

字段初始值简写语法

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}

这里是因为参数与结构体字段同名,所以可以进行简写。但是你像是active他就不是形参,所以就不能简写。

结构体更新语法

fn main() {
    // --snip--
 
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

这里 .. 表示其他字段使用user1的参数,这里就能省很多无用的代码。

以任何顺序指定字段。

但是..user1必须放在最后。

无字段命名的元祖结构体

struct Color(i32,i32,i32)

无字段的类单元结构体

struct A

容易理解的编程

fn area(width: u32, height: u32) -> u32 {

函数 area 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。

fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

这个版本却有一点不明确了:元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分。

不过当在屏幕上绘制长方形时就有问题了!我们必须牢记 width 的元组索引是 0,height 的元组索引是 1。如果其他人要使用这些代码,他们必须要搞清楚这一点,并也要牢记于心。很容易忘记或者混淆这些值而造成错误,因为我们没有在代码中传达数据的意图。

这就是重点,因为没有表现出意图,让人难以理解,难以理解自然也就难以书写代码。

结构体的作用

我们使用结构体为数据命名来为其赋予意义。我们可以将我们正在使用的元组转换成一个有整体名称而且每个部分也有对应名字的结构体。

一句话就是,就是赋予意义。

给结构体添加方法

其实更像类了。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}
 
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
 
fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
 
    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

就是impl后面跟着结构名,然后就是正常写函数,不过传到函数的参数用&self。这里的&self其实就是rectangle:&Rectangle

Rust同名方法与字段的使用

我们可以出于任何目的,在同名的方法中使用同名的字段。在 main 中,当我们在 rect1.width 后面加上括号时。Rust 知道我们指的是方法 width。当我们不使用圆括号时,Rust 知道我们指的是字段 width。

其实就是加上括号就是方法,不加括号就是字段。

Rust有自动引用和解引用功能

c/c++.->,.用来直接在对象上调用方法,而->用来在指针上调用方法。但是rust不存在->,所以是自动给你解引用,直接用.即可。

Rust结构体总结

结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。在 impl 块中,你可以定义与你的类型相关联的函数,而方法是一种相关联的函数,让你指定结构体的实例所具有的行为。

枚举

什么是结构体什么是枚举

结构体给予你将字段和数据聚合在一起的方法,像 Rectangle 结构体有 width 和 height 两个字段。而枚举给予你一个途径去声明某个值是一个集合中的一员。

如何定义枚举

enum Ip{
    v4,
    v6,
}

v4v6是枚举里面的成员

使用枚举值

let a = IP::v4;

注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。

枚举值的附加

enum IP{
    v4(u8,u8,u8,u8),
    v6(String),
}

枚举也能附加方法

结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,也可以在枚举上定义方法。

Option

rust不存在空值。不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是Option<T>

enum Option<T>{
    None,
    Some<T>,
}

Option的赋值

let some_number = Some(5);
let some_char = Some('e');
 
let absent_number: Option<i32> = None;

因为我们在 Some 成员中指定了值,Rust 可以推断其类型。对于 absent_number,Rust 需要我们指定 Option 整体的类型,因为编译器只通过 None 值无法推断出 Some 成员保存的值的类型。这里我们告诉 Rust 希望 absent_number 是 Option<i32> 类型的。

当有一个 Some 值时,我们就知道存在一个值,而这个值保存在 Some 中。当有个 None 值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值。那么, Option<T> 为什么就比空值要好呢?

所以是为什么?

总之option非常的重要

match控制流

可以把 match 看成是一种分拣机,每个东西都会进入自己应该去的地方. 必须得全部匹配,每一个成员都不能落下.

也就是说匹配模式必须覆盖所有的可能.包括None

其实也就是穷举.

不需要模式匹配的值

Rust 还提供了一个模式,当我们不想使用通配模式获取的值时,请使用 _ ,这是一个特殊的模式,可以匹配任意值而不绑定到该值。这告诉 Rust 我们不会使用这个值,所以 Rust 也不会警告我们存在未使用的变量。

    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => reroll(),
    }
 
    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn reroll() {}

这时候我都需要重新投掷了,自然不需要匹配出来的值

if let

如果觉得match太繁琐,那就使用if let

模块系统

模块系统的内容

  1. Crates:一个模块的树形结构,形成了库和二进制项目
  2. 模块和use:允许控制作用域和路径的私有性
  3. 路径

Crate

二进制项

fn main函数. 能够被编译为可执行程序.

没有main函数,也不会编译为可执行程序,但会提供一些函数之类的可用的东西. 大多数是间说的crate都是库.

含有复数个crate,并且包含一个Cargo.toml文件,这个文件会阐述如何去构建这些crate.

包中可以最多包含一个库,任意多的二进制crate.

crate根节点

  1. 对库而言是src/lib.rs
  2. 对二进制文件而言是src/main.rs

模块

在crate根文件中使用 mod 模块名 来声明模块.

父模块可以嵌套子模块.

在除了根节点以外的其他文件中可以定义子模块.

访问控制

一个模块的代码默认对其父模块私有.

若要共有,应在声明时候用pub mod代替mod.

使用use来快捷使用

引用 crate::garden::vegetables::A,可以前面添加 use,也就是 use crate::garden::vegetables::A,接着就无需输入全部,直接使用 A 即可使用.

conda的使用

如何查看已经有的虚拟环境

conda env list

如何激活某个虚拟环境

conda activate 虚拟环境的名字

不过前提是你得已经创建了这个虚拟环境

如何创建虚拟环境

conda create -n 虚拟环境名称 python=python的版本。

这个python的版本也可以不加,默认是最新版。

如何查看已经安装的包

conda list