Gawainx' Blog

Antarx is the heart of scorpio.

【Code】初探Python类型标注

干点轻松的事情,看了一下Python官方文档里面关于类型标注的知识,怎么说,Python官方对于类型检查这个问题的认识,远远比那些Python吹成熟多了(好像说了一句废话,其实想表达的是按照现在Python的发展程度,过于灵活的类型系统早就成了发展的桎梏,官方的态度也表明了这一点,只是还有很多人没有意识到,还沉浸在不用写类型的快感中而已)。作为一个Python黑,对于Python建立健全的类型检查机制这件事上,我是举双手双脚赞成的。

从最基本出发

先来看看最基本的类型检查
def foo(x: int, y: int) -> str:
return f”{(x+y)=}”
限定了foo的参数类型必须为int,返回值为字符串类型。

在实际运行中,如果非要传入float或者其他实现了加法运算的类型,代码是可以正常运行的。只不过在PyCharm中,代码编写过程会提示参数错误的Warning。Mypy里面也会有提示,但我没有测试。

也就是说,类型检查目前(Python3.8版本)也只是起到了道德约束的作用。

Tuple

这时候很容易会想到,如果我函数返回多个值,能不能用类型检查呢?

当然可以,只需要引入Tuple类型

1
2
3
4
from typing import Tuple

def foo(x: int, y: int) -> Tuple[int, int]:
return 2x, 2y

Tuple[int, int]表示返回值是长度为2的元组类型,第一个元素和第二个元素都是int类型。

返回同类型多值的时候,比如Tuple[int, int, int]可以简写成Tuple[int, ...]

如果返回值类型不同,只需要写成Tuple[str, int, dict, float]这样,表示返回四个参数,类型分别是字符串,整数,字典和浮点数。

Union

这时候问题又来了,“有些时候我真的不确定能返回啥的嘛”

可能返回两种类型的时候(比如做JSON解析,最外层可以是dict或者Array),需要引入Union关键字

1
2
3
4
from typing import Union

def foo(filename: str) -> Union[dict, list]:
return json.load(open(filename))

Union[dict, list]即表示返回类型是字典或者列表类型(只能二选一)。

Optional

这时候又有钢筋拍案而起了,“我特么就喜欢给你返回None,空数据我就给你None,传入可以None,返回可以None,就看你咋整”

当然可以整,只需要引入Optional关键字

1
2
3
4
5
6
7
from typing import Optional

def foo(x: Optional[int]) -> Optional[str]:
if x is None:
return None
else:
return f"{x=}"

Optional[str]等价于Union[str, None],表示返回值为str类型或者None

Literal:不能枚举之痛

Python的枚举类型,用起来是很麻烦的,每次写Python的时候我都很怀念Swiftview.setColor(.red)之类的写法,省去了很多麻烦。

在Python里面很多喜欢用字符串来表示特定一些范围的东西,比如训练时候选用优化器,有把"Adam"或者"SGD"直接传入函数的操作。

问题就来了,字符串灵活性太高了,不开着官方文档根本不知道有哪些可以传入,有时候一个typo就会导致debug半天。

Literal就是为解决这个问题而来的

1
2
3
4
5
6
7
8
9
def validate_simple(data: Any) -> Literal[True]:  # always returns True
...

MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
...

open_helper('/some/path', 'r') # Passes type check
open_helper('/other/path', 'typo') # Error in type checker

它限定了参数只能是特定的范围中选择。完美解决上面的痛苦

啊不它根本不完美,我还是更喜欢Swift的枚举推断。

More Fun

仔细挖掘还可以看到有趣的东西。

函数名类型Callable[[int], str]表示(int)->str的函数。第一个位置为传入参数的类型和数值,第二个位置表示返回值类型。

不可变常量:Python也可以有Final。(Python3.8+)

1
2
3
4
5
6
7
8
MAX_SIZE: Final = 9000
MAX_SIZE += 1 # Error reported by type checker

class Connection:
TIMEOUT: Final[int] = 10

class FastConnector(Connection):
TIMEOUT = 1 # Error reported by type checker

类变量:静态语言中static干的事情,也被搬到了Python上面

1
2
3
class Starship:
stats: ClassVar[Dict[str, int]] = {} # class variable
damage: int = 10 # instance variable

调用的时候只能使用Starship.stats

Beyond

官方文档还有很多很有意思的,比如范型和函数重载等等,改天看完敲完代码再更新。从现在开始我写的每一行Python代码都会全力使用上面说到的所有新特性。

Python曾经困扰过我很长很长一段时间,从大三(2015)就开始学的语言,其实直到去年年初我还觉得我没有入门(可以翻历史记录,去年七月的时候还有对Python的吐槽),当中各种痛苦与挣扎,甚至有那么一瞬间会觉得如果博士不能毕业的话,Python肯定是最大的元凶,因为用它真的太痛苦了。直到后面追上新文档的改变之后才感觉打开新世界的大门,现在越用越顺手,甚至开始把平常常用的代码段整理成库发布到PyPI上面。

很庆幸当初没有因为厌恶和憎恨而逃避它,而是选择去了解它。

与此同时也庆幸是Python变成了我想要的样子,而不是我改变自己去适应以前的Python。

