Slient Plant

  • 首页

  • 关于

  • 标签

  • 归档

使用Intel NUC作为家用服务器

发表于 2022-06-05 | 更新于 2022-06-11 | 评论数:
本文字数: 3.1k | 阅读时长 ≈ 3 分钟
  • 1. 硬件与安装
  • 2. PVE 使用
    1. 硬件与安装
  • 1.1. Intel NUC11 与 proxmox
  • 1.2. 安装 PVE
    2. PVE 使用
  • 2.1. 安装 Openwrt 作为旁路由
  • 2.1.1. 设置 Openclash
  • 2.2. 设置旁路由
  • 2.2.1. 修改 Openwrt 设置
  • 2.2.2. 修改主路由设置
  • 2.2.3. windows 机器设置 chrome 浏览器
  • 2.3. Pi-Hole
  • 2.4. HomeAssistant
  • 2.5. aliyundrive-webdav
  • 2.6. Samba

1. 硬件与安装

1.1. Intel NUC11 与 proxmox

从初代到11代,NUC 捡漏指南

Intel NUC 11 低功耗,安静,体积比 Mac mini 还小,硬件强大,配上 16G 内存,一块 120G 的普通 SSD 作为 proxmox 的安装盘,一块 1T nvme 接口 的 SSD 作为数据盘,可以满足大部分的需求。

使用 proxmox 作为虚拟化管理软件,可以方便地创建系统级容器(LXC)

1.2. 安装 PVE

https://pve.proxmox.com/wiki/Prepare_Installation_Media#_instructions_for_windows

使用 Etcher 烧录 ISO 文件至 USB 盘,插入目标主机,开机进入 BIOS 编辑引导盘从 USB 盘开始,安装 PVE

2. PVE 使用

使用 PVE 创建虚拟机或 LXC

2.1. 安装 Openwrt 作为旁路由

  • IMG 镜像文件来源https://github.com/ApertureG/OpenWrt/releases
  • PVE 安装 Openwrt 设置 https://felixqu.com/2021/09/05/setup-openwrt-on-pve/

    注意复制上传的镜像文件路径以及创建虚拟机之后编辑引导顺序

2.1.1. 设置 Openclash

Openwrt 镜像中已经安装了 openclash,登录 Openwrt 在服务中找到 openclash

配置文件管理,可以先手动上传一份配置文件,再设置配置文件订阅

2.2. 设置旁路由

2.2.1. 修改 Openwrt 设置

登录 openwrt

网络 -> 接口 LAN -> 修改

一般配置 -> 基本配置 -> IPv4 网关: 填写主路由 IP
                     -> IPv4 广播: 设置为主路由网段,最后一位为 255
                     -> 使用自定义的 DNS 服务器: 填写主路由 IP
                     
         -> 物理设置 -> 桥接接口: 取消勾选
                     -> 接口: 选择对应的硬件 LAN 接口

DHCP 服务器 -> IPv6 设置 -> DHCPv6 服务: 修改为已禁用

保存&应用

2.2.2. 修改主路由设置

登录主路由

内部网络(LAN) -> DHCP 服务器 -> 基本设置 -> 默认网关: 填写为 Openwrt 的 IP
                                         -> DNS 服务器设置: 填写为 Openwrt 的 IP

这样的设置为主路由不关闭 DHCP 服务器,只设置 DHCP 的默认网关

保存&应用

2.2.3. windows 机器设置 chrome 浏览器

设置后发现接入家用网络的 windows 机器出现以下现象

  • github 无法访问
  • youtube 可以访问,但是无法播放视频

解决方法

chrome://settings 搜索 dns

security -> Advanced -> Use secure DNS 选择 With Cloudflare 1.1.1.1

2.3. Pi-Hole

可以选择创建 Ubuntu 容器安装 Pi-Hole 用于广告过滤以及网络流量监控

在加入 Pi-Hole 之后的网络拓扑

Pi-Hole 使用的anti-AD 列表

2.4. HomeAssistant

安装 HomeAssistant 将小米设备接入 Apple HomeKit,通过 Siri 控制家族智能设备

following the instruction of https://www.youtube.com/watch?v=tZflVxBq6Sw

HomeAssistant 虚拟机启动报错问题:

in the Proxmox VM. When you see the Proxmox logo when booting a VM, hit escape and then go to Device Manager -> Secure Boot Configuration then disable Attempt Secure Boot.

2.5. aliyundrive-webdav

将阿里云盘作为文件夹挂载到 windows 电脑中

https://opssh.cn/luyou/189.html

https://github.com/messense/aliyundrive-webdav

2.6. Samba

设置 Sambda 服务器,用于局域网共享文件夹

将 1T 的 SSD 格式化之后挂载到 PVE. 挂载路径 /mnt/data

创建用于共享的文件夹 /mnt/data/share

PVE 创建 Ubuntu LXC ,创建后不启动,如 LXC ID 为 104

在 PVE 上编辑配置文件 /etc/pve/lxc/104.conf 加入 bind mount ,将共享文件夹挂载到容器内的 /mnt/share 路径

mp0: /mnt/data/share,mp=/mnt/share

启动容器,安装 Samba

apt update && apt upgrade -y
apt install net-tools #Needed for ifconfig, etc.
apt install samba
# 新建用户
adduser --system smbusr
# 设置密码
smbpasswd -a smbusr

编辑文件 /etc/samba/smb.conf 加入

