因为自己偶尔才会用用 Python 写写脚本,但是每次想写的时候就要查半天的语法...所以在这里记录一下 Python 基础的快速入门的一些东西。
python -m venv <project_name>/.venv source <project_name>/.venv/bin/activateshell
如果你去官网看的话这里是直接将虚拟环境创建在了项目目录中,也就是直接调用了
python -m venv <project_name>
,这样也可以, 但是会导致项目根目录会多出很多虚拟环境的配置文件,并且这些文件都是不会进版本控制的,所以在这里创建一个子文件夹.venv
来管理会更好!
运行完后命令左边会出现<env_name>
:
(my_env) [root@192.168.0.1 my_env]#shell
在这种状况下,所有 Python 环境均与外界隔离,包括 pip
的版本。
使用下面的指令退出虚拟环境:
deactivateshell
由于 .venv
目录一般不会进入版本控制系统,所以如果想要想生产,则需要所有需要的依赖。
使用下面的指令导出/导入依赖:
# 导出
pip freeze >requirements.txt
# 导入
pip install -r requirements.txt
bash
更新 pip
:
python3 -m pip install --upgrade pippython
def main():
print("hello")
if __name__ == "__main__":
main()
python
from typing import Mapping
def hello(val: str, map: Mapping[str, str]) -> str:
pass
python
from enum import Enum
class Server(Enum):
SERVER_1 = ('192.168.0.1', 'root', '123456')
SERVER_2 = ('192.168.0.2', 'root', '123456')
def __init__(self, host: str, user: str, password: str):
self.host = host
self.user = user
self.password = password
printf(Server.SERVER_1.host)
python
from typing import Protocol
from typing import List
# 任何拥有 close 方法的实例都可以被推进去
closeable_list: List[Closeable] = []
class Closeable(Protocol):
def close(self) -> None:
pass
python
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('192.168.0.1', username='root', password='123456')
# 这里需要看情况决定是否执行 source /etc/profile,这里不会主动加载环境变量,在外面传 env 参数也没用...
_, stdout, _ = ssh.exec_command('source /etc/profile; env')
print(''.join(stdout.readlines()))
python
使用前要给用户生成一个 token。
from jenkinsapi.jenkins import Jenkins
JOB_NAME = 'xxx'
SERVER = 'http://192.168.0.1:8080'
jenkins = Jenkins('http://192.168.0.1:8080', 'user', 'token')
params = {
'Branch': 'master'
}
jenkins.build_job(JOB_NAME, params)
job = jenkins[JOB_NAME]
qi = job.invoke(build_params=params)
if qi.is_queued() or qi.is_running():
print('等待任务构建完成...')
qi.block_until_complete()
build = qi.get_build()
if not build.is_good():
raise RuntimeError(f'Build failed, check {server}/job/{JOB_NAME}/{build.buildno}/pipeline-graph/ for more details.')
python
import zipfile
import os
import sys
def zip_dir(directory_path: str, output_path=None):
# Get the base name of the directory to include in the zip
base_name = os.path.basename(directory_path)
# Create a zip file at the specified output path
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
# Walk through the directory and add each file or directory to the zip file
for root, dirs, files in os.walk(directory_path):
# Create an archive name with the top-level directory included
arcname_root = os.path.join(base_name, os.path.relpath(root, start=directory_path))
# Add directory entries
if not files and not dirs:
# Handle the case of empty directories
zipf.write(root, arcname=arcname_root + '/')
for file in files:
# Get the full path of the file
full_path = os.path.join(root, file)
# Create the relative path for the file in the zip
arcname = os.path.join(arcname_root, file)
zipf.write(full_path, arcname)
python
def input_bool(text: str, default: bool = False):
tip: str
exp: str
if default:
tip = '(Y/n)'
else:
tip = '(y/N)'
out = input(f'{text} {tip}')
if out == '':
return default
elif out == 'Y' or out == 'y':
return True
elif out == 'N' or out == 'n':
return False
else:
return input_bool(text, default)
python
因为踩了很多坑,所以记录一下这个项目是怎么搭的。
最后用了这些东西:
因为是nodejs项目,所以这边我还研究了很久怎么在 ts 文件上打断点进行debug,在开发环境下是不会用到 webpack 的。
nodejs版本:18。
先npm init
创建一个package.json
文件,然后安装所需的依赖:
yarn add eslint @typescript-eslint/parser --dev yarn add typescript ts-loader ts-node tsconfig-paths --dev yarn add webpack webpack-cli source-map-support --devshell
之后在package.json
里添加"type": "module"
的属性,这样就可以直接在项目里直接使用ESModules了,这个东西在webpack里天生支持按需导入,不需要额外配置(要了解更多的话可以去搜Tree Sharking
)。
这步比较简单,就直接过了,基本没有什么坑。
// .eslintrc.cjs
module.exports = {
// 下面这行必须加
parser: '@typescript-eslint/parser',
rules: {
// 这些是我常用的一些规则
quotes: ["error", 'single'],
'key-spacing': ["error", { "beforeColon": false }],
semi: [2, 'never'],
'block-spacing': 'error',
'object-curly-spacing': ["error", "always"],
indent: ['error', 2]
},
// 这里也要加,不然用import会报错
parserOptions: {
"ecmaVersion": 7,
"sourceType": "module"
}
};
javascript
这里的配置都是可以直接用的,碰到的坑在后面说。
// tsconfig.json
{
"include": ["src/**/*.ts"],
"compilerOptions": {
"module": "esnext",
"lib": ["ES2022"],
"isolatedModules": true,
"esModuleInterop": true,
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"target": "ESNext",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"sourceMap": true,
"outDir": "dist/dev",
"paths": {
// 记住这个别名,待会要考
"~/*": ["./src/*"]
}
},
"ts-node": {
"experimentalSpecifierResolution": "node",
"esm": true,
"transpileOnly": true
}
}
json
这里碰到的第一个坑,就是后缀问题。
这个问题只有在 nodejs + esm 才会有,什么意思呢,来看下面的代码:
// ------------------------------------------
// util.ts
export default "hello world"
// ------------------------------------------
// index.ts
import util from './util'
console.log(util)
javascript
看上去没有什么问题,然后我们用ts-node执行一下:
throw new ERR_MODULE_NOT_FOUND( ^ CustomError: Cannot find module 'xx\src\util' imported from xx\src\index.tsshell
如果这个时候你去网上搜,你基本碰到的回答都是让你加上js
后缀:
// index.ts
import util from './util.js'
console.log(util)
javascript
首先不说这个丑的一批,而且我后面还发现这玩意还会导致另外一个bug:在用webpack打包的时候,如果你加了js
后缀,webpack会直接提醒你找不到xx/src/util.js
,坑爹呢这不是!
所以肯定是不能加后缀的,然后我也是在网上翻了好久,才找到这个参数:experimentalSpecifierResolution
,虽然前面带了个experimental
,但其实已经很稳定了,直接在tsconfig.json
中添加配置:
{
// ...
"ts-node": {
// 把值改为node
"experimentalSpecifierResolution": "node",
// 这个忘了当时为啥要加了,不加好像也不会报错
"esm": true
}
}
json
或者使用命令行参数:--experimental-specifier-resolution=node
。
加完之后,不带文件后缀也可以成功运行,webpack打包也不会有任何影响。
可以看到我开头提供的tsconfig.json
里面有个这样的配置:
{
"paths": {
// 记住这个别名,待会要考
"~/*": ["./src/*"]
}
}
json
例如我们有这样的目录结构:
src ├── util │ └── StringUtils.ts └── index.tstext
我们在index.ts
里面导入StringUtils
就可以这样写:
import StringUtils from '~/util/StringUtils'
typescript
这个功能其实可有可无,但是我就是有强迫症,就是不想用相对路径!
首先啥都不加,直接ts-node运行,居然还报错了:
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base)); ^ CustomError: Cannot find package '~' imported from 'xxx/src/index.ts'shell
好好好,这都能报错,去查了一下,才知道这玩意是给webpack
那些玩意提供声明的:
// webpack.config.cjs
module.exports = {
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'~': path.resolve(__dirname, 'src')
}
},
}
javascript
在这里,你只写webpack的别名配置,在 ts 里是会报错的,因为ts才不会管你webpack的配置,所以才需要我们的tsconfig.json
来提供一个声明。
行,报错了我就去搜,几下就搜到了,不就是加个tsconfig-paths
吗,加上命令行参数:-r tsconfig-paths/register
,开跑!
结果万万没想到,又爆了相同的错。。。。
然后又翻了很久的Github(真的很久),终于被我找到了:ESM loader that handles TypeScript path mapping。
复制里面的loader.js
,然后修改启动命令为:node --loader ./scripts/loader.js src/index.ts
,就可以正常使用别名了。
因为我们是nodejs项目,肯定是不能少了打断点调试的。
我们可以直接用tsc编译项目为js代码后,直接用Webstorm进行Debug。
因为Webstorm运行js文件基本不需要配置,直接右键点几下就跑起来了。
但是这样很傻批,我们还要分两步进行,而且 ts 文件的变动可能会导致我们在 js 文件上打的断点消失。
谢天谢地,Webstorm是真的很聪明(牛逼),我们只需要简单配置几下就可以直接在 ts 上打断点运行了。
配完后,直接在ts文件上断点就可以停住。
至于为什么要用 Webpack,是因为我最后不想带着node_modules
这个累赘来上生产,最后直接打包成一个文件多爽,直接node xxx.js
就跑起来了。
这里是我最后用到的配置:
// webpack.config.cjs
const path = require('path');
const webpack = require('webpack')
module.exports = function (env, args) {
return {
entry: './src/index.js',
target: 'node',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'~': path.resolve(__dirname, 'src')
}
},
output: {
filename: (pathData) => {
return pathData.chunk.name === 'main' ? 'main.cjs' : 'libs.cjs';
},
},
plugins: [
new webpack.SourceMapDevToolPlugin({
exclude: ['libs.cjs']
})
],
optimization: {
splitChunks: {
chunks: 'all'
},
},
};
}
javascript
注意文件后缀是cjs,不然用module.exports
会报错。
打包时直接用webpack --mode=production
就可以了。
在Webpack打包后,我们的所有代码都被压缩到一行了,而且变量名都变得六亲不认了,想象一下,假如运行过程中报一个错,你能定位到问题发生在哪吗。。。
所以这个时候我们需要用到 sourcemap 来对我们的代码进行索引。
直接使用 sourcemap 文件是不行的,因为这玩意是给浏览器用到,我们需要导入依赖 source-map-support 来加载sourcemap。
这里你可以把 sourcemap 分离成一个单独的文件,也可以让它内嵌到代码里面。
这里我推荐内嵌到代码里面,便于后面代码分发,没必要分出来。
在 Webpack 添加配置devtool: 'inline-source-map'
。
还没完,也要在tsconfig.json
里面添加"sourceMap": true
的配置,如果少了这一步,最终生成的 sourcemap 行数会对不上,因为这个时候 Webpack 只会对编译后的 js 文件来构建索引,而 ts 编译后的文件中,空行(一行什么内容都没有的)会被删除,因此导致行数对不上。
最后在代码入口添加加载的代码:
import sourceMapSupport from 'source-map-support'
sourceMapSupport.install()
typescript
如果你观察生成的文件,会发现生成 sourcemap 会导致文件变得非常大,基本会变大 5 ~ 6 倍左右。
如果你把 sourcemap 分成单独的文件,然后打开开一下,会发现 Webpack 也给 node_modules
里面的代码生成了 sourcemap!
作为一个强迫症患者,我是绝对不能忍受这种情况的!
我们肯定是想给自己的代码生成精准的 sourcemap,而第三方库,可以考虑不生成,或者只使用简单的 sourcemap。
翻了一下 Webpack 文档,发现有个 SourceMapDevToolPlugin 插件可以指定/排除为哪些模块生成 sourcemap。
试了一下,在exclude
属性里面不管怎么填,都无法忽略掉node_modules
。
在查了一下午的文档以及翻看了源码之后,终于知到怎么配了:
// webpack.config.cjs
module.exports = {
output: {
filename: (pathData) => {
return pathData.chunk.name === 'main' ? 'main.cjs' : 'libs.cjs';
},
},
plugins: [
new webpack.SourceMapDevToolPlugin({
exclude: ['libs.cjs']
})
],
optimization: {
splitChunks: {
chunks: 'all'
},
},
}
javascript
加上上面的配置,就可以做到把node_modules
里面的代码全部打到libs.cjs
中,而我们的业务代码全部打到main.cjs
中,同时配置我们的SourceMapDevToolPlugin
不为libs.cjs
生成 sourcemap。
之前导入模块我们为了省略后缀,在配置中添加了experimentalSpecifierResolution: node
参数,在node20上,这个参数仍然可用,但是已经有了更好的替代。
文档:Loaders
并且官方也给了一个样例来代替上面的启动参数:commonjs-extension-resolution-loader。
这里直接摆上我用的代码:
// extension-loader.js
/**
* 处理ts-node导入时必须加后缀
*/
// https://github.com/nodejs/loaders-test/blob/main/commonjs-extension-resolution-loader/loader.js
import { isBuiltin } from 'node:module'
import { dirname } from 'node:path'
import { cwd } from 'node:process'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { promisify } from 'node:util'
import resolveCallback from 'resolve/async.js'
const resolveAsync = promisify(resolveCallback)
const baseURL = pathToFileURL(cwd() + '/').href
export async function resolve(specifier, context, next) {
const { parentURL = baseURL } = context
if (isBuiltin(specifier)) {
return next(specifier, context)
}
// `resolveAsync` works with paths, not URLs
if (specifier.startsWith('file://')) {
specifier = fileURLToPath(specifier)
}
const parentPath = fileURLToPath(parentURL)
let url
try {
const resolution = await resolveAsync(specifier, {
basedir: dirname(parentPath),
// For whatever reason, --experimental-specifier-resolution=node doesn't search for .mjs extensions
// but it does search for index.mjs files within directories
extensions: ['.js', '.json', '.node', '.mjs', '.ts'],
})
url = pathToFileURL(resolution).href
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
// Match Node's error code
error.code = 'ERR_MODULE_NOT_FOUND'
}
throw error
}
return next(url, context)
}
javascript
// path-loader.js
/**
* 处理ts路径别名报错
*/
import { resolve as resolveTs } from 'ts-node/esm'
import * as tsConfigPaths from 'tsconfig-paths'
import { pathToFileURL } from 'url'
const { absoluteBaseUrl, paths } = tsConfigPaths.loadConfig()
const matchPath = tsConfigPaths.createMatchPath(absoluteBaseUrl, paths)
export async function resolve (specifier, ctx, defaultResolve) {
const match = matchPath(specifier)
let realPath
if (match) {
realPath = pathToFileURL(`${match}`).href
} else {
realPath = specifier
}
const r = await defaultResolve(realPath, ctx)
return resolveTs(r.url, ctx, defaultResolve)
}
export { load, transformSource } from 'ts-node/esm'
javascript
// register-hooks.js
import { register } from 'node:module'
register('./extension-loader.js', import.meta.url)
register('./path-loader.js', import.meta.url)
javascript
然后把我们的启动命令换成:node --import register-hooks.js src/index.ts
。
移除掉tsconfig.json
里的experimentalSpecifierResolution
,然后就可以正常启动了。
最近突然碰到有关用户授权的问题了,发现自己真的是一点都不会,所有写篇博客记一下常见指令以及碰到过的坑。
useradd [username]
passwd [username]
userdel [username]
usermod -aG [groupname] [username]
groupadd [groupname]
chown用于修改文件/文件夹的所属权以及其所属的用户组。
chown [username] [directory/file]
# 递归授权
chown -R [username] [directory]
shell
chown :[usergroup] [directory/file]
# 递归授权
chown -R :[usergroup] [directory]
shell
chown [username]:[usergroup] [directory/file]
# 递归授权
chown -R [username]:[usergroup] [directory]
shell
chmod用于细化对所有者,用户组,以及其它用户的相关权限。
一共用三种类型的用户,它们的名字以及符号为:
例如使用ll
输出的信息:
drwx--x--x 2 username groupname83 11月 13 15:28 foldershell
第一部分开头的字符表示的是文件的类型:
后面连续9个字符,每3个为一组,分别表示用户所有者、用户组、其它用户的权限。 例如上面的例子:
例如要给用户组(u)添加读和写的权限:
chmod g+rw [directory/file] chmod -R g+rw [directory]shell
移除权限:
chmod g-rw [directory/file]shell
很简单,也就是中间有个加减符号,其实还有个等号,这个就类似于替换了,会覆盖掉之前的权限。
另外,给所有用户授权用a就行了:
chmod a+rw [directory/file]shell
其实也可以用数字替代rwx,但是这样有点不便于记忆。。
提问:是不是只要某个用户有一个文件夹的读权限,就能打开文件夹了?
大部分可能都会是这样认为的,这也确实比较符合我们的认知,都能读了,还不能打开文件夹?
那么实际呢?我们来试一下。
我们用测试用户(testuser)来进行测试:
[testuser@localhost opt]# ll drwx---r-- 2 root root 36 11月 13 16:44 backupshell
这里我们可以看到,testuser拥有backup文件夹的读权限,尝试进入一下:
[testuser@localhost opt]$ cd backup bash: cd: backup: 权限不够shell
发现权限是不够的...
这里也不卖关子了,这里其实是需要执行权限才能进入文件夹,这里也是比较容易忽略的一点。
提问:是不是某个用户只要有一个文件的可执行权限,就可以直接执行文件了?
这里就不卖关子了,答案是不一定。
同样这也是一个非常容易被忽略的问题,以为只要给了可执行权限就能执行了。
来演示一下反例:
drwx------ 2 bim root 36 11月 13 16:44 backupshell
这里我们的backup文件夹里面放有可执行文件,但是这个文件夹没有对外授权。
[root@localhost backup]# ll 总用量 23796 -rwx-----x 1 bim root 12691576 11月 13 16:44 mysql [root@localhost backup]# pwd /opt/backupshell
在里面的可执行文件mysql,对其它人拥有可执行权限,我们来切换用户试一下:
[root@localhost backup]# su testuser [testuser@localhost backup]$ /opt/backup/mysql bash: /opt/backup/mysql: 权限不够shell
可以发现权限不够,即使你拥有可执行权限。
解决方法在前面也说了,只需要给可执行文件的父目录,也就是这里的backup目录授权可执行就行了,这里就不多演示了。
写过ReactNative的都知道,ReactNavigation库里面有一个非常牛逼的类型声明,它可以根据你传入的参数,来判断是否需要第二个参数。
例如下面的定义:
interface RouteDef {
home: undefined
shop: {
time: number
}
}
typescript
那么它的路由方法就变得很牛逼了:
// 实际的泛型肯定不是这么传的,因为我写的时候没用RN了,所有忘了咋用的了
const nav = useNavigation<RouteDef>()
// 正确
nav('home')
// 错误: ts提示这里只需要一个参数
nav('home', undefined)
// 错误,ts提示这里需要两个参数
nav('shop')
// 正确
nav('shop', { time: Date.now() })
// 错误: 第二个参数需要{ time: number }类型,而提供的是number
nav('shop', Date.now())
typescript
很牛逼啊有没有,连参数的数量都给你变了!而且还能根据属性的定义来决定参数。
那么这玩意具体是咋写的呢?我仔细研究了一下,研究完后,仿佛开启了新大门!
我这里直接写一个Demo来看:
type EmitFuncArgs<Events, Key extends keyof Events> = void extends Events[Key]
? [evt: Key]
: [evt: Key, data: Events[Key]]
type EmitFunc<Events> = <T extends keyof Events> (...args: EmitFuncArgs<Events, T>) => string
export interface AppEvents {
ON_LOGOUT: string
ON_LOGIN: void
}
typescript
先来看下面的EmitFunc
定义,首先这玩意的泛型接收一个Events类型(直接把它看成下面的AppEvents接口就行),然后它的参数是EmitFuncArgs
决定的。
来看EmitFuncArgs
,可以发现这玩意居然返回了一个数组,并且还用上了三目运算符。
到了这里,大伙都应该可以理解是怎么玩的了,就是首先判断值是不是void,如果是,则返回一个长度为1的数组,反之则返回长度为2的数组,其中的第二个 参数为接口定义的类型。