转博了之后平时的工作重心从写代码慢慢的转移到科研读论文复现论文等上面来。选研究方向的时候也费了好大一番功夫去纠结,一开始想继续做微服务和SDN的东西,后面却逐渐发现往深入去做的话又兜兜转转回到了通信的轨道上了。后面终于下定决心做知识图谱的东西。整个十一月都在看知识图谱的论文,主要集中在知识表示领域的几种Translation模型上。

新款 Mac mini 购买可行性分析(持续更新)

10 月 30 号苹果的新品发布会,我望眼欲穿的Mac mini 终于在时隔四年之后迎来了“大更新”,正如之前很多 KOL 所“预言”的一样,是一次面向专业人士的一次更新,最高支援了 i7 六核处理器和 64GB 内存和 2TB 固态,还有万兆以太网接口可选。昨天也同步更新了大陆的价格信息。网络上对于这款产品的评价也是褒贬不一,到底这款产品是不是值得购买呢。

gxd-cli : 一种快速创建多容器工具

通过 docker run 命令行启动容器的时候,配置网络、挂载卷是一件非常麻烦的事,gxd-cli将这些麻烦的工作简化成修改配置文件TOML达成在不需要记忆繁琐的 docker 命令行参数就能快速启动多容器。

功能列表

  • 创建多容器,创建每个容器过程可以配置一下选项
    • 挂载卷(支持以pwd指代当前路径)
    • 指定容器的网络
    • 自定义容器名
    • 设定容器暴露的端口
  • 创建网络
  • 快速生成模板文件

安装

支持从源码构建,构建之前首先保证系统已经安装golangdep
步骤如下:

1
2
3
git clone git@github.com:gawainx/gxd-cli.git
dep ensure -update
go install

安装完毕后在命令行通过gxd-cli调用。

项目地址

gawainx/gxd-cli

  1. 【TED演讲】怎样的压力,会让一个人放弃生命?

  2. 「TED/4P字幕」语言如何形塑出我们的思考方式,多学一门语言的好处 /How language shapes the way we think

  3. 邪教组织如何重新连接大脑

  1. 【TED双语】推动学习革命_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

WB Yeats Poems Inspired By Maud Gonne | Indie Author Orna Ross

> He Wishes for the Cloths of Heaven,
  Had I the heavens’ embroidered cloths,
  Enwrought with golden and silver light,
  The blue and the dim and the dark cloths
  Of night and light and the half-light,
  I would spread the cloths under your feet:
  But I, being poor, have only my dreams;
  I have spread my dreams under your feet;
  Tread softly because you tread on my dreams.

Docker client for golang 使用教程(二):网络

端口绑定

将微服务放到 docker 容器中运行的时候,端口绑定是一个无可避免的问题。在 docker 命令行中,可以通过简单的-p 8080:80解决问题。但在 golang client 中,问题却变得复杂起来。

首先来看创建容器的函数签名func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error),client 把运行配置拆分成了container.Configcontainer.HostConfig ,也就是容器内部设置和宿主机设置两项。

要实现端口绑定,首先要在容器设置中设定暴露的端口(exposed ports)。

1
2
3
4
5
6
7
exports := make(nat.PortSet, 10)
port, _ := nat.NewPort("tcp", "80")
exports[port] = struct{}{}
// in config:
cli.ContainerCreate(ctx, &container.Config{
ExposedPorts:exports,
}

然后,在 host.config 中,设置Host 端口与容器暴露出来的端口的绑定。

1
2
3
4
5
6
7
8
9
10
11
ports := make(nat.PortMap)
pb := make([]nat.PortBinding,0)
pb = append(pb,nat.PortBinding{
HostPort:"8080",
})
ports[port] = pb

//in Host.config
&container.HostConfig{
PortBindings:ports,
}

至此,在代码中就实现了端口绑定的操作。然而,如果只执行到这一步,编译器一般会报非常诡异的

类型不匹配错误
类型不匹配错误

参考go操作docker - 简书的解决方法,删除gopath里面pkg下面docker的vendor里面相应的connections包,然后运行go get github.com/docker/go-connections/nat ,问题解决。

相关链接

golang 为微服务的开发带来了无可比拟的便利。使用的时候也自然而言发现一些问题,因为 golang 不像 Java 有 Maven 这样的打包工具,而是直接编译成二进制可执行文件,所以在开发机(macOS)上编译出来的可执行文件是无法在服务器或者 docker 容器里运行的,如果把源代码提交上去服务器编译,又会带来重新下载依赖包的麻烦(golang 的包依赖关系管理方面的缺失是我认为 golang 为数不多的缺点之一)。最近一直在思考有没有类似 Makefile 的方式来解决这件事(如果只想交叉编译的话直接用 go build或者借助 gox 等工具也不是不可以,可还是,不够方便)。直到之前 ing 大神给我推荐了 Hugo 这个静态博客框架,虽然目前因为找不到合适的博客主题没有从 hexo 迁移过去,但看源代码的时候有了一个重要的收获,就是mage
关于 mage 的基本安装和使用详见mage 使用教程(一)

Golang 中处理 JSON 格式数据主要依赖encoding/json这个库,很多教程(包括 Go 语言圣经)讲 JSON 数据处理时都会定义一个结构体对应于 JSON 数据的各个字段,这种处理方法在 JSON 中字段相对固定时非常实用。但对于字段可能不断变化或者只有一两个字段是固定的时候,如何处理这个问题往往令很多人感到困惑。最近研究 gin 这个库的时候发现一个思路非常值得学习借鉴。