[Share]
    comment = Share
    path = /mnt/share
    read only = no
    writeable = yes
    browsable = yes
    force user = smbusr
service smbd restart
ufw allow samba

Generated using Emacs 29.0.50 (Org mode 9.4.6)

windows11使用WSLg方式使用Emacs

发表于 2022-05-31 | 更新于 2022-06-11 | 评论数:
本文字数: 2.1k | 阅读时长 ≈ 2 分钟
  • 1. wslg
  • 2. emacs
    1. wslg
  • 1.1. 中文字体
    2. emacs
  • 2.1. 安装 Emacs 29
  • 2.2. Emacs 中文输入法 pyim
  • 2.3. Emacs 复制文本至 Windows 剪贴板

1. wslg

在 windows 下使用原生的 Emacs 一直是个麻烦的事情,比如以下问题

  • windows 文件路径形式的匹配,
  • 缺少 Linux 常用命令(使用如 GnuWin32 , coreutils 等项目)
  • 环境变量的设置
  • 编译麻烦

而且最大的问题出在性能上,无法和在 Linux/MacOS 上使用经验相媲美。

windows 11 推出后支持使用 wslg 的形式使用 Linux 应用图形化界面。目前看来在 windows 上以这种形式使用 Emacs 体验十分不错。

在安装好 WSL2 之后,选择使用 Ubuntu 20.0 创建虚拟机。

1.1. 中文字体

配置 Ubuntu 系统的中文字体, 按照这篇文章 的 1.5.1 及 1.5.2 两个章节操作,安装中文支持及配置中文字体。

2. emacs

2.1. 安装 Emacs 29

使用本地编译方式安装 Emacs 29.0.50

  • –with-native-compilation Emacs Lisp bytecode is translated to C and then machine code, yielding performance benefits.
  • WSLg uses Wayland and Emacs 29.0.50 master has the pure GTK feature instead of relying on the older X window system.
git clone git://git.sv.gnu.org/emacs.git emacs-source
cd emacs-source

sudo apt install build-essential libgtk-3-dev libgnutls28-dev libtiff5-dev libgif-dev libjpeg-dev libpng-dev libxpm-dev libncurses-dev texinfo
# Native JSON
sudo apt install libjansson4 libjansson-dev
# Native Complilation
sudo apt install libgccjit0 libgccjit-10-dev gcc-10 g++-10

./audogen.sh

export CC=/usr/bin/gcc-10 CXX=/usr/bin/gcc-10
./configure --with-native-compilation --with-json --with-pgtk

make -j6

sudo make install

2.2. Emacs 中文输入法 pyim

使用 wslg 方式使用 Emacs,windows 的中文输入法在 Emacs 应用上无法生效。使用 Emacs 原生的中文输入法 pyim 来解决这个问题,无需使用系统 输入法来回切换,使用起来也十分流畅。

以下是使用 use-package 配置 pyim 使用五笔输入法的例子,需要安装 pyim pyim-wbdict

(use-package pyim
:ensure nil
:config
(use-package pyim-wbdict
:config
:ensure nil
:config (pyim-wbdict-gbk-enable))


(setq default-input-method "pyim")
(setq pyim-default-scheme 'wubi)
(pyim-wbdict-v86-enable)
;; (pyim-wbdict-v98-enable)
;; (pyim-wbdict-v98-morphe-enable)
;; (pyim-wbdict-v86-single-enable)

(setq pyim-page-tooltip 'popup)
(setq pyim-page-length 5)
(add-hook 'emacs-startup-hook #'(lambda () (pyim-restart-1 t)))
)

2.3. Emacs 复制文本至 Windows 剪贴板

TODO

Generated using Emacs 29.0.50 (Org mode 9.4.6)

代码之外的功夫

发表于 2020-05-22 | 评论数:
本文字数: 2.4k | 阅读时长 ≈ 2 分钟
  • 1. 代码之外的功夫
  • 2. 信息的信噪比
  • 3. 提高搜索信息的效率
  • 4. 知识组织的步骤
  • 5. 善用工具
  • 6. 学习
    1. 代码之外的功夫
    2. 信息的信噪比
  • 2.1. 信息不再贫乏
  • 2.2. 认知科学: 大脑的主动识别模式
  • 2.3. 提高信息来源的质量
  • 2.4. 高质量的信息来源
    3. 提高搜索信息的效率
  • 3.1. 时代的改变
  • 3.2. 正确地使用搜索引擎
  • 3.3. 搜索引擎之外
    4. 知识组织的步骤
  • 4.1. 学习计划的制定和安排
  • 4.2. 学习材料的收集和储存
  • 4.3. 资料的阅读和消化
  • 4.4. 知识的思考和产出
    5. 善用工具
  • 5.1. 你是工具增强过的人类
  • 5.2. 使用好的工具
    6. 学习
  • 6.1. 知识更新速度快
  • 6.2. 动机
  • 6.3. 方式
  • 6.4. 自我教育

1. 代码之外的功夫

根据我在公司的分享材料整理, 细节大多靠自己口头表述, 此处只列出大纲

2. 信息的信噪比

在今天的中国,你基本上不用做什么,只需要不使用中国互联网,你就很自然地超过大多数人了

如何超过大多数人

2.1. 信息不再贫乏

随手可及的信息

  • 独立博客
  • 播客
  • 聚合类网站
  • 应用
  • 社交网站

缺乏的是有效的搜索信息以及快速从大量的信息中提取有用信息的技能

2.2. 认知科学: 大脑的主动识别模式

一个视错觉的经典例子: 正方形的轮廓线并没有被直接描画出来,但是人眼能够立刻从背景中识别出一个白色的正方形形状

信息质量很重要

2.3. 提高信息来源的质量

  • 拒绝劣质消息来源
  • 朋友圈/微博/抖音不是学习的好地方
  • 阅读第一手资料(权威来源,官方文档,知名公司技术博客,原始出处的书籍和文章)
  • 阅读英文文档(中文翻译可能滞后)

2.4. 高质量的信息来源

出版社 O'Reilly / No Starch Press / The Pragmatic Bookshelf / Manning Publications

聚合类网站 InfoQ / Solidot / Plannet Python / Hacker News

技术博客 coolshell 湾区日报

3. 提高搜索信息的效率

3.1. 时代的改变

搜索引擎改变了人类记忆和搜索知识的方式

百科全书式的记忆重要性下降了

重视知识的组织和联系

3.2. 正确地使用搜索引擎

掌握搜索引擎的高级搜索技巧 (自行 Google)

3.3. 搜索引擎之外

垂直细分领域

  • stackoverflow / stackexchange / segmentfault
  • github (很多人意料之外,在 github 其实可能搜出很有用的信息)

4. 知识组织的步骤

  • 学习计划的制定和安排
  • 学习材料的收集和储存
  • 资料的阅读和消化
  • 知识的思考和产出

4.1. 学习计划的制定和安排

4.2. 学习材料的收集和储存

知识的获取 来源

  • RSS
  • BLOG
  • Podcasts

应用

  • Pocket
  • Evernote / Notion

4.3. 资料的阅读和消化

好记性不如烂笔头

知识的加工和组织 处理知识,加深神经元的连接

纸和笔 重视纸和笔,成本最低,自由度最高,使用最便利

应用

  • PDF 阅读软件 MarginNote3
  • 平板 iPad/Surface
  • 思维导图

康奈尔笔记法: 用自己的话进行复述,列出大纲,使用自己的理解

4.4. 知识的思考和产出

产出 记录

hexo static website generator

github pages websites for you and your projects

markdown

5. 善用工具

5.1. 你是工具增强过的人类

你手上的手机的算力超过 50 年前 1969 年美国执行登月行动时整个 NASA 的计算能力

设想自己在没有智能手机和笔记本情况下的生产力

5.2. 使用好的工具

  • 高效的工作环境 ( ConEmu / zsh / FreeCommander / PyCharm )
  • 熟悉你的工具 掌握每天都使用的工具是提高效率的重要部分
  • 投资硬件 (屏幕/键盘/工作台/轨迹球)

最重要的是,用好手上的工具,比盲目追求好工具/新工具重要

6. 学习

6.1. 知识更新速度快

前端: jQuery AngularJS Angular2/4/6 Vue.js React TypeScript NodeJs Deno WebAssembly

后端: Hadoop Spark Storm 微服务 容器 K8S

6.2. 动机

回报

学习的回报相比其它行业周期更短

正向反馈循环

一件事有兴趣才会做得好,做得好才会更有兴趣,更有兴趣才会做得更好 没有人会喜欢一直做一件自己做不好的事情

6.3. 方式

  • 时间管理之外, 应该更重视精力管理
  • 重视睡眠
  • 系统性学习: 深度工作 ( Deep Work )
  • 碎片化学习: 泛读, 利用好碎片化时间, 播客(音频可以填充碎片时间或者不方便看书的时间)
  • 保持技术敏感

6.4. 自我教育

  • MIT OpenWareCourse
  • Coursera
  • edX
  • Khan Academy
  • 网易公开课
  • TED 演讲

Generated using Emacs 29.0.50 (Org mode 9.4.6)

Python 项目开发规范

发表于 2019-10-20 | 评论数:
本文字数: 16k | 阅读时长 ≈ 15 分钟
  • 1. Python 项目开发规范
  • 2. 代码风格
  • 3. 语言规范
  • 4. 编程原则与最佳实践
  • 5. References
    1. Python 项目开发规范
  • 1.1. 阅读指南
    2. 代码风格
  • 2.1. 代码格式器
  • 2.1.1. black 的使用
  • 2.2. import
  • 2.2.1. 禁止使用 import *
  • 2.2.2. import 语句的顺序
  • 2.3. 命名
  • 2.3.1. 描述
  • 2.3.2. 避免使用的命名
  • 2.3.3. 命名约定
  • 2.3.4. 变量名形式
  • 2.4. Main 方法
  • 2.5. 注释
  • 2.5.1. 解释为什么
  • 2.5.2. 错误的注释比没有注释还糟糕
  • 2.5.3. docstring
  • 2.5.4. inline comment
  • 2.6. 类型注解
    3. 语言规范
  • 3.1. 导入
  • 3.1.1. 例外
  • 3.2. 包
  • 3.3. 类
  • 3.4. 异常
  • 3.4.1. 自定义异常
  • 3.4.2. 异常抛出的形式
  • 3.4.3. 注意异常捕获的范围
  • 3.4.4. 尽量减少 try except 语法块内代码的数量
  • 3.4.5. 使用 try except else finally 结构
  • 3.4.6. assert
  • 3.5. 全局变量
  • 3.6. 列表表达式 (list compreshensions)
  • 3.7. 默认迭代器和操作符
  • 3.8. lambda 表达式
  • 3.9. 条件表达式
  • 3.10. 函数参数默认值
  • 3.11. True/False 求值
  • 3.12. 格式化字符串
  • 3.12.1. 例外
    4. 编程原则与最佳实践
  • 4.1. 使用 pylint
  • 4.2. 自底向上编程
  • 4.3. 防御式编程
  • 4.4. 避免使用 magic number
  • 4.5. 处理字典 key 不存在时的默认值
    5. References

1. Python 项目开发规范

本文旨在提供 Python 项目的开发规范参考以及工具指南, 一些最佳实践以及通用的编程原则.

本文对于风格规范/工具/流程的选择不可避免的有偏向性, 不同的项目组可以根据自身的情况去调整.

每个团队和项目都有自己的代码风格与编程约定,重点不在于哪种风格规范/工具/流程更好, 而在于开发者可以在 项目中使用一致的风格规范/工具/流程. 这有助于开发者理解项目以及提高项目的可维护性.

1.1. 阅读指南

由于 Python 2 将于 2020 年正式停止维护, 新的工程项目禁止使用 Python 2, 应当使用 Python 3, 推荐使用 >= Python 3.6.5 的版本.

请注意以下所有的讨论都是基于 Python 3, 或者是在忽略/没有考虑兼容 Python 2 的情况下进行.

2. 代码风格

代码风格应该遵循 PEP8 风格

代码风格指南是关于代码格式的细致规定, 但是幸运的是我们不需要记住这些多代码风格的规则. 使用代码格式器(formatter)可以帮助我们自动格式 化代码以遵循代码风格.

流行的 Python 风格有

  • PEP8 风格
  • Google style

我们选择的 PEP8 风格. 具体的细节规定请自行阅读 PEP8 文档.

2.1. 代码格式器

formatter 选择使用 Black.

流行的代码格式器有

  • Black PEP8
  • yapf 对应 Google style
  • autopep8 PEP8

Black 使用的代码风格可以视为比 PEP8 风格还严格的一个子集, 格式化之后的代码风格可以通过 PEP8 风格的检查.

2.1.1. black 的使用

black 可以通用命令行使用对单个文件或者整个文件夹的文件进行格式化. 建议将 black 添加到 PyCharm 的 external tool 中并绑定快捷键.

配置 PyCharm

File -> Settings -> Tools -> External Tools

the Click + icon to add a new external tool with the following values:
- Name: Black
- Description:
- Programe: 选择安装的 Black.exe
- Arguments: $FilePath$

使用 右击文件 -> External Tools -> Black

绑定快捷键

File -> Settings -> Keymap
搜索 external tool 找到 Balck 对应的条目, 点击编程绑定一个快捷键 Alt+Shift+F

2.2. import

使用独立的一行导入一个模块

Yes

import os
import sys

from extres.vcenter.datatype import SaltCallFailedException
from extres.vcenter.datatype import VCenterInitializeOptions
from extres.vcenter.datatype import VCenterVMCloneSpec

No

import os, sys

from extres.vcenter.datatype import SaltCallFailedException, VCenterInitializeOptions, VCenterVMCloneSpec
# or
from extres.vcenter.datatype import (
SaltCallFailedException,
VCenterInitializeOptions,
VCenterVMCloneSpec,
)

2.2.1. 禁止使用 import *

原则上禁止避免使用 import *, 应该显式地列出每一个需要导入的模块

使用 import * 会污染当前命名空间的变量, 无法找到变量的定义是来哪个模块, 在被 import 的模块上的改动可 能会在预期外地影响到其它模块, 可能会引起难以排查的问题.

在某些必须需要使用或者是惯用法 from foo import * 的场景下, 应该在模块 foo 的末尾使用 __all__ 控制被导出的变量.

# foo.py
CONST_VALUE = 1
class Apple:
...

__all__ = ("CONST_VALUE", "Apple")

# bar.py
# noinspection PyUnresolvedReferences
from foo import *

2.2.2. import 语句的顺序

使用 PyCharm 的 Optimize imports 功能格式化导入语句, 避免手动管理 import 语句的顺序, 除非 某些包的 import 需要有明确的先后顺序.

注意 Optimize imports 功能会将模块内没有使用的 import 语句删除, 需要小心这一点以避免误删 import 引起问题.

在这种情况下需要加上注释以说明没有使用到的 import 语句需要保留.

# noinspection PyUnresolvedReferences
from foo import *

2.3. 命名

名字(变量名,函数名,类名,方法名)本身是表达代码信息的重要组成部分, 选取合适的名字就是选取了合适的抽象.

合适的名字可以让阅读代码本身就得到清晰的信息, 减少需要注释或者外部文档进行说明的需求.

2.3.1. 描述

函数名, 类方法以动词开头; 类名, 类属性以名词开头.

2.3.2. 避免使用的命名

  • 在任何地方都避免使用短横线 -
  • 避免使用 __xxx__ 形式的名字, 这种形式的名字(magic method)保留给 Python 解析器使用
  • 避免使用单个字符作为作为变量名(异常或者迭代器除外)

2.3.3. 命名约定

  • 不要使用双下划线开头的形式(__XXX), 使用单下划线开头(_XXX)的形式以表示是私有变量.

    虽然双下划线开关的变量 Python 解释器会有特殊的处理(name mangling), 但是 Python 语言没有真正的私有变量, 使用双下划线变量名会影响可读性以及让该变量相关的单元测试更难编写, 我们强烈不推荐使用这种形式, 更推 荐统一使用单下划线开头的形式

  • 将相互关联的类以及函数放在一个模块中, 不需要限制一个模块只能有一个类
  • 类的命名使用 CamelCase 命名(例如 CapWords), 但是类所在的文件名使用 snake_case(例如 cap_words.py)
  • 单元测试中的方法名可以使用下划线, 这样的模式 test_ 是允许的, 例如 testPop_EmptyStack

2.3.4. 变量名形式

名字 公开变量 内部变量
包 lower_with_under  
模块 lower_with_under _lower_with_under
类 CapWords _CapWords
异常 CapWords  
函数 lower_with_under() _lower_with_under()
全局常量/类常量 CAPS_WITH_UNDER _CAPS_WITH_UNDER
全局变量/类变量 lower_with_under _lower_with_under
对象变量 lower_with_under _lower_with_under (protected)
方法名 lower_with_under _lower_with_under (protected)
函数/方法 参数名 lower_with_under  
局部变量名 lower_with_under  

2.4. Main 方法

在编写可执行的 Python 脚本时, 必须使用 if __name__ == '__main__'

单元测试, 自动文档生成以及静态代码分析等工具都会 import 模块, 必须使用 __name__ 检查以防止该脚本被 import 时会被运行,这通常会造成严重的后果!

可执行脚本必须提供=main=方法作为入口.


def main():
...

if __name__ == "__main__":
main()

2.5. 注释

2.5.1. 解释为什么

一些非直观的代码需要进行注释, 注释应当从解释代码的意义, 和为什么这么实现的角度出发, 简单和不言自明的代码不需要进行注释.

bad

# 遍历 items 输出 i
for i in items:
print(str(i))

good

# true if and only if i is a power of 2
if i & (i-1) == 0:
...

2.5.2. 错误的注释比没有注释还糟糕

当代码发生变化时, 相关的注释也需要同步更新. 错误的注释往往比没有注释还具有误导性.

2.5.3. docstring

公开的类与函数应当使用 docstring 描述作用, 入参与返回值的作用.

2.5.4. inline comment

避免将代码与注释写在同一行, 在代码块之前进行注释

inline comment 是指在代码与注释在同一行, 注释在行尾.

if i & (i-1) == 0:  # true if i is a power of 2

不少编辑器以及风格检查器都会有行长限制检查, inline comment 容易使行长超过限制以及带来阅读上的障碍.

No

_referring_method_schema = getattr(referring_method, "_swagger_auto_schema", None)  # add swagger documentation of download_media_type
_swagger_auto_scema = swagger_auto_schema( # generate GET action swagger schema
...
)
if "post" in bind_to_methos: # generate POST action swagger schema
...

Yes

# add swagger documentation of download_media_type
_referring_method_schema = getattr(referring_method, "_swagger_auto_schema", None)

# generate GET action swagger schema
_swagger_auto_scema = swagger_auto_schema(
...
)

# generate POST action swagger schema
if "post" in bind_to_methos:
...

2.6. 类型注解

强烈推荐尽可能多地使用类型注解

Python 3 PEP484 加入 type hint 的特性为 Python 添加了一个类型系统. 该特性可以帮助开发者在开发阶段提前发现问题, 以及帮助大型项目使用 type checker 对代码进行分析.

Python 3 使用类型注解(type annotations)的方法来加入类型信息, Python 3 依旧是一门动态语言, 解析器在运行 时(runtime)会忽略所有的类型注解.

我们鼓励尽可能多地使用类型注释, 请熟悉 PEP484 的内容.

示例

def hello(name: str) -> None:
print(f"Hello, {name}")

类型注释规范请参考 Type Annotated Code , 考虑到目前采用 type hint 特性的项目还不多, 对于类型注释的规 范暂不作规定.

在使用类型注解的同时应该使用 type checker 工具来进行静态代码分析.

可供选择的工具有:

  • mypy from dropbox
  • pytype from google
  • pyright from microsoft
  • pyre-check from facebook

我们的选择是使用 mypy 1

使用 PyCharm 开发的团队可以使用 mypy plugin .

我们推荐在持续集成(CI)流程中加入对代码类型检查的步骤.

3. 语言规范

3.1. 导入

仅对包和模板使用导入

使用一致的方式来表明对象的来源, x.Obj 表示 Obj 对象定义在模块 x 中.

使用 import x 来导入包和模块.

使用 from x import y , 其中 x 是包前缀, y 是不带前缀的模块名.

使用 from x import y as z, 如果两个要导入的模块都叫做 y 或者 y 太长了.

例如, 模块 sound.effects.echo 可以用如下方式导入后, 使用 EchoFilter

from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)

不使用直接 import 模板内成员的做法. 如:

from sound.effects.echo import EchoFilter
...
EchoFilter(input, output, delay=0.7, atten=4)

3.1.1. 例外

在使用 typing 模块时, 不需要遵守这条规则

from typing import Any, List, Dict

3.2. 包

使用模块的全路径名来导入每个模块

使用绝对导入(absolute import), 不使用相对导入(relative import), 即使模块在相同的文件夹中.

避免包名冲突, 或者由于路径的问题导入错误的包. 更容易定位到模块.

使用相对导入依赖于文件之间的相对位置, 在大型项目中不同的包内也许有同名的模块, 这可能会在代码重构移动 文件时带来潜在的问题.

3.3. 类

类没有必要继承 object, 除非你需要考虑兼容 Python 2

Yes

class MyClass:
pass

NO

# only for pyton2 compatibility
class MyClass(object):
pass
在 __init__ 方法中定义所有的实例变量

Yes

class Square:
def __init__(self, side):
self._side = side
self.area = side ** 2

No

class Square:
def __init__(self, side):
self._side = side

def get_area(self):
# 在 __init__ 之外定义了实例变量 self.area
self.area = self._side ** 2
return self.area

3.4. 异常

谨慎地使用异常

3.4.1. 自定义异常

自定义异常类名以 Error 结尾, 继承 Exception

模块可以定义自己的异常类, 必须继承已经存在的异常类, 名字必须以 Error 结尾.

大多数情况下如果没有特殊的处理要求, 类的 body 简单地使用 pass 即可.

class MyError(Exception):
    pass

3.4.2. 异常抛出的形式

使用抛出异常实例的形式

Yes

raise MyError()

# or

raise MyError("Error Message")

No

raise MyError

3.4.3. 注意异常捕获的范围

不要使用 except: 捕获所有异常

No

try
...
except:
...

# or

try
...
except Exception as e:
...

除非:

  • 你会再次抛出异常
  • 你明确地知道你不会再次抛出异常, 你会在这里正确地处理这个异常. 比如在 thread 内记录下错误并阻止这个异常向上传递后引起 thread 退出

在异常这方面, Python 非常宽容, except: 会捕获包括 Python 语法错误在内的任何错误. 使用 except: 很 容易隐藏真正的 bug

3.4.4. 尽量减少 try except 语法块内代码的数量

try except 语法块内代码的数量越多, 在你预期之外的代码抛出异常的机会就会越大, 在这种情况下 try except 会将真正有可能出错的代码隐藏起来.

3.4.5. 使用 try except else finally 结构

使用完整的 try/except/else/finally 结构, 会让代码逻辑变得更清晰

try:
# 可能的异常抛出点
...
except MyError as e:
# 处理异常情况
...
else:
# 正常情况
...
finally:
# 无论正常或者异常情况下都需要运行的代码
# 比如说释放资源
...

3.4.6. assert

assert 只用于确保代码内部的正确性, 不能用于确保调用是正确的或者有异常发生

不要使用 assert 来进行参数校验, 合适的情况下使用 build-in 的异常.

assert 不能代替异常, 如果后继需要检查出错的情况, 使用异常而不是 assert.

而且 assert 语句在运行时可能会被关闭, 比如 python -O 解析器会忽略所有 assert 语句以提高性能.

Yes

def connect_to_next_port(self, minimum):
"""Connects to the next available port.

Args:
minimum: A port value greater or equal to 1024.

Returns:
The new minimum port.

Raises:
ConnectionError: If no available port is found.
"""

if minimum < 1024:
# Note that this raising of ValueError is not mentioned in the doc
# string's "Raises:" section because it is not appropriate to
# guarantee this specific behavioral reaction to API misuse.
raise ValueError('Minimum port must be at least 1024, not %d.' % (minimum,))
port = self._find_next_open_port(minimum)
if not port:
raise ConnectionError('Could not connect to service on %d or higher.' % (minimum,))
assert port >= minimum, 'Unexpected port %d when minimum was %d.' % (port, minimum)
return port

No

def connect_to_next_port(self, minimum):
"""Connects to the next available port.

Args:
minimum: A port value greater or equal to 1024.

Returns:
The new minimum port.
"""

assert minimum >= 1024, 'Minimum port must be at least 1024.'
port = self._find_next_open_port(minimum)
assert port is not None
return port

3.5. 全局变量

尽量避免使用全局变量

尽量避免使用全局变量, 全局变量的生命周期是永久的, 只有在程序退出时占用的内存才会被清除.

使用类变量来代替, 但也有一些例外情况.

  • 脚本的默认选项
  • 模块级的常量. 例如 PI = 3.14.159, 常量应用使用全大写, 使用下划线连接单词.
  • 有时候用全局变量来缓存值或者作为函数返回值很有用.
  • 如果有需要, 全局变量应该仅在模块内部使用, (命名使用下划线开头 _XXX), 外部模块通过该模块的公 共方法来访问全局变量.

3.6. 列表表达式 (list compreshensions)

适用于简单的情况, 禁止在列表表达式中使用多重 for 循环或者多重过滤

列表表达式以及生成器表达式(generator expressions)提供了一种简洁的方式来创建列表和生成器, 而不必使用 map filter 或者 lambda 2.

创建简单的过滤器表示式以及映射表达式, 使用列表表达式, 原则是表达式的内容不过度复杂.

复杂的逻辑请使用 for 循环.

Yes

evens = [str(x) for x in range(0, 100) if x % 2 == 0]

result = ((x, complicated_transform(x))
for x in some_function(paramter)
if x is not None)

No

evens = list(map(str, filter(lambda x: x% 2 == 0, range(0, 100))))

result = ((x, y, z) for x in range(10) for y in range(5) if x != y for z in range(5) if y != z)

3.7. 默认迭代器和操作符

如果类型支持, 就使用默认迭代器和操作符. 比如列表, 字典及文件等.

容器类型, 像字典和列表, 定义了默认的迭代器和关系测试操作符(in 和 not in).

如果可能的话使用默认的迭代器和操作符, 大多数 build-in 类型定义了迭代器方法.

需要注意的一点是, 这样去遍历容器的话, 不能在遍历时修改容器.

Yes

for key in adict: ...
if key not in adict: ...
for k, v in adict.items(): ...

if obj in alist: ...

for line in afile: ...

No

for key in adict.keys(): ...
if not adict.hash_key(key): ...

for line in afile.readlines(): ...

3.8. lambda 表达式

lambda 方法体为一行表达式的情况下可以使用

例: sorted(data, key=lambda x: x.key)

当方法体比一行表达式复杂应该使用=def=定义的函数, 通常可以将这些帮助函数定义在需要使用 lambda 的函数内, 作为嵌套函数.

示例

def sort_data(data):
def get_key(item):
# some logic to extract key from item
...

sorted(data, key=get_key)

标准库的=operator=有一些常用的函数, 优先考虑使用. 如可以用 operator.mul 来代替 lambda x, y: x * y.

3.9. 条件表达式

简单的情况下适用, 复杂情况或多重条件判断时使用完整的 if else 语句.

条件表达式类于 C/Java 里的三元操作符, 可以用来简化简单的 if 语句.

if cond:
x = 2
else:
x = 1
return x

# 简化为
return x = 2 if cond else 1

当 if 判断条件太复杂或者有多重判断的情况使用条件表达式会破坏可读性

No

bad_example = 1 if predicate1(value) else 2 if predicate2(value) else 3

3.10. 函数参数默认值

禁止使用可变对象作为函数参数的默认值

函数参数的默认值为可变对象,比 [], 函数如果修改了该变量, 会影响默认值的值.

我们视这种用法是一种会引起潜在问题的错误用法.

Yes

def foo(a, b=None):
if b is None:
b = []

No

def foo(a, b=[]):
...

示例:

def foo(a=[]):
a.append(1)
print(a)

foo() # 输出 1
foo() # 输出 1 1
foo() # 输出 1 1 1

3.11. True/False 求值

在可能的情况下使用隐式的 False 值

在 Python 语言中以下的值在被作为布尔值使用时候, 会被认为是 False.

内建类型的"空值":

0, None, [], {}, ''

Python 的惯用法是使用这些隐式的 False 值, 例如 if foo: 而不是 if foo != []:.

优点是可读性强, 速度更快, 大多数情况下都不会引起问题, 但是需要注意以下几点:

  • '0' 字符串 0 是被当作 True 对待.
  • 永远不要直接比较布尔值, 如 if value == False:, 使用 if not value:. 如果需要区分 False 与 None 的情况, 使用类似这样的语句 if not x and x is not None:.
  • 检查 None 时必须使用 if foo is None: 或者 is not None. 当检查某个值是否为 None 时如果使用 if not foo: 不要忘记 foo 如果为其它的"空值"也会通过检查.
  • 判断 seqences 类型(strings, lists, tuples)是否为空, 使用 if seq: or if not seq, 而不是 if len(seq): or if not len(seq):.
  • 数字 0 会被当作 False, 在判断是否为数字=0=的时候, 需要直接比较数字 0: if foo == 0:

3.12. 格式化字符串

在 Python 3 中推荐使用 f-string, 不使用老式的 % 以及 str.format 进行格式化.

bad

name = "World!"
print("Hello, %s" % name)
print("Hello, {}".format(name))

better

name = "World!"
print(f"Hello, {name}")

f-string tutorial

3.12.1. 例外

在使用标准库 logging 进行日志输出时, 仍然推荐使用 %s 的格式化风格,而不是 f-string.

logger = logging.getLoggerName(__name__)
name = "World!"
logger.debug("Hello, %s", name)

# instead of
# logger.debug("Hello, {name}")

这是因为包含 %s 的字符串和变量是作为参数传递给 logger 的方法,logging 类库对于 %s 有特殊的优化, 以及对于变量的字符串化会推迟到最后 3.

而直接使用 f-string 作为第一个变量传递给 logger 的方法, 相当于先将变量字符串化然后再传递给 logger 的方法.

4. 编程原则与最佳实践

4.1. 使用 pylint

使用 pylint

pylint 官网

pylint 是一个在 Python 源代码中查找 bug 的工具. 对于 C 和 C++ 这样的强类型静态语言来说, 这些 bug 通常由编译器来捕获. 由于 Python 的动态特性, 有些警告可能不对. 不过虚报的情况应该比较少.

确保对你的代码运行 pylint. 在 CI 流程中加入 pylint 检查的步骤.

抑制不准确的警告, 以便其他正确的警告可以暴露出来。

使用 PyCharm 开发的团队可以使用pylint plugin .

Pylint 的介绍与入门

4.2. 自底向上编程

自底向上编程(bottom up): 从最底层,依赖最少的地方开始设计结构及编写代码, 再编写调用这些代码的逻辑, 自底向上构造程序.

  • 采取自底向上的设计方式会让代码更少以及开发过程更加敏捷.
  • 自底向上的设计更容易产生符合单一责任原则(SRP) 的代码.
  • 组件之间的调用关系清晰, 组件更易复用, 更易编写单元测试案例.

举一个简单的例子: 现需要编写调用外部系统 API 获取数据来完成业务逻辑的代码.

应该先编写一个独立的模块将调用外部系统 API 获取数据的接口封装在一些函数中, 然后再编写如何调用这些函数 来完成业务逻辑. 而不是先写业务逻辑, 然后在需要调用外部 API 时再去实现相关代码, 这会产生调用 API 的代码直 接耦合在业务逻辑中的代码.

4.3. 防御式编程

使用 assert 语句确保程序处于的正确状态

不要过度使用 assert, assert 应该只用于确保核心的部分.

注意 assert 不能代替运行时的异常, 不要忘记 assert 语句可能会被解析器忽略.

assert 语句通常可用于以下场景: - 确保公共类或者函数被正确地调用

例如一个公共函数可以处理 list 或 dict 类型参数, 在函数开头使用 `assert isinstance(param, (list, dict))`
确保函数接受的参数是 list 或 dict
  • assert 用于确保不变量. 防止需求改变时引起代码行为的改变
if target == x:
run_x_code()
elif target == y:
run_y_code()
else:
run_z_code()

假设该代码上线时是正确的, target 只会是 x, y, z 三种情况, 但是稍后如果需求改变了, target 允许 w 的 情况出现. 当 target 为 w 时该代码就会错误地调用 run_z_code, 这通常会引起糟糕的后果.

我们可以使用 assert 来确保不变量

assert target in (x, y, z)
if target == x:
run_x_code()
elif target == y:
run_y_code()
else:
assert target == z
run_z_code()

不要使用 assert 的场景:

  • 不要使用 assert 在校验用户输入的数据, 需要校验的情况下应该抛出异常
  • 不要将 assert 用于允许正常失败的情况, 将 assert 用于检查不允许失败的情况.
  • 用户不应该直接看到 AssertionError, 如果用户可以看到, 将这种情况视为一个 BUG

4.4. 避免使用 magic number

赋予特殊的常量一个名字, 避免重复地直接使用它们的字面值. 合适的时候使用枚举值 Enum.

使用常量在重构时只需要修改一个地方, 如果直接使用字面值在重构时将修改所有使用到的地方.

No

def get_potential_energy(mass, height):
return mass * height * 9.81

# Django ORM
Config.objects.filter(enabled=1)

Yes

GRAVITATIONAL_CONSTANT = 9.81

def get_potential_energy(mass, height):
return mass * height * GRAVITATIONAL_CONSTANT

class ConfigStatus:
ENABLED = 1
DISABLED = 0

Config.objects.filter(enabled=ConfigStatus.ENABLED)

4.5. 处理字典 key 不存在时的默认值

使用 dict.setdefault 或者 defaultdict

例子

# group words by frequency
words = [(1, 'apple'), (2, 'banana'), (1, 'cat')]
frequency = {}

No

for freq, word in words:
if freq not in frequency:
frequency[freq] = []
frequency[freq].append(word)

Yes

for freq, word in words:
frequency.setdefault(freq, []).append(word)

或者使用 defaultdict

from collections import defaultdict

frequency = defaultdict(list)

for freq, word in words:
frequency[freq].append(word)

5. References

  • Google Python Style Guide
  • When to Use Assert
  • Bottom up programming
  • Refactoring

Footnotes:

1

mypy 的开发直接影响了 PEP484 的诞生, 而且有非常成功的使用案例, 例如 dropbox 如何对 4 百万行 Python 代码进行类型检查

2

注意在 Python 3 中 map filter 返回的是生成器而不是列表, 在隋性计算方面有所区别.

3

logging optimization

Generated using Emacs 29.0.50 (Org mode 9.4.6)

Python 动态创建模块

发表于 2019-02-23 | 更新于 2019-10-20 | 评论数:
本文字数: 980 | 阅读时长 ≈ 1 分钟
  • 1. 使用 types.ModuleType
  • 2. import 模块
    1. 使用 types.ModuleType
    2. import 模块

1. 使用 types.ModuleType

几乎 Python 中所有的东西都是对象, Python 标准库 types 提供了 build-in 对象的类型. 我们需要 types.ModuleType 来动态创建 module.

import types

hello = types.ModuleType("hello")

现在我们创建了一个空白的模块, 现在我们可以创建一个函数并将函数加入到 hello 模块中.

def say_hello():
    print("hello")

setattr(hello, "say_hello", say_hello)

现在可以通过 hello.say_hello 来调用 say_hello

hello.say_hello()

2. import 模块

以上动态创建的模块 hello, 并不能通过 import hello 的方式使用.

import hello
ModuleNotFoundErrorTraceback (most recent call last)
in
----> 1 import hello

ModuleNotFoundError: No module named 'hello'

Python 的 import 机制很简明, sys.modules 在其中是关键点.

当我们使用 import hello 的时候, Python 解析器会在 sys.modules 字典中查找 key 为 'hello' 的值. 如果 key 存在, 返回已经被初始化的模块.

如果 key 不存在的话, Python 解析器会遍历 PYTHONPATH 中的文件系统, 寻找名字为 hello 的文件或者模块, 如果找到的话初始化 hello 并注册到 sys.modules 中, 否则抛出 ImportError 异常.

Generated using Emacs 29.0.50 (Org mode 9.4.6)

12…5
Randall Wang

Randall Wang

Just another coder . Heavy Emacs user

22 日志
16 标签
RSS
GitHub
0%
© 2022 Randall Wang
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Gemini v7.0.0