硬件 | 规格 |
---|---|
尺寸 | 46.8 mm x 112.6 mm x 119.4 mm (1.84” x 4.43” x 4.7”) |
中央处理器 | Intel® Core™ 四核 i5-8250U 3.4GHz |
内存 | 内建2组SO-DIMM DDR4插槽 2400MHz 最高支持 64GB |
有线网络 | 内建千兆网卡 (Intel i219V) |
无线网卡 | 可扩展Intel® Dual Band Wireless-AC 3168 |
图形处理器 | Intel® UHD Graphics 620 |
音频 | Realtek ALC255 |
HDMI视频输出 | HDMI2.0a ws HDCP 2.2 4096 x 2304 @ 60Hz |
MiniDP视频输出 | DP1.2a ws HDCP 2.2 4096 x 2304 @ 60Hz |
扩展槽 | 内建1组M.2 SSD (2280) 插槽 PCIe X4 /SATA 支持Intel傲腾内存 1x PCIe M.2 NGFF 2230 A-E key slot 支持 WiFi+BT card |
存储 | 支持 2.5 英寸 HDD/SSD, 7.0/9.5 mm (6 Gbps SATA3) |
OpenCore配置文件我放到github了 https://github.com/ruanimal/GB-BRi5H-8250-hackintosh
igfxonln=1
应该可以解决)igfxonln=1
应该可以解决)安装过程大部分参考dortania,这里采用的是双系统安装方法。
先安装Windows10, 然后在Windows10下配置OpenCore,制作macOS安装镜像。
预先下载的工具软件
其他
前置知识
具体步骤参考这个,gibMacOS已被官方教程废弃1. 使用gibMacOS的gibMacOS.bat,下载最新macOS2. 使用gibMacOS的MakeInstall.bat,制作安装U盘
添加必要的Kexts驱动到U盘EFI/OC/Kexts文件夹,不同的机型需要的有所区别
添加必要的Kexts驱动到U盘EFI/OC/ACPI文件夹,不同的机型需要的有所区别
从opencore的文档可以看出,我们至少需要以下几个SSDT
为了更完美,我们还需要以下SSDT,这些可以等macOS安装完之后,根据情况再考虑是否制作
具体SSDT的制作过程比较繁琐,详细过程参考文档
注意:配置文件中kext的顺序是有影响的,建议前两个是lilu和virtualsmc
如果bios没有相关设置项,可以跳过
DisableIoMapper
to YES)注意:这时我们先不要替换无线网卡为DW1820A,可能会导致系统安装失败或者卡死。
如果只使用免驱的独立显卡,可以跳过这一步
前面配置macOS安装U盘时,其实已经对显卡做了简单驱动,如果一切功能正常,也可以跳过这一步。
显卡驱动不正常会影响双屏显示,睡眠假死,睡眠变重启,HDMI音频输出等
内置显卡的驱动,注意做的是以下几个点
AAPL,ig-platform-id
,必要时仿冒设备iddevice-id
这一步使用hackintool的应用补丁功能可以完成,详细步骤参考这个
注意:hackintool在配置过程中,有时改动会被重置,应用补丁时注意二次确认
声卡驱动大部分工作由AppleALC自动完成,我们需要的是选择正确的layout-id.
建议先去github的release日志上查看,是否在某次release添加了你主板的layout-id。如果没有相关记录,那就只能尝试wiki中对应声卡型号的所有可能的layout-id
详细步骤参考这个
USB定制直接影响睡眠是否正常
使用hackintool完成USB定制,主要过程
详细步骤参考这个
无线网卡驱动和系统序列号影响iMessage, sidecar等Apple服务使用。
在config.plist中DeviceProperties->Add
配置项下加入设备信息, 注意替换PCI设备地址
<key>PciRoot(0x0)/Pci(0x1C,0x5)/Pci(0x0,0x0)</key> |
加入Airport驱动AirportBrcmFixup.kext, 加入蓝牙驱动
brcmfx-country=#a
这一步是为了启动苹果内置电源管理,影响睡眠
详细步骤参考这个
使CPU的变频挡位更多,台式机不是特别必要。
可以使用CPU-S
软件来查看变频状态
详细步骤参考这个
如果上面的SSDT和驱动都做好了,睡眠的问题基本不大了,可能要设置下系统参数,禁止休眠到硬盘。
详细步骤参考这个
到此黑苹果配置基本完成了,我们可以去除boot-args
中的-v
等debug启动参数,添加相关声音和图片资源,配置config.plist启动OpenCore图形界面。(在开机中启用声音,个人觉得不太不要)
详细步骤参考这个
上面的OpenCore的各种配置都是在U盘上完成的,当配置完成没啥问题之后,我们就可以把OpenCore的文件迁移到硬盘了。
这样就不需要每次都从U盘启动了,并且开启会默认进入macOS
\EFI\BOOT\BOOTx64.efi
文件到启动条目,并上移到第一位Getting Things Done(GTD),是一种时间管理的方法,确切地说是事项管理的方法,通过GTD可以将所有事项管理起来,提高效率的同事,降低心智损耗,减少个人焦虑
焦虑是由于缺乏控制力,组织管理,以及缺少准备和行动不足造成的。
目的:
价值观促使我们首先在清单上积累了大量任务
静水对石子的反应:依物体的质量和力度作出相应的反应,既不过激也不会置之不理
阮云:物来顺应
学会对那些承诺的事情加以控制
收集所有那些“经常唤醒你模糊记忆”的事情,然后着手计划如何一一解决掉
在大脑中不留任何事情
自下而上的工作方法
“横向”管理涉及的所有行动
通常你大脑中盘踞问题的数量与其解决效率成反比
横向管理:收集(工作篮) - 加工 - 组织管理 - 检视(至少每周一次) - 执行
确定某一时刻行动的“四标准法”
总体检视工作的“六极四顾法”
git是一个分散式版本控制软件,最初由林纳斯·托瓦兹(Linus Torvalds)創作,於2005年以GPL釋出。最初目的是为更好地管理Linux内核开发而设计。
初始版本由Linus大神在两个星期内写出来,之后基本一统文件版本控制的天下。
工作区:就是你在电脑里能看到的目录。
暂存区:英文叫stage, 或index。一般存放在”git目录”下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
版本库:工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
branch:分支,相当于不同的平行世界
remote:远程仓库
origin:默认的远程仓库名词
HEAD:一个指向当前版本号的指针
git config --global user.name yourname
设置用户名,如果不加--global
则只对当前项目生效git config --global user.email youremail
设置邮箱git config --global core.editor vim
设置默认编辑器为vim,可替换为你自己喜欢的git config --global core.quotepath false
显示中文路径名
git init
初始化git仓库git clone <repository> [<directory>]
复制项目
git status
查看当前仓库状态,绿色为未commit的stage内容,红色为未stage、为添加到版本库内容git show <commit-hash-id|tag_name>
查看某次commit的修改内容git add filename
保存文件修改到暂存区git rm filename
删除工作区的文件和git记录git rm --cached filename
删除暂存区的新文件git diff
查看工作区和暂存区(stage)的不同git diff --cached
比较的是暂存区和版本库的差别git diff HEAD
可以查看工作区和版本库的差别git diff commit1 commit2
比较不同commitgit diff commit1 commit2 -- filepath
比较不同commit指定文件更改git commit
提交暂存区内容到当前分支,会新加一个commitgit commit --amend
追加暂存区内容到当前分支的最近一个commitgit rebase -i HEAD~4
合并最近4个commitgit rebase 目标分支名称
把本分支改动放到目标分支之后
git branch -a
查看所有分支信息git branch new_branch_name
新建分支git branch -d branch_name
删除分支git branch --set-upstream dev origin/dev
指定本地dev分支与远程origin/dev分支的链接git checkout branch_name
切换到分支头部git checkout tags/tag_name
切换到某个tag对应的版本git checkout -b new_branch_name
新建分支,并切换到新分支git checkout -b dev origin/dev
创建远程origin的dev分支到本地,并切换到新分支git merge dev
合并dev到当前分支,保留commit信息git merge --squash dev
合并dev分枝代码到暂存区,不保留commit信息git tag
查看tag列表git tag tag_name
打taggit tag -d tag_name
删除taggit push --delete origin tagname
删除远程tag
git remote -v
查看远程仓库详细信息git remote add <name> <url>
添加远程仓库git remote rename <old> <new>
重命名远程仓库git remote remove <name>
删除远程仓库git remote set-url [--push] <name> <newurl>
设置远程仓库链接git remote prune origin
清除本地无用remotegit pull
<远程主机名> <远程分支名>:<本地分支名> 从远程仓库更新git fetch git@github.com:ruanimal/Mysite.git
从其他仓库获取代码,但不更新到本地分支git push <远程主机名> <本地分支名>:<远程分支名>
提交到远程仓库git push origin <本地分支名>
将本地分支推送与之存在“追踪关系”的远程分支(通常两者同名)git push
如果当前分支只有一个追踪分支,那么主机名都可以省略。git push -f
强制提交
git submodule add -b branch {url} [module_name]
. 已有仓库,添加子模块git submodule set-branch --branch master module_name
子模块切换追踪分支到mastergit submodule update --remote
更新子模块代码, 分支和代码与.gitmodules中的配置同步git clone --recursive {url}
. clone代码时,拉取子模块代码git submodule update --init
克隆项目后, 首次拉取子模块代码git rm {submodule_folder}
删除子模块
git log
查看commit的历史git log -p
git log -p -2
查看最近2次的更新内容git log --graph
命令可以看到分支合并图。git whatchanged filename
显示文件更改的相关commitgit checkout [commit id] -- file
丢弃工作区文件修改git restore filename
丢弃工作区文件修改git reset HEAD filename
可以把暂存区的修改撤销掉(unstage),重新放回工作区git restore --staged filename
可以把暂存区的修改撤销掉(unstage),重新放回工作区git reset –mixed
此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和index信息git reset –soft
回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可git reset –hard
彻底回退到某个版本,本地的源码也会变为上一个版本的内容,若无版本号则回退到最新的版本。git reset [commit-id] -- filename
以commit的版本替换stage的文件git revert [commit-id]
是用一次新的commit来逆向操作之前的commit
git gc
压缩历史信息来节约磁盘和内存空间git <command> --abort
一般用于中断某次操作git cherry-pick <commit_id>
捡取某一个commit到当前分支(包含commit变更的内容和注释)git filter-branch --tree-filter 'rm -f testme.txt' HEAD
从git仓库中永久删除某个文件的历史git rebase -r <commit_id> --exec 'git commit --amend --no-edit --reset-author'
将HEAD到commit_id为止的commit的提交用户信息重置
MySQL-Python
$ wget https://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-1.2.5.zip |
若提示“EnvironmentError: mysql_config not found”
$ sudo apt-get install libmysqld-dev
DATABASES = { |
mysql -uroot -p[密码]
create database name
use name
mysql –uroot –p[密码] -Dtest< .sql
django1.6环境加south
python manage.py syncdb # 创建表 |
django1.8+
python manage.py makemigrations appname # 建立迁移脚本 |
由于在Python2中字符串有两种类型str
和unicode
,他们都是basestring
的子类。
str类型,即是ascii字符或者经过encode的unicode,一个字符占用1byte。ascii码是美国信息交换标准代码,主要用于显示现代英语和其他西欧语言,用一个字节储存一个字符,所以ascii字符最多只有256(2^8)个。
而unicode包含了256个ascii码之外还包含其他各个国家的文字的编码,所以unicode的一个字符占用2个字节,这样理论上一共最多可以表示2^16(即65536)个字符,远大于256。
utf-8是unicode的一中实现,是unicode的一种编码方式。而且utf-8的编码方式,包含了ascii码的内容,也就是utf-8兼容ascii。
打个比方,unicode是商品,utf-8就是打包好的一个个包裹(encode),打包是为了传输和储存的方便。而不同的编码之间不能直接互相转换,都需要转成unicode,也就是decode。
所以碰到一个str,你就得明白它是encode过的,你得调用相应的decode方法才不会乱码。
python2解释器的默认编码方式是ascii,如果我们给系统的输入是非ascii编码的字符,系统在尝试解码时就会出现UnicodeDecodeError
'2' > s = |
这种方法较为普遍import sys
reload(sys)
sys.setdefaultencoding('utf-8')
常用于文本文件操作import codecs
with codecs.open('somefile.txt', 'r', 'utf-8') as fp:
pass
此乃釜底抽薪,一劳永逸的办法。
]]>re是python中的正则表达式处理模块,本文是为了总结re模块的用法。
至于正则表达式的写法可以看正则表达式30分钟入门教程
re.complie(pattern, flags=0)
把正则编译为_sre.SRE_Pattern object
对象,在多次匹配的时候可提高运行效率。r'\w(o)') pattern = re.compile(
'doooo') pattern.match(
<_sre.SRE_Match object at 0x7f45fc3d9990>
'doooo').group(1) pattern.match(
'o'
r'\w(o)', 'doooo') re.match(
<_sre.SRE_Match object at 0x7f45ec0be0a8>
r'\w(o)', 'doooo').group(1) re.match(
'o'
re.search(pattern, string, flags=0)
寻找整个文本,直到发现一个匹配的位置,返回MatchObject,未找到返回None
re.match(pattern, string, flags=0)
检查文本的开头是否匹配,返回MatchObject,未找到返回None
re.split(pattern, string, maxsplit=0, flags=0)
通过正则表达式分割字符,返回list对象。若表达式里使用了组,组匹配到的内容会加到结果中。r'g(o)', 'Wo-rds, words, words.') re.split(
['Wo-rds, words, words.']
r'\wo', 'Wo-rds, words, words.') re.split(
['', '-rds, ', 'rds, ', 'rds.']
r'\w(o)', 'Wo-rds, words, words.') re.split(
['', 'o', '-rds, ', 'o', 'rds, ', 'o', 'rds.']
re.findall(pattern, string, flags=0)
查找所有匹配的对象,返回list。r'g(o)', 'Wo-rds, words, words.') re.findall(
[]
r'w(o)', 'Wo-rds, words, words.') re.findall(
['o', 'o']
r'w(o)(r)', 'Wo-rds, words, words.') re.findall(
[('o', 'r'), ('o', 'r')]
re.sub(pattern, repl, string, count=0, flags=0
替换,返回替换后的字符。
repl参数可以为一个函数,该函数接受MatchObject,返回值为替换的字符串。r'w(o)(r)',r'*', 'Wo-rds, words, words.') re.sub(
'Wo-rds, *ds, *ds.'
def repl(match):
return '***%s***|+++%s+++' % (match.group(1), match.group(2))
r'w(o)(r)', repl, 'Wo-rds, words, words.') re.sub(
'Wo-rds, ***o***|+++r+++ds, ***o***|+++r+++ds.'
flags是正则表达式匹配的一些选项
re.DEBUG
输出匹配过程中的调试信息re.IGNORECASE
忽略大小写re.LOCALE
本地化标志re.MULTILINE
多行模式,使^
和$
匹配每行的行首和行尾re.DOTALL
使.
匹配换行符re.UNICODE
使\w
等匹配unicode字符re.VERBOSE
使正则表达式使用""""""
能够跨多行,并且每行使用#
添加注释# -*- coding:utf-8 -*- |
输出未使用compile: 5.53200006485
通过re.match调用compile后的对象: 11.4990000725
使用compile: 1.66799998283
_sre.SRE_Pattern object
对象进行match操作,效率大概比re.mathc高几倍。re.match(pattern, string)
通过调用re._compile(pattern).match(string)
来实现的,这样相当于每次匹配都进行了两次compile。class ClassA(object): # 1 |
ClassA
类,它有一个类变量num1
,还有一个实例变量num2
。 a1.num1
的时候,实际上是引用类变量num1,因此a1.num1 is ClassA.num1
值为True
。 a1.num1 = 3
的时候,其实给a1绑定了一个属性num1,这是动态语言的特性,此时a1.num1 is ClassA.num1
值为False
。a1.num1
的时候,先是在a1自己的命名空间内查找num1,没找到就在所属类的命名空间找,还没有就抛出AttributeError: 'ClassA' object has no attribute 'num3'
.a1.num1 = 3
之后,a1自己的命名空间内找到了num1,就不继续往上查找了。a1.__class__.num1
来访问。以上这些,也适用于staticmethod
和classmethod
。
class FatherI(object): |
__mro__
或者super
。__mro__
属性显示了一个类的继承树,也就是记录了所有属性和方法的查找顺序,由于此处是新式类,mro为广度优先。__mro__
是一个元祖,所以我们可以用__mro__[1]
这种方式来访问继承树树上每个类的属性和方法。super(ClassA, a1).num1
这个语句的意思其实是,在__mro__
中访问<class 'ClassA'>
后一个类,也就是<class 'FatherI'>
的num1,所以此处值是11taskman |
上面的例子是一个Django项目,Python包的导入都是相对于主程序
来说的。
这个项目的主程序就是manage.py
,跟manage.py同级的文件夹(含有__init__.py
)如task
,man
就称之为包,若同级有文件就称之为顶级模块。
在这个项目里所有Python包的导入,都应该写完整路径, 或者使用相对当前文件的相对路径。
例如在文件taskman/task/views.py
中,想要导入taskman/man/setting.py
,就得这样。## 绝对路径 import
from man.setting import some_setting
# or
from man import setting
再例如, 在文件taskman/task/views.py
中,想要导入taskman/task/urls.py
,就得这样。## 绝对路径 import
from task.urls import some_setting
# 或者
from task import urls
## 相对路径import
from .urls import some_setting
from . import urls
ValueError: Attempted relative import in non-package
Version 1 (WSL1)
升级到Version 2 (WSL2)
之后,底层实现方式发生了改变。
由于使用Hyper-V来实现WSL2,使得WSL更像虚拟机,一个能访问本地硬盘的虚拟机。
这带来一些便利,能够把它当做独立服务器来使用,可玩性就增强很多。当然,这也导致WSL上的端口不能从外部访问到,总之有利有弊。
虽然能够配置端口转发,曲线救国突破这个缺陷,但是有些服务的端口是约定俗成的(比如samba),更换端口号(原端口号被windows占用)之后其他设备可能识别不到服务。
经过一番思考后,觉得给WSL2开启桥接模式,直接连接物理网络才是相对最好的方案。
参考官方文档安装WSL2
在PowerShell中执行以下命令
启用WSL2
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart |
启用虚拟机平台
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart |
启用Hyper-V
dism.exe /online /enable-feature /featurename:Microsoft-Hyper-V /all /norestart |
设置WSL2为默认
wsl --set-default-version 2 |
然后重启系统, 安装适用于 x64 计算机的 WSL2 Linux 内核更新包
这里选择Debian作为示例
Microsoft Store
中搜索Debian并安装在PowerShell执行wsl -l --all -v
确认WSL版本为2
PS C:\Users\ruan> wsl -l --all -v |
如果VERSION不为为2,在PowerShell执行wsl --set-version Debian 2
进行升级
由于WSL2底层使用的是Hyper-V虚拟机,所以我们可以修改虚拟交换机的类型,来启用桥接网络
打开Hyper-V管理器 -> 操作 -> 虚拟交换机管理器
, 修改WSL的连接类型为“外部网络”
由于WSL2默认网络模式是NAT,我们把虚拟交换机改为桥接后,默认的ip和路由以及DNS解析将会失效
以下操作在Debian系统内执行
ip addr flush dev eth0
ip addr add 192.168.123.31/24 dev eth0
ip route delete default
ip route add default via 192.168.123.1 dev eth0
这些操作在重启WSL2虚拟机后会失效,如果需要永久修改,请配置静态ip
新建/etc/wsl.conf
,防止WSL2覆盖DNS配置文件
[network] |
编辑/etc/resolv.conf
, 清除原配置,添加以下内容
nameserver 192.168.123.1 |
重启后桥接可能会失败,而且上不了网,可以取消勾选外网网口的“Hyper-V可扩展的虚拟交换机”选项,然后重新配置桥接
由于WSL2的MAC地址每次重启后都会变化,所以桥接后DHCP的ip也是非固定的,参考issue。
目前没有好的解决办法,一些依赖MAC地址的服务,可能会工作不正常。如samba的域名访问。
谷歌百度一键搜索, 在百度页面上搜谷歌,在谷歌的页面上搜索百度,无需切换,无需重新输入搜索词。
详细描述:
虽然谷歌比较好用,结果也准确,但搜索中文这方面却也弱了点,百度还是有点用。谷歌百度一键搜索, 在谷歌的页面上搜索百度,在百度页面上搜谷歌,无需切换,无需重新输入搜索词。
目前支持http(s)://www.baidu.com, http(s)://www.google.com.hk, http(s)://www.google.com
创建:2015.11.25
作者:ponder.work
forked from: raywill/BaiGoogleDu
下载crx文件:github
拖入到 chrome://extensions/ 页面,即可完成安装。
2015年11月28日 版本: 1.2.1
2016年12月12日 版本: 1.2.2
提取文本中的电驴、磁力、迅雷、旋风、快车链接。也可作为一个简易的文本编辑器。
功能采用python和正则表达式实现,界面采用wxPython类库,并通过py2exe打包。
http://pan.baidu.com/s/1sjP1KBj
2015.09.13 V2.4
增加直接提取网址源码功能
2015.09.12 V2.3
修正不能正确提取部分迅雷链接
这个小工具是本人Python的一个习作,水平有限,如有bug可以在这里留言,或者给我发邮件admin@ponder.work。
]]>经过本强迫症的探索,终于找到基于 Karabiner-Elements + Automator + Logseq 的完美生词本方案。
最后的效果是,快捷键取词的同时记录单词卡片到Logseq对应的笔记。
参考知乎文章安装好《朗道英汉字典5.0》
这是为了有个释义简洁的词典,方便后续生成生词本词条
使用 macOS 自带应用 Automator(自动操作)编写workflow,将当前鼠标所在位置的文本提取并保存制卡。
首先打开 Automator.app 新建一个 Quick Aciont(快速操作)
然后依次拖入“获得词语定义”,“运行Shell脚本”等步骤,并调整如下几个位置的选项。
修改脚本里的代码为如下内容,生词本路径相应替换,并相应位置新建好生词本文件。# -*- coding:utf-8 -*-
from __future__ import unicode_literals, print_function
import sys, os, io, subprocess
FILE=os.path.expanduser("~/weiyun_sync/!sync/logseq-note/pages/生词本.md")
output = []
text = sys.argv[1].decode('utf8') if sys.version_info.major == 2 else sys.argv[1]
lines = [i.strip() for i in text.splitlines() if i.strip()]
if len(lines) < 2:
exit(0)
word = lines[0]
if lines[1][0] == '*':
output.append('- {}\t{} [[card]]'.format(word, lines[1]))
lines = lines[2:]
else:
output.append('- {}\t [[card]]'.format(word))
lines = lines[1:]
output.append('\t- {}'.format(lines[0]))
for line in lines[1:]:
output.append('\t ' + line)
old_words = set()
with io.open(FILE, 'r', encoding='utf8') as fp:
for line in fp:
parts = line.split()
if line.startswith('-') and len(parts) > 1:
old_words.add(parts[1])
if word not in old_words:
with io.open(FILE, 'a', encoding='utf8') as fp:
fp.write('\n')
fp.write('\n'.join(output))
fp.write('\n')
subprocess.check_call(['osascript', '-e', u'display notification "添加 {}" with title "生词本"'.format(word)])
else:
subprocess.check_call(['osascript', '-e', u'display notification "跳过 {}" with title "生词本"'.format(word)])
选择路径保存好 workflow,然后在 键盘 - 快捷键 - 服务
中能看到新建的workflow。
为它设置快捷键 command + shift + alt + 1
Karabiner-Elements 是 macOS 平台的一个重新映射快捷键的软件。
这里我们使用它将“查询单词”和“触发workflow”整合在一起,当然它还支持很多用途,这里就不赘述了。
注意确保Karabiner相关权限,并且设置中下图相关设备是勾选状态
安装好Karabiner-Elements后,打开它的配置文件
路径在 /Users/<用户名>/.config/karabiner/karabiner.json
在 profiles -> complex_modifications -> rules
列表中增加一项配置,内容如下。
然后保存,Karabiner会自动加载新的配置。
这里是将鼠标的侧键(靠前的)映射为查单词的快捷键,实现一键查词。
也可以根据需要更改按键,通过EventViewer可以查看按键代码,配置文件格式可参考官方文档
{ |
可以看到 Logseq 中卡片生成的效果
模式扩展(globbing),类似C语言中的宏展开,我们通常使用的通配符*
就是其中之一。
Bash 一共提供八种扩展,前4种为文件扩展,只有文件路径确实存在才会扩展。
~
波浪线扩展?
问号扩展*
星号扩展[]
方括号扩展{}
大括号扩展$var
变量扩展$(date)
命令扩展$((1 + 1))
算术扩展波浪线~
会自动扩展成当前用户的主目录。~user
表示扩展成用户user
的主目录。如果用户不存在,则波浪号扩展不起作用。
bash-5.1$ echo ~/projects/ |
?
字符代表文件路径里面的任意单个字符,不包括空字符。
只有文件确实存在的前提下,才会发生扩展。
bash-5.1$ touch {a,b}.txt ab.txt |
*
字符代表文件路径里面的任意数量的任意字符,包括零个字符。
bash-5.1$ ls *.txt |
方括号扩展的形式是[...]
,只有文件确实存在的前提下才会扩展。
[^...]
和[!...]
。它们表示匹配不在方括号里面的字符
方括号扩展有一个简写形式[start-end]
,表示匹配一个连续的范围
bash-5.1$ ls [ab].txt |
大括号扩展{...}
表示分别扩展成大括号里面的所有值
大括号也可以与其他模式联用,并且总是先于其他模式进行扩展。
bash-5.1$ echo {1,2,3} |
Bash 将美元符号$
开头的词元视为变量,将其扩展成变量值
bash-5.1$ echo $HOME |
$(...)
可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值。
bash-5.1$ echo $(date) |
$((...))
可以扩展成整数运算的结果
bash-5.1$ echo $((1+1)) |
单引号用于保留字符的字面含义,在单引号里转义字符和模式扩展都会失效。
bash-5.1$ ls '[ab].txt' |
双引号比单引号宽松,三个特殊字符除外:美元符号($
)、反引号(`
)和反斜杠(\
)。这三个字符,会被 Bash 自动扩展。
也就是说,相比单引号在双引号中变量扩展,命令扩展,算术扩展以及转义字符是有效的。
bash-5.1$ echo "$((1+1))" |
# 双引号中使用单引号 |
Here 文档(here document)是一种输入多行字符串的方法,格式如下。
它的格式分成开始标记(<< token
)和结束标记(token
), 一般用字符串EOF
作为token
<< token |
例如
bash-5.1$ cat << EOF |
Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(<<<
)表示。
它的作用是将字符串通过标准输入,传递给命令。
bash-5.1$ cat <<< foobar |
bash 是基于标准输入在不同进程间交互数据的,大部分功能都是在操作字符串,所以变量的默认类型也是字符串。
声明时等号两边不能有空格。
Bash 变量名区分大小写,HOME
和home
是两个不同的变量。
bash-5.1$ foo=1 |
# 查看所有变量, 其中包含父进程export的变量 |
用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。
如果希望子进程能够读到这个变量,需要使用export命令。
bash-5.1$ bash -c set | grep foo |
平时所说的环境变量,就是init进程export输出的。子进程对变量的修改不会影响父进程,也就是说变量不是共享的。
# 查看环境变量 |
下面是一些常见的环境变量。
BASHPID
:Bash 进程的进程 ID。BASHOPTS
:当前 Shell 的参数,可以用shopt
命令修改。DISPLAY
:图形环境的显示器名字,通常是:0
,表示 X Server 的第一个显示器。EDITOR
:默认的文本编辑器。HOME
:用户的主目录。HOST
:当前主机的名称。IFS
:词与词之间的分隔符,默认为空格。LANG
:字符集以及语言编码,比如zh_CN.UTF-8
。PATH
:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。PS1
:Shell 提示符。PS2
: 输入多行命令时,次要的 Shell 提示符。PWD
:当前工作目录。RANDOM
:返回一个0到32767之间的随机数。SHELL
:Shell 的名字。SHELLOPTS
:启动当前 Shell 的set
命令的参数TERM
:终端类型名,即终端仿真器所用的协议。UID
:当前用户的 ID 编号。USER
:当前用户的用户名。Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。
$?
: 上一个命令的退出码, 0为成功,其他为失败$$
: 当前进程的pid$_
: 为上一个命令的最后一个参数$!
: 为最近一个后台执行的异步命令的进程 ID。$0
: bash脚本的参数列表,0是脚本文件路径,1到n是第1到第n个参数${varname:-word}
: 如果变量varname存在且不为空,则返回它的值,否则返回word${varname:=word}
: 如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word。${varname:+word}
: 如果变量名存在且不为空,则返回word,否则返回空值。它的目的是测试变量是否存在。${varname:?message}
: 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。declare
命令的主要参数(OPTION)如下。
-a
:声明数组变量。-A
:声明关联数组变量。-f
:输出所有函数定义。-F
:输出所有函数名。-i
:声明整数变量。-p
:查看变量信息。-r
:声明只读变量。-x
:该变量输出为环境变量。bash 有字符串,数字,数字,关联数组四种数据类型,默认是字符串,其他类型需要手动声明。
语法 varname=value
bash-5.1$ s1=abcdefg |
语法 ${#varname}
bash-5.1$ echo ${#s1} |
语法 ${varname:offset:length}
, offset为负数的时候,前面要加空格,防止与默认值语法冲突。
bash-5.1$ s1=abcdefg |
${variable#pattern}
: 删除最短匹配(非贪婪匹配)的部分,返回剩余部分${variable##pattern}
: 删除最长匹配(贪婪匹配)的部分,返回剩余部分匹配模式pattern可以使用*
、?
、[]
等通配符。
$ myPath=/home/cam/book/long.file.name |
${variable%pattern}
: 删除最短匹配(非贪婪匹配)的部分,返回剩余部分${variable%%pattern}
: 删除最长匹配(贪婪匹配)的部分,返回剩余部分$ path=/home/cam/book/long.file.name |
如果匹配pattern
则用replace
替换匹配的内容
${variable/pattern/replace}
: 替换第一个匹配${variable//pattern/replace}
: 替换所有匹配$ path=/home/cam/foo/foo.name |
使用 declare -i
声明整数变量。
# 声明为整数,可以直接计算,不需要使用$符号 |
Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。
number
:没有任何特殊表示法的数字是十进制数(以10为底)。0number
:八进制数。0xnumber
:十六进制数。base#number
:base
进制的数。bash-5.1$ declare -i a=0x77 |
((...))
语法可以进行整数的算术运算。
支持的算术运算符如下。
+
:加法-
:减法*
:乘法/
:除法(整除)%
:余数**
:指数++
:自增运算(前缀或后缀)--
:自减运算(前缀或后缀)如果要读取算术运算的结果,需要在((...))
前面加上美元符号$((...))
,使其变成算术表达式,返回算术运算的值。
bash-5.1$ echo $((1+1)) |
array=(item1 item2)
语法可初始化数组,括号内可以换行,多行初始化可以用#
注释。
# 直接初始化数组 |
array[index]
语法可访问数组元素,不带index访问则是访问数组首个元素。
bash-5.1$ a=(1 2 3) |
${#array[@]}
和 ${#array[*]}
可访问获得数组长度
bash-5.1$ a=(1 2 3) |
${!array[@]}
或 ${!array[*]}
, 可以获得非空元素的下标
bash-5.1$ a=(1 2 3) |
${array[@]:position:length}
的语法可以提取数组成员。
bash-5.1$ a=({1..10}) |
数组末尾追加元素,可以使用+=
赋值运算符。
bash-5.1$ a=({1..10}) |
删除一个数组成员,使用unset
命令。
bash-5.1$ a=(1 2 3) |
declare -A
可以声明关联数组,关联数组使用字符串而不是整数作为数组索引。
除了初始化外,使用方法和数组基本相同
bash-5.1$ declare -A a |
#
表示注释,每行从#
开始之后的内容代表注释,会被bash忽略.
bash-5.1$ echo 1111 # 222 |
bash 和常规编程语言一样使用if
作为分支条件的关键字, fi
作为结束的关键字,else
和elif
子句是可选的
其中if
和elif
的condition
所判断的内容是命令的状态码是否为0,为0则执行关联的语句。
因为bash中分号(;)和换行是等价的,所以有下面两种风格,其他多行语句也是类似的 |
这里的condition
可以是多个命令,如command1 && command2
,或者command1 || command2
,则if判断的是这两个命令的状态码的逻辑计算结果。
condition
也是可以是command1; command2
, 则则if判断的是最后一个命令的状态码。
这里最常用的condition
是test
命令, 也就是[[]]
和[]
. test
是bash的内置命令,会执行给定的表达式,结果为真满足则返回状态码0, 否则返回状态码1.
下文循环语言的condition
也是相同的,就不赘述了
bash-5.1$ test 1 -eq 1 |
[[]]
和[]
的区别是[[]]
内部支持&&
,||
逻辑判断,所以以下三种写法是等价的。
由于[
和]
是命令, 所以两侧一定要有空格,也是就是[ 1 -eq 1 ]
,否则bash会认为命令找不到。
test |
判断条件支持且(&&)或(||)非(!)
not |
使用[
和test
时,变量引用注意加双引号,否则得不到正确的结果,[[
则不需要。
bash-3.2$ echo "$SSH_CLIENT" |
bash默认数据类型为字符串,所以常见的 >
, <
是用于字符串判断。
注意:字符串判断不支持>=
和<=
, 得使用逻辑组合来替代
-z string
:字符串串长度为0-n string
: 字符串长度大于0string1 == string2
: string1 等于 string2string1 = string2
: string1 等于 string2string1 > string2
: 如果按照字典顺序string1排列在string2之后string1 < string2
: 如果按照字典顺序string1排列在string2之前下面的表达式用于判断整数。
[ integer1 -eq integer2 ]
:如果integer1
等于integer2
,则为true
。[ integer1 -ne integer2 ]
:如果integer1
不等于integer2
,则为true
。[ integer1 -le integer2 ]
:如果integer1
小于或等于integer2
,则为true
。[ integer1 -lt integer2 ]
:如果integer1
小于integer2
,则为true
。[ integer1 -ge integer2 ]
:如果integer1
大于或等于integer2
,则为true
。[ integer1 -gt integer2 ]
:如果integer1
大于integer2
,则为true
。以下表达式用来判断文件状态。仅列举常用判断,详细支持列表参考 https://tldp.org/LDP/abs/html/fto.html
[ -a file ]
:如果 file 存在,则为true
。[ -d file ]
:如果 file 存在并且是一个目录,则为true
。[ -e file ]
:如果 file 存在,则为true
, 同-a
。[ -f file ]
:如果 file 存在并且是一个普通文件,则为true
。[ -h file ]
:如果 file 存在并且是符号链接,则为true
。[ -L file ]
:如果 file 存在并且是符号链接,则为true
, 同-h
。[ -p file ]
:如果 file 存在并且是一个命名管道,则为true
。[ -r file ]
:如果 file 存在并且可读(当前用户有可读权限),则为true
。[ -s file ]
:如果 file 存在且其长度大于零,则为true
。[ -w file ]
:如果 file 存在并且可写(当前用户拥有可写权限),则为true
。[ -x file ]
:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
。bash也支持,switch case,语法如下。
case EXPRESSION in |
例如a=2
case $a in
1)
echo 11
;;
2)
echo 22
;;
*)
;;
esac
while
循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。condition
与if语句的相同,就不赘述了。
while condition; do |
until
循环与while
循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。
until condition; do |
for...in
循环用于遍历列表的每一项。
for variable in list; do |
常见的几种用法for i in 1 2 3; do
echo $i
done
for i in {1..3}; do
echo $i
done
list=(1 2 3)
for i in ${list[@]}; do
echo $i
done
for
循环还支持 C 语言的循环语法。
for (( expression1; expression2; expression3 )); do |
上面代码中,expression1
用来初始化循环条件,expression2
用来决定循环结束的条件,expression3
在每次循环迭代的末尾执行,用于更新值。
注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号$
。
例如for ((i=1; i<=3; i++)); do
echo $i
done
Bash 提供了两个内部命令break
和continue
,用来在循环内部跳出循环。
break
命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。continue
命令立即终止本轮循环,开始执行下一轮循环。
Bash 函数定义的语法有两种,其中fn
为定义的函数名称。
第一种 |
函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的。
${N}
:函数的第一个到第N个的参数。$0
:函数所在的脚本名。$#
:函数的参数总数。$@
:函数的全部参数,参数之间使用空格分隔。$*
:函数的全部参数,参数之间使用变量$IFS
值的第一个字符分隔,默认为空格,但是可以自定义。funcname arg1 arg ... argN
的语法进行函数调用。主要函数的返回值和输出值(标准输出)的区别,这和主流编程语言不同
add() { |
return
命令用于从函数返回一个值。返回值和命令的状态码一样,可以用$?
拿到值。return
也可以不接具体的值,则返回值是return命令的上一条命令的状态码。
如果不加return
,则返回值是函数体最后一条命令的状态码。
function func_return_value { |
Shebang(也称为Hashbang)是一个由井号和叹号构成的字符序列#!
, 其出现在可执行文本文件的第一行的前两个字符。
在文件中存在Shebang的情况下,类Unix操作系统的程序加载器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令.
例如,shell脚本
echo Hello, world!
python 脚本#!/usr/bin/env python -u
print("Hello, world!")
每个命令都会返回一个退出状态码(有时候也被称为返回状态)。
成功的命令返回 0,不成功的命令返回非零值,非零值通常都被解释成一个错误码。行为良好的 UNIX 命令、程序和工具都会返回 0 作为退出码来表示成功,虽然偶尔也会有例外。
状态码一般是程序的main函数的返回码,如c,c++。
如果是bash脚本,状态码的值则是 exit
命令的参数值。
当脚本以不带参数的 exit
命令来结束时,脚本的退出状态码就由脚本中最后执行的命令来决定,这与函数的 return
行为是一致的。
特殊变量$?
可以查看上个命令的退出状态码
文件描述符在形式上是一个非负整数。指向内核为每一个进程所维护的该进程打开文件的记录表。
当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
每个Unix进程(除了可能的守护进程)应均有三个标准的POSIX文件描述符,对应于三个标准流:
0
:标准输入1
:标准输出2
:错误输出手动指定描述符exec 3<> /tmp/foo #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.
系统自动分配描述符,bash4.1开始支持(在macos报错,原因不明)!/bin/bash
FILENAME=abc.txt
exec {FD}<>"$FILENAME"
echo 11 >&FD
echo 22 >&FD
FD>&-
command > file
: 将输出重定向到 file。command < file
: 将输入重定向到 file。command >> file
: 将输出以追加的方式重定向到 file。n > file
: 将文件描述符为 n 的文件重定向到 file。n >> file
: 将文件描述符为 n 的文件以追加的方式重定向到 file。n >& m
: 将输出文件 m 和 n 合并。n <& m
: 将输入文件 m 和 n 合并。所以命令中常见的ls -al > output.txt 2>&1
, 就是将标准输出和错误输出都重定向到一个文件。
等价于ls -al &>output.txt
,本人偏好这种写法,比较简洁。
IFS决定了bash在处理字符串的时候是如何进行单词切分。
IFS的默认值是空格,TAB,换行符,即\t\n
echo "$IFS" | cat -et |
例如,在for循环的时候,如何区分每个itemfor i in `echo -e "foo bar\tfoobar\nfoofoo"`; do
echo "'$i' is the substring";
done
也可以自定义OLD_IFS="$IFS"
IFS=":"
string="1:2:3"
for i in $string; do
echo "'$i' is the substring";
done
IFS=$OLD_IFS
linux进程分前台(fg)和后台(bg)。
在命令的末尾添加&
可以将命令后台执行,一般配合输出重定向使用。
jobs
可以查看当前bash进程的子进程,并通过fg
和bg
进行前台和后台切换。
%1
代表后台的第一个进程,以此类推%N
代表第n个.
control + Z
可以将当前前台程序暂停,配合bg
可以将其转后台。
wait [pid]
可以等待子进程结束,如果不带pid参数则等待所有子进程结束。
bash-5.1$ sleep 100 |
可以利用jobs对后台进程并发数目进行控制for i in {1..30}; do
sleep $((30+i)) &
if [[ $(jobs | wc -l ) -gt 10 ]]; then
jobs
wait
fi
done
wait
每一个bit有0、1两种状态,所以 Bitmap 适合应用于判断是否存在、桶排序(不含重复元素),具体来说可以用bitmap记录ip信息,实现布隆过滤器等等。
Bitmap 可以看成是个二维数组,第一维取出的元素是byte,然后用第二维的index去访问该byte对应的位。
由于正常访问内存最小的单位的字节,操作具体的位需要位运算。
注意:这里的位移操作都是逻辑位移
一个byte有8bit, 设置某个位为1,需要用到按位或 |
第0个bit: byte |= 0b10000000
第1个bit: byte |= 0b01000000
...
第7个bit: byte |= 0b00000001
所以:设置byte的第n个bit(n取0到7): byte |= (0b10000000 >> n)
判断某个位是否为1,需要用到按位与 &
过程和上面类似, 满足byte & (0b10000000 >> n) == (0b10000000 >> n)
,则该位为1
将某个位置为0,需要用到按位与 &
第0个bit: byte &= 0b01111111
第1个bit: byte &= 0b10111111
...
第7个bit: byte &= 0b01111110
所以:第n个bit(n取0到7): byte &= ((0b10000000 >> n) ^ 0b11111111)
Python中由于没有无符号数,所以算掩码(0b01111111)时不能用按位取反。
将使用到的掩码设置为常量,性能会更好,这里的实现主要是为了体现思路,便于理解
BYTE_WIDTH = 8 |
通用monkey.patch_all() 所有io操作函数, gevent可以以同步的方式编写异步代码. 在不更改代码的同时就可以使系统并发性能得到指数级提升。
这里有一个局限, c扩展中的io操作无法被patch, 会导致整个server阻塞
这里使用flask写了web server 用于测试# filename: flask_app.py
import time
from flask import Flask, request
app = Flask(__name__)
def test():
time.sleep(0.2)
return 'hello'
if __name__ == "__main__":
app.run()
安装依赖:pip install flask gunicorn gevent
启动服务器:gunicorn -w 1 --bind 127.0.0.1:5000 flask_app:app
测试性能:siege -c 20 -r 1 'http://127.0.0.1:5000/test'
由于只有一个worker进程,可以看到只有5qps,每个请求sleep 0.2秒,是符合预期的。
启动服务器:gunicorn -w 1 -k gevent --bind 127.0.0.1:5000 flask_app:app
测试性能:siege -c 20 -r 1 'http://127.0.0.1:5000/test'
可以看到,性能有接近20倍的提升
这个程序通过sleep 2s 来模拟阻塞的io操作,所以每次调用会阻塞2s
|
然后,自然是把这个C文件编成动态链接库:
Linux下的编译:
gcc -c -fPIC sleep.c |
然后在我们的web server 中调用这个动态库,使用ctypes调用
def socket_block(): |
测试后发现,gevent失效了,整个服务基本是串行阻塞状态
使用线程, 基本没啥用, 不能解决问题
|
使用 siege 进行测试, 看请求耗时, 所有请求基本是串行的, gevent没有效果➜ ~/projects siege -c 10 -r 1 'http://127.0.0.1:5000/test' -v
** SIEGE 4.0.4
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.03 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 16.02 secs: 9 bytes ==> GET /test
HTTP/1.1 200 20.03 secs: 9 bytes ==> GET /test
HTTP/1.1 200 20.04 secs: 9 bytes ==> GET /test
问题解决
|
使用 siege 进行测试, 请求没有被阻塞
➜ ~/projects siege -c 5 -r 1 'http://127.0.0.1:5000/test' -v |
webserver主体进程使用gevent, 将阻塞的c扩展网络io操作放到另一个进程中执行, 可以改造成一个服务
具体可以用这下面两种实现
还有一种对动态库进行patch的方案(greenify),只在linux下有效,就没怎么研究了
]]>它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定,比如在Python中通过dict来实现。
使用dict的方案有个局限,就是没办法应用在大规模数据上。
以英语单词为例,我们有三个单词apple,banana,cherry。
如果用dict来存的话是, 查询的准确性是100%,空间占用也是100%
如果我们对准确性要求降低一些,我们可以只记录单词的首字母;
首字母不存在dict中的话,该单词不存在,否则有可能存在。
记录首字母的方法虽然有效,但是由于同首字母的单词很多,只记录首字母准确率太低了。
所以我们可以依次记录前2个字母,只有这两个字母都存在,我们才认为该单词存在。
以此类推,为了提高准确率,我们可以从前2个字母增加到前n个字母。
但是也不能太大,太大的话,dict里的每个字母被重复设置的次数过多,准确率反而会下降。
以上方案还有一个问题,单词中每个字母的分布是很不均匀的,导致准确率对不同的单词也不稳定。
可以不直接记录字母,而是对单词应用哈希函数,并将结果按一个数值(比如26)取模,如hash('apple') % 26
,当做一个单词的一个字母来记录。
同时应用多个不同的哈希函数,并记录取模后的值,相当于记录该单词的多个字母。
根据上文的分析,可以实现Python版本的布隆过滤器。
用bitmap代替dict,节省空间占用。
使用Python内置的hash
函数作为哈希函数。
哈希前对元素增加不同的后缀再调用,替代理论里的多个不同哈希函数。
BIT_SIZE = 5000000 |
话接上回,说是接手了一个82年的拉菲Python项目,这次又发现了一个新坑
项目中用了一个上下文类,用于存储本次请求的一些数据,在开发过程中我想把这个上下文类dump成json,详细分析里面的数据,然而发现上下文类的行为不符合预期
上下文类大概是这样
class Context(): |
测试之后发现,结果明显不符合预期,两个属性只输出了一个from test_property import Context
c = Context()
'a': 1} c.input_json = {
'b'] = 2 c.result_dict[
...
get _result_dict
c.to_dict()
{'_input_json': None, '_result_dict': {'b': 2}}
看测试代码我们可以发现,在访问result_dict
属性的时候,property是工作正常的(有对应的print)
但是对应设置input_json
的时候, 却没有看到对应的print输出
所以可以断定,此处的property工作不正常。
仔细看代码后,我发现Context
是旧式类。可以看到,A
, B
, C
三中类的写法,其中A
和B
都是旧式类<type 'classobj'>
, C
是新式类。(旧式类只在Python2中存在)。
我们这里Context
的写法和B
是一样的。
class A: |
然后自然就怀疑旧式类对property装饰器的支持存在问题。
一通google之后,确定旧式类是不支持property。
确切地说,是对property的支持不完整,具体来说有以下3点。
golang将程序编译成一个可执行文件,部署起来特别方便。
那么Python是否也有类似解决方案呢?单一可执行文件,免去安装Python环境的麻烦,也避免了直接暴露源码程序。
经过多次搜索之后找到解决方案 exxo
注意:exxo只支持linux64平台
首先下载安装exxowget https://bintray.com/artifact/download/mbachry/exxo/exxo-0.0.7.tar.xz # 下载
tar xf exxo-0.0.7.tar.xz # 解压
mv exxo /usr/local/bin # 移动到可执行文件目录(可选)
通过 exxo 创建一个python虚拟环境,用于编译我们的程序
exxo venv /tmp/myenv # 创建环境 |
先写一个简单的程序: aa.pyimport os
def main():
os.system('ls -al')
if __name__ == '__main__':
main()
再根据这个程序编写setup.py, 这一步是关键,不熟悉的同学可以去学习一下setuptools的语法。
#condig=utf8 |
执行exxo build
编译,生成文件dist/pyaa
最后,测试该文件,功能正常。$ ./dist/pyaa
总用量 19348
drwxrwxr-x 4 ruan ruan 4096 3月 1 11:25 .
drwxrwxr-x 6 ruan ruan 4096 2月 29 11:48 ..
-rw-rw-r-- 1 ruan ruan 87 3月 1 11:14 aa.py
drwxrwxr-x 2 ruan ruan 4096 3月 1 11:26 dist
-rw-r--r-- 1 ruan ruan 19784872 8月 29 2016 exxo-0.0.7.tar.xz
drwxrwxr-x 2 ruan ruan 4096 3月 1 11:24 __pycache__
-rw-rw-r-- 1 ruan ruan 505 3月 1 11:25 setup.py
在工作中发现有获取linux具体开关机时间和类型的需求,可以通过分析/var/log/wtmp
日志文件得到。
通过last -x -F
可以将/var/log/wtmp
输出以下格式。
root pts/1 172.16.3.245 Sun Jun 12 10:14:47 2016 - Tue Jun 14 08:34:31 2016 (1+22:19) |
用户 | 终端类型 | 登录ip | 开始时间 | 结束时间 | 持续时长 |
---|---|---|---|---|---|
root | pts/5 | 172.16.3.129 | Fri Jun 17 14:03:50 2016 | Fri Jun 17 14:03:42 2016 | (00:00) |
每一行为一条会话记录,有以下6个部分。
reboot
的行,获取此行的“开始时间”得到这一次的开机时间。reboot
行的下一行为shutdown
,则为正常关机,获取此行的“开始时间”得到关机时间。若持续时间为(00:00)
则为重启。reboot
行的下一行不为shutdown
,则为不正常关机,下面的登录会话的“结束时间”必然伴随着crash
。找到最近的一条crash
记录,拿到这条记录的“开始时间”和“持续时长”,相加即是关机时间。在类UNIX系统中,一切被打开的文件、端口被抽象为文件描述符(file descriptor)
从python3.4开始,文件描述符默认是non-inheritable,也就是子进程不会共享文件描述符。
一般为了实现多进程、多线程的webserver,服务端口fd必须设置为继承(set_inheritable),这样才能多进程监听一个端口(配合SO_REUSEPORT)
典型的是使用flask的测试服务器的场景,这里我们写一段代码模拟。
import socket, os |
我们通过lsof -p {pid}
可以看到这两个进程的所有文件描述符
server进程, 可以看到服务端口的fd是4COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME
ptpython 6214 cwd DIR 253,0 4096 872946898 /
...
ptpython 6214 0u CHR 136,13 0t0 16 /dev/pts/13
ptpython 6214 1u CHR 136,13 0t0 16 /dev/pts/13
ptpython 6214 2u CHR 136,13 0t0 16 /dev/pts/13
ptpython 6214 3r CHR 1,9 0t0 2057 /dev/urandom
ptpython 6214 4u sock 0,7 0t0 58345077 protocol: TCP
ptpython 6214 5u a_inode 0,10 0 8627 [eventpoll]
ptpython 6214 6u unix 0x0000000000000000 0t0 58368029 socket
ptpython 6214 7u unix 0x0000000000000000 0t0 58368030 socket
sleep子进程,也拥有fd=4的文件描述符COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME
python 18022 cwd DIR 253,0 4096 872946898 /
...
python 18022 0u CHR 136,13 0t0 16 /dev/pts/13
python 18022 1u CHR 136,13 0t0 16 /dev/pts/13
python 18022 2u CHR 136,13 0t0 16 /dev/pts/13
python 18022 4u sock 0,7 0t0 58345077 protocol: TCP
如果server进程退出时,sleep进程没有退出,fd=4对应的端口就被占用了,服务也就不能正常启动了。
import os |
使用subprocess库而不是os来启动子程序, 通过close_fds参数关闭多余的文件描述符import subprocess
subprocess.call("python -c 'import time;time.sleep(1000)'", shell=True, close_fds=True)
谈到一致性哈希(Consistent hashing),就得先讲一下分布式存储。
比如我们有2000w条数据,一台机器存不下,那么我们可以把分成10份每份200w条存到10台机器上。
这样存储就不成问题,但是查询效率很低,查一条数据要每台机器都查一遍。
如果这些数据能够分类,每一类存到一台机器上,查询前先知道数据的类别,就可以直接定位到某台机器,效率就高了。
那么就得找到一个通用而且均匀的分类方法,可以想到先哈希再取模hash(data) % N
现有这几个数据apple, banana, cherry,durian,希望存储到有3台机器的服务.# 假设自定义了hash函数,有以下返回
hash("apple") % 3 == 30 % 3 == 0
hash("banada") % 3 == 31 % 3 == 1
hash("cherry") % 3 == 32 % 3 == 2
如果B机器宕机了,需要将取模余数和机器重新映射,这时发现3/4的数据都需要迁移
其实当B机器宕机时,取模的除数可以不改成2,依然是hash(data) % 3
,这样余数就不会变,只需要把余数和机器的映射改一下,将原先B机器的映射到A机器上,这样只需要迁移1/4的数据.
但是现在取模的除数和机器数目相等,只能应对机器减少的情况,增加机器就没法处理了。
这时可以用一个比较大的数作为除数(比如3000),把除数在一定范围内的都映射到某台机器,增加机器只需要调整余数和机器的映射就行了。
到这里一致性哈希的基本原理已经介绍完了,但对于新增服务器的情况还存在一些问题。
新增的服务器D只分担了C服务器的负载,服务器 A 和 B 并没有因为 D 服务器的加入而减少负载压力。
针对这个问题,可以把D当做多台机器,均匀地放置,这样所有机器的负载都得到分担,也就是所谓的引入虚拟节点。
下面用Python来实现一致性哈希,这里实现不带虚拟节点的版本。
代码实现和上文分析,有几点细节有些不同
hashlib.sha1
作为哈希函数2^32 - 1
,这是C语言中unsiged int
的最大取值。import hashlib |
最近有个需求, 在Java环境环境运行xgboost模型, 查询资料后发现PMML可以解决这个问题.
PMML是数据挖掘的一种通用的规范,它用统一的XML格式来描述我们生成的机器学习模型。这样无论你的模型是sklearn,R还是Spark MLlib生成的,我们都可以将其转化为标准的XML格式来存储。当我们需要将这个PMML的模型用于部署的时候,可以使用目标环境的解析PMML模型的库来加载模型,并做预测。
可以看出,要使用PMML,需要两步的工作:
常见的方法是用sklearn2pmml
, 主要思路是将Booster模式转为Scikit-Learn Wrapper interface
的XGBModel
, 然后再使用PMMLPipeline
保存为PMML模型文件
import xgboost as xgb |
使用这种方法, 可能会出现因为模型包含中文信息导致转换失败Standard output is empty
Traceback (most recent call last):
File "<stdin>", line 7, in <module>
File "/Users/ruan/.pyenv/versions/2.7.16/lib/python2.7/site-packages/sklearn2pmml/__init__.py", line 262, in sklearn2pmml
print("Standard error:\n{0}".format(_decode(error, java_encoding)))
UnicodeEncodeError: 'ascii' codec can't encode character u'\u6708' in position 1: ordinal not in range(128)
'ascii' codec can't encode character u'\u6708' in position 1: ordinal not in range(128)
也可以jpmml-xgboost工具, 将xgboost模型直接转换为PMML
注意这里的模型文件, 需要是通过xgboost.Booster.save_model
方法保存的.
wget https://github.com/jpmml/jpmml-xgboost/releases/download/1.3.15/jpmml-xgboost-executable-1.3.15.jar |
fmap文件的每行的格式是: 特征索引\t特征名称\tq
特征名称可以随意命名, 在调用PMML预测时会用到
例如: fmap.txt0 fea0 q
1 fea1 q
2 fea2 q
3 fea3 q
4 fea4 q
...
java -jar jpmml-xgboost-executable-1.3.15.jar \ |
python可以使用pypmml
库, 测试我们转换好的PMML, 并和原始模型进行分数比较
from pypmml import Model |
java环境执行, 需要用到pmml-evaluator
和pmml-evaluator-extension
库
具体代码参考 https://www.cnblogs.com/pinard/p/9220199.html
<script>、<img>、<link>
这些包含 src 属性的标签可以加载跨域资源。(只能GET)如何绕过同源策略?
<script>
允许跨域的特点,设置标签的src为目标域,动态生成需要的javascript内容Access-Control-Allow-Origin: https://foo.example // 所允许的来源域 |
参考
名称 | 逐步做到 | |
---|---|---|
第一式 | 墙壁俯卧撑 | 3 x 50 次 |
第二式 | 上斜俯卧撑 | 3 x 40 次 |
第三式 | 膝盖俯卧撑 | 3 x 30 次 |
第四式 | 半俯卧撑 | 2 x 25 次 |
第五式 | 标准俯卧撑 | 2 x 20 次 |
第六式 | 窄距俯卧撑 | 2 x 20 次 |
第七式 | 偏重俯卧撑 | 2 x 20 次 |
第八式 | 单臂半俯卧撑 | 2 x 20 次 |
第九式 | 杠杆俯卧撑 | 2 x 20 次 |
最终式 | 单臂俯卧撑 | 1 x 100 次 |
名称 | 逐步做到 | |
---|---|---|
第一式 | 肩倒立深蹲 | 3 x 50 次 |
第二式 | 折刀深蹲 | 3 x 40 次 |
第三式 | 支撑深蹲 | 3 x 30 次 |
第四式 | 半深蹲 | 2 x 50 次 |
第五式 | 标准深蹲 | 2 x 30 次 |
第六式 | 窄距深蹲 | 2 x 20 次 |
第七式 | 偏重深蹲 | 2 x 20 次 |
第八式 | 单腿半深蹲 | 2 x 20 次 |
第九式 | 单腿辅助深蹲 | 2 x 20 次 |
最终式 | 单腿深蹲 | 2 x 50 次 |
名称 | 逐步做到 | |
---|---|---|
第一式 | 垂直引体 | 3 x 40 次 |
第二式 | 水平引体向上 | 3 x 30 次 |
第三式 | 折刀引体向上 | 3 x 20 次 |
第四式 | 半引体向上 | 2 x 15 次 |
第五式 | 标准引体向上 | 2 x 10 次 |
第六式 | 窄距引体向上 | 2 x 10 次 |
第七式 | 偏重引体向上 | 2 x 9 次 |
第八式 | 单臂半引体向上 | 2 x 8 次 |
第九式 | 单臂辅助引体向上 | 2 x 7 次 |
最终式 | 单臂引体向上 | 2 x 6 次 |
名称 | 逐步做到 | |
---|---|---|
第一式 | 坐姿屈膝 | 3 x 40 次 |
第二式 | 平卧抬膝 | 3 x 35 次 |
第三式 | 平卧屈举腿 | 3 x 30 次 |
第四式 | 平卧蛙举腿 | 2 x 25 次 |
第五式 | 平卧直举腿 | 2 x 20 次 |
第六式 | 悬垂屈膝 | 2 x 15 次 |
第七式 | 悬垂屈举腿 | 2 x 15 次 |
第八式 | 悬垂蛙举腿 | 2 x 15 次 |
第九式 | 悬垂半举腿 | 2 x 15 次 |
最终式 | 悬垂直举腿 | 2 x 30 次 |
名称 | 逐步做到 | |
---|---|---|
第一式 | 短桥 | 3 x 50 次 |
第二式 | 直桥 | 3 x 40 次 |
第三式 | 高低桥 | 3 x 30 次 |
第四式 | 顶桥 | 2 x 25 次 |
第五式 | 半桥 | 2 x 20 次 |
第六式 | 标准桥 | 2 x 15 次 |
第七式 | 下行桥 | 2 x 10 次 |
第八式 | 上行桥 | 2 x 8 次 |
第九式 | 合桥 | 2 x 6 次 |
最终式 | 铁板桥 | 2 x 30 次 |
名称 | 逐步做到 | |
---|---|---|
第一式 | 靠墙顶立 | 2 分钟 |
第二式 | 乌鸦式 | 1 分钟 |
第三式 | 靠墙倒立 | 2 分钟 |
第四式 | 半倒立撑 | 2 x 20 次 |
第五式 | 标准倒立撑 | 2 x 15 次 |
第六式 | 窄距倒立撑 | 2 x 12 次 |
第七式 | 偏重倒立撑 | 2 x 10 次 |
第八式 | 单臂半倒立撑 | 2 x 8 次 |
第九式 | 杠杆倒立撑 | 2 x 6 次 |
最终式 | 单臂倒立撑 | 2 x 5 次 |
装饰器本质上是函数替换. 装饰器被调用会返回一个函数, 被装饰函数会被返回的这个函数替换.
要使用装饰器,先得定义一个装饰器函数,然后在需要装饰的函数的前一行使用@
符号加上装饰器名称。
下面是一个简单是例子, hello
函数被running
装饰器装饰, running
返回了fuck
函数, 此时调用hello
就变成了调用fuck
, 实现了函数功能的改变.
def fuck(who='nobody'): |
output
replace <function hello at 0x1052e98c8> to <function fuck at 0x104d15f28> |
装饰器在这里的效果等效于函数嵌套,不过看起来有点别扭。
def fuck(who='nobody'): |
注意:一旦通过@running
装饰了函数,不管被装饰函数是否运行,python解释器都会执行一遍running函数。
前面这个装饰器将hello函数替换成fuck函数之外就没有别的功能了, 只是为了演示装饰器的原理, 并没有什么实际用处
现在我们写一个计时器装饰器.
还是将hello函数替换成fuck, 这里我们将fuck函数的定义移动到runing内部, fuck函数在调用hello的同时, 实现计时功能.
这样有一个好处, 这个fuck函数就不是全局可见的, 不会污染全局环境, 还可以用到闭包的一些特性.
import time |
输出
`hello` is running... |
上一个装饰器也有一个问题,因为装饰器本质上是函数替换. 就是经过装饰的函数一些属性变了, 比如hello.__name__
变成了fuck
。
所以我们要将这些属性复制到新函数上, 同时由于fuck函数已经没有fuck功能了, 我们将它重命名为wrapper
import time |
output
hello |
这个问题也可以通过python内置的functools.wraps
装饰器解决,这个装饰器对原函数的一些属性进行了复制。
import time |
上一个装饰器还有一个缺点是, 装饰器不能接受参数, 现在我们来实现带参数的装饰器
带参数的装饰器其实就是多嵌套一层函数
import time |
output
`hello` is running at mac |
有时我们希望装饰器更为通用, 适用于带参数和不带参数的场景
import time |
output
`hello` is running at linux |
用gdb调试Python程序,主要有两个部分
py-bt
等以py-为前缀的Python扩展命令,可以调试Python程序我们需要通过原生gdb命令,如n
(next),b
(break)等,使Python程序运行到我们需要调试的位置。
然后,通过py-print
等命令输出我们想要的变量信息
不同的Python环境,配置方法不太一样,这里推荐ubuntu 20.04以上版本
sudo apt install gdb python3 python3-dbg |
run or r –> executes the program from start to end.
break or b –> sets breakpoint on a particular line.
disable -> disable a breakpoint.
enable –> enable a disabled breakpoint.
next or n -> executes next line of code, but don’t dive into functions.
step –> go to next instruction, diving into the function.
list or l –> displays the code.
print or p –> used to display the stored value.
quit or q –> exits out of gdb.
clear –> to clear all breakpoints.
continue –> continue normal execution.
py-bt: 输出Python调用栈
py-bt-full: 输出Python调用栈
py-down: 在调用栈向下一级
py-list: 显示代码
py-locals: 输出locals变量
py-print: 输出
py-up: 在调用栈向上一级
一个斐波那契数列函数fib.py
import time
def fib(n):
time.sleep(0.01)
if n == 1 or n == 0:
return 1
for i in range(n):
return fib(n-1) + fib(n-2)
fib(100)
python3 fib.py &
gdb python3 148
, 148为Python的进程idgdb输出,注意所需要的symbols是否都加载了GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from python3...
Reading symbols from /usr/lib/debug/.build-id/02/526282ea6c4d6eec743ad74a1eeefd035346a3.debug...
Attaching to program: /usr/bin/python3, process 148
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.31.so...
Reading symbols from /lib/x86_64-linux-gnu/libpthread.so.0...
Reading symbols from /usr/lib/debug/.build-id/4f/c5fc33f4429136a494c640b113d76f610e4abc.debug...
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Reading symbols from /lib/x86_64-linux-gnu/libdl.so.2...
Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libdl-2.31.so...
Reading symbols from /lib/x86_64-linux-gnu/libutil.so.1...
Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libutil-2.31.so...
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...
Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libm-2.31.so...
Reading symbols from /lib/x86_64-linux-gnu/libexpat.so.1...
(No debugging symbols found in /lib/x86_64-linux-gnu/libexpat.so.1)
Reading symbols from /lib/x86_64-linux-gnu/libz.so.1...
(No debugging symbols found in /lib/x86_64-linux-gnu/libz.so.1)
Reading symbols from /lib64/ld-linux-x86-64.so.2...
(No debugging symbols found in /lib64/ld-linux-x86-64.so.2)
0x00007fec057c10da in __GI___select (nfds=nfds@entry=0, readfds=readfds@entry=0x0, writefds=writefds@entry=0x0, exceptfds=exceptfds@entry=0x0, timeout=timeout@entry=0x7fff99ce33a0) at ../sysdeps/unix/sysv/linux/select.c:41
41 ../sysdeps/unix/sysv/linux/select.c: No such file or directory.
gdb调试Python没有pdb那么方便,主要是没法直接给python代码打断点,断点都是打在解释器代码中的。
所以,定位到脚本对应位置比较麻烦,需要一点耐心。
(gdb) py-list |
PyPI(英语:Python Package Index,简称PyPI)是一个用于存储Python写成的软件包的软件存储库,我们平时用pip安装的库就是来自于PyPI
而且,通过PyPI我们可以把自己写的库代码分享给别人,这也是开源的乐趣之一。
├── MANIFEST.in |
整体代码结构,其中dingtalk_log_handler是我写的一个用于发日志到钉钉群的库,也是这次要发布的库。
先看下库的主体代码dingtalk_log_handler/__init__.py
, 由于功能比较简单,逻辑就都写在__init__.py
里了
import base64 |
setup.py指引了打包工具如何打包我们的库,功能与类似Makefile
from setuptools import setup, find_packages |
具体打包的功能由setuptools.setup
函数实现,我们只需要修改对应的参数即可
这些参数影响打包的行为,以及在PyPI页面上显示的信息
主要的参数说明,详细信息参考文档
pip install xxx
时的名称pip search xxx
的时候可以看到README.md
文件中读取具体内容pip freeze
输出的格式相同打包时默认只会包含包代码和一些必要的文件,见列表
如果要包含其他资源文件,必须编写MANIFEST.in
来说明说明
MANIFEST.ininclude README.md
recursive-include dingtalk_log_handler *
global-exclude __pycache__
global-exclude tmp
global-exclude *.py[co]
语法和shell的通配符语法类似
include <pattern> <pattern2> ...
: 包含项目根目录匹配通配符的文件recursive-include <dir-pattern> <pattern> <pattern2> ...
: 递归地包含指定目录匹配通配符的文件global-exclude <pattern> <pattern2> ...
: 递归地排除匹配通配符的文件执行: pip3 install wheel && python3 setup.py sdist bdist_wheel
输出文件dist
├── dingtalk-log-handler-0.0.2.tar.gz
└── dingtalk_log_handler-0.0.2-py3-none-any.whl
我们可以通过PyPI的测试站点 来练习库文件的上传,并测试效果
pip install twine -U
twine upload --repository testpypi dist/*
熟悉流程之后,就可以换成正式站点,指定正式仓库(–repository pypi)上传文件
这样全世界的人都能看到你的库了。
可以在这里找到我这次上传的库
可以看到 setup.py 文件里的很多信息会对应体现在PyPI网页上
Python2 vs Python3
Name | Python2 | Python3 | Addtion Info |
---|---|---|---|
try | try except ValueError, e | try except ValueError as e | |
exception | ValueError(‘aa’).message | - | python3中可用ValueError(‘aa’).args[0] 替代 |
__import__ |
__import__ |
__import__ |
推荐importlib.import_module替代,可移植性更好 |
关键字 | 函数 | ||
unicode | unicode | str | python2默认的string是bytes, Python3中是unicode |
bytes | str | bytes | |
division | 1 / 2 | 1 // 2 | |
division | 1 / 2.0 | 1 / 2 | |
不等于操作符 | <> | != | 这个操作费在Python3中已废弃,使用!=替代 |
round | round(0.5) == 1.0 | round(0.5) == 0 | Python3内建的 round 是四舍六入五成双的机制 |
xrange | xrange | range | |
range | range(1,2) | list(range(1,2)) | |
reduce | reduce | - | Python使用functools.reduce替代 |
dict.keys | dict.keys() | list(dict.keys()) | python的dict遍历不保证顺序, 同一个字典py2和py3的遍历顺序可能不一样 |
dict.iterkeys | dict.iterkeys() | dict.keys() | |
dict.items | dict.items() | list(dict.items()) | |
dict.iteritems | dict.iteritems() | dict.items() | |
内置库 | commands | - | 用subprocess替代 |
内置库 | sys.setdefaultencoding | - | |
内置库 | Queue | queue | |
内置库 | ConfigParser | configparser |
Python2
str('')
和 unicode(u'')
'xxx'
形式输入的字符,实际储存的值是xxx
经过系统默认字符集encode过的字节串(bytes),如’\xe8\x86\x9c’u'xxx'
形式输入的字符,实际储存的值是xxx
对应的unicode码, 如u'\u819c'
'膜法' |
Python3
'膜法' |
open函数,与unicode的问题是相关联的
t
(文本)模式的,操作的是unicode,并以utf8作为默认编码python2
'/mnt/d/11.txt', 'r') aa = open( |
python3
'/mnt/d/11.txt', 'r') # 11.txt is saved as utf8 aa = open( |
首先得说一下UML,统一建模语言(英语:Unified Modeling Language,缩写 UML)是非专利的第三代和规约语言。
UML涵盖了很多类型的图,主要都是应用于软件开发行业。
在UML系统开发中有三个主要的模型:
功能模型:从用户的角度展示系统的功能,包括用例图。
对象模型:采用对象,属性,操作,关联等概念展示系统的结构和基础,包括类别图、对象图。
动态模型:展现系统的内部行为。包括序列图,活动图,状态图。
而PlantUML是一个开源项目,除了支持快速绘制上面这些类型的图表之外,还支持很多图表,具体查看官方网站。
PlantUML通过编写文本的方式来定义UML图表,有点类似markdown,然后生成图表
示例:@startuml
' Split into 4 pages
page 2x2
skinparam pageMargin 10
skinparam pageExternalColor gray
skinparam pageBorderColor black
class BaseClass
namespace net.dummy #DDDDDD {
.BaseClass <|-- Person
Meeting o-- Person
.BaseClass <|- Meeting
}
namespace net.foo {
net.dummy.Person <|- Person
.BaseClass <|-- Person
net.dummy.Meeting o-- Person
}
BaseClass <|-- net.unused.Person
@enduml
输出效果
我们需要一个编辑器/IDE来编辑和预览UML图表,这里使用vscode
渲染方式有两种
方便起见,我们直接用官方的服务器
@startuml |
由于PlantUML支持的图表类型比较多,每种图表的语法还不太一致,这里只简单介绍通用的语法,具体的图表还是建议查看官方文档。
也建议大家,在使用过程中记录适合自己的每种图表的模板,作图效率大大提高。
注意:语句类型是和图关联的,也就是说某些语句只能在某个图表用,而且PlantUML会根据你用到的语句猜测你是什么图表,然后进行渲染。
以@startuml
和@enduml
标记一个图表,还可以在开始处记录图表的名称(这里是hello-world)
@startuml hello-world |
'
在行首/'
开始, 以 /'
结束note left|right|top|bottom of XXX
@startuml hello-world |
对象: 图表中的方块,一般出现才关系的两端,有些需要额外定义(如接口,类)
关系:一般用形如箭头的字符来表示对象之间的关系,字符不同样式不同
@startuml hello-world1 |
@startuml |
@startuml |
在开发django网站时发现,用户登录后不能跳转到之前的页面,google了很多答案,讲得也不清楚。
其实就是渲染登陆表单时,将原链接带到action参数里,view函数接收到参数后进行重定向。
<a href="/account/login/?next={{request.path}}">登录</a> |
def login(request): |
<form action="/account/login/?next={{next_url}}" method="post" > |
在用django的admin进行管理的时候,对于指定的用户角色,不希望他看到特定状态的foreignkey,可以采用以下方案。
当然,也可通过自定义form解决该问题。
class FactoryOrderItemInline(admin.TabularInline): |
为了简化部署,brpc 服务在 Docker 容器中运行。本地测试时功能一切正常,上到预发布环境时请求全部超时。
由于业务代码,brpc,docker环境,机房都是新的,在排查问题的过程中简直一头雾水。(当然根本原因还是水平不足)
发现请求超时后,开始用CURL测试接口,用真实数据验证发现请求都耗时1s,这和用c++的预期完全不符。
首先是怀疑业务代码有问题,逐行统计业务代码耗时,发现业务代码仅耗时10+ms。
用空数据访问接口,发现耗时也只有20+ms,这时开始怀疑brpc是不是编译得有问题,或者说和libtorch编译到一起不兼容。
这时我请教了一位同事,他对brpc比较熟悉,然后他说是curl实现的问题,和brpc没关系, 参考 brpc issue
curl传输的时候,会设置 Expect: 100-continue, 这个协议brpc本身没有实现, 所以curl会等待一个超时。 |
所以这个1s超时是个烟雾弹,线上client是Python,不会有这个问题。
接着又是一通疯狂测试,各种角度体位测试。发现本机测试是ok的,透过lvs请求(跨机房)就会卡住直到超时,而且小body请求一切正常,大body请求卡住。
这时又开始怀疑brpc编译的不对,导致这个超时(brpc编译过程比较曲折,导致我不太有信心)。
于是我在这个服务器Docker中运行了另一个brpc服务,发现是一样的问题。
为了确认是否是brpc的问题,又写了个Python 的 echo server 进行测试,发现在docker中是一样的问题,但是不在docker中运行有一切正常。
这是时就可以确定,与代码无关,是docker或者lvs的问题,一度陷入僵局。
完全没思路,于是找来了运维的同学,这时运维提了一下,这个机房的mtu会小一点,于是一切串起来了。
马上测试,发现机房的mtu只有1450,而docker0网桥的默认mtu是1500,这就很能解释小body没问题,大body卡死。
~# netstat -i |
修改docker0的mtu,重启docker.service,一切问题都解决了。
cat << EOF > docker/daemon.json |
这句话说得并不太懂,应该是“找到问题等于解决了90%”。
在互联网时代,找到了具体问题,在一通Google,基本等于解决了问题。你始终要确信,这个问题不应该只有你遇到。
在这个例子上,curl的误导大概花了一半的时间去定位。所以,定位问题首先得明确问题,如医生看病一样,确认问题发生的现象(卡住),位置(docker容器中)、程度(永久)、触发原因(大请求body)。
找专业人士寻求帮助是非常高效的,会大大缩短定位问题的时间,因为他们会运用经验和知识快速排除错误选项。
你所拥有的知识,并不是究竟的知识,也就是它所能应用的范围并不适应当前的场景,还有可能误导你。
你所缺失的知识,让你看不清前方正确的道路。
按我的知识,docker0是网桥,等价于交换机,除了性能问题,不应该导致丢包,就一直没往这个方向考虑(当然这块的知识也不扎实)。
MTU也不应该导致丢包,交换机应该会进行IP分片。
但忽略了一个点,虚拟的网桥并不是硬件网桥,可能并没有实现IP分片的逻辑(仅丢弃),又或者没有实现 PMTU(Path MTU Discovery)。
上面这点存疑,但更直接的原因是服务的IP包的 Don't fragment
flag 为1,也就是禁止分片(为什么设置还不清楚)。
最近接手了一个82年的拉菲Python项目,简直酸爽。
Python版本用的是2.7.5,里面用的第三方库各种老旧,潜藏了不少深坑。这次我们就先讲enum库的坑,其他有空再聊。
enum是一个枚举类型的第三方库,在Python3.4以后就作为官方库存在了,使用参考。
所以如果用新版本的Python就不会存在这个问题了,版本老旧害死人啊。
刚接手这个项目,在搭建环境的时候,装完依赖发现enum库不能按照预期工作(没有完善的requirements.txt是个坑)
正式环境import sys
sys.version
'2.7.5 (default, Nov 2 2019, 14:10:22) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-23)]'
from enum import Enum
class Weekday(Enum):
0 # Sun的value被设定为0 Sun =
1 Mon =
2 Tue =
3 Wed =
4 Thu =
5 Fri =
6 Sat =
type(Weekday.Sun)
<type 'int'>
Weekday.Sun
0
开发环境
import sys |
很明显可以看到,Weekday.Sun
的行为在两个环境不一致(其实第一个库用法不对)。
Weekday.Sun
的类型是int
Weekday.Sun
的类型是<enum 'Weekday'>
一开始我以为的enum库的bug,在不同版本的Python中行为不一致。
打开库源码路径一看,还真是大吃一惊。
原来两个环境的enum库压根就不一样,实现方式也不太一样,所以行为不一样。
安装的是:enum==0.4.7 pypi
目录结构:enum.py
enum-0.4.7-py2.7.egg-info
├── PKG-INFO
├── SOURCES.txt
├── dependency_links.txt
├── installed-files.txt
├── not-zip-safe
├── requires.txt
└── top_level.txt
只包含了enum.py这个文件,而且里面的代码实现很糟糕,和Python官方库的行为不一致。
也没找到源码管理仓库,总体来说质量糟糕。
安装的是:enum34==1.1.6 pypi
目录结构:enum
├── LICENSE
├── README
├── __init__.py
└── __init__.pyc
enum34-1.1.6.dist-info
├── DESCRIPTION.rst
├── INSTALLER
├── METADATA
├── RECORD
├── WHEEL
├── metadata.json
└── top_level.txt
和上面的库不同的是,这个库是个Python包,通过元类的方法实现枚举类,行为和Python3的官方库一致。
代码质量较高,并且有Python2和Python3的兼容代码。
这个老项目用的是第一个库,牵涉的东西比较多,暂时不能改动,只能将错就错。
这个两个问题都与Python Zen的“显式胜于隐含”的理念相冲突,这个是很不应该的。
在同一目录存在同名包和模块的情况下,疑似Python会优先加载包,没有查到确切的文档。
具体的加载行为,后面看一下源码分析一下。
一开始调侃说,82年的Python项目。其实这个项目是在2017年左右启动的,但是里面使用的大部分是过时的技术。
为何呢?无非是舒适区,或者说紧跟时代是一件挺难的事情。
]]>当执行 docker stop xxx
时,docker会向主进程(pid=1)发送 SIGTERM
信号
如果在一定时间(默认为10s)内进程没有退出,会进一步发送 SIGKILL
直接杀死程序,该信号既不能被捕捉也不能被忽略。
一般的web框架或者rpc框架都集成了 SIGTERM
信号处理程序, 一般不用担心优雅退出的问题。
但是如果你的容器内有多个程序(称为胖容器,一般不推荐),那么就需要做一些操作保证所有程序优雅退出。
信号是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。
应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。
常见信号:
信号名称 | 信号数 | 描述 | 默认操作 |
---|---|---|---|
SIGHUP | 1 | 当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 | 终止进程 |
SIGINT | 2 | 程序终止(interrupt)信号,在用户键入 Ctrl+C 时发出。 | 终止进程 |
SIGQUIT | 3 | 和SIGINT类似,但由QUIT字符(通常是Ctrl /)来控制。 | 终止进程并dump core |
SIGFPE | 8 | 在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术错误。 | 终止进程并dump core |
SIGKILL | 9 | 用来立即结束程序的运行。本信号不能被阻塞,处理和忽略。 | 终止进程 |
SIGALRM | 14 | 时钟定时信号,计算的是实际的时间或时钟时间。alarm 函数使用该信号。 | 终止进程 |
SIGTERM | 15 | 通常用来要求程序自己正常退出;kill 命令缺省产生这个信号。 | 终止进程 |
下面以 supervisor 为例,Dockerfile 如下
FROM centos:centos7 |
正常情况,容器退出时supervisor启动的其他程序并不会收到 SIGTERM
信号,导致子程序直接退出了。
这里使用 trap
对程序的异常处理进行包装trap <siginal handler> <signal 1> <signal 2> ...
新建一个初始化脚本,init.sh
/usr/bin/supervisord -n -c /etc/supervisord.conf &
trap "supervisorctl stop all && sleep 3" TERM INT
wait
修改 ENTRYPOINT 为如下ENTRYPOINT ["sh", "/root/init.sh"]
对比数据,可以发现和请求中的某个时间字段有关。关键逻辑代码如下,主要是一个时间差的计算。// string date_str = "2019-01-01 00:00:00";
// string date_appl = "2012-01-01 00:00:00";
double test_a(string& date_str, string& date_appl, tm& tm_appl) {
stringstream ss(date_str.substr(0,10));
int date_sec;
ss >> date_sec; // stringstream to int
time_t ts_date_1 = date_sec + 8*3600;
tm* tm_date = gmtime(&ts_date_1); // timestemp to tm
tm_date->tm_hour = 0;
tm_date->tm_min = 0;
tm_date->tm_sec = 0;
double seconds = difftime(mktime(&tm_appl), mktime(tm_date)); // diff timestemp
return seconds;
}
根据之前的经验,肯定是该函数内部某些操作非线程安全的, 通过google搜索(关键词: gmtime thread safe)和询问朋友,得到以下信息。
bug应该是来自gmtime
,该函数返回的是tm结构体指针,指向的是一个 static 结构体,所以不是线程安全,可以用gmtime_r
函数替换。
修改之后double test_a(string& date_str, string& date_appl, tm& tm_appl) {
stringstream ss(date_str.substr(0,10));
int date_sec;
ss >> date_sec; // stringstream to int
time_t ts_date_1 = date_sec + 8*3600;
tm tm_date;
gmtime_r(&ts_date_1, &tm_date);
tm_date.tm_hour = 0;
tm_date.tm_min = 0;
tm_date.tm_sec = 0;
double seconds = difftime(mktime(&tm_appl), mktime(&tm_date));
}
经过测试,bug得以解决。但是同时发现一个问题,程序的qps下降了1/3。
编程测试代码测试对比gmtime_r
和gmtime
耗时上并没有显著差别。而且如果不调用gmtime_r
只声明tm tm_date
,qps也是一样下降。
考虑到这个函数,在服务中调用次数比较多,而且自动变量的栈空间在函数调用时就会分配, tm结构体又比较大,应该对耗时有影响。
尝试修改tm tm_date
为static tm tm_date
静态分配内存, qps恢复正常了。
但是由于我们需要在多线程环境中使用,最终修改为static thread_local tm tm_date
。
在浏览器中打开/search.xml发现以下错误。显然xml中有非法字符,xml解析产生了错误。
将search.xml文件保存,并用python打开,找到具体出错的位置。
utf8解码之后可以发现\x10
非法字符,将其删除,重新生成文章问题解决。
'./tmp.xml', 'rb').read() xxx = open( |
svn和git都是常用的版本管理软件,但是git无论在理念或是功能上都比svn更为先进。但是有的公司是以svn作为中央仓库,这时git与svn代码的同步就可以通过 git-svn这个软件进行,从而用git管理svn代码。最后的效果相当于把svn仓库当作git的一个remote(远程仓库),而你本地的代码都是通过git来管理,只有push到svn时才会把你本地的commit同步到svn。
首先看一看用于测试的svn项目结构,svn的仓库路径是file:///d/Projects/svn_repo
,可以用svnadmin create svn_repo
命令新建。该仓库有2个分支,1个tag,属于svn标准布局。
SVN项目结构:/d/proj1
├── branches
│ ├── a
│ │ └── readme.txt
│ └── b
│ ├── 11.txt
│ └── readme.txt
├── tags
│ └── v1.0
│ ├── 11.txt
│ └── readme.txt
└── trunk
└── readme.txt
命令格式:git svn clone <svn仓库路径> [本地文件夹名] [其他参数]
相当于git clone
示例: git svn clone file:///d/Projects/svn_repo proj1_git -s --prefix=svn/
参数说明:
-s
告诉 Git 该 Subversion 仓库遵循了基本的分支和标签命名法则,也就是标准布局。-s
参数其实是-T trunk -b branches -t tags
的缩写,这些参数告诉git这些文件夹与git分支、tag、master的对应关系。--prefix=svn/
给svn的所有remote名称增加了一个前缀svn,这样比较统一,而且可以防止warning: refname 'xxx' is ambiguous.
现在,看下用git-svn克隆的项目情况(运行git branch -a),此处git的分支情况是与svn文件夹对应的。* master
remotes a
remotes b
remotes.0 tags/v1
remotes trunk
如果svn上的commit次数非常多, git svn clone 就会非常慢,一般超过几百个版本就要大概十分钟。此时可以在clone的时候只下载部分版本,
命令:git svn clone -r<开始版本号>:<结束版本号> <svn项目地址> [其他参数]
示例:git svn clone -r2:HEAD file:///d/Projects/svn_repo proj1_git -s
说明:其中2为svn版本号,HEAD代表最新版本号,就是只下载svn服务器上版本2到最新的版本的代码.
简单来说就是,首次新建分支会记录和svn远程对应分支的追踪关系,之后你的所有commit都是在本地的;并且和纯git管理的项目没有区别,只是在git svn rebase
和git svn dcommit
的时候才会和svn仓库发生关系
git checkout -b <本地分支名称> <远程分支名称>
git checkout -b a svn/a
git svn rebase
从svn上更新代码, 相当于svn的update。git svn dcommit
提交你的commit到svn远程仓库,建议提交前都先运行下git svn rebase。git chechout -b a svn/a
此处新建了一个本地分支a,与svn的a分支对应。git checkout -b feature1
在a分支的基础上,开一个本地feture1分支git svn rebase
和 git svn dcommit
,这样feature1的commit也会提交到svn的a分支上。命令:git svn branch <分支名称>
示例:git svn branch c_by_git
说明:在svn仓库上建了了一个c_by_git分支
分支情况 a
* master
remotes a
remotes b
remotes c_by_git
remotes.0 tags/v1
remotes trunk
svn rm <svn分支路径> -m <commit信息>
svn rm file:///d/Projects/svn_repo/branches/c_by_git -m 'rm branch'
git branch -D -r <远程分支名称>
git branch -D -r svn/c_by_git
命令:git svn tag <tag名称>
示例:git svn tag v1.1
说明:在svn仓库上建了一个v1.1tag
删除svn目录svn rm <svntag路径> -m <commit信息>
示例:svn rm file:///d/Projects/svn_repo/tags/v1.1 -m 'rm tag'
删除远程跟踪分支git branch -D -r <远程分支名称>
示例:git branch -D -r svn/tags/v1.1
说明:svn的tag和分支在git看来是一样的,所以此处还是用的git branch
如果本地和svn都进行了修改,则不能快速前进,git svn rebase 会出现错误。
这时应该按以下步骤操作:
手动修改冲突文件,修改完成后git add
git rebase --continue
git svn dcommit
以上讲的都是svn仓库是标准的情况,如果不标准,则以下几个地方都会有所不同。主要就是每个步骤基本都要添加svn的具体路径。
先看看,示例项目的结构,仓库路径是file:///d/Projects/svn_repo2
。这个项目主分支是dev文件夹,branch1和tag1文件夹分别代表的是一个分支和tag。
svn项目结构:/d/proj2
├── branch1
│ └── file1.txt
├── dev
│ └── file1.txt
└── tag1
└── file1.txt
命令:git svn clone <svn项目地址,要包含具体分支路径> [本地文件夹名]
示例:git svn clone file:///d/Projects/svn_repo2/dev proj2_svn
命令:
git config --add svn-remote.<远程分支名称>.url <svn地址,要包含具体分支路径>
git config --add svn-remote.<远程分支名称>.fetch :refs/remotes/<远程分支名称>
示例:
git config --add svn-remote.svn/branch1.url file:///d/Projects/svn_repo2/branch1
git config --add svn-remote.svn/branch1.fetch :refs/remotes/svn/branch1
说明:此处的“远程分支名称”可以随意填写,只要这三个保持一致即可。建议都给他们增加svn/
前缀,这样svn的所有分支显示起来会比较一致,与上面clone时的--prefix=svn/
类似。
命令:
git svn fetch <远程分支名称>
获取svn仓库该分支的代码git checkout -b <本地分支名> <远程分支名称>
示例:
git svn fetch svn/branch1
git checkout -b branch1 svn/branch1
分支情况:* branch1
master
remotes/git-svn
remotes/svn/branch1
如果都从源码编译安装软件,依赖的维护过于复杂,初始编译工具链的版本可能也不满足需求,如 gcc 版本过低。
如果申请 sudo 权限或者请求更新系统或安装 docker,后期责任难以界定,运维和管理员一般也不会同意。
所以,最优方案还是有需求的用户在个人目录维护自己的工具链和环境。下文方案为围绕 HomeBrew 构建。
如果你的系统比较新,可以直接尝试安装 HomeBrew
。
基于上面讨论的内容,公用服务器一般存在系统版本低的问题,是 centos7 或者 centos6 也毫不稀奇,而且如 glibc 等库的版本也非常低。
安装 HomeBrew 有两个强依赖,git 及 curl,而且依赖的版本都比较高,centos7 的版本也不能满足。
另外,由于 Brew 不少软件都需要从源码编译,gcc 和良好的网络环境也不可缺少。
幸好 miniconda 能够解决以上几点问题。miniconda 只是提供 HomeBrew 安装的依赖,后续可以删除。
配置 conda 源(可选): 新建 .condarc
,包含以下内容
channels: |
下载及安装 miniconda
下载 |
这些环境变量也可以配置到 bashrc 等文件,使之永久生效
设置 curl 和 git,可选 |
由于没有 root 权限,HomeBrew 需要手动安装。
由于是手动安装,位置与默认安装位置不同,很多预编译的包就不能用了,都得从源码编译,所以网络和机器性能以及耐心很重要。
下载,也可使用清华源 https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git |
在科学记数法中,一个数被写成一个”实数”与一个10的n
次幂的积
$$\pm a \times 10 ^ n$$
其中:
[1, 10)
区间内的实数,可称为有效数或尾数。类似的,二进制的科学计数法则是 $\pm a \times 2 ^ n$ ,不同的是 $a$ 必须是[1, 2)
区间内的实数。
所以浮点数的二进制表示,就是用二进制位表示为 $\pm a \times 2 ^ n$ 。
所以,我们可以将一定长度的二进制位分成三个部分,用来分别表示 $\pm$、$n$、$a$ ,而IEEE 754就是具体实现的标准。
IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。
IEEE 754规定,对于32位的浮点数,最高的1位表示符号 $\pm$ 记为s
(sign),接着的8位表示指数 $n$ 记为E
(exponent),剩下的23位为有效数 $a$ 记为M
(fraction)。
8位二进制位,能表示的256个数值,由于指数有正有负,标准规定从-127开始计数,也就是-127到128(与有符号数的实现不同),而且-127和128被用作特殊值处理,见下方特殊值。
同时,由于M的整数部分永远是1,我们可以只表示其小数部分,记为N
。
也就是最终可表示为 $s \times 2 ^ E \times (1+N)$ 。
具体各部分拆解如下,其中 $a_0$ 到 $a_{31}$ 对应32个二进制位的值,为0
或者1
。
$$s = (-1)^{a_{0}}$$
$$E = -127 + a_{1}\times 2^{7} + a_{2}\times 2^{6} + \dots + a_{8}\times2^0$$
$$N = a_{9}\times 2^{-1} + a_{10}\times 2^{-2} + \dots + a_{31}\times2^{-23}$$
以十进制的 $-5.0$ 为例,可表示为 $-1.25 \times 2 ^ 2$。那么,s=1,N=0.25,E=2。
具体来说
$$s=(-1)^1=-1$$
$$E=-127 + 2^7 + 2^0 = 2$$
$$N=2^{-2}=0.25$$
这里有三个特殊值需要指出:
全为1
并且尾数的小数部分是0,这个数是±∞(同样和符号位相关)全为1
并且尾数的小数部分非0,这个数表示为非数(NaN)。单精度浮点数各种极值情况:
类别 | 正负号 | 实际指数 | 有偏移指数 | 指数域 | 尾数域 | 数值 |
---|---|---|---|---|---|---|
零 | 0 | -127 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | 0.0 |
负零 | 1 | -127 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | −0.0 |
1 | 0 | 0 | 127 | 0111 1111 | 000 0000 0000 0000 0000 0000 | 1.0 |
-1 | 1 | 0 | 127 | 0111 1111 | 000 0000 0000 0000 0000 0000 | −1.0 |
最小的非规约数 | * | -126 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0001 | ±2−23 × 2−126 = ±2−149 ≈ ±1.4×10-45 |
中间大小的非规约数 | * | -126 | 0 | 0000 0000 | 100 0000 0000 0000 0000 0000 | ±2−1 × 2−126 = ±2−127 ≈ ±5.88×10-39 |
最大的非规约数 | * | -126 | 0 | 0000 0000 | 111 1111 1111 1111 1111 1111 | ±(1−2−23) × 2−126 ≈ ±1.18×10-38 |
最小的规约数 | * | -126 | 1 | 0000 0001 | 000 0000 0000 0000 0000 0000 | ±2−126 ≈ ±1.18×10-38 |
最大的规约数 | * | 127 | 254 | 1111 1110 | 111 1111 1111 1111 1111 1111 | ±(2−2−23) × 2127 ≈ ±3.4×1038 |
正无穷 | 0 | 128 | 255 | 1111 1111 | 000 0000 0000 0000 0000 0000 | +∞ |
负无穷 | 1 | 128 | 255 | 1111 1111 | 000 0000 0000 0000 0000 0000 | −∞ |
NaN | * | 128 | 255 | 1111 1111 | non zero | NaN |
* 符号位可以为0或1 . |
def binary_to_float(data): |
NAS的文件系统一直是我比较纠结的一个点。NAS的系统基本上是基于Linux(Unix),文件系统不是ntfs,数据迁移不方便,数据恢复工具也没那么全。
WSL就完美解决了这个问题,用Linux提供服务,数据最终还是落在ntfs上,而且重要的是everything也能用上。
openmediavault(OMV)是一个基于Debian的NAS系统,而且能在原生Debian系统上自行安装,正好能够实现我们的功能。
需要安装WSL2并启用桥接网络,同时安装好Debian系统
参考本人这篇文章
由于openmediavault对systemd有强依赖,而WSL的系统默认是由/init
启动的,会导致安装出错
所以得先把systemd的问题解决。
经过一番google,找到了systemd-genie能够解决问题
systemd-genie 依赖 dotnet-runtime-5.0, 所以把dotnet源配好,参考文档
wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb |
官网提供了安装脚本, 执行完会配置好systemd-genie源并安装curl -s https://packagecloud.io/install/repositories/arkane-systems/wsl-translinux/script.deb.sh | sudo bash
但是安装过程中,发现systemd-genie源的访问有些问题,所以只能从gitbub下载安装cd /tmp && wget https://github.com/arkane-systems/genie/releases/download/1.30/systemd-genie_1.30_amd64.deb
dpkg -i systemd-genie_1.30_amd64.deb
apt --fix-broken install
修改/root/.bashrc
, 添加以下内容if [[ ! -v INSIDE_GENIE ]]; then
echo "Starting genie"
exec /usr/bin/genie -s
fi
主机名和网口配置重启WSL后可能会失效,建议修改配置文件来实现
修改/etc/wsl.conf
,增加hostname = openmediavault
[network]
hostname = openmediavault
generateResolvConf = false
修改/etc/genie.ini
,设置update-hostname
为false[genie]
secure-path=/lib/systemd:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
unshare=/usr/bin/unshare
update-hostname=false
clone-path=false
clone-env=WSL_DISTRO_NAME,WSL_INTEROP,WSLENV
注意,由于WSL的mac地址每次重启都会变,该问题暂时无法解决,参考issue
所以这里采用静态IPv4地址和DHCPv6来配置网络地址
新建/etc/systemd/network/lan.network
,内容如下[Match]
Name=eth0
[Network]
Description=lan
DHCP=ipv6
Address=192.168.123.31/24
Gateway=192.168.123.1
DNS=192.168.123.1
LLDP=true
EmitLLDP=true
然后执行执行systemctl enable systemd-networkd && systemctl start systemd-networkd
参考官方文档
由于要安装的包比较多,建议先把Debian软件源替换成国内镜像,参考这个
添加软件源keyring
apt-get install --yes gnupg |
添加openmediavault软件源
cat <<EOF >> /etc/apt/sources.list.d/openmediavault.list |
安装
export LANG=C.UTF-8 |
参考官方文档wget -O - https://github.com/OpenMediaVault-Plugin-Developers/packages/raw/master/install | bash
OMV里的主机名和网络配置就配置成和上面配置文件里的一样,防止OMV的服务工作异常
修改主机名,防止WSL默认主机名过长(必须小于15位),导致samba配置失败
打开系统 -> 网络 -> 添加 -> 以太网
,配置网口ip,这里配置为DHCP自动获取
打开系统 -> 常规设置 -> Web管理员密码
,修改管理员密码,默认密码为admin:openmediavault
由于WSL2读写本机硬盘是使用的微软的驱动,OMV并不支持,默认只能识别到根目录的虚拟硬盘。
又因为WSL2的本地硬盘都挂载在/mnt/
路径下,所以只要能将/mnt/
目录共享就ok了
这里参考openmediavault-sharerootfs的实现
修改/etc/openmediavault/config.xml
配置文件, 在fstab
标签下增加mntent
挂载点配置,修改完如下<!--省略其他部分-->
<fstab>
<mntent>
<uuid>79684322-3eac-11ea-a974-63a080abab18</uuid>
<fsname>/dev/sdb</fsname>
<dir>/</dir>
<type>ext4</type>
<opts>errors=remount-ro</opts>
<freq>0</freq>
<passno>1</passno>
<hidden>1</hidden>
</mntent>
</fstab>
打开访问权限管理 -> 共享文件夹 -> 添加
,添加D盘(/mnt/d)作为共享文件夹,然后在samba服务中就能引用这个共享文件夹了
IPython项目起初是Fernando Pérez在2001年的一个用以加强和Python交互的子项目。在随后的16年中,它成为了Python数据栈最重要的工具之一。
简单来说,我们可以把IPython当成一个学习Python语言、数据分析、机器学习的平台。
这里我们使用miniconda,防止机器学习和图形显示相关的库出问题. 使用pyenv的同学,也可以用pyenv来安装miniconda。
下载:wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh
安装: sh Miniconda3-latest-MacOSX-x86_64.sh
conda create -n ipython
conda activate ipython
pip install ipython pandas ipykernel matplotlib
VSCode在安装Python插件之后,就可以很方便得查看编辑IPython Notebook了
在扩展商店,搜索“Python”, 并安装
选择IPython环境
创建hello.ipynb
Notebook
一个典型的例子就是,SSH 登录远程计算机,打开一个远程窗口执行命令。这时,网络突然断线,再次登录的时候,是找不回上一次执行的命令的。因为上一次 SSH 会话已经终止了,里面的进程也随之消失了。
Tmux 可以维持和管理我们的远程终端会话,和服务断线重连后也不会丢失工作状态, 同时可以在一个终端连接中开启多个窗口(window)和窗格(pane)。
比如,下面就包含了2个窗口和3个窗格
具体 Tmux 的细节和使用可以参考 阮一峰的文章
但是 Tmux 也存在以下几个问题(个人观点)
而 iTerm2 内置了 Tmux 绑定功能,可以将 tmux 的窗口和窗格映射成原生的窗口和窗格,可以用 iTerm2 的菜单和快捷键来操作窗口,解决了前3点问题。
至于第4点,rzsz 由于 tmux 的实现机制决定了是无解的。
然而 lonnywong 实现了替代方案 trzsz ,完美解决了文件上传下载的问题,亲测非常好用。
可以对 tmux 窗口的映射进行一些定制
iTerm2 对于 tmux 会话有一个profile,建议对终端颜色和外观进行一些定制化,将原生窗口和 tmux 窗口区分开来。
对于终端机器的 Tmux 版本有要求,需要支持-CC
命令
具体方法
tmux -CC
tmux -CC attach
tmux -CC new -A -s main
ssh -t user@host 'tmux -CC new -A -s main'
还可以使用 iTerm 的 tmux dashboard 来管理多个会话。
echo 'set -g default-terminal "screen-256color"' >> ~/.tmux.conf
限流的目的是通过对并发请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队 或等待、降级。
一般使用的限流算法有漏桶(Leaky Bucket)和令牌桶(Token Bucket)。
这里有个需要注意的点,这两种算法的名称和示意图都是为了便于理解,实现时并不需要一模一样。
漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter )时,可以用于流量整形 (Traffic Shaping ) 和流量控制 (Traffic Policing ),漏桶算法的描述如下。
漏桶的关键在于漏出速度恒定,超出的流量会被丢弃,最终请求看起来会是这样,峰值完全被砍掉了,过于粗暴了,适用的场景不多。
网上常见的一种实现是用个队列直接存储请求来模拟漏桶,其实大可不必,内存空间占用大而且效率低。
其实只需要通过一个队列记录请求时间,结合漏桶漏出速率进行计算,然后移动窗口就可以实现漏桶了。
这里的实现对漏桶进行了简化,漏桶的容量恰好等于单位时间漏出的量。
from time import time, sleep |
令牌桶算法,是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。
令牌桶算法的描述如下。
由于令牌放置速度恒定,取出速度不限,所以令牌桶的限流是有一定弹性的,能够接受请求的一定波动。
通过计算令牌桶容量和产生速率就可以实现令牌桶,并不需要真的实现“把令牌放桶里”和“取出令牌”。
from time import time, sleep |
注意:以上算法都是单机单线程的实现,如果需要多个机器限流则需要将桶的状态通过redis等外部服务来存储。
换个角度思考,这句话用在学习上,是再贴切不过了。当我们接触一个新领域时,其中的每个知识细节,都是魔鬼,稍有不慎就会被魔鬼击败,产生厌学情绪,再起不能。只有除尽这些魔鬼,才能攀上知识的高峰。
但光学会这些细节的点还远远不够,你的知识都是僵化的,是零散不成体系的的,难以真正应用。
就如将英语单词表背得滚瓜烂熟,倘若不识得语法与文法,断然是写不出文章的。
总地来说,学习分为两个阶段,“为学日益”和“为道日损”,也就是积累具体细节知识点,然后再将这些知识点进行归纳总结整理,将知识抽象并构建体系。
注:此处的“为学日益”和“为道日损”与道德经中原义不同
华罗庚教授曾把读书的过程归纳为“由薄到厚”与“由厚到薄”两个阶段,也是异曲同工;硅谷钢铁侠马斯克常说的“第一性原理”,就是第二步“为道日损”所得到的结果;再进一步,禅宗所说的“渐修”和“顿悟”也是一样的。
这两个阶段总体上是同等重要的,但在学习的不同阶段又该有所侧重。
下面将理论展开论述,我们又能引申出一些结论,得到一些新的看法。
先讲第一阶段,这一阶段积累具体细节知识点,那么多大规模的知识点可以成为一个细节呢?
这点因人而异,因当前知识储备而异,评判标准就是能否说出该知识点的定义和用途,如若不能就应该继续往下看构成该知识点的子知识点,直至满足标准为止。
由此可知,学习是有极限的,当最小的知识点都不能领会时,继续学习就无从谈起了。
就如数学,当不能理解1 + 1 = 2
时,在这之上构建的一切都与你无缘了。
所以学习还是要靠智商的,只不过要求极低。
在这个阶段比较有效的方法有
当学习到达一定的阶段,就该对知识进行归纳整理,以便进入下一阶段的学习。
因为人脑所能同时关注的知识点是有上限的,如果不加以归纳整理进行抽象,体现出来就是学了新的忘了旧的。
日常生活的很多场景都与这个有关,比如人们在争辩的时候经常会扣帽子,这就是一种抽象,降低了受众的认知难度,在传播中是有利的。“xx人偷井盖”,这就是典型的扣帽子,一个地方人何其多,每个人又不一样,贴切地描述出这个整体是非常难的,扣帽子就是舍弃了其他特征,用某几个特征来概括整体,这样带了的坏处就是不准确,失之偏颇。
这一阶段影响的是对知识的应用,也就是将一系列知识点抽象成一个整体,用于构造其他知识。
抽象有好的有坏的,知识点的名称就是对其内涵的一种抽象,好的抽象应该是可以望文生义的。就像写代码,好的函数命名应该是表达了函数的用途,也就是对具体实现该函数语句的抽象。
类比也是一种抽象,而且是一种极其高效的抽象,将具体的细节和已有的知识点关联起来。但类比也是危险的,很容易做出不合适的类比,也就是引喻失义。所以类比更像是方便法门,一条通向“第一性原理”的捷径,有其适用场景,不应该作为“第一性原理”本身牢记。
由此可以看出很多学习的方法其实都走在歧路上。
如学英语,一味在单词和例句中深挖,最终还是不能流畅的写作交流,这是第二阶段缺失造成的。
又如罗辑思维,听他灌输各种大而无当的大道理,只会显得很轻浮,除了增加谈资之外无有用处,这是第一阶段缺失造成的。
最后,希望我这胡言乱语对读者有些微帮助。
]]>有两种常见的实现方法
特点
这里的Python实现是方案1
具体步骤
from math import log, ceil |
运行 sudo passwd
根据提示输入root帐户密码。
运行 ls /usr/share/lightdm/lightdm.conf.d/ -al
-rw-r–r– 1 root root 72 12月 3 03:57 50-greeter-wrapper.conf
-rw-r–r– 1 root root 68 12月 3 03:57 50-guest-wrapper.conf
-rw-r–r– 1 root root 51 12月 3 03:57 50-xserver-command.conf
-rw-r–r– 1 root root 118 4月 16 17:08 60-lightdm-gtk-greeter.conf
就是greeter这个文件,不同的发行版可能名字不同。
运行 sudo gedit /usr/share/lightdm/lightdm.conf.d/60-lightdm-gtk-greeter.conf
改完以后是这样[SeatDefaults]
autologin-user=root
greeter-session=lightdm-gtk-greeter
greeter-show-manual-login=true
all-guest=false
/root/.profile
在刚修改完root权限自动登录后,发现可能开机出现以下提示:
Error found when loading /root/.profile |
运行 gedit /root/.profile
打开文件后找到mesg n
,将其更改为tty -s && mesg n
。
最近在开发的lightgbm树模型,发现服务在处理了一定量请求后会卡死,请求无响应。
pstack
之后发现, 进程卡在libgomp.so这个动态库的函数中. 证实确实是卡死
Thread 8 (Thread 0x7f8eb7900700 (LWP 1859)): |
首先尝试google lightgbm hang
, 看了前几条记录.
发现,github上的一个issue, 顺着发现官网文档上早就记录里这个问题, 并且提供了解决办法.
意思是说,OpenMP这个库在多线程fork的时候, 存在bug, 对于C/C++程序必须在fork完成之后再使用OpenMP的功能.
所以解决方法有两个
上面说了可以设置nthreads=1
, 但是在python库中, 我并没有找到如何设置.
查看文档之后发现, 可以通过pip参数编译安装单线程版本.
pip install lightgbm --install-option=--nomp |
安装完成后可以将site-packages中库文件复制备份, 方便部署. 参考路径
~/.pyenv/versions/3.6.9/lib/python3.6/site-packages/lightgbm |
配置intel工具链, 并从零开始编译, 整体流程比较复杂, 也不建议.
这里推荐使用conda来配置环境, 免去很多编译配置烦恼.
conda添加intel channel: conda config --add channels intel
安装相关库
conda create -n idp intelpython3_core python=3 |
存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。
计算机系统中的存储设备都被组织成了一个存储器层次结构,从上至下,设备的访问速度越来越慢、容量越来越大,并且越便宜。
高层的空间速度快容量小,为了充分利用,就必须有个数据替换的规则,决定数据的去留。这就是所谓的缓存淘汰算法。
常见的方法
Least Recently used(LRU) 是最常用的缓存淘汰算法,一般译为“最近最少使用”,不太贴切,其实应该是“最不是最近使用”,也就是将最近一次访问时间最远的数据淘汰掉。
LRU正好体现了时间局部性,也就是,如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。
既然是缓存自然需要数据结构记录key和value,可以使用hashmap来存储,查询和设置的复杂度为O(1)。
同时还需要记录数据最近一次访问时间的次序,可以想到用线性结构存储,由于频繁插入删除,可以用链表实现,新数据在头部,老数据在尾部。
由于需要频繁删除数据,而单向链表没有记录前驱节点信息,需要遍历链表,复杂度为O(N),所以使用双向链表。
具体的原则
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。
缓存污染,是指系统将不常用的数据从内存移到缓存,造成常用数据的挤出,降低了缓存效率的现象。
常见改进算法有LFU,LRU-K
首先需要实现双向链表,引入头节点,并将链表首未连在一起,这样插入和删除的时候就不需要额外判断链表的头部和尾部,简化了实现。
hashmap则使用Python的dict
class Node: |
在linux服务器上工作,常常和rm
打交道,难免手滑删除了重要的东西。而且linux又没有回收站功能,一旦删错东西真是欲哭无泪。别问我为什么知道,说多了都是泪。
主要思路是用mv
命令代替rm
,将要删除的东西移动到回收站目录。
mkdir ~/bin
vim ~/bin/trash
# 将下面的内容写入mv -v $@ ${trash_bin}
chmod +x ~/bin/trash
# 增加执行权限
# 将以下内容追加到"~/.bashrc"中,回收站是以每天的日期新建文件夹 |
苹果在10.14的系统上增加了密码最小长度的限制
可以通过这个命令去除
pwpolicy -clearaccountpolicies |
注意:去除限制之后,通过time machine备份的系统,还原到有限制的机器上,可能会导致一些bug。如卡死在登陆页面,部分应用的资源库损坏等等。
如果出现上面的问题,可以尝试重置下系统设置(选择任意一种,建议选1或2)
进入恢复模式(开机按command+r),删除对应用户账户配置文件(恢复模式下文件挂载路径可能不同), 重新开机
mv ~/Library/Preferences ~/Library/Preferences.bak |
清除机器配置状态,重新设置一个新账户(开机按住command+s;)
/sbin/mount -uaw |
sudo spctl --master-disable |
macOS 10.14 以后系统动画越来越复杂,对显卡要求比较高,导致配置比较老的设备运行起来很卡顿。
特别是big sur系统,使用intel核显的机器都不建议安装。
通过关闭一些动画和特效可以让系统更流畅些
使用 karabiner 可以对系统全局快捷键和应用快捷键进行修改。
通过配置 karabiner 将 command,control,option 按键进行重映射,可以让 macOS 的键位 和 Windows/Linux 接近。
macOS 的命令应该是 Unix 风格的,和 Linux 有些不同,可选参数必须放在前面,会有些不方便。
比如 mac
➜ ~ /bin/ls /tmp -al
ls: -al: No such file or directory
可以安装 gnu 工具,覆盖这些命令brew install coreutils findutils gnu-tar htop
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
export MANPATH="/usr/local/opt/coreutils/libexec/gnuman:$MANPATH"
创建文件 ~/Library/KeyBindings/DefaultKeyBinding.dict
这里是清除了默认的 Control + Command + 方向键
的行为
修改完成后必须重新打开现有app才能生效
{ |
字符含义
Prefix | Meaning |
---|---|
~ |
⌥ Option key |
$ |
⇧ Shift key |
^ |
^ Control key |
@ |
⌘ Command key |
# |
keys on number pad |
参考:
# 关闭提示音 |
用户执行crontab -e
之后定时任务存储位置/private/var/at/tabs/<username>
macos 默认没有设置hostname,导致终端PS1
显示时会用网卡的mac地址作为主机名,看起来比较别扭。
可以通过sudo scutil --set HostName <name>
设置想要的主机名
➜ ~ hostname |
在终端中输入defaults write com.apple.systempreferences AttentionPrefBundleIDs 0 ; killall Dock
在 /etc/hosts 中加入# disable mac update
0.0.0.0 swdist.apple.com.edgekey.net
0.0.0.0 swdist.apple.com.akadns.net
不是100%有效defaults write com.apple.Dock position-immutable -bool yes; killall Dock
._
开头的隐藏文件defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true |
鲁迅曾说过,悲剧是把美好的东西撕碎给人看。人生,又何尝不是呢。活着的故事虽然与时代背景有些关系,小人物在其中很是无力,被历史的洪流裹挟着近乎无路可走。但是,看当今的世上,在我们的视野之外,也还有无数的悲剧在发生。当悲剧发生在别人身上时,那是新闻,那是故事;当发生在你身上时,你却无处述说。所以,我很少看新闻,特别是报道苦难的新闻,看多了会觉得悲伤而又无力。
其实某个角度来说,社会一直都没有变过,也就是阶级。每个小镇上都龙二,也有富贵;只是后来他们的地位转换了。阶级就如同金字塔一般,每个人就其占有资源的多少处在不同的层级上,下层的人想往上爬,一切都是赤裸裸的丛林法则。每个人都在往上面看,羡慕上方的美好生活,有谁会往下看一眼,下面是一群穷困潦倒的富贵,如同蝼蚁,在苦苦挣扎。
只要有社会存在,阶级是必然存在的。假使都不算父辈积累的资源,从零开始,大家都处于通过生产物品进行交换的阶段。由于每个人天生是不同的,体力、智商、寿命等属性的总值是不同的,这样大家的生产率就会不同,几代几十代之后差距越来越大(和复利类似),阶级就不可避免地产生了。改朝换代也好,革命也好,都只是重新洗牌而已。
唉,越想越是悲哀。
]]>[mysqld] |
启动MySQL |
$ tree data |
mysql> use mysql; update user set password=password('m654321') where user='root'; flush privileges; |
这时我们可以把表的数据分区存储,安装数据值的前缀或者时间字段来分区。
CREATE TABLE test_part ( |
alter table test_part drop partition p1;
不可以删除hash或者key分区。
一次性删除多个分区,alter table test_part drop partition p1,p2;
ALTER TABLE test_part ADD partition (partition p20190306 VALUES LESS THAN (TO_DAYS(‘2019-03-07 00:00:00’)));
创建储存过程DROP PROCEDURE IF EXISTS proc1;
DELIMITER $$
SET AUTOCOMMIT = 0$$
CREATE PROCEDURE proc1()
BEGIN
DECLARE v_cnt DECIMAL (10) DEFAULT 0 ;
dd:LOOP
INSERT INTO test_part VALUES (
FLOOR(RAND()*100),
FLOOR(RAND()*1000),
UUID(),
DATE_ADD('2019-03-04 00:00:00', INTERVAL FLOOR(v_cnt / 5000) MINUTE)
);
COMMIT;
SET v_cnt = v_cnt+1 ;
IF v_cnt = 10000000 THEN LEAVE dd;
END IF;
END LOOP dd ;
END;$$
DELIMITER ;
```
调用储存过程
call proc1;`
我家里的路由器是矿渣newifi3, 刷了OpenWrt系统,可玩性还是非常强的。
而且路由器作为24小时在线的设备,很适合作为网络设备的控制中心,比如使用WOL唤醒其他设备。
之前就写过一个Python服务,用来控制其他设备的唤醒和睡眠。但是由于newifi3的rom空间十分有限,usb又十分不稳定,Python环境在路由器上还是太重了。
所以就想到了利用路由器默认的uhttpd网页服务器,自己编写CGI脚本来实现相应功能。
以下操作均需要ssh登陆到路由器后台
编辑/etc/config/uhttpd
文件
在config uhttpd main
部分添加list interpreter ".lua=/usr/bin/lua"
, 使uhttpd能够执行lua文件
重启uhttp服务:/etc/init.d/uhttpd restart
修改后的配置大致如下# Server configuration
config uhttpd main
# HTTP listen addresses, multiple allowed
list listen_http 0.0.0.0:80
list listen_http [::]:80
# HTTPS listen addresses, multiple allowed
list listen_https 0.0.0.0:443
list listen_https [::]:443
# Redirect HTTP requests to HTTPS if possible
option redirect_https 1
# Server document root
option home /www
# 此处省略其他配置
# List of extension->interpreter mappings.
# Files with an associated interpreter can
# be called outside of the CGI prefix and do
# not need to be executable.
# list interpreter ".php=/usr/bin/php-cgi"
# list interpreter ".cgi=/usr/bin/perl"
list interpreter ".lua=/usr/bin/lua" # 我们添加的内容
# 此处省略其他配置
从上面的配置可以看到,web的默认路径在/www
我们就在这里新建一个文件夹ctl
,来存放我们的CGI脚本, 用于实现NAS的睡眠和唤醒。
目录信息如下/www/
├── cgi-bin
│ └── luci
├── ctl
│ ├── sleep.lua
│ └── wakeup.lua
├── index.html
下面开始编写CGI脚本,需要复习以下CGI协议,可以参考本人的文章
再花几分钟熟悉一下lua语法
睡眠的实现是在NAS上写了个简单的Web服务,基于systemctl suspend
来实现睡眠
然后访问Web服务,触发NAS的睡眠。这里为了和下面的唤醒统一处理,所以通过路由器的CGI转发了一道,直接访问也是可以的。#!/usr/bin/lua
-- /www/ctl/sleep.lua
io.stdout:write("Content-Type: text/plain\r\n\r\n")
local status = os.execute("wget -q -O - --timeout 1 -t 1 http://192.168.123.100:8888/ctl/sleep > /dev/null")
if status == 0 then
io.stdout:write("sleep succ\n")
else
io.stdout:write("sleep fail, already sleep?\n")
end
使用:curl http://192.168.123.1/ctl/sleep.lua
唤醒是基于WOL,原理是在网络上广播特定格式的网络包,目标机器的网卡接受到以后唤醒机器。
#!/usr/bin/lua |
使用:curl http://192.168.123.1/ctl/wakeup.lua
最近终于找到一个接近完美的方案:Hyper-V + NFS + openmediavault
我对 NAS 几点硬性要求
所以之前一直用 windows 作为 NAS 系统,最大的问题是不支持共享文件夹的回收站。
现有 NAS 方案的对比
good | bad | |
---|---|---|
windows smb | 性能 | 无法扩展功能(如smb回收站,timemachine) |
wsl1 samba | 性能略低于smb,功能可扩展 | 部分共享文件无法打开, 配置略繁琐 |
wsl2 samba(omv) | 功能可扩展 | 无法解决桥接网络,需要更好的cpu |
nfs | 性能最强 | 无法扩展功能 |
群晖 / OMV / FreeNAS | 功能多,功能可扩展 | 无法管理底层文件 |
windows + nfs + vmware(群晖) | 可管理底层文件,功能可扩展 | 物理机休眠虚拟机无法唤醒 |
windows + nfs + hyper-v(omv) | 功能可扩展,性能接近smb,可休眠 | 需要更好的cpu,hyperv使用略繁琐 |
本方案的思路受之前文章的启发 在WSL2中安装openmediavault(OMV)
WSL2 的便利之处在于让 linux 系统能以一个足够快的速度访问 windows 文件,据了解是使用 9p(Plan 9 9p remote filesystem protocol) 协议进行文件共享。
那么我们只要在 Hyper-V 上运行虚拟机,并通过某种方式(这里选择NFS)共享文件到虚拟机里,就能绕开 WSL2 的缺点。
实现 NAS 的底层文件系统由 windows 管理, 上层文件共享等应用由虚拟机中的 NAS 系统处理,兼顾了稳定性和扩展性。
软件环境选择
DISM /Online /Enable-Feature /All /FeatureName:Microsoft-Hyper-V
安装 omv-extras
wget -O - https://github.com/OpenMediaVault-Plugin-Developers/packages/raw/master/install | bash |
安装插件:openmediavault-sharerootfs(共享系统分区的文件夹) openmediavault-remotemount(远程挂载)
配置页面:服务 -> SMB/CIFS -> 设置 -> 高级设置 -> 扩展选项
veto files = /.Trashes/$RECYCLE.BIN/System Volume Information/.recycle/ |
默认的回收站不能按天对文件进行分类,不方便进行清理
配置页面:服务 -> SMB/CIFS -> 共享 -> 共享文件夹
启用回收站选项,并在扩展选项中增加
recycle:repository = .recycle/%U/today |
windows 系统中,增加定时任务,删除NFS共享目录中的回收站文件到系统回收站
Python 代码如下,超过一定时间的文件会被送到 windows 回收站
import time |
也可考虑将上文 openmediavalut 替换成黑群晖系统,牺牲定制化能力的同时大大增加易用性。
当然,你也可以两者双修
先说一下,我现在玩GBA的平台是安卓的Pizza Boy GBA
模拟器加游戏手柄,总体游戏体验感觉比GBA真机还好,毕竟现在的设备机能放在这,而且还有倍速播放相关功能。
我这次玩的是口袋妖怪叶绿386
改版,在网上找的金手指虽然说是386版本金手指,但是经过实际测试都是叶绿原版并不支持386,比如这个。
金手指的原理其实和通用的单机游戏作弊工具(cheat engine, 金山游侠等)相同,都是修改游戏内存数据。其关键就是找到我们想改的数据的内存地址,所以常规方法都是通过工具搜索相关数值,比如金钱数量,一般要经过多次搜索,可以参考这个教程。
但GBA好像有些不同,我也曾用cheat engine搜索过,并不能找到相关的内存地址,可能是模拟器的机制导致数值并不能被直接搜索。而且安卓手机上由于权限的问题,类似cheat engine这种读内存的机制肯定是需要root的,这条路就行不通了
最后还是只能走金手指的路子了,毕竟模拟器原生支持,所以又回到了最初的问题,怎么找到正确的金手指。
幸好,Windows平台常用的GBA模拟器Visual Boy Advance(VBA)带有金手指查找功能,所以先用VBA找到正确的金手指,然后再转换成Piaaz Boy GBA
支持的金手指格式。
网上能找到的金手指一般是类似03005102:63
格式的,冒号前后表示的是16进制表示的地址和对应的值。而网上找的金手指不对,肯定是因为前面的地址不对。
用VBA加载口袋妖怪叶绿386,先过一段剧情,来到第一个城市(常磐市)的商店,查看商品列表,并选择一个商品。
打开菜单:金手指 -> 查找金手指,点击开始,然后输入21
,点击查找,可以看到有几十个变量。
增加想购买的数量到23
,然后再次输入23,点击查找,可以看到就剩余一个地址了,显然这个就是我们想要的,如果还有多个地址,就重复这两个流程,再次修改数量再查找。
选择这条地址记录,添加金手指,把数字设置为98,就会自动添加到金手指列表,并启用该金手指。
金手指列表,我们添加的处于启用状态
在游戏中确认购买,可以看到购买的数量变成98了,但是总价没有变,金手指生效了。
但是这金手指只能修改购买数量,不在商店列表里的商品并不能购买,所以我们必须找到当前选中商品对应的地址,再添加一条相关的金手指。
网上查到的金手指虽然地址不对,但是对应的值(道具的id)应该是对的,下面是口袋妖怪的一些道具id。0001 大师球
0002 超力怪兽球
0003 超级球(比怪兽球更厉害些)
0004 怪兽球(普通的球)
0005 砂狐球(砂狐乐园专用球)
0006 触网球(容易抓水和虫类的怪兽)
0007 大布斯球(容易抓海底的怪兽)
0008 尼斯道球(怪兽越弱越容易抓)
0009 利比道球(容易抓抓过的球)
000a 达伊玛球(回合数越长越容易抓)
000b 高基石球(抓到的怪兽变亲密)
000c 布雷密球(珍惜怪兽球)
000d 伤药(体力恢复20)
000e 解毒药(恢复毒状态)
由于已经找到了购买数量的地址,根据编程的常识,选中商品的地址应该也在这个地址附近。
所以打开工具 -> 内存查看器
定位到地址030050C2
在游戏中,分别选中“解毒药”和“伤药”,并观察内存变化(每次改动完点刷新)。
由上两张图可以看到,有个地址的值由000E
变为000D
,正好等于这两个道具的id。
所以该地址030050CA
就是我们想要的商品地址,我们就增加一条金手指030050CA:0001
将购买的商品改为大师球。
启用这两条金手指,在游戏中点击购买,可以看到生效了。
Pizza Boy GBA并不支持VBA格式的金手指,所以必须进行格式转换,由于VBA格式金手指和CodeBreaker格式金手指转换起来比较接近,只要将地址首位的0
替换为8
就行了。
转换前030050C2:0062
030050CA:0001
转换后830050C2 0062
830050CA 0001
以上查找方法理论上对所有GBA游戏都适用,祝大家玩得愉快!!!
一直在寻找比较好的番茄工作法工具,但是都不那么满意。
物理番茄钟,主要问题是不够灵活,比如调整番茄时长,而且不能和GTD清单同步。
手机app的话,番茄钟结束的提醒声音过于吵闹,特别在公共场合,比如公司或者图书馆;如果静音或者震动的话,又常常感知不到,导致经常关注番茄钟时间,不能集中精力。
也用过安卓智能手表,但目前番茄工作法相关应用还是很少,没有找到合适的,而且手表续航太短,高强度使用基本得每天充电,心智负担大。
之前也用过手机番茄app配合手环,通过手环震动提醒番茄钟结束,方法可行,但是通知经常触达不到,体验不好。
最近小米手环6上市了,经过几天摸索,发现配合滴答清单的专注功能,以及小米穿戴的通知提醒,总体体验挺好,通知也准确,应当是目前最好的番茄工作法方案了。
下面讲一下具体如何配置
小米穿戴app - 我的 - 开启通知相关权限
小米穿戴app - 我的 - 消息通知
小米穿戴app - 我的 - 设备更多设置 - 震动模式 - App通知提醒 - 添加震动模式(可选)
滴答清单app - 番茄专注 - 开始
pip install future -U
├── ReadMe.md |
这一步用于测试的解释器是Python2
这一步是避免使用过于古老的Python2语法,将项目代码升级为更现代的Python2代码。
执行: futurize --stage1 -w src tests
可能涉及的改动# 异常处理
- except Exception, err:
+ except Exception as err:
# 字典元素判断
- if lr_space_dict.has_key(str(feature)):
+ if str(feature) in lr_space_dict:
# import方式改变
- from workflow import WorkFlow
+ from .workflow import WorkFlow
# print
- print 'load model %s' % self.name
+ print('load model %s' % self.name)
这一步用于测试的解释器是Python2
一般在升级过程中,不直接移除Python2支持,否则一旦发现问题难以回滚。
也不方便确认,代码改动是否产生了非预期的变化。
执行:futurize --stage2 -x libfuturize.fixes.fix_unicode_keep_u -w src tests
注意:由于futurize关于unicode的处理存在一些bug,所以字符编码的我们自己单独处理(见后文)。这里的-x libfuturize.fixes.fix_unicode_keep_u
参数,跳过对unicode的自动处理,不将代码中的unicode替换为str。同时,我们要逐一去除文件中自动添加的from builtins import str
,避免类似import unicode as str
的行为。
可能涉及的改动:
# map() -> list(map()) |
这一步用于测试的解释器是Python2和Python3
正确处理字符的原则:
也就是:外部数据(字节)-> decode -> unicode -> encode -> 输出数据(字节
启用默认unicode支持:futurize --stage1 --unicode-literals src tests -w
涉及的改动:# 所有文件的头部会增加下面的语句,作用是将源码中的所有字符串视作unicode
# 也就是 "中" 会等效于 u"中", 不需要`u`作为unicode的前缀
+ from __future__ import unicode_literals
涉及的改动:# str() -> unicode()
- feature = str(feature)
+ feature = unicode(feature)
# open -> io.open
- with open(v) as f:
+ with io.open(v, encoding='utf8') as f:
# redis增加必要的decode代码
- return self.connection.get(key)
+ res = self.connection.get(key)
+ return res.decode('utf8') if res is not None else None
为了代码在Python2和Python3都正确运行,必须给Python增加unicode函数。
如果后面代码不需要Python2支持,则这一步的改动可以去除,并且把所有的unicode调用改为str即可。
实现import six
if six.PY2:
from __builtin__ import unicode
else:
class unicode(str):
def __new__(cls, unicode_or_bytes=''):
if isinstance(unicode_or_bytes, bytes):
return str.__new__(cls, unicode_or_bytes, encoding='utf8')
return str.__new__(cls, unicode_or_bytes)
这一步用于测试的解释器是Python2和Python3
python的字典遍历是不保证顺序的,不同版本解释器遍历顺序可能不同。
如果你的代码对遍历顺序有依赖,建议固定遍历顺序,可以使用OrderedDict,或者遍历前排序,或者指定遍历的key。
Python2和Python3的四舍五入行为不一样。
如果有数值处理相关的代码,建议做如下修复.
def python2round(number, ndigits=0): |
至此,Python2到Python3的迁移已然完成,你获得了支持python3和python2的代码。
值得小酌一杯
asyncio
是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio
的编程模型就是一个消息循环。我们从asyncio
模块中直接获取一个EventLoop
的引用,然后把需要执行的协程扔到EventLoop
中执行,就实现了异步IO。
import asyncio |
async def
定义的每一个函数, 本质上是一个协程.
当await调用一个函数时, 程序就切换到该函数中执行了, 当执行完成时, 程序又回到await调用处继续执行.
当函数的调用链上碰到系统io相关函数时,程序执行的控制权就会回到eventloop主循环,eventloop就会调度执行别的函数,等到该函数的io就绪时,再从该函数暂停的地方继续执行。
再搭配上支持非阻塞io的异步库, 这样就实现了高效的异步编程.
所有异步函数是在同一个线程中执行的, 在该进程内我们还可启用其他线程, 执行其他同步代码. 下图展示了Python中协程、线程、进程的逻辑关系。
一旦使用async, 整个线程内都必须使用异步, 否则整个线程都会阻塞。
所以,必须另起线程, 将同步操作放在线程中执行。
import asyncio |
输出hello world 1533904346.37133
hello world 1533904346.371683
hello world 1533904346.37199
hello world 1533904346.372059
hello world 1533904346.372122
hello world 1533904346.372176
hello world 1533904346.372245
hello world 1533904346.372295
num 0
num 3
num 4
num 1
num 5
num 6
num 7
num 2
Complete in 4.015289068222046 seconds
[Finished in 4.2s]
因为这里我的线程池大小为2, 所以8个sleep整体耗时4s, 但是’hello world’ 几乎是同一时刻输出的, 这就是asyncio的魅力所在.
asyncio使用async
和await
的语法,使得原来回调函数形式的异步代码变为同步代码,更易于理解。
使用asyncio可以方便地构建高性能的网络服务, 单进程qps可以轻松地达到2000以上。
基于asyncio也出现了一些异步web框架,比如sanic,相比传统框架性能提升较大。
支持的非常多语言,有Lua, Perl, PHP, Python, R, Ruby, C#, Java, JavaScript, Go, Scheme 等。
下面用到的C++代码
头文件/* utils.h */
using namespace std;
class Utils {
public:
Utils();
string Float2String(float Num);
};
具体实现代码/* utils.cpp */
Utils::Utils() {}
string Utils::Float2String(float Num)
{
ostringstream oss;
oss<<Num;
string str(oss.str());
return str;
}
/* example.i */ |
其中%include "std_string.i"
, 实现了c++的string自动转python的str
swig -c++ -python example.i |
注意编译example_wrap.cxx
时引入对应版本的python头文件,不同系统的路径可能有所不同。
import example |
循环import是很多Python初学者都会遇到问题,网上有也有很多文章讲解决方法,比如这篇,不清楚的可以自行查阅,这里就不赘述了。
那么,为啥老司机也会遇到这个问题呢?这段时间一直在搞把redis复刻一个python版本,在复刻代码时就遇到了这个问题。而且我也使用了延迟import,却没能解决。
下面我们来详细分析下
先看两段代码
run.pyimport logging
import sys
logging.basicConfig(level='DEBUG',
format='[{asctime} {module}.{funcName:<11}] {message}', style='{')
logging.info(sys.modules['__main__'])
logging.info('begin load')
class Server:
pass
server = Server()
def start():
from foo import do_someting
logging.info('call')
assert not hasattr(server, 'name')
server.name = 'aaa'
logging.info(repr(server))
logging.info('%s\t%s', repr(Server), id(Server))
do_someting()
logging.info('end load')
if __name__ == '__main__':
start()
foo.pyimport logging
import sys
logging.info('begin load')
def do_someting():
logging.info('begin call')
import run
logging.info(repr(run.server))
logging.info('%s\t%s', (run.Server), id(run.Server))
if hasattr(run.server, 'name'):
logging.info('found attr name')
else:
logging.info('not found attr name')
logging.info('end call')
logging.info('end load')
再看执行python run.py
的结果[2020-06-20 10:57:03,659 run.<module> ] <module '__main__' from '/Volumes/study/Projects/code_snippet/circular_import/run.py'>
[2020-06-20 10:57:03,659 run.<module> ] begin load
[2020-06-20 10:57:03,659 run.<module> ] end load
[2020-06-20 10:57:03,662 foo.<module> ] begin load
[2020-06-20 10:57:03,662 foo.<module> ] end load
[2020-06-20 10:57:03,662 run.start ] call
[2020-06-20 10:57:03,662 run.start ] <__main__.Server object at 0x1064eb940>
[2020-06-20 10:57:03,662 run.start ] <class '__main__.Server'> 140250245614784
[2020-06-20 10:57:03,662 foo.do_someting] begin call
[2020-06-20 10:57:03,663 run.<module> ] <module '__main__' from '/Volumes/study/Projects/code_snippet/circular_import/run.py'>
[2020-06-20 10:57:03,663 run.<module> ] begin load
[2020-06-20 10:57:03,663 run.<module> ] end load
[2020-06-20 10:57:03,663 foo.do_someting] <run.Server object at 0x1065d8b50>
[2020-06-20 10:57:03,663 foo.do_someting] <class 'run.Server'> 140250247610512
[2020-06-20 10:57:03,663 foo.do_someting] not found attr name
[2020-06-20 10:57:03,663 foo.do_someting] end call
这里已经用延迟导入,这个典型方法,解决了执行时报错的问题
但是,还是可以发现几个问题
先复习下import机制
import 语句结合了两个操作;它先搜索指定名称的模块,然后将搜索结果绑定到当前作用域中的名称。 import 语句的搜索操作定义为对 __import__()
函数的调用并带有适当的参数。 __import__()
的返回值会被用于执行 import 语句的名称绑定操作。
对 __import__()
的直接调用将仅执行模块搜索以及在找到时的模块创建操作。 不过也可能产生某些副作用,例如导入父包和更新各种缓存 (包括 sys.modules),只有 import 语句会执行名称绑定操作。
sys.modules
是一个字典,缓存了已加载的模型,以模块名称为key,模块对象为value。
执行import 语句时,先在sys.modules
缓存中查询该模块,如已存在者返回该对象,否则从文件系统中加载该模块。
[2020-06-20 10:57:03,662 run.start ] <__main__.Server object at 0x1064eb940> |
从上面的这行输出可以看出,当run作为程序入口时,模块名称变为了__main__
, 查看 sys.modules
,也只发现了__main__
,没有发现run
.
所以, 当do_someting
import run
模块时,肯定是发现没有加载,最终导致加载了两次,Server类id不一致也可以理解了。
所以只要能从sys.modules
正确地找到run模块,问题就可以解决。
具体来说有三种方法
修改foo.py, 把import run
改为import __main__ as run
import logging
import sys
logging.info('begin load')
def do_someting():
logging.info('begin call')
import __main__ as run
logging.info(repr(run.server))
logging.info('%s\t%s', (run.Server), id(run.Server))
if hasattr(run.server, 'name'):
logging.info('found attr name')
else:
logging.info('not found attr name')
logging.info('end call')
logging.info('end load')
修改sys.modules
,增加keyrun
,指向__main__
模块sys.modules['run'] = sys.modules['__main__']
启动文件单独使用一个文件,里面不包含其他代码。
这时__main__
模块变成了bar, 这时run模块的名称就不会改变了,import行为也就正常了
bar.pyfrom run import start
start()
上面三种方法,殊途同归,结果都是一样的。
[2020-06-20 11:43:31,508 run.<module> ] begin load |
因为C是编译型语言,可以理解为模块的导入在编译期就完成了,也就不会出现模块的循环依赖,而且全局对象的内存位置也在编译期就固定了。
而Python作为解释型语言,模块的导入加载和执行是混在一起的,所有对象都是可以更改的,也就容易出现这种问题。
切记:
复杂Python程序的入口文件最好保持单一的文件,不要混入其他对象定义,谨慎使用if __name__ == '__main__'
写法。
从大学开始接触Python,到现在也差不多四年了,也算小有所成。期间也有很多人问我如何学习Python,也只是零散地回答,刚好最近要做个Python的分享,就将这一块东西整理一下。
本文作为Python学习的指路文章,是个人在Python学习过程中的经验总结。
阅读的时候不需要太细致,略读即可。
读完后,能够对Python学习的各方面有个大致的概念,在学习过程少踩一些坑,收获就很大了。
Python(音:派森),是一种强类型的动态语言,由吉多·范罗苏姆 创造,第一版发布于 1991 年。
Python的创始人为吉多·范罗苏姆。1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承。之所以选中Python作为程序的名字,是因为他是BBC电视剧——蒙提·派森的飞行马戏团的爱好者。
吉多·范罗苏姆(荷兰语:Guido van Rossum,1956年1月31日-),生于荷兰哈勒姆,计算机程序员,为Python程序设计语言的最初设计者及主要架构师。
在Python社区,吉多·范罗苏姆被人们认为是“仁慈的独裁者”(BDFL),意思是他仍然关注Python的开发进程,并在必要的时刻做出决定。
1982年,在阿姆斯特丹大学获得数学和计算机科学硕士学位,后来他在多个研究机构工作。
2002年,获得由自由软件基金会颁发的2001年自由软件进步奖。
2003年,获得了荷兰UNIX用户小组奖。
2006年,被美国计算机协会(ACM)认定为著名工程师。
2005年,加入Google。他用Python语言为Google写了面向网页的代码浏览工具Mondrian,之后又开发了Rietveld。在那里他把一半的时间用来维护Python的开发。
2012年,Dropbox宣布吉多·范罗苏姆加入Dropbox公司。
Python的设计哲学是“优雅”、“明确”、“简单”。
Python开发者的哲学是“用一种方法,最好是只有一种方法来做一件事”,也因此它和拥有明显个人风格的其他语言很不一样。
在设计Python语言时,如果面临多种选择,Python开发者一般会拒绝花俏的语法,而选择明确没有或者很少有歧义的语法。
这些准则被称为“Python格言”。在Python解释器内运行import this可以获得完整的列表。
import this |
Python 是一门简单且简约的语言。阅读一份优秀的 Python 程序代码就如同在阅读英语文章一样,尽管这门英语要求十分严格!
Python 这种伪代码式的特质正是它的一大优势。它能够让你专注于解决问题的方案,而不是语言本身。
由于其开放源码的特性,Python 已被移植到其它诸多平台。
如果你小心地避开了所有系统依赖型的特性, 你所有的 Python 程序可以在其中任何一个平台上工作,不必作出任何改动。
你可以在 GNU/Linux、Windows、FreeBSD、Macintosh、 Solaris、 OS/2、 Andorid 等等平台上运行 Python!
有关这一特性,需要一些详细的解释。
Python 不需要将其编译成二进制码, 你只需要直接从源代码运行该程序。
这使得 Python 程序更便携且易于迁移,你只需要将 Python 程序拷贝到另一台电脑便可让它立即开始工作!
Python 同时支持面向过程编程与面向对象编程。
在面向过程的编程语言中,程序是由仅仅带有可重用特性的子程序与函数所构建起来的。
在面向对象的编程语言中,程序是由结合了数据与功能的对象所构建起来的。
与 C++ 或 Java 这些大型语言相比,Python 具有其特别的、功能强大又简单的方式来实现面向对象编程, 而且万物皆对象
。
Python 很容易和其他语言一同开发, 从而享受到各种不同语言的便利。
如果你需要代码的某一重要部分能够快速地运行,或希望算法的某些部分不被公开,你可以在 C 或 C++ 语言中编写这些程序,然后再将其运用于你的 Python 程序中。
同时,你也可以在你的 C 或 C++ 程序中嵌入 Python,从而向你的程序用户提供脚本功能。
Python 标准库的规模非常庞大, 可谓自带军需
。
它能够帮助你完成诸多事情,包括正则表达式、文档生成、单元测试、多线程、数据库、网页浏览器、CGI、FTP、邮件、XML、XML-RPC、HTML、WAV 文件、密码系统、GUI,以及其它系统依赖型的活动。
除了标准库以外,你还可以在 PYPI(Python Package Index) 中发掘许多其它高质量的库。
string 字符串
一个由字符组成的不可更改的有序列。又单引号或双引号包围,如: "strings"
。
byte 字节串
一个由字节组成的不可更改的有序列。常用来表示二进制数据,如: b"bytes"
float 浮点数
浮点数, 精度与系统相关, 不区分单精度双精度。如: 3.1415926
int 整数
整数,不区分是否是长整型。如:3
complax 复数
复数,如:3+2.7j
list 列表
可以包含多种类型的可改变的有序列,如: [1, "a", b"c", ]
tuple 元组
可以包含多种类型的不可改变的有序列,如: (1, "a", b"c", )
set 集合
与数学中集合的概念类似。无序的、每个元素唯一。,如: {1, "a", b"c", }
dict 字典
一个可改变的由键值对组成的无序列。如: {"a": 1, "b": 2}
bool 布尔值
逻辑值。只有两个值:真: True
、假: False
。
name = value
1 a = |
主要的算术运算符与C/C++类似。
+, -, *, /, //, **, ~, %
分别表示加法或者取正、减法或者取负、乘法、除法、整除、乘方、取补、取模。>>, <<
表示右移和左移。&, |, ^
表示二进制的AND, OR, XOR运算。>, <, ==, !=, <=, >=
用于比较两个表达式的值,分别表示大于、小于、等于、不等于、小于等于、大于等于。~, |, ^, &, <<, >>
这些运算符, 必须应用于整数。and, or, not
表示逻辑运算。is, is not
用于比较两个变量是否是同一个对象。in, not in
用于判断一个对象是否属于另外一个对象。for
for i in [1,2,3]: |
while
1 i = |
break 结束循环
for i in [1, 2, 3]: |
continue 跳过本次循环
for i in [1, 2, 3]: |
if/else, 其中else不是必须的
1 a = |
函数的多条语句的集合
Python内置了很多有用的函数,我们可以直接调用。
要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。
-1) abs( |
def my_abs(x): |
类是面向对象的重要组成元素, 是创造对象的模板, 比如Person类.
而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同, 实例是数据和方法的集合.
class Person(object): |
Python经常被用于Web开发, Python定义了WSGI标准应用接口来协调Http服务器与基于Python的Web程序之间的通信。
一些Web框架,可以让程序员轻松地开发和管理复杂的Web程序。
下面介绍最为常见的几个web框架
Django的主要目标是使得开发复杂的、数据库驱动的网站变得简单。
Django注重组件的重用性和“可插拔性”,敏捷开发和DRY法则(Don’t Repeat Yourself)
大而全的Web框架,适合快速建站,自带的网站管理后台功能(admin)非常强大。
Flask被称为“microframework”,因为它使用简单的核心,同时提供很好的扩展性, 可以用Flask-extension加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。
小而美,适合写api服务,只需10行代码就能写好一个简单服务。Flask的扩展性固然很好,但是如果上面的功能你都需要,那么还是用Django比较好。
Tornado全称Tornado Web Server,是一个用Python语言写成的Web服务器兼Web应用框架。
性能强劲的异步框架,但是如果出问题调起来比较蛋疼。而且有了gevent之后,个人觉得用tornado的性能意义不大。
Sanic是异步框架新秀, 基于原生asyncio构建, 性能强劲, 适合编写高性能服务.
Tkinter 是 Python 的标准 GUI 库。Tkinter只适合简单界面的程序开发,界面效果是系统原生框体,总之是比较丑。
如果是简单的工具开发,可以用这个。
wxPython是Python语言的GUI工具包,作为Python的扩展模块实现,包装了wxWidgets。
可配置项比tkinter多些,配起来也比较复杂。
PyQt应该是最全的跨平台GUI解决方案了,linux上不少程序都是PyQt开发的,而且只要程序颜值也ok,个人推荐。
在很多操作系统里,Python是标准的系统组件。Python标准库包含了多个调用操作系统功能的库。
通过调用这些库, 可以编写很多系统管理的脚本. 例如, 通过pywin32这个第三方软件 包,Python能够访问Windows的COM服务及其它Windows API。
一般说来,Python编写的系统管理脚本在可读性、性能、代码重用度、扩展性几方面都优于普通的shell脚本, 可作为shell脚本的替代。
一般来说100行以上的shell脚本就可以考虑使用Python编写。
归功于Python提供覆盖了完善的标准库,以及Python代码的可读性和可维护性,Python在云计算这块也有较广的应用。
OpenStack是一个美国宇航局和Rackspace合作研发的开源云计算软件
OpenStack是基础设施即服务(IaaS)软件,提供了IaaS整套解决方案,让任何人都可以自行创建和提供云计算服务。
此外,OpenStack也用作创建防火墙内的“私有云”(Private Cloud),提供机构或企业内各部门共享资源。
据了解,腾讯云大量使用Python做网络以及计算资源的管理。
用Python实现的类matlab的第三方库,用以绘制一些高质量的数学二维图形。
基于Python的科学计算第三方库,提供了矩阵,线性代数,傅立叶变换等等的解决方案。
Pandas对numpy进行了封装,是用于数据分析、数据建模、数据可视化的第三方库。
基于Python的matlab实现,旨在实现matlab的所有功能。
很多游戏使用C++编写图形显示等高性能模块,而使用Python或者Lua编写游戏的逻辑、服务器。
Pygame是跨平台Python模块,专为电子游戏设计。
PyGame适合开发小游戏,大型游戏难度颇大。
cocos2d是一个基于MIT协议的开源框架,最初的Cocos2D框架是使用Python编写的,基于pyglet开发。
可以用于构建游戏、应用程序和其他图形界面交互应用。被大量应用于手机游戏市场,愤怒的小鸟就是基于cocos2d开发。
一个简单的爬虫,使用 requests + BeatifulSoup就可以快速实现。
requests请求获取html,通过BeatifulSoup进行解析,就能很轻松拿到想要的数据。
Scrapy 是一个爬虫的框架, 而不是一个简单的爬虫.
它整合了爬取, 处理数据, 存储数据的一条龙服务. 能够高效的开发, 爬取网页, 记录数据。
对于整个机器学习,应用层面放眼望去基本就是Python与R的天下了,而R更偏向与统计学领域,而在深度学习Python简直是红透了半边天。
Scikit-learn项目最早由数据科学家 David Cournapeau 在 2007 年发起,需要NumPy和SciPy等其他包的支持,是Python语言中专门针对机器学习应用而发展起来的一款开源框架。
Scikit-learn的基本功能主要被分为六大部分:分类,回归,聚类,数据降维,模型选择和数据预处理。
Scikit-learn本身不支持深度学习,也不支持GPU加速,因此这里对于MLP的实现并不适合于处理大规模问题。
TensorFlow 是一个针对深度学习的库,并藉由和谷歌的关系赢得了许多关注。
Keras是基于TensorFlow,Theano与CNTK的高阶神经网络API。
keras+tensorflow应该算是比较好的一种解决办法。对于初学者可以用keras搭搭积木,熟悉之后可以和tensorflow配合起来实现很多复杂功能。
上面说了这么多Python的应用场景,接下来就说下Python的局限。
由于Python本身是解释性语言的,以及GIL的限制,使得其不可避免地由于性能问题被大家诟病。
这里说下个人的几点看法
如果是5年前问这个问题,可能还需要犹豫一下,今天来看毫无疑问是选择3。
到2020年Python2就会完全失去支持,也不会有新的版本出现。还有人专门设立了一个网站进行Python2的死亡倒计时。
Python3还是Python2的问题,其实基本是每一个学习Python的人都会问的问题。
Python3还是Python2的区别主要在unicode的处理,以及一些内置库的命名上,具体可以看这里
强烈建议学习Python使用类unix系统(如macOS, Ubuntu),虚拟机也可以, 会减少很多疑难问题。
访问 并下载最新版本的 Python 其安装过程与其它 Windows 平台的软件的安装过程无异。
注意:请务必确认你勾选了 Add Python 3.x to PATH 选项。
sudo apt-get update && sudo apt-get install python3
yum update && yum install python3
对于 Mac OS X 用户,你可以使用 Homebrew 并通过命令 brew install python3
进行安装。
对于macOS,linux,bsd等类unix系统来说,最好的安装管理Python的方式是通过pyenv。
pyenv 是 Python 版本管理工具。 pyenv 可以改变全局的 Python 版本,安装多个版本的 Python, 设置目录级别的 Python 版本,还能创建和管理 virtualenv 。所有的设置都是用户级别的操作,不需要 sudo 命令。
Conda是一个开源跨平台语言无关的包管理与环境管理系统。
可以通过创建不同环境来管理Python版本,支持windows,mac,linux。可以解决一些由于系统库缺失或者版本不对导致的问题。
这里推荐使用miniconda,体积较小,学习Python完全够用了。
如果有机器学习相关需求的,可以选择安装anaconda
pycharm是jetbrains出品的ide,应该是宇宙最强的吧,有用过他家其他产品的应该很容易上手,有免费的社区版,功能基本够用。
唯一的不足是内存占用有点多,反应偏慢。
功能齐全适合新手使用
vscode 微软出品,免费开源。
安装Python插件之后可作为ide使用,功能也很全,本人主力使用。
Sublime Text是我第一个使用的代码编辑器,插件很多,响应很快。
vscode、atom一开始都是从Sublime Text上抄功能的。但是随着一段时间的发展Sublime Text渐渐赶不上这两家了,比较它只有一个开发者同时开发3个平台。
通过安装各种插件,基本上都是达到vscode、atom同等水平的易用性,需要多花一些时间配置。
不推荐新手使用
通过打印变量的值进行调试,是最传统也是最直接的调试方法。但是过多的print信息比较难以清理
通过PyCharm等现代IDE,可以很方便得进行单步调试,查看当前程序的运行状态,变量的值等。
参考教程 https://zhuanlan.zhihu.com/p/62610785
Python自带logging模块,通过简单配置就可以方便地进行日志控制,具体可以参考本人这篇文章
启动python
命令就可以进入IDLE,通过IDLE,我们可以交互式地查看输入语句的执行结果,适用于新库的使用探索。
内置的IDLE功能比较简单,使用起来不是很方便。
其实有不少第三方的idle,实现了代码补全、历史记录等实用功能,非常推荐使用。
下面是我常用的两个第三方idle
pip install ptpython
安装,执行ptpython
启动pip install ipython
安装,执行ipython
启动pdb是Python内置的单步调试库,通过打断点可以方便地对Python代码逐行调试。
pdb的使用需要预先在源码中引入,对于已经运行的程序无法调试。
这时就可以用到gdb进行调试,通过加载gdb的python插件,可以方便地对运行中的Python程序调试,对程序假死卡住的问题定位很有帮助。
记住一个原则,不要过早优化。
一般的应用场景Python的性能都是够用的,如果真的遇到问题,下面也有几个方法解决
通过将程序中计算密集,耗时最多的部分通过编写C扩展的形式脱离出来,在用Python去调用该扩展。该方法可以绕过GIL,能基本彻底解决性能问题,但开发成本基本高。
pypy是用Python自身实现的解释器。针对CPython的缺点进行了各方面的改良, 最重要的一点就是Pypy集成了JIT(即时编译器),性能得到很大的提升, 可达CPython的几十倍以上的性能。
使用方便,可惜兼容性不佳,有些第三方库不能很好地支持,如numpy。
如果是IO密集型的性能问题,可通过异步的方式解决问题。
pep8是python最普遍接受的代码规范,只要认真阅读一遍 PEP 8,并尽量遵守,你的代码就足够 Pythonic 了。
Google Python风格规范,对pep8规范进行了扩充,并且有很多实际的例子。个人推荐
import logging |
outputwarning division by zero
error division by zero
exception division by zero
Traceback (most recent call last):
File "test.py", line 5, in <module>
1/0
ZeroDivisionError: division by zero
logging.getLogger
获取一个记录器, 一般以模块名称命名Python日志支持的级别
级别 | 何时使用 |
---|---|
DEBUG | 详细信息,典型地调试问题时会感兴趣。 |
INFO | 证明事情按预期工作。 |
WARNING | 表明发生了一些意外,或者不久的将来会发生问题软件还是在正常工作。 |
ERROR | 由于更严重的问题,软件已不能执行一些功能了。 |
CRITICAL | 严重错误,表明软件已不能继续运行了。 |
每个record对象代表一条日志
record拥很多有的属性, 可扩展, 主要被formatter使用
日志记录器的记录方法每次被调用就会生成一条record对象, 并且将record的所有属性都设置好.
record主要有以下属性
name | logger名称 |
---|---|
msg | 日志文本 |
args | 格式文本的参数 |
levelname | 级别名称 |
module | 模块名称 |
exc_info | 异常相关信息 |
lineno | 行号 |
funcName | 打日志的函数名称 |
threadName | 线程名 |
processName | 进程名称 |
日志处理器决定了日志的存储的方式, 存储大小,存储的位置
日志处理器通过handle方法处理日志, 不同的处理器行为差别很大
一些常见的日志处理器
filter是附加在logger上的, 对日志进行细粒度控制, 例如根据一些条件判断是否记录日志, 修改日志record的内容等
例如上面这个ContextFilter, 就将第二条日志过滤掉了.
根据配置,将日志record的属性格式化成字符串
解释完这些名词, 然后看这个这个流程图, 就会明白整个日志的处理流程
开发过程中觉得比较好的一个日志配置
LOGGING = { |
基本信息
'%(levelname)s %(asctime)s %(name)s %(funcName)s %(lineno)d %(process)d %(thread)d %(message)s' |
正确地使用日志级别, 不要乱用级别
ERROR:该级别的错误也需要马上被处理。当ERROR错误发生时,已经影响了用户的正常访问,是需要马上得到人工介入并处理的。
WARNING:该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要及时查看并处理的。因此此种级别的日志也不应太多,能不打WARNING级别的日志,就尽量不要打;
INFO:该种日志记录系统的正常运行状态, 占日志的大部分;
DEBUG:开发过程中的调试信息。
先看看一开始的问题,可以看到这里lambda函数的返回值一直在变。
xx = [] |
输出如下
a: 3 |
由于Python没有块级作用域,所以循环会改变当前作用域变量的值,也就是循环变量泄露。
注意:Python3中列表推导式循环变量不会泄露,Python2中和常规循环一样泄露。
x = -1 |
输出如下
6 : for x inside loop |
再讲一下闭包,在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。
这里所谓的引用可以也就是内部函数记住了变量的名称(而不是值,这个从ast语法树可以看出),而变量对应的值是会变化的。
如果在循环中定义闭包,引用的变量的值在循环结束才统一确定为最后一次循环时的值,也就是延迟绑定(lazy binding)。
所以下面的例子,xx
的所有匿名函数的返回值均为3
xx = []
for i in [1,2,3]:
xx.append(lambda: i)
再分析一开始的问题,这里的匿名函数引用了变量i
,而i
是全局变量,所以再次使用i
作为循环变量时,列表中的匿名函数引用的值就被覆盖了。
正确做法:
遍历序列,比较两个元素,如果前面的大于后面的就交换两者的位置。其实称之为冒泡排序不如加沉底排序,因为每一轮比较,这一轮轮最大都会被排到序列末尾,其实沉底更为贴切。
原始版本
def bubble_sort(arry): |
改进版本
def bubble_sort(arry): |
还是先来看看选择排序的思想。选择排序的思想非常直接,不是要排序么?那好,我就从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推,就可以完成整个的排序工作了。
def selection_sort(arry): |
插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
def Insertion_sort(arry): |
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本,我称之为分组插入排序
。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
def shell_sort(arry): |
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
def merge_sort(arry): |
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。一个序列大于基准,一个小于基准。再对这两个序列进行同样的操作,以此类推直到所有元素都排列好。
# 该实现是原位排序, 所以和上面的步骤有点区别 |
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。
def bucket_sort(array): |
堆排序在 top K 问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的,虽然实质上还是一维数组。二叉堆是一个近似完全二叉树 。
二叉堆具有以下性质:
父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)。
构造最大堆:若数组下标范围为1~n,考虑到单独一个元素是大根堆,则从下标n/2+1开始的元素均为大根堆。于是只要从n/2开始,向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。
堆排序(HeapSort):由于堆是用数组模拟的。得到一个大根堆后,数组内部并不是有序的。因此需要将堆化数组有序化。思想是移除根节点,并做最大堆调整的递归运算。第一次将heap[1]与heap[n]交换,再对heap[1…n-1]做最大堆调整。第二次将heap[1]与heap[n-1]交换,再对heap[1…n-2]做最大堆调整。重复该操作直至heap[1]和heap[2]交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个数组就是有序的了。
最大堆调整(sink):该方法是提供给上述两个过程调用的。目的是将堆的末端子节点作调整,使得子节点永远小于父节点 。
def heap_sort(array): |
下面为以上八种排序算法指标对比情况:
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | 〇(n^2) | 〇(n) | 〇(n^2) | 〇(1) | 稳定 |
选择排序 | 〇(n^2) | 〇(n^2) | 〇(n^2) | 〇(1) | 不稳定 |
插入排序 | 〇(n^2) | 〇(n) | 〇(n^2) | 〇(1) | 稳定 |
希尔排序 | 〇(nlog(n))~〇(n^2) | 〇(n^1.3) | 〇(n^2) | 〇(1) | 不稳定 |
堆排序 | 〇(nlog(n)) | 〇(nlog(n)) | 〇(nlog(n)) | 〇(1) | 不稳定 |
归并排序 | 〇(nlog(n)) | 〇(nlog(n)) | 〇(nlog(n)) | 〇(n) | 稳定 |
快速排序 | 〇(nlog(n)) | 〇(nlog(n)) | 〇(n^2) | 〇(log(n))~〇(n) | 不稳定 |
桶排序 | 〇(n+k) | 〇(n+k) | 〇(n^2) | 〇(n) | 稳定 |
pytest是一个非常成熟的全功能的Python测试框架, 简单灵活, 容易上手, 具有很多第三方插件,并且可以自定义扩展.
pip install pytest |
先写个测试代码tmp.py
def add(a, b):
return a + b
def test_add():
assert add(1, 1) == 2
def test_add_fail():
assert add(1, 2) == 2
使用方法usage: py.test [options] [file_or_dir] [file_or_dir] [...]
所以我们执行 pytest tmp.py
输出结果, 两个测试用例一个通过一个失败, 通过用绿色 . 表示, 失败红色 F 表示.=============================== test session starts ===============================
platform darwin -- Python 3.6.9, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: /Users/ruan/projects
plugins: pylint-0.15.1, mypy-0.5.0, celery-4.3.0
collected 2 items
tmp.py .F [100%]
==================================== FAILURES =====================================
__________________________________ test_add_fail __________________________________
def test_add_fail():
> assert add(1, 2) == 2
E assert 3 == 2
E + where 3 = add(1, 2)
tmp.py:92: AssertionError
=========================== 1 failed, 1 passed in 0.10s ===========================
测试用例目录优先级: 命令行参数目录 > 配置文件中的testpaths配置项 > 当前目录
支持的配置文件: pytest.ini,tox.ini,setup.cfg
测试用例查找规则:
__init__.py
的目录)norecursedirs
test_*.py
或者 *_test.py
的文件, 并以包名的全路径导入Test
开头的类(该类不能有 init 方法), 的以test
为前缀的方法.test
为前缀的函数.pytest --fixtures, --funcargs 查看可用的 fixtures |
使用示例
执行单个模块中的全部用例: py.test test_mod.py
执行指定路径下的全部用例: py.test somepath
执行匹配stringexpr
表达式的用例: py.test -k stringexpr
运行指定模块中的某个用例: pytest test_mod.py::test_func
运行某个类下的某个用例: pytest test_mod.py::TestClass::test_method
执行测试用例时输出print内容: pytest -s test_mod.py
import pytest |
fixture
是 pytest 特有的功能,它用 pytest.fixture 标识,定义在函数前面, 起到依赖注入的作用.
在编写测试函数的时候,可以将此函数名称做为传入参数,pytest 将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数。
import pytest |
fixture接收scope
和autouse
参数
不同的scope表明了fixture的作用访问和执行先后顺序, 以下fixture执行顺序从上到下依次执行. 同级别是fixture按照依赖关系决定先后次序.
autouse参数决定了fixture是自动执行, 还是在被用做参数传入时才执行
scope='session'
: 会话级别, 测试开始时执行一次, 在整个测试的过程不变scope='module'
: 模块级别, 每个模块测试开始时执行一次, 在整个模块测试的过程不变scope='function', autouse=True
: 函数级别, 在每个函数开始前自动执行.scope='function'
: 函数级别(默认是这个级别).setup\teardown
是指在模块、函数、类开始运行以及结束运行时执行一些动作。
例如: 数据库连接管理, 临时文件清理.
pytest支持的setup\teardown
钩子
# 模块级别 |
以上这些钩子, 也都可以用 fixture 的方式等效实现, 例如:
|
从广义理解,conftest.py
是一个本地的 per-directory
插件,在该文件中可以定义目录特定的 hooks 和 fixtures。
pytest
框架会在它测试的项目中寻找 conftest.py 文件,然后在这个文件中寻找针对整个目录的测试选项.
总结起来,conftest.py
文件大致有如下几种功能:
Fixtures: 用于给测试用例提供静态的测试数据,其可以被所有的测试用于访问,除非指定了范围
加载插件: 用于导入外部插件或模块:
pytest_plugins ="myapp.testsupport.myplugin" |
测试根路径: 如果将 conftest.py 文件放在项目根路径中,则 pytest 会自己搜索项目根目录下的子模块,并加入到 sys.path 中,这样便可以对项目中的所有模块进行测试,而不用设置 PYTHONPATH 来指定项目模块的位置。
marker
的作用是,用来标记测试,以便于选择性的执行测试用例。
Pytest 提供了一些内建的 marker, 这里列了几个个人觉得有用的, 详细的请看文档
@pytest.mark.skip(reason=None)
@pytest.mark.skipif(condition)
@pytest.mark.tryfirst
@pytest.mark.trylast
例子:# 如果是window平台, 跳过该测试用例
class TestPosixCalls:
def test_function(self):
"will not be setup or run under 'win32' platform"
也可以自定义markers, 这些markers只是具有名称, 只起了标记作用, 通过-m
参数执行指定的marker的测试用例.
例如pytest -m hello
, 只执行test_one测试用例
def test_one():
assert False
def test_two():
assert False
通过pip安装, 例如pylint插件pip install pytest-pylint
通过添加参数来使用插件pytest --pylint tmp.py
常用插件
可以在项目根目录放置pytest.ini
来控制pytest的行为
例如:[pytest]
addopts = --pylint --mypy ; 指定默认追加的参数
; testpaths = /home/test/ ; 测试用例路径
; minversion = 1.1 ; 依赖的pytest的最低版本
; norecursedirs = xx ; 不搜索测试用例的路径
; console_output_style ; 控制台测试报告格式
; 记录测试用例中用到的markers, 通过--markers参数可以显示出来
; markers =
; webtest: Run the webtest case
; hello: Run the hello case
最近发生了一个服务故障,在一个同事对日志服务做了改动后,服务的耗时就变大了很多,数据大量积压。
通过观察监控报表,我们发现从他的改动上线后,服务器的CPU使用率大幅增加,基本处于满载状态。
粗略审查代码并没有发现问题,我们只能紧急回滚了服务,日志消费也恢复到正常状态了。
但是根本问题还没有找到,所以我请出了line_profiler来分析程序的具体耗时情况。
line_profiler是个代码耗时分析器,可以逐行分析代码的耗时情况。
line_profiler包含两个部分
首先,安装:pip install line_profiler -U
给需要分析的代码加上@profile
装饰器, @profile
装饰器不需要被import,kernprof在运行时会注入依赖,kernprof会记录被装饰函数的耗时情况,有多个函数也可以都加上装饰器。
def main(run_type):
with open('data.txt','r') as f:
data=f.readline()
input_data = json.loads(data)
source = input_data.get('source', '')
service = input_data.get('service', 0)
apply_no = input_data.get('applyNo', 0)
info = input_data.get('info', '')
cust_no = input_data.get('cust_no', 0)
job.run(source, service, apply_no, info, run_type)
然后,运行代码:kernprof -l service.py
, 统计耗时情况, 执行完成后生成耗时报告文件service.py.lprof
最后,耗时报告解析:python -m line_profiler service.py.lprof
,会生成可阅读的耗时报告。
耗时报告示例Timer unit: 1e-06 s
Total time: 4.18908 s
File: service.py
Function: main at line 38
Line # Hits Time Per Hit % Time Line Contents
==============================================================
38 @profile
39 def main(run_type):
40 1 45.0 45.0 0.0 with open('data.txt','r') as f:
41 1 517.0 517.0 0.0 data=f.readline()
42 1 6564.0 6564.0 0.2 input_data = json.loads(data)
43 1 20.0 20.0 0.0 source = input_data.get('source', '')
44 1 6.0 6.0 0.0 service = input_data.get('service', 0
45 1 5.0 5.0 0.0 apply_no = input_data.get('applyNo',
46 1 5.0 5.0 0.0 info = input_data.get('info', '')
47
48 1 1.0 1.0 0.0 cust_no = input_data.get('cust_no', 0)
49
50 1 4181914.0 4181914.0 99.8 job.run(source, service, apply_no, info, run_type)
耗时报告会逐行显示代码耗时和调用情况
可以看到,总耗时4.18908秒,job.run
占用了99.8%
的时间。
接下来我们分析下job.run
的耗时情况Timer unit: 1e-06 s
Total time: 4.16909 s
File: job.py
Function: inout at line 69
Line # Hits Time Per Hit % Time Line Contents
==============================================================
69 @profile
70 def run(self, apply_no, info, req_type, run_type):
... 省略部分逻辑
112 1 2.0 2.0 0.0 if req_type == 'credit':
113 1 3370673.0 3370673.0 80.8 sql = db_utils.generate_sql(info1, sql, val, columns)
114 else:
115 sql = db_utils.generate_sql(info1, sql, val, columns)
116 1 6948.0 6948.0 0.2 safe3_db.executor(sql)
可以看到,113行的生成SQL语句占用了80%的时间,SQL反倒不怎么耗时,显然是不正常的。
再看下db_utils.generate_sql
函数的具体代码
可以发现遍历字段列表(item_list)的时候,调用了get_json_value
,该函数每次都会调用copy.deepcopy
。
众所周知,对象的深拷贝是十分消耗cpu资源的,并且这里深拷贝的次数会随着字段数的增加而线性增长。
把深拷贝行为去除后,服务耗时和cpu占用都回归到了正常水准。
def generate_sql(info, sql, val, item_list): |
Python是一门动态强类型语言, 动态性是它鲜明的特点.
但是动态性在给程序员充分的自由的同时, 也带来了一些不好的负面效应. 特别是在团队协作的时候, 不好的队友会引发许多难以定位的问题.
同时动态性也大大削弱了ide的作用, 代码提示, 重构等一些功能远不如静态语言来得可靠.
class Person: |
比如这个代码片段, ide很难准确识别introduce_someone
的参数应该是Person
类的实例, 它只能单纯地从文本上分析, 并把所有可能的单词都提示出来.
而且当调用introduce_someone
, 传入了不合适的对象, 也很难通过静态检查发现.
类型标记的出现就解决了这些问题.
类型标记就是, 给变量, 参数, 函数附加上类型信息. 类似Java等静态语言的变量声明信息.
Python 从3.5开始, 引入了类型标记系统, 并在后面的版本有所增强.
类型标记的基本语法 变量名: 标记
, 标记可以是字符串, 对象或者Type aliases
(类型别名)
name: str = 'tom' |
class Person: |
函数的类型标记比变量多了一项: 返回值, 通过->
与函数名连接在一起.
如果调用introduce_someone
, 参数不是Person
类的实例. 静态检查会发现以下错误.error: Argument 1 to "introduce_someone" has incompatible type "str"; expected "Person"
Found 1 error in 1 file (checked 1 source file)
mypy 实用工具是一款针对 Python 的静态类型检查程序, 也可以和pytest一起配合使用.
pip install mypy |
mypy my_program.py my_src_folder |
Python内置typing库提供了许多有用的工具来辅助类型标记
类型别名(运行时的标识函数), 帮助更好地进行类型标记, 这些别名可以进行组合.
from typing import List, Dict, Tuple, Sequence |
静态检查结果, 三个调用错误都发现了error: List item 0 has incompatible type "str"; expected "float"
error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str"
error: Argument 1 to "print_address" has incompatible type "List[object]"; expected "Tuple[str, int]"
Found 3 errors in 1 file (checked 1 source file)
使用 NewType() 辅助函数创建派生的类型标记.
静态类型检查器会将新类型视为它是原始类型的子类, 可用于发现逻辑错误(比如: 虽然都是数字, 其实含义不同)
from typing import NewType |
标记为可调用对象, 期望特定签名的回调函数的框架可以将类型标注为 Callable[[Arg1Type, Arg2Type], ReturnType]。
例如:from typing import Callable
def feeder(get_next_item: Callable[[], str]) -> None:
# Body
def async_query(on_success: Callable[[int], None],
on_error: Callable[[int, Exception], None]) -> None:
# Body
通过TypeVar来定义一个泛型类型标记, 限制对象的可选类型
from typing import TypeVar |
Any 是一种特殊的类型。
静态类型检查器将所有类型视为与 Any 兼容,反之亦然, Any 也与所有类型相兼容。
所有返回值无类型或形参无类型的函数将隐式地默认使用 Any 类型(没有类型标记的代码, 模式标记就是Any)
from typing import Any |
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写。
用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP协议工作于客户端-服务端架构为上。
浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。
Web服务器根据接收到的请求后,向客户端发送响应信息。
无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息
HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范。
规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体。类似于下面这样:
<method> <request-URL> <version> |
GET请求
curl -XGET -H 'Content-type:application/json' 'http://127.0.0.1:5001/api/_echo?a=1&b=2' -d '{"test": 1, "USER": "qqhc_clac_user"}'
请求报文GET /api/_echo?a=1&b=2 HTTP/1.1
User-Agent: curl/7.19 (x86_64-redhat-linux-gnu) libcurl/7.19 NSS/3.13 zlib/1.2 libidn/1.18 libssh2/1.2
Host: 127.0 :5001
Accept: */*
Content-type:application/json
Content-Length: 37
{"test": 1, "USER": "qqhc_clac_user"}
注意: 这里的行与行之间的分隔符为 \r\n
POST请求
curl -XPOST 'http://127.0.0.1:5001/api/_echo?a=1&b=2' -d 'c=3&d=4'
POST /api/_echo?a=1&b=2 HTTP/1.1 |
HTTP/1.1 200 OK |
HTTP之状态码
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
1xx:指示信息--表示请求已接收,继续处理 |
常见的状态码
HTTP1.1 版本
GET 请求指定的页面信息,并返回实体主体。 |
在很久很久以前, 那时Web开发还比较简单,开发者经常会去操作web服务器,并且他会写一些HTML页面放到服务器指定的文件夹(/www)下。
这些HTML页面,就在浏览器请求页面时使用。
常见Web服务器
最常见的Web服务器有Apache和Nginx, 也是静态资源服务器.
问题就出现了,你只能获取到静态内容。
倘若你想让访问者看到有多少其他访问者访问了这个网站呢,或者倘若你想让访问者去填写这样一个表单,包含有姓名和邮件地址呢?
所以出现动态技术. 静态资源服务器可以直接处理静态页面, 并且将动态请求转发给CGI程序处理。
通用网关接口
(Common Gateway Interface/CGI)
CGI是动态网页的第一种解决方案. CGI协议描述了服务器和请求处理程序之间传输数据的一种标准。
遵循CGI协议的请求处理程序称为CGI程序\脚本
#!C:\\Python27\\python.exe |
每当客户请求CGI的时候,WEB服务器就请求操作系统生成一个新的CGI解释器进程(如php-cgi.exe),CGI 的一个进程则处理完一个请求后退出,下一个请求来时再创建新进程。
当然,这样在访问量很少没有并发的情况也行。可是当访问量增大,并发存在,这种方式就不适合了。于是就有了fastcgi。
FastCGI(快速通用网关接口),是CGI的增强版本。其目的在于,减少Web服务器与CGI程序之间交互的开销,使得服务器可以同时处理更多的请求。
FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。
一般情况下,FastCGI的整个工作流程是这样的:
FastCGI的PHP实现
WSGI:全称是Web Server Gateway Interface
,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server
如何与web application
通信的规范。
要实现WSGI协议,必须同时实现web server和web application,当前运行在WSGI协议之上的web框架有Torando, Flask, Django
WSGI server负责从客户端接收请求,将request转发给application,将application返回的response返回给客户端;
WSGI application接收由server转发的request,处理请求,并将处理结果返回给server。application中可以包括多个栈式的中间件(middlewares),这些中间件需要同时实现server与application,因此可以在WSGI服务器与WSGI应用之间起调节作用:对服务器来说,中间件扮演应用程序,对应用程序来说,中间件扮演服务器。
WSGI协议其实是定义了一种server与application解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的server和application组合实现自己的web应用。
例如uWSGI和Gunicorn都是实现了WSGI server协议的服务器,Django,Flask是实现了WSGI application协议的web框架,可以根据项目实际情况搭配使用。
WSGI将 web 组件分为三类: web服务器,web中间件,web应用程序, wsgi基本处理模式为
WSGI Server -> (WSGI Middleware)* -> WSGI Application |
wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。
wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。
def application (environ, start_response): |
有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。
Django是一个开放源代码的Web应用框架,由Python写成。采用了MVT的软件设计模式,即模型Model,视图View和模板Template。
microapp |
中间件的执行流程 (Django1.9 以后的版本)
1、执行完所有的request方法 到达视图函数。
2、执行中间件的其他方法
3、经过所有response方法 返回客户端。
注意:如果在其中1个中间件里 request方法里 return了值,就会执行当前中间件的response方法,返回给用户 然后 报错。。不会再执行下一个中间件。
一个简单的中间件
# -*- coding:utf-8 -*- |
顾名思义,所谓装饰器就是对原有的对象做一些装饰,也就是给已有的对象添加一些功能。
假如我现在想在函数运行时输出一些信息
def running(func): |
输出`my_sum` is running...
I cat't sum right now!
要使用装饰器,先得定义一个装饰器函数,然后在需要装饰的函数的前一行使用@
符号加上装饰器名称
。
在这里的效果等效于running(my_sum)(),不过看起来有点别扭。
注意:一旦通过@running
装饰了函数,不管被装饰函数是否运行,python解释器都会执行一遍running
函数。
上一个装饰器用起来还行,但是有一个致命的问题,它不能装饰带参数的函数。所以我们在装饰器内部定义_wrapper
函数,并返回它。这个函数接收所有位置参数*args
,和关键字参数*kwargs
,在_wrapper
内部执行func(*args, **kwargs)
。
def running(func): |
输出`my_sum` is running...
3
_wrapper
这也是python中最普通的装饰器,假如需要在my_sum
运行之后添加一些功能,则可以改成这样。import time
def running(func):
def _wrapper(*args, **kwargs):
start = time.time()
print '`%s` is running...' % func.__name__
_result = func(*args, **kwargs)
print 'run `%s` takes %s seconds' % (func.__name__, time.time()-start)
return _result
return _wrapper
上一个装饰器也有一个问题,就是经过装饰的my_sum.__name__
变成了_wrapper
。
这个问题可以通过python内置的functools.wraps
解决,这个装饰器对原函数的一些属性进行了复制。
import time |
输出`my_sum` is running...
run `my_sum` takes 1.0 seconds
3
my_sum
如果一个在函数中存在yield关键字,那么这个函数就构成了生成器。生成器是一个函数,它生成一个序列,以便在迭代中使用。
调用这个函数,并不会马上开始执行函数体中的代码,而是返回一个生成器对象,通过调用生成器对象的next()
方法(python3中是__netx__()
)执行函数。
那么,具体的函数中语句的执行顺序是怎么样的呢?
def func(x=10): |
输出********************
the beginning of function
before yield 0
-> yielding: 0
********************
after yield 0
before yield 1
-> yielding: 1
********************
after yield 1
Traceback (most recent call last):
File "D:\Projects\test.py", line 28, in <module>
print '-> yielding: %s' % gen.next()
StopIteration
func(2)
,这时函数的所有语句都没有执行,返回一个生成器赋值给gen
。gen.next()
,函数从头开始执行,运行完yield语句暂停住了。gen.next()
,从停下的地方继续,直到遇到遇到下一个yield,运行完yield语句又暂停住了。gen.next()
,运行完print 'after yield', i
,由于循环次数已满,找不到下一个yield,就出现StopIteration
错误包是指含有__init__.py
的文件夹,模块就是一个.py文件。
python的包和模块是先查找buil-in moudle
然后是sys.path
这个list里的所有路径。
python task/admin.py
,sys.path[0]是'/home/code/taskman/task'
sys.path[0]
是实际文件所在路径,详见''
。''
,这个值等于当前工作目录也就是os.getcwd()
的值,也就是linux的pwd的值,你可以用os.chdir
来改变工作路径。还是分析之前的那个例子
taskman |
# task/admin.py 文件 |
taskman
假如我们要直接运行task/admin.py
这个文件,直接python task/admin.py
,会抛出ImportError
。
直接python task/admin.py
,此时的sys.path[0]应该是'/home/code/taskman/task'
,此时task/admin.py所需的man.setting显然不在task
目录下,自然就是ImportError
。
这时如果往admin.py加入sys.path.append('/home/code/taskman')
语句,就可以把man
这个包所在目录加入到sys.path,这样就解决了。
也可以使用python -m task.admin
将admin.py当作一个模块来执行,此时sys.path[0]是当前工作目录/home/code/taskman
,所以一切正常。
taskman
运行python -m task.admin
会提示No module named task
,因为在那个目录下找不到task
这个包。
此时只能在脚本前部添加一下语句import os, sys
os.chdir('/home/code/taskman/task')`
sys.path.append('/home/code/taskman')
这样在这个脚本的包导入正常,同时还可以通过os.system('python -m task.models')
运行项目内其他模块,而不用管当前工作目录在那里。
还有一个霸道的解决方法是,通过export PYTHONPATH="/home/code/taskman"
将项目路径,添加到python环境变量里,这样当解释器启动时项目路径就会直接添加到sys.path中。不过这样会影响整个python环境,可能出现包重名。
当然还可以在Python的site-packages目录下新建一个some.pth
文件,把需要的路径往里面写,这样每一个Python解释器进程的sys.path都会包含这个目录。可能会影响其他用户的程序,不推荐使用。
当Python程序的入口文件是符号链接时sys.path[0]
是实际文件所在路径,所以可能会出现找不到模块的情况。➜ tree
.
├── aa.py -> abc/aa.py
├── abc
│ └── aa.py
└── b.py
1 directory, 3 files
➜ cat abc/aa.py
import sys
print(sys.path)
abc=1
from b import *
➜ cat b.py
b=2
➜ python aa.py
['/private/tmp/tmp1/abc',
'/Users/ruan/.pyenv/versions/3.8.7/lib/python38.zip',
'/Users/ruan/.pyenv/versions/3.8.7/lib/python3.8',
'/Users/ruan/.pyenv/versions/3.8.7/lib/python3.8/lib-dynload',
'/Users/ruan/.pyenv/versions/3.8.7/lib/python3.8/site-packages']
Traceback (most recent call last):
File "aa.py", line 5, in <module>
from b import *
ModuleNotFoundError: No module named 'b'
sys.path
列表决定sys.path[0]
, 也就是python /home/a/b/c.py
的sys.path[0]
是/home/a/b/
-m
参数,会将工作路径加入到sys.path[0]
, 也就是 python -m a.b.c
的sys.path[0]
是/home
PYTHONPATH
,some.pth
文件会影响sys.path
列表''
总是等效于当前工作路径, 也就是os.chdir
改变而改变。lz = 'dashabi'
吗。正常的语句是这样u'这是一个坑' aa =
aa
u'\u8fd9\u662f\u4e00\u4e2a\u5751'
坑在这里u'这是一个坑', aa =
aa
(u'\u8fd9\u662f\u4e00\u4e2a\u5751',)
看出区别没有,坑的后面有个逗号,平时这逗号没什么卵用,但在赋值语句的末尾会将原来的对象转化为tuple。
和aa = (u'这是一个坑',)
是一样的效果。
由于树莓派用的是Linux系统,所以常见的有两种备份方式
这两种备份方式各有利弊:
基于文件的备份占用空间小,而且可以在系统在线时操作,比较方便,但是当要还原整个系统时就会比较麻烦(引导重建等等)。
基于磁盘的备份就比较简单粗暴了,直接克隆硬盘,恢复时直接还原映像文件就好了,但是由于是整盘备份,空间占用比较大。
我这里是采用的第二种备份方式,通过缩减分区大小,排除未使用空间来减小备份文件大小
首先需要将树莓派的SD取下,插入到一台Linux机器上。
这一步操作比较耗时,跟SD卡大小和速度有关,基本在几分钟到几十分钟不等。
如果不想把备份文件存到网络存储上,该步骤可以忽略
sudo mount.cifs -o vers=2.0,user=${nas_user},password=${nas_password},uid=$(id -u),gid=$(id -g) \ |
sudo dd if=/dev/sda of=/mnt/backup.img bs=1M count=6000 status=progress |
备份耗时跟SD卡大小和速度有关,基本在几分钟到几十分钟不等。
bs参数代表备份文件大小的单位,这里是1M
count代表有多少bs,也就是备份大小是count*bs=6000M
这个数值需要根据你磁盘使用空间来计算,取一个大于已使用空间的值就好了。
例如,我这个SD卡已使用的空间是 4M + 256M + 5.24GiB = 5625.76M
,则备份大小取6000M
还原系统和新安装系统是一样的,用官方的Raspberry Pi Imager还原备份镜像文件即可
还原完成之后,还需要使用Gparted将缩小的分区还原到原来的大小,耗时大概十几秒。
然后插卡开机即可
]]>127.0.0.1:12345
向 127.0.0.1:6379
发送 SLAVEOF 127.0.0.1 6379
, 则服务器(12345)将成为服务器(6379)的从服务器。
Redis的复制功能分为同步(RDB文件)和命令传播(同步写命令)两个阶段,具体步骤如下。
SYNC
SYNC
后执行BGSAVE
生成RDB文件,同时用缓冲区记录之后所有写命令。由于RDB的生成发送非常耗时,主从短暂断线的情况下,也需要重复生成,主从同步的效率就非常低了。
Redis从2.8版本开始, 使用PSYNC
命令替换SYNC
,增加了部分同步功能,对断线重连的情况进行了优化。
主从服务器都记录了复制偏移量,记录了主服务器发出的字节数和从服务器收到的字节数,并且主服务器使用一个复制积压缓冲区记录最近发出的数据(FIFO)。
同步时,从服务器会发送复制偏移量。
同时,主服务器每次启动时会生成运行id,防止主服务器重启后复制混乱。
dict 是 redis 最重要的数据结构,db、hash、以及服务器内部需要用到hashmap的场景都是用dict来实现的。学习 dict 的源码,我们可以学到hashmap的原理及实现。
哈希表元素节点typedef struct dictEntry {
// 键,指向SDS(Redis字符串实现)
void *key;
// 值, 联合值, 可以是整数或者指针
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
哈希表typedef struct dictht {
// 哈希表元素数组
dictEntry **table;
// 哈希表大小,初始值为4
unsigned long size;
// 哈希表大小掩码,用于计算索引值,总是等于 size - 1
unsigned long sizemask;
// 该哈希表已有节点的数量
unsigned long used;
} dictht;
字典typedef struct dict {
// 类型特定操作函数
dictType *type;
// 私有数据,保存了需要传给那些类型特定函数的可选参数
void *privdata;
// 哈希表,ht[1]在rehash的时候使用
dictht ht[2];
// rehash 索引,当 rehash 不在进行时,值为 -1
int rehashidx;
// 目前正在运行的安全迭代器的数量
int iterators;
} dict;
结构体,储存不同类型字典的操作函数指针,实现了多态typedef struct dictType {
// 计算哈希值的函数
unsigned int (*hashFunction)(const void *key);
// 复制键的函数
void *(*keyDup)(void *privdata, const void *key);
// 复制值的函数
void *(*valDup)(void *privdata, const void *obj);
// 对比键的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 销毁键的函数
void (*keyDestructor)(void *privdata, void *key);
// 销毁值的函数
void (*valDestructor)(void *privdata, void *obj);
} dictType;
redis 的 dict 本质上就是个hashmap,其中的关键是哈希算法。
哈希函数(英语:Hash function)又称散列算法、散列函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值的指纹。
比如取模函数就是一种最简单的对整数的哈希算法。
当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用 MurmurHash2 算法来计算键的哈希值。
具体求索引的过程
// 求哈希值 |
当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时,我们称这些键发生了冲突。
常见冲突解决方法
开放地址法:按照一定顺序寻找下一个可用位置(x为当前位置)
再哈希法:依次使用多个哈希函数
Redis的哈希表使用链地址法(separate chaining) 来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用 next指针构成一个单向链表,被分配到同一个索 引上的多个节点可以用这个单向链表连接起来,这就解决了键冲突的问题。
还有一种常用的冲突解决办法是再哈希法,就是同时构造多个不同的哈希函数。
当H1 = hashfunc1(key) 发生冲突时,再用H2 = hashfunc1(key) 进行计算,直到冲突不再产生,这种方法不易产生聚集,但是增加了计算时间。
随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(used/size)维持在一个合理的范围之内,程序需要对哈希表的大小进行相应的扩展或者收缩, 这个过程就是rehash。
这里redis采用的装载系数为1,扩容系数为2
Redis对字典的哈希表执行rehash的步骤如下:
ht[1]
哈希表分配空间ht[0]
中的所有键值对rehash到ht[1]
上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]
哈希表的指定位置上ht[0]
,将ht[1]
设置为ht[0]
,重置ht[1]
所谓渐进式,是指rehash动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。
由于redis是单线程的, 哈希表里保存的键值对又可能非常多,一次性将这些键值对全部rehash到ht[1],会导致服务器在一段时间内停止服务。
所以需要渐进式 rehash,在字典的每个添加、删除 、查找和更新操作的时候,顺便进行部分元素的 rehash(目前实现是rehash一个元素),避免了集中式rehash而带来的庞大计算量。
rehash 示例代码def dictRehash(d: rDict, n: int) -> int:
if not dictIsRehashing(d):
return 0
while (n):
n -= 1
if d.ht[0].used == 0: # rehash 完成了
del d.ht[0].table
d.ht[0] = c_assignment(d.ht[1])
_dictReset(d.ht[1])
d.rehashidx = -1
return 0
assert d.ht[0].size > d.rehashidx
# 找到第一个需要移动的元素
while d.ht[0].table[d.rehashidx] is None:
d.rehashidx += 1
de = d.ht[0].table[d.rehashidx]
while de: # 移动该元素(包含整个冲突链表)到ht[1]
nextde = de.next
h = dictHashKey(d, de.key) & d.ht[1].sizemask
de.next = d.ht[1].table[h]
d.ht[1].table[h] = de # 复制dictEntry元素
d.ht[0].used -= 1
d.ht[1].used += 1
de = nextde
d.ht[0].table[d.rehashidx] = None
d.rehashidx += 1
return 1
除了渐进式rehash,对于redis的多个db,也会有定时任务进行主动rehash,防止服务器长期没有执行命令时,数据库字典的 rehash 一直没办法完成。
Redis内部所有字符串都由SDS来表示,其本质就是动态字节数组,和python的bytearray
类似。
/* |
使用SDS时,一般是通过指向buf数组的指针而不是sdshdr,这样相关接口就和C字符串兼容。同时需要使用到len和free相关属性时,通过计算指针偏移来得到sdshdr指针,整体设计比较高效。static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
创建比较简单,注意buf末尾的\0
,以及最后返回的buf指针sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
// 根据是否有初始化内容,选择适当的内存分配方式
if (init) {
// zmalloc 不初始化所分配的内存
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
// zcalloc 将分配的内存全部初始化为 0
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
// 设置初始化长度
sh->len = initlen;
// 新 sds 不预留任何空间
sh->free = 0;
// 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
if (initlen && init)
memcpy(sh->buf, init, initlen);
// 以 \0 结尾
sh->buf[initlen] = '\0';
// 返回 buf 部分,而不是整个 sdshdr
return (char*)sh->buf;
}
既然是动态数组,就会涉及到容量调整。
Redis的调整策略,当所需空间小于SDS_MAX_PREALLOC(当前版本是1MB)时是指数增长, 否则线性增长SDS_MAX_PREALLOC的大小。
sds sdsMakeRoomFor(sds s, size_t addlen) { |
压缩列表的优点是节省内存,缺点是插入元素的复杂度较高平均O(N)最坏O(N^2)
, 但是在小数据量的情况下,这种复杂度也是可以接受的。
压缩列表是由一系列entry
组成的结构。entry
记录了当前节点的大小和前置节点的大小,所以可以双向插入和遍历。
ziplist 又4个主要部分组成
0xFF
, 表示列表末尾ziplist 结构示例图area |<---- ziplist header ---->|<----------- entries ------------->|<-end->|
size 4 bytes 4 bytes 2 bytes ? ? ? ? 1 byte
+---------+--------+-------+--------+--------+--------+--------+-------+
component | zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |
+---------+--------+-------+--------+--------+--------+--------+-------+
^ ^ ^
address | | |
ZIPLIST_ENTRY_HEAD | ZIPLIST_ENTRY_END
|
ZIPLIST_ENTRY_TAIL
上图我们向ziplist添加了3个entry元素,向list头部插入(redis内部使用时一般向尾部插入),后面会详细解析这些元素。
每个ziplist节点由一下3个部分组成
之前的3个节点的二进制详情
节点迭代器结构体 zlentrytypedef struct zlentry {
unsigned int prevrawlensize; // 编码 prevrawlen 所需的字节大小
unsigned int prevrawlen; // 前置节点的长度
unsigned int lensize; // 编码 len 所需的字节大小
unsigned int len; // 当前节点值的长度
unsigned int headersize; // 当前节点 header 的大小, 等于prevrawlensize + lensize
unsigned char encoding; // 当前节点值所使用的编码类型
unsigned char *p; // 指向当前节点的指针,也就是内存entry的prelen字段
} zlentry;
注意:zlentry结构体和ziplist中实际存储的entry结构是不一样的,zlentry只是为了遍历时操作entry时便利一些,类似序列化和反序列化。在需要对entry操作时,把对应位置的信息取出存到zlentry结构体中
prelen 记录了以字节为单位的前一个节点长度,有两种情况
255这个数字为啥舍弃不用呢?因为255已经作为列表结束的标志位,避免出现误导。
encoding 记录了当前节点的编码类型,编码时先尝试将内容转成数字,失败则当做字符串处理。
个人觉得ziplist的精华就在entry的encoding,对让内存的每一个bit都重复表示了信息。
下表中的0和1表示具体的二进制位, b表示该位置可能为0或者1
编码 | 占用空间/字节 | 表示类型 | 具体含义 |
---|---|---|---|
00bbbbbb | 1 | 字节数组 | content的长度为6bit, 也就是0-63 |
01bbbbbb bbbbbbbb | 2 | 字节数组 | content的长度为14bit, 也就是0-16383 |
10000000 bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb | 5 | 字节数组 | content的长度为32bit, 也就是0-4294967295 |
11110001 到 11111101 | 1 | 数字 | 用4个bit直接表示数字0-12, content长度为0 |
11111110 | 1 | 数字 | content为int8_t, 长度2字节 |
11000000 | 1 | 数字 | content为int16_t, 长度2字节 |
11010000 | 1 | 数字 | content为int32_t, 长度4字节 |
11100000 | 1 | 数字 | content为int64_t, 长度8字节 |
11110000 | 1 | 数字 | content为24bit有符号整数, 长度3字节 |
可以看到ziplist为了节省内存空间,表示信息时真是细扣到每一个bit,非常高效。
但是也有个不足,就是代码变得复杂了。
由于prelen和encoding和content这3个部分都是变长的,每一次插入和删除元素都得计算列表内存长度的变化。
而且由于prelen的变长,可能会触发后面所有节点连锁更新prelen的值.
本来节点插入时只需要复制一次该节点以后所有节点的内存,这时复杂度为O(n), 触发连锁更新之后,这时候列表的插入复杂度就会变为O(n^2)。
当list底层实现为ziplist时,插入原始的逻辑
主要涉及到各种长度和偏移量的计算,比较繁琐
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { |
判断是不是要把list转换为链表
void listTypeTryConversion(robj *subject, robj *value) { |
跳跃表优点
O(logN)
, 最坏O(N)
复杂度的节点查找,效率可以和平衡树相当因为ziplist
内存占用较小,所以Redis使用作为有序集合的初始底层结构。
如果一个有序集合包含的元素数量比较多(大于zset-max-ziplist-entries
),又或者有序集合中元素的成员是比较长的字符串时(大于zset-max-ziplist-value
),Redis就会将其底层结构转换为跳跃表。
跳跃表节点, 其中zskiplistLevel
成员是柔性数组typedef struct zskiplistNode {
// 成员对象
robj *obj;
// 分值
double score;
// 后退指针
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度,经过的节点数目
unsigned int span;
} level[]; // 柔性数组
} zskiplistNode;
跳跃表typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
int level;
} zskiplist;
所谓跳跃表,就是多层链表(redis中的实现是最多32层)通过额外的链接提高效率,从低层到高层,节点之间的跨度逐渐变大。
跨度越大则查找效率越高,所以查找时是从高层往底层查找。
如果节点的最高层高为x,则可以认为该节点就存储在低x层,则表头到该节点的跨度之和为该节点的rank(排位),所有节点的最大层高为跳跃表层高。
因为跳跃表是多层链表,所以插入节点的关键是找到每一层插入的位置,以及插入位置的跨度变化,还有新节点的跨度计算。
python 版跳跃表插入实现# 比较节点大小
def _node_lt(node: zskiplistNode, score: float, obj: robj):
if node.score < score:
return True
if (node.score == score and
compareStringObjects(node.obj, obj) < 0):
return True
return False
def zslInsert(zsl: zskiplist, score: float, obj: robj) -> zskiplistNode:
# update list记录的是每一层, 新节点需要插入的位置(新节点x的backward节点指针)
update: List[Opt[zskiplistNode]] = [None for _ in range(ZSKIPLIST_MAXLEVEL)]
# rank[i]: 从高到低, 到第i层为止经过的所有node的span总和, 也就是节点的排序
# 用于计算新节点各层的span, 以及新节点的后继节点各层的span
rank = [0 for _ in range(ZSKIPLIST_MAXLEVEL)]
x = zsl.header
# 从高层开始遍历
for i in range(zsl.level-1, -1, -1):
rank[i] = 0 if i == zsl.level-1 else rank[i+1]
# 找到每一层x需要插入的位置, 并更新rank
while x.level[i].forward and _node_lt(x.level[i].forward, score, obj):
rank[i] += x.level[i].span
x = x.level[i].forward
# 对于每一层i, 新节点会插入到update[i].level[i]之后
update[i] = x
level = zslRandomLevel() # 取一个随机层数, 使zskiplist,每层节点更为均衡
# 新节点层高增大的情况,更新扩展层的默认跨度
if level > zsl.level:
for i in range(zsl.level, level):
rank[i] = 0
update[i] = zsl.header
update[i].level[i].span = zsl.length
zsl.level = level
# 更新节点x和前驱节点已有层的跨度
x = zslCreateNode(level, score, obj)
for i in range(level):
x.level[i].forward = update[i].level[i].forward
update[i].level[i].forward = x
x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
update[i].level[i].span = (rank[0] - rank[i]) + 1
# 更新前驱节点扩展层的跨度,x节点这些层没有后继节点,所以跨度为0
for i in range(level, zsl.level):
update[i].level[i].span += 1 # type: ignore
# 设置新节点的后退指针, level[0]才有后退指针
x.backward = None if update[0] == zsl.header else update[0]
if x.level[0].forward:
x.level[0].forward.backward = x
else:
zsl.tail = x
zsl.length += 1
return x
跳跃表的查找则是从高层向低层查找,沿着最高层链表前进;遇到大于目标值的节点,则往下一层,直到找到相等的值为止。
经过的所有节点的跨度相加即是目标节点的rank。
def zslGetRank(zsl: zskiplist, score: float, obj: robj) -> int: |
查找score=2.0的o2对象的过程
所谓持久化,就是将Redis在内存中的数据库状态以某次格式保存到磁盘里面,避免数据意外丢失。
Redis有两种持久化方式:RDB (Redis Database)、AOF (Append Only File)
RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,包含了Redis数据库的所有数据。
RDB是将redis中所有db中的所有键值对以如下格式进行储存
有两个命令可以生成RDB文件,SAVE
和 BGSAVE
。生成RDB文件时,redis会遍历所有非空db的所有键值对按一定格式存储到RDB文件中。
SAVE
命令会在当前进程进行,期间服务器会阻塞,不能处理任何请求。BGSAVE
命令会fork一个子进程来创建RDB(利用Copy-on-write),服务继续处理命令请求。
为了避免竞争条件和性能问题,SAVE
和 BGSAVE
任意时刻只能有一个在执行。
用户可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,就会触发RDB保存.
save选项的格式是 save seconds option_times
。例如save 900 1
,若服务器在900秒之内, 对数据库进行了至少1次修改,则执行BGSAVE。
BGSAVE也已可能会阻塞请求,因为磁盘io满了,这时如果有fsync操作,服务也会阻塞。
可以设置 no-appendfsync-on-rewrite yes
, 在子进程处理和写硬盘时, 主进程不调用 fsync() 操作。
服务器启动时会自动载入RDB文件,Redis并没有专门用于载人RDB文件的命令。
如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。
由于RDB生成的机制决定了,RDB文件总是会和redis内存有部分不一致,RDB文件会缺少从上次BGSAVE开始到当前时刻的所有改动。AOF持久化的存在就是为了解决该问题。
AOF持久化是通过保存执行的写命令来记录数据库状态。
因为Redis的命令请求协议是纯文本格式,所以AOF文件类似如下。*2\r\n$6\r\nSELECT\r\n$l\r\nO\r\n
*3\r\n$3\r\nSET\r\n$3\r\nmsg\r\n$5\r\nhello\r\n
appendfsync决定如何刷新文件缓存到硬盘,该选项的值直接影响的效率和安全性。
当故障停机时,文件缓冲区内的数据会丢失。
appendfsync有以下选项
AOF的还原就是模仿客户端逐条执行文件里的命令。
AOF的还原时机也是服务启动时,并且在还原过程中能正常执行的只有 PUBSUB 等模块。
步骤如下:
由于AOF是直接记录的写命令而不是数据库状态,所以文件中包含很多冗余语句,导致文件膨胀。
比如下面的这些命令,其实最终数据库状态等价于lpush numbers 333
, 前4条语句都是冗余的。127.0.0.1:6379> lpush numbers 111
(integer) 1
127.0.0.1:6379> lpush numbers 222
(integer) 2
127.0.0.1:6379> lpop numbers
"222"
127.0.0.1:6379> lpop numbers
"111"
127.0.0.1:6379> lpush numbers 333
(integer) 1
127.0.0.1:6379> lrange numbers 0 -1
1) "333"
为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写(rewrite) 功能。
AOF文件重写是遍历redis的所有键值对,生成对应的redis命令,写入到一个新的文件中,并替换旧AOF文件。
所以AOF文件重写和旧AOF文件并没有关系,更应该称之为AOF重生成。
AOF重写程序在子进程里执行, 这样做可以同时达到两个目的:
在AOF重新过程中,所有命令会额外会写一份到AOF重写缓冲区中,当新AOF文件生成时,父进程会将AOF重写缓冲区的内容追加到新AOF文件中,并替换旧AOF文件。
为防止AOF重写失败,AOF缓冲区在重写过程中依然正常工作。
注意:redis主从是基于RDB + 命令传播
,并没有利用AOF文件,与MySQL的binlog不同。
算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。
使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。
SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。
其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,保持自增性且不重复。
主要分为 5 个部分:
所以理论上每秒可以生成1000 * 1024 * 4096 = 4194304000
个id,完全足够使用
有几个需要注意的点
from time import time |
给windows的资源管理器添加一个右键菜单,调用你想要运行的程序或脚本,理论上可以实现任意功能。
那么我们就开始py一个脚本吧, 创建目录联接到指定目录
import os, sys, subprocess |
修改注册表,添加右键菜单
复制这段保存为 add_to_share.reg 双击执行导入
Windows Registry Editor Version 5.00 |
数独的解法需 遵循如下规则:
虽然玩法简单,但提供的数字却千变万化,所以很适合用程序来求解。
类似这种需要穷举的问题一般采用回溯法,也就是暴力求解,在最坏的情况下时间复杂度为指数。
回溯法采用试错的思想,它尝试分步的去解决一个问题。在解决问题的过程中,如果现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。
回溯法通常用最简单的递归方法来实现,下面是回溯法的一般结构。
理解回溯法首先要理解递归,理解递归的过程,以及递归的返回值。可以通过查看斐波那契数列的递归实现的调用栈来理解递归的过程。
def backtracking(args): |
回到数独解法上来
首先看最简单的暴力解法
这里有几个点和模板不一样
can_stop
判断get_all_choices
最多有 行 x 列 x 数字取值 = 9 x 9 x 9
种情况# 0 代表空位 |
所谓的剪枝,就是递归每一层的时候,并不是所有情况都是有效的,可以跳过这些分支。
以该题为例,第一行第三列可选取值并不是1到9,而是1,2,4
,而且随着我们不断把空位填满,越后面的点选择越少。
可以用集合存储每个空位的行、列、九宫方向的可选值,三者交集就是该点的可选值。(用位替换集合还可以进一步优化算法)
因为填充一个空位,会影响该位置行、列、九宫上的所有空位的取值,所有不直接存储该点的可选值。
EMPTY = 0 |
https://zh.wikipedia.org/wiki/%E6%95%B8%E7%8D%A8
https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF%E6%B3%95
https://www.jianshu.com/p/8e694d079a76
最近在在一台服务器上发现, 一个服务的工作进程会异常退出, 但部署有相同代码的其他服务却没有类似的情况.
查看日志发现以下错误
Traceback (most recent call last): |
在上面的错误输出里有一个关键词 RLIMIT_NPROC
, 涉及到了linux的Resouce limit.
在Linux系统中,Resouce limit指在一个进程的执行过程中,它所能得到的资源的限制,比如进程的core file的最大值,虚拟内存的最大值等。
Resouce limit的大小可以直接影响进程的执行状况。其有两个最重要的概念:soft limit 和 hard limit。
softlimit是指内核所能支持的资源上限, hard limit在资源中只是作为softlimit的上限。当你设置hard limit后,你以后设置的softlimit只能小于hard limit。
要说明的是,hardlimit只针对非特权进程,也就是进程的有效用户ID(effective user ID)不是0的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE),softlimit则只有内核上限。
resource:可能的选择有
RLIMIT_AS 进程的最大虚内存空间,字节为单位。
RLIMIT_CORE 内核转存文件的最大长度。
RLIMIT_CPU 最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。然而,可以捕捉信号,处理句柄可将控制返回给主程序。如果进程继续耗费CPU时间,核心会以每秒一次的频率给其发送SIGXCPU信号,直到达到硬限制,那时将给进程发送 SIGKILL信号终止其执行。
RLIMIT_DATA 进程数据段的最大值。
RLIMIT_FSIZE 进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
RLIMIT_LOCKS 进程可建立的锁和租赁的最大值。
RLIMIT_MEMLOCK 进程可锁定在内存中的最大数据量,字节为单位。
RLIMIT_MSGQUEUE 进程可为POSIX消息队列分配的最大字节数。
RLIMIT_NICE 进程可通过setpriority() 或 nice()调用设置的最大完美值。
RLIMIT_NOFILE 指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
RLIMIT_NPROC 用户可拥有的最大进程数。
RLIMIT_RTPRIO 进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。
RLIMIT_SIGPENDING 用户可拥有的最大挂起信号数。
RLIMIT_STACK 最大的进程堆栈,以字节为单位。
可以看到RLIMIT_NPROC
是指的 是用户可拥有的最大进程数.
通过读取/proc/${PID}/limits
文件, 可以得知对应进程的Resouce limit数值
$ sudo cat /proc/13839/limits |
可以看到, Max processes
一项的Soft Limit为1024, Hard Limit为516033, 与错误输出中的信息一致.
通过ps -ef -T |grep $(whoami) | wc -l
可以查得当前用户的总进程(线程)数目, 结果是1016
.
显然1016已结接近Soft Limit了, 当程序尝试启动更多进程时就回出错, 所以我们需要增大Soft Limit.
通过修改/etc/security/limits.d/90-nproc.conf
配置文件
增加以下内容, 将RLIMIT_NPROC设置为10240* soft nproc 10240
执行ulimit -u
, 确认已经生效 ulimit -u
10240
但是问题依然存在, 不能解决.
再次查看进程的RLIMIT_NPROC
, 发现并没有变化.$ cat /proc/13839/limits |grep 'Max processes'
Max processes 1024 516033 processes
但是从终端启动新服务, 其RLIMIT_NPROC
数值已经是10240了.
由此初步判断, 应该是修改limits配置文件, 只对新启动的进程生效.
由于工作进程都是由supervisor托管的, 查看了supervisord进程的RLIMIT_NPROC
, 发现也是1024.
由supervisord启动的所有进程的父进程都是supervisord进程, 都继承了它的RLIMIT_NPROC
数值.
最终, 将supervisord进程重启, 问题得到解决.
这个服务是个多进程的tornado服务, 共10个工作进程. 但是最终用了241个进程/线程(Linux进程和线程某种意义上是等价的).
$ ps -ef -T |grep sync360 | grep mid | wc -l |
这台服务器上类似的服务又有三四个, 1024的量很快就用完了.
所以问题又回到了OpenBLAS
身上
OpenBLAS是高度优化的线性代数库, 很多机器学习的库都依赖了OpenBLAS.
OpenBLAS通过多线程的方式来加速计算, 所以241个进程很很好解释了.
由于我的服务器是24核心的, 一般多线程程序的默认线程数与cpu核心数一致.
所以: 241 = 10 * 24 + 1
因为服务本身已经是多进程的, 再启用OpenBLAS的多线程, 加速的意义其实不大了.
查阅资料后发现, 如果应用是多线程的,将会与多线程下的OpenBLAS发生冲突。因此OpenBLAS只能运行在单线程下。(未充分验证)
可以通过设置环境变量OPENBLAS_NUM_THREADS
, 调整OpenBLAS的线程数.
设置之后, 重启程序, 出现另一个类似的问题libgomp: Thread creation failed: Resource temporarily unavailable
这里的libgomp是OpenMP的动态库, OpenMP会被用来优化编译OpenBLAS.
所以我们将环境变量OMP_NUM_THREADS
也设置为1
在python启动文件前面加入以下语句import os
os.environ['OPENBLAS_NUM_THREADS'] = '1'
os.environ['OMP_NUM_THREADS'] = '1'
重启程序, 发现只使用了11个进程, 与预期相符合, 问题解决.
该问题的根本原因是最大进程数RLIMIT_NPROC
不够用.
所以解决方法有两个
通过修改/etc/security/limits.d/90-nproc.conf
配置文件, 增大最大进程数
同时注意, 修改最大进程数等limits配置文件, 只对新启动的进程生效. 需要将相关进程都重启
通过关闭多线程, 来减少占用的进程数.
设置环境变量OPENBLAS_NUM_THREADS
和OMP_NUM_THREADS
的值为1
Esc
退出功能,比如退出输出面板↑↓←→
上下左右移动光标,注意不是不是KJHL!Alt
显示菜单栏Ctrl + Shift + P
调出命令板(Command Palette)Ctrl + `
调出控制台Ctrl + K, Ctrl + 数字
代码折叠,数字代表折叠的层次Ctrl + Shift + [ / ]
收起/展开选中的代码。Ctrl + Shift + N
创建一个新窗口Ctrl + N
在当前窗口创建一个新标签Ctrl + W
关闭当前标签Ctrl + Shift + T
恢复刚刚关闭的标签F11
切换普通全屏Shift + F11
切换无干扰全屏Alt + Shift + 2/3/4
左右分组,分2到4组Alt + Shift + 8/9
上下分组,分2到3组Alt + Shift + 5
上下左右分组Ctrl + 数字键
跳转到指定组Ctrl + Shift + 数字键
将当前标签移动到指定组Ctrl + P
跳转到指定文件,输入文件名后可以打开对应文件@ 符号跳转
输入@symbol跳转到symbol符号所在的位置,比如函数或类# 关键字跳转
输入#keyword跳转到keyword所在的位置: 行号跳转
输入:12跳转到文件的第12行。Ctrl + R
跳转到指定符号 @Ctrl + G
跳转到指定行号 :Ctrl + ;
跳转到指定关键字Ctrl + /
注释当前行,取消注释Ctrl + Enter
在当前行下面新增一行然后跳至该行Ctrl + Shift + Enter
在当前行上面增加一行并跳至该行Ctrl + ←/→
进行逐词左右移动Ctrl + Shift + ←/→
进行左右逐词选择Ctrl + ↑/↓
上下移动当前显示区域Ctrl + Shift + ↑/↓
上下移动当前行,或者选中行Ctrl + KU/KL
转换当前单词为大写/小写Ctrl + D
选择当前词,进入多重编辑Ctrl + K
多重编辑时跳过当前选中的Ctrl + U
多重编辑,回退选中Ctrl + Shift + L
将当前选中区域打散Ctrl + J
把当前选中区域合并为一行Ctrl + M
在起始括号和结尾括号间切换Ctrl + Shift + M
快速选择括号间的内容Ctrl + Shift + J
快速选择同缩进的内容Ctrl + Shift + Space
快速选择当前作用域(Scope)的内容F3
跳至当前关键字下一个位置Shift + F3
跳到当前关键字上一个位置Alt + F3
选中当前关键字出现的所有位置Ctrl + F/H
进行标准查找/替换,之后Alt + C
切换大小写敏感(Case-sensitive)模式Alt + W
切换整字匹配(Whole matching)模式Alt + R
切换正则匹配(Regex matching)模式Ctrl + Shift + H
替换当前关键字Ctrl + Alt + Enter
替换所有关键字匹配Ctrl + Shift + F
多文件搜索&替换在不需要对象映射的时候,使用core而不是orm,可以降低数据库操作成本,提高性能。
from sqlalchemy import create_engine |
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey, Index |
# 单个表 |
from sqlalchemy import MetaData |
## 获得连接对象 |
基本查询示例,复杂过滤条件查看文档
# select * from users |
from sqlalchemy.sql import update, select, delete, insert |
1) stmt = users.delete().where(users.c.id == |
监听connect事件,在初始化数据库连接时设置数据库参数变量,或者执行语句
参考文档from sqlalchemy import event
engine = create_engine(
"postgresql://user:pass@hostname/dbname"
)
def connect(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("SET some session variables")
cursor.close()
使用ORM可以简化数据库的操作,使数据操作更加面向对象,并且程序逻辑和具体数据库解耦。缺点是会有一定的性能损耗。
Python中的ORM主要有Django ORM,SQLAlchemy, peewee; 其中Django ORM只能和Django框架一起使用,SQLAlchemy功能比较全,peewee较为轻量。
SQLAlchemy还可以不使用其ORM,只使用SQLAlchemy core作为一个通用数据库连接器。
from sqlalchemy import Column, Integer, String, DateTime, TIMESTAMP, text |
SQLAlchemy使用类似sqlite:///test.sqlite3
的URI来表示数据库连接
格式为:dialect+driver://username:password@host:port/database
, 具体查看官方文档
from sqlalchemy import create_engine |
from sqlalchemy import create_engine |
from sqlalchemy import create_engine |
# 插入单条 |
一些基本查询,更多查看官方文档## select * from table11
session.query(SomeData).all()
# return: [<__main__.SomeData object at 0x1040e6278>, <__main__.SomeData object at 0x103aa13c8>, <__main__.SomeData object at 0x103aa1438>]
## 通过主键获取数据
session.query(SomeData).get(1)
## select * from table11 where status='2' order by the_time limit 1
data = session.query(SomeData).filter_by(status='2').order_by(SomeData.the_time).first()
# data: <__main__.SomeData object at 0x103aa13c8>
## select * from table11 where status in ('1', '2')
session.query(SomeData).filter(SomeData.status.in_(['1', '2'])).all()
## select status, message from table11
session.query(SomeData.status, SomeData.message).all()
# return: [('1', 'aa'), ('2', 'bb'), ('2', 'cc')]
## 原始sql语句
stmt = text("SELECT message, id, status FROM table11 Where status=:status")
stmt = stmt.columns(SomeData.message, SomeData.id, SomeData.status,)
session.query(SomeData).from_statement(stmt).params(status='1').all()
## 遍历大表,每N条数据请求数据库一次
for p in session.query(SomeData).yield_per(5):
print(p)
多表join查询for u, a in session.query(User, Address).\
filter(User.id==Address.user_id).\
'jack@google.com').\ filter(Address.email_address==
all():
print(u)
print(a)
<User(name='jack', fullname='Jack Bean', nickname='gjffdd')>
<Address(email_address='jack@google.com')>
## 更新单条 |
session.query(SomeData).filter_by(id=2).delete() |
from contextlib import contextmanager |
Windows Subsystem for Linux
(简称WSL)是一个为在Windows 10上能够原生运行Linux二进制可执行文件(ELF格式)的兼容层。它是由微软与Canonical公司合作开发,目标是使纯正的Ubuntu 14.04 “Trusty Tahr”映像能下载和解压到用户的本地计算机,并且映像内的工具和实用工具能在此子系统上原生运行。
WSL提供了一个微软开发的Linux兼容内核接口(不包含Linux代码),来自Ubuntu的用户模式二进制文件在其上运行。
此功能组件从Win10 Insider Preview build 14316开始可用,正式版是Win10 RedStone1才可用,并且只有64位系统才有此功能。我目前的系统是预览版Insider Preview build 14986,相比正式版Win10 RedStone1版本WSL功能会完善些,但系统就不稳定些了。建议大家还是用Win10 RedStone1吧
WSL 的出现解决了很大程度上解决了Windows用户使用linux工具链的需求,同时也解决部分用户(比如我)在Linux与Windows之间切换的麻烦。
WSL的优点
WSL的不足
systemd
,iptables
等由于本文写作时间已经比较久了,强烈建议查看MS的官方教程WSL install guide
开启开发者模式: 设置 > 更新及安全 > 针对开发人员 > 开发人员模式
启用WSL功能:资源管理器地址栏输入 “控制面板\程序\程序和功能”,选择启用或关闭Windows功能,勾选适用于Linux的Windwos子系统(beta),重启系统
下载Linux镜像:按Win + X
选择“命令提示符”或者“Windows PowerShell”,在命令行中输入bash
,按提示操作。安装完成后使用也是在命令行中输入bash
可以用mintty
作为WSL的终端,替换命令提示符,获得更类似linux的体验。github中已经有人做好了,下载使用即可goreliu/wsl-terminal
解压运行open-wsl.exe,你就得到了一个漂亮的linux终端。
新建文本文件,用记事本打开,输入一下内容,替换里面的两处路径,保存重命名为wsl.reg
,运行。Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\Background\shell\wsl]
@="WSL Here"
"Icon"="\"D:\\PortableApps\\wsl-terminal\\open-wsl.ico\""
[HKEY_CLASSES_ROOT\Directory\Background\shell\wsl\command]
@="D:\\PortableApps\\wsl-terminal\\open-wsl.exe"
apt-get -y install xorg xfce4
x server
,可选xming
和VcXsrv
,这里选用VcXsrv启动x server
WSL的配置,在wsl的bash中运行
echo "export DISPLAY=:0.0" >> ~/.bashrc # 设置屏幕为x server |
在WSL里运行startxfce4
,就此大功告成
运行 sudo nano /etc/network/interfaces
将文件修改成如下:auto eth0 # eth0是网卡名称,你的不一定是这个,可通过ifconfig查看
iface eth0 inet static
address 192.168.157.129 # 地址
gateway 192.168.157.2 # 网关
netmask 255.255.255.0 # 掩码
运行 sudo nano /etc/resolvconf/resolv.conf.d/base
把文件改成 nameserver 192.168.157.2
把192.168.157.2
改成你需要的dns,这里因为是vmware的NAT模式,所以dns和网关是一样的。
BITCOUNT
命令实现用到了variable-precision SWAR 算法。
BITCOUNT
命令要解决的问题:统计一个位数组中非0二进制位的数量。在数学上被称为“计算汉明重量(Hamming Weight)”
目前已知效率最好的通用算法为variable-precision SWAR 算法。
该算法通过一系列位移和位运算操作,可以在常数时间内计算多个字节的汉明重量,并且不需要使用任何额外的内存。
以下是一个处理32位长度位数组的算法实现,一共分4步。uint32_t swar(uint32_t i){
i = (i & 0x55555555) + ((i>>1) & 0x55555555); // 步骤1
i = (i & 0x33333333) + ((i>>2) & 0x33333333); // 步骤2
i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F); // 步骤3
i = (i * 0x01010101) >> 24; // 步骤4
return i;
}
这里我们以i=0x12345678
(二进制位为00010010001101000101011001111000
)为例,讲解算法过程
我们可以把i的二进制位理解成:长度为32的数组,每个元素取值区间[0,1],每个元素正好能代表这个位是不是1.
所以,问题就可以转化为,求这个数组的和。
根据分治法的思想,我们可以把相邻的两个数字相加,得到长度为16的数组,每个元素取值区间[0,2]。
并以此类推,最终求出总和。
这一步用到0x55555555
作为掩码,其二进制表示为01010101010101010101010101010101
此时i可理解为长度为32的数组,每个元素取值区间[0,1],元素宽度1bit。
通过i & 0x55555555
运算,取得了i的奇数位置元素,存储为16个2bit整数;
通过(i>>1) & 0x55555555
运算,取得了i的偶数位置元素,存储为16个2bit整数;
两者相加,相当于16组2bit整数按位相加,问题就转化成了2bit的二进制加法。
由于原数组每个元素取值区间[0,1],所以每组相加的结果会在[0,2]区间内,2bit刚好存储。
最终得到长度为16的数组,每个元素取值区间[0,2]。
这一步用到0x33333333
作为掩码,其二进制表示为00110011001100110011001100110011
此时i可理解表示为长度为16的数组,每个元素取值区间[0,2],元素宽度2bit。
通过i & 0x33333333
运算,取得了i的奇数位置元素,存储为8个4bit整数;
通过(i>>1) & 0x33333333
运算,取得了i的偶数位置元素,存储为8个4bit整数;
两者相加,相当于8组4bit整数按位相加,问题就转化成了4bit的二进制加法。
由于原数组每个元素取值区间[0,2],所以每组相加的结果会在[0,4]区间内,4bit刚好存储。
最终得到长度为8的数组,每个元素取值区间[0,4]。
这一步用到0x0F0F0F0F
作为掩码,其二进制表示为00001111000011110000111100001111
此时i可理解表示为长度为8的数组,每个元素取值区间[0,4],元素宽度4bit。
通过i & 0x0F0F0F0F
运算,取得了i的奇数位置元素,存储为4个8bit整数;
通过(i>>1) & 0x33333333
运算,取得了i的偶数位置元素,存储为4个8bit整数;
两者相加,相当于4组8bit整数按位相加, 问题就转化成了8bit的二进制加法。
由于原数组每个元素取值区间[0,4],所以每组相加的结果会在[0,8]
区间内,8bit足够存储。
最终得到长度为4的数组,每个元素取值区间[0,8]。
按照上面的思路,本来应该继续将长度为4的数组转换为长度为2的数组。
但是这里由于4个8bit整数相加存在简便运算,就不继续往下合并了。
到这一步是时i=0x02030404
,为了求出最终结果,我们可以想到位移的办法将每8bit取出(参考ip掩码计算),然后再依次相加。
最终结果也就是 (i & 0xFF) + ((i>>8) & 0xFF) + ((i>>16) & 0xFF) + ((i>>24) & 0xFF)
为了理解算法里的做法,这里需要简单的数学推导// 将0x01010101转化成多项式表达
0x01010101 == 2**24 + 2**16 + 2**8 + 2**0
// 两边同乘以i
i * 0x01010101 == i * 2**24 + i * 2**16 + i * 2**8 + i * 2**0
// 2的乘方运算转化为位移运算
i * 0x01010101 == (i<<24) + (i<<16) + (i<<8) + (i<<0)
// 两边同时右移24位
(i * 0x01010101)>>24 == ((i<<24)>>24) + ((i<<16)>>24) + ((i<<8)>>24) + ((i<<0)>>24)
// 将左移和右移合并,并考虑溢出,最终结果一致
(i * 0x01010101)>>24 == (i & 0xFF) + ((i>>8) & 0xFF) + ((i>>16) & 0xFF) + ((i>>24) & 0xFF)
windows下的网络共享只有SNAT那一部分,比如各自免费wifi软件。少了DNAT,外部网络就无法访问内部。
前置要求,需要两张网卡,无线或者有线均可。
打开:控制面板 》网络和Internet 》网络和共享中心 》更改适配器设置
右键A网卡 > 属性 > 共享 > 勾选允许 (win10可能有下拉选择,下拉选中B网卡)
netsh是Windows自带的端口转发/端口映射工具。
支持IPv4和IPv6,命令即时生效,重启系统后配置仍然存在。
add v4tov4 [listenport=]integer>|servicename> \ |
将192.168.8.108的22端口映射到本地的2222端口
这样外部就可以通过本地的对外ip来ssh访问192.168.8.108了netsh interface portproxy add v4tov4 listenport=2222 connectaddress=192.168.8.108 connectport=22
一般情况下使用下列命令进行查看
netsh interface portproxy show all |
部分分享已经失效,这里补一下: https://pan.baidu.com/s/1ehg5VNksD7u-eWm1KFmPSw
提取码: g2fx
善用佳软,节约时间,提高效率。
一款基于谷歌浏览器优化的浏览器,本人日用。
对谷歌浏览器添加以下优化
轻巧绿色的本地音乐播放器,颜值也高。
下载地址 官方网站
视频播放器,腾讯几款良心作品之一,推荐日常使用。
下载地址 官方网站
视频播放器,推荐高级玩家使用。
下载地址 安装版 by qiuquan.cc
一款截图工具,全屏截图、窗口截图、滚动截图、录屏,一应俱全。
本篇文章的图都是用的这个。
下载地址 绿色免安装
看图王功能还是不错的,就是广告恶心。去广告版就完美解决了这个问题,一般人我还不告诉他。
下载地址 去广告安装版 by qiuquan.cc
屏幕取色工具,取得屏幕任意一点的颜色RGB数值。
下载地址 绿色免安装
韩国产压缩解压软件,免费好用,比winrar更为人性化,比如自动解压等等。
下载地址 官方网站
OCR文字识别利器。
图片转文字,可处理图片及pdf文档;处理扫描版pdf使之可搜索;屏幕截图转文字。懒人必备。
下载地址 安装版
电子书管理工具。
可以对各种电子书进行格式转换,比如epub、mobi之前互相转换。也可以作为本地电子书阅读器。
下载地址 官方网站
全文搜索工具
可以在搜索整个文件夹下的的文件,匹配你想要的文字,写东西的时候查起来甚是方便,旁征博引不是梦。
下载地址 便携版 by portablesoft.org
福昕PDF套件,pdf编辑工具,功能不差,但比Adobe Acrobat小巧。
下载地址 绿色免安装
福昕PDF阅读器,Adobe Reader替代品,绿色小巧。
下载地址 安装版 by repaik.com
应该是windows上最好用的邮件客户端吧,腾讯良心产品之一,张小龙作品。
下载地址 官方网站
markdown编辑器,实时预览,比较好用功能也比较全,我博客的文章都是用这个编辑器写的。
下载地址 官方网站
思维导图工具,众多思维导图工具工具里最好用的一款。
下载地址 绿色便携版 by 米苑
系统记事本完美替代品,小巧功能也不少。
下载地址 绿色版 by zd423
pdf文件目录书签编辑器,挺有用的。
下载地址 绿色版
轻量,轻度使用足矣;如果以操作office为主要工作还是上微软的吧。
下载地址 绿色版 by 未止
腾讯另一款良心产品,无广告比搜狗好多了,配上我做的皮肤,不能再赞。
下载地址 官方网站 & 自制极简皮肤
一款词典软件,词典比有道词典丰富,官方支持多种词典,英汉汉英、日语、德语、成语应有尽有。
下载地址 官方网站 & 词库下载
文本编辑器,写起代码爽到飞起,我每天都用它写Python。
下载地址 自用Python配置
现在的百度网盘不是会员下载龟速,用这款可以无限试用加速,悄悄的拿不要声张。
下载地址 密码:441e
下载视频的好帮手,打开可以解析优酷、土豆等视频的下载地址。然后调用这个下载器下载视频。
下载地址 便携版下载器
老板牌下载软件,这就不用说了,贡献一下我在使用的版本。
下载地址 安装版 by qiuquan.cc
windows命令行的最佳替代品。
硬盘信息查看。
可看硬盘健康度、温度、通电时间、SSD数据写入量、硬盘SMART数据。
下载地址 绿色版
windows更新清理工具
用于解决windows的C盘可用空间越来越小的问题,谨慎操作。
下载地址 官方网站
硬盘分区工具
功能强大,数据无价请谨慎操作。
另一款分区工具,操作比较傻瓜,特色功能是无损分区大小调整。注意,如果数据很多的话,调整分区可能要几个小时。
数据无价,谨慎操作。
下载地址 官方网站
重复文件查找工具,解放硬盘空间。
可通过多种方式识别重复文件,并且引导你选择删除。
下载地址 绿色版
金山毒霸的垃圾清理功能的提取版,只包含垃圾清理功能,其他一概没有。强烈推荐。
下载地址 绿色版 by zd423
虚拟机软件,搭个测试环境什么的很合适。
vmware workstation功能强大,就是体积太大。这个版本100m左右,基本功能却一个不少,还要什么自行车。
下载地址 精简安装版 by repaik.com
让windows8/10的开始菜单变成win7那种,强烈推荐。
下载地址 安装版 by zd423
跟国内版一比,国际版简直是活雷锋,实在要用360的同学就用国际版吧。
下载地址 官方网站
漂亮又实用的windows防火墙,可以用来禁止一些程序联网,统计程序的网络使用情况。
下载地址 官方网站
查看每个文件夹的空间使用情况,删除无用文件,解放硬盘空间。
下载地址 绿色版
软件卸载工具,简洁好用。
下载地址 绿色版 by zd423
误删数据恢复
比如手贱误删了数据,或者u盘被格式化了,用这个或许能救命。数据无价,这个不保证成功率,发现数据误删请马上停止对这个硬盘的写入操作,并用这个软件试试。
下载地址 便携版
另一款数据恢复软件
下载地址 绿色版
又一款数据恢复软件,比较适用于误删除恢复。
下载地址 绿色版 by zd423
文件快速搜索工具
比系统自带搜索快N倍,什么东西都给你搜出来,藏小黄片的可小心了,科科。
下载地址 绿色版 by zd423
软件快速启动&文件搜索工具,适合我这种软件特别多的,让你再也不会在杂乱的桌面上找图标。
下载地址 官方网站
给windows的资源管理器加上标签页,用起来就像chrome浏览器
下载地址 官方网站
文本、文件夹比较工具,比较他们之间的不同之处。
下载地址 绿色版 by zd423
选中文本自动复制到剪贴板。
下载地址 绿色版
可以让所有的windows程序都透明,透明度可选。
下载地址 https://pan.baidu.com/s/1dFhtk1j 密码: vg6r
调节屏幕色温,相当于安卓手机上的护眼模式。
下载地址 官方网站
查看电脑的操作记录,可以看看你平时用电脑都在干什么,或者别人平时在用你的电脑干什么,科科。
下载地址 绿色版
可以让每一个窗口都可以在最前,一边查资料一边写东西很合适。
使用方法:将软件的“+”字拖动到你需要置顶的窗口的标题栏上。
下载地址 绿色版
让每一款软件都拥有多标签模式,适合疯狂开窗口的人,比如我。
下载地址 绿色版 by 大眼仔
brpc + xgboost
实现的,而xgboost官方是不支持在多线程环境下使用的(1.2.0版本之前)
这个模型服务已经有两年多了,显然当时用的版本是不支持多线程的,有位同事当时修改了xgboost的源码,解决了多线程的问题,在线上也稳定运行到现在。
那么,问题来了。最近有个新需求,用到了xgboost的pred_leaf
功能,然后就发现并发请求时0.1%
的模型结果不对。
调试过程中,首先删除其他模型,排除干扰。然后开关pred_leaf
功能批量对比测试,确认当该功能关闭时,模型结果是正常的。
所以,很有可能pred_leaf
参数导致程序走到和之前不同的分支,而这个分支的多线程问题并未修复。
那么,解决问题的思路就是:确定之前同事修改了什么,找到与pred_leaf相关的函数,尝试做相同修复。
首先得确定同事都修改了什么,由于代码历史比较就远了了,而且xgboost的改动并没有加入到git,也没有明显的版本号等标识,这个地方也就比较头疼了。
唯一能确定的是,xgboost版本的是0.6左右,修改源码时参考了文章。
所以只能从git下载最新的代码,然后git --no-pager log --stat
查看每个commit的改动情况,使用beyond compared对比 + 人肉二分查找
,最终定位到对应的commit。
找到了对应的commit之后,通过对比改动和结合上文参考文章,其实就是两类改动。
具体来说,以PredLoopSpecalize
为例, 修改前inline void PredLoopSpecalize() {
const int nthread = omp_get_max_threads();
InitThreadTemp(nthread, model.param.num_feature);
for (bst_omp_uint i = 0; i < nsize - rest; i += K) {
const int tid = omp_get_thread_num();
RegTree::FVec& feats = thread_temp[tid];
// thread_temp 为成员变量
// 省略其他逻辑
}
}
inline void InitThreadTemp(int nthread, int num_feature) {
int prev_thread_temp_size = thread_temp.size();
if (prev_thread_temp_size < nthread) {
thread_temp.resize(nthread, RegTree::FVec());
for (int i = prev_thread_temp_size; i < nthread; ++i) {
thread_temp[i].Init(num_feature);
}
}
}
修改后inline void PredLoopSpecalize() {
const int nthread = omp_get_max_threads();
std::vector<RegTree::FVec> local_thread_temp; // 改动点
int prev_thread_temp_size = local_thread_temp.size();
if (prev_thread_temp_size < nthread) {
local_thread_temp.resize(nthread, RegTree::FVec());
for (int i = prev_thread_temp_size; i < nthread; ++i) {
local_thread_temp[i].Init(model.param.num_feature);
}
}
for (bst_omp_uint i = 0; i < nsize - rest; i += K) {
const int tid = omp_get_thread_num();
RegTree::FVec& feats = local_thread_temp[tid]; // 改动点
// thread_temp 为成员变量
// 省略其他逻辑
}
}
显然问题就在thread_temp
, 做为类成员,它不是线程安全的,通过替换为栈上的local_thread_temp
,不同进程访问的地址不同,自然就不存在冲突了。
通过查找PredLoopSpecalize
函数的调用链,可以发现其修改只会影响正常预测,pred_leaf则是不同的分支,显然线程安全的问题依然存在。// 调用链
XGBoosterPredict()
--> LearnerImpl::Predict()
--> LearnerImpl::PredictRaw() // if 正常predict
--> GBTree::PredictBatch()
--> CPUPredictor::PredictBatch()
--> CPUPredictor::PredLoopInternal()
--> Dart::PredLoopSpecalize()
--> GBTree::PredictLeaf() // if pred_leaf
--> GBTree::PredictContribution()
--> ObjFunction::PredTransform()
// LearnerImpl::Predict() 源码
void Predict(DMatrix* data, bool output_margin,
std::vector<bst_float>* out_preds, unsigned ntree_limit,
bool pred_leaf, bool pred_contribs) const override {
if (pred_contribs) {
gbm_->PredictContribution(data, out_preds, ntree_limit);
} else if (pred_leaf) {
gbm_->PredictLeaf(data, out_preds, ntree_limit);
} else {
this->PredictRaw(data, out_preds, ntree_limit);
if (!output_margin) {
obj_->PredTransform(out_preds);
}
}
}
所以,最后采用与PredLoopSpecalize
相同方法对PredictLeaf
进行修复,问题得以完美解决。
作者:北岛、李陀
最早识得北岛这个作家,是因为他的诗句,并为之倾倒。
卑鄙是卑鄙者的通行证,
高尚是高尚者的墓志铭,
看吧,在那镀金的天空中,
飘满了死者弯曲的倒影。
这本书收集了三十篇記憶文字,是作者们对于”上世纪70年代”这一段特殊历史时期的印象和回忆。
不知是因为年轻还是饥饿,我们几乎每时每刻低头觅食,仿佛猪狗。
阮云:我们难以想象的事,却以极平淡的语句说出,让人更加难以承受。
历史在原来作痛的地点消失了。
这个世界还会好吗,梁漱溟临终前的话。
阮云:这世界曾经好过吗,其实它从未真正好过,只是有时我们居住在它稍微好一点的那部分里。
大家极力地忘却七十年代,以为这样它就未发生过。
阮云:人遇到痛苦的事,为了让自己好受一点,会不自觉地忘却它。这正是人的优点之一,然而也正是可悲之处。当痛苦的承受者都忘却了,痛苦的制造者会怎么想呢?
普通百姓的日常生活,才是被历史忽略的最重要的部分。
阮云:你我百年之后,世上还有几许痕迹。
读啊写啊,可这都什么年代了,可别玩枪口上撞。
阮云:读书人呐,过于理想乃至于幼稚。毛主席为啥读资治通鉴十七遍,而不是其他。
历史永远记录的是大人物的大事,而小人物的日常感受我们无从得知。
现在,大家笑朝鲜,那可真是“好了伤疤忘了疼”,小孩不知道也就算了,大人也一般傻。
政治面貌调查和元朝人分四等有何区别。
阮云:在性质上有区别,一个是伟光正的社会主义的,一个是腐朽的资本主义的。
小桥报有人痴立,泪撒春帘一饼茶 —— 《己亥杂诗》龚自珍
谁复留君住,叹人生几番离合,便成迟暮。—— 《金缕曲》纳兰性德
]]>阮云:何须几番离合,一见杨过便已把终身误了。
简介:政治寓言,可惜在过往历史中已经能找到他的影子了,老大哥、真理部等梗的来源。说的是在1984年世界被三个国家瓜分,他们均高度集权,改变历史,改变语言(所谓新话);建立健全的监控系统,控制人们的思想和行为。
本篇文章仅为测试人工智能的最新研究进展。
博主云:他人为敌国,很多时候你的痛苦在别人眼中什么都不是。当然,反过来也是成立的。
古人云:我死后,哪管洪水滔天。
博主云:清朝的愚民是不让你明白,而更高明的是只给你简单的无可证伪的假信息,因为当世界只存在一种东西时就无所谓真假了。
性压抑能导致歇斯底里,这是求之不得的,因为它能转化成战争狂热和对领袖的崇拜。
一个等级社会只有建立在贫穷和无知的基础上才可能存在。
朝鲜:你们慢慢聊,我先走了。
博主云:正因如此,佛家产生在等级制度最为森严的印度。此生遭罪,为前世造业;今生修福,为投生净土。
ISIS:招募“圣战士” 死后能获72位纯洁处女。
统治集团下台可能的情况:被征服;统治效率低,被造反;让强大的充满不满的中等阶级集团出现;或者是自己丧失了统治的信心和意志。这四者一般综合起来作用。
大众只要不让他们掌握比较的标准,他们就甚至永远不会意识到他们在受压迫。
博主云:比较让我们变得不幸福。在不知道一块钱一斤的苹果之前,十块钱一斤的苹果是很便宜的。
]]>Big brother is watching you !
事实上,组织中只有细胞,是不再有人的。发明组织的人,是按机器原理设计的,个体的人在组织中,类似某个螺丝、刀片一般的部件。任何个人主义和自由主义,都是组织所不允许的;组织只会冠冕堂皇地提倡集体主义,会用无数教条来帮助你遗忘作为人的个性。
我们那一代人,许多是真正的理想主义者,而蒋介石从孙中山那里继承而来的国家体制,是违背现代宪政的“三一律”——一个领袖,一个主义,一个政党。当基本的人权都要被这个政府所钳制时,如果有另外一个党打出“要自由民主,要结社言论自由”的口号时,你说它能不吸引我们这些爱国哀民而又轻身躁进企图改造社会的理想青年吗?
博主云:所以我说从来不看好学生运动的,改造社会这种事情从来不是光靠满腔热情所能做到的,作为领袖没有一点的手腕是很难成就的。而那些政客更不用说了,他们永远是利益至上的,不知理想为何物。最好的状态是有个理想残存的野心家,然而毕竟是奢望。
生命不是话剧,可以彩排一次再正式登台。他们的悲剧一次性上演,就挥霍完了他们的一生。
博主云:我们从未踏入同一条河流,人生之无奈正出于此。
迅翁尝云:人最痛苦的莫过于梦醒之后无路可走。于我,则常是中宵酒醒之后,无路可走而深陷回忆,牵出无数往事的余痛。
老李认为——红卫兵的造反初衷源于那一代的神圣使命感,他们并不单纯,至少不像我们今天想象的那么幼稚。但动机不错的行动并不能保证结果的正确。他认为,知识分子应该为此首先承担罪责,全国各地的“文革”之火并非文盲引起,主要的“纵火犯”都是书生,他们只是没想到“革命最后会革到自己头上”。而在此之前,整个知识群体的道义缺失,客观上默许和纵容了暴政的为所欲为
捡粪的孩子多,牲口的遗矢有限,便不免有人终日碌碌而仅仅捡得几十粒羊矢。也有的持之以恒地跟踪一群牛,焦急地守望牛翘起尾巴,端起撮箕去抢接,甚至为此掀起混战。
博主云:现在看来甚是可笑,但这就是真实的生活,唏嘘!
他说世界上总有一些走投无路的人,需要花点小钱买个安慰;而我们这些废人也是生灵,也要活命,这叫天生人必养人。
本文为整理以前的读书笔记
《冬吴相对论》是一档商业脱口秀广播节目,节目由原凤凰卫视著名主持人梁冬与《21世纪商业评论》发行人吴伯凡共同主持。
本书就是节目的内容集结而成,对于经济知识的扫盲有些意义,但也有电视节目的通病,知识流于细碎,没有体系。
初学者用来初步了解经济学现象,还是很有意义的。
读得实在是太久了,很多点都忘记了
物化,比如乔布斯作为苹果的教主
阮云:这个词有些难理解,我觉得就是以物(苹果)为主体,人反而是依附
KISS,keep it simple stupid
阮云:然而现实却是相反,很多是人为增加复杂度
杠杆解,症状解
阮云:症状解就是头痛医头,脚痛医脚。杠杆解就是釜底抽薪,像杠杆一样四两拨千斤的解法。
领导者与管理者的区别
品牌是瞬间联想或者叫瞬间认知
阮云:其实就是潜意识
管理学,第五级领导
公共成本
智慧就是敬畏,没有敬畏就没有智慧。
阮云:所谓敬畏,就是有所不为
奢侈品是一种现代拜物教,被赋予超乎寻常的感情诉求的会有身份标志的一种产品
阮云:比如前些年的苹果产品
特权的溢价
阮云:商品被作为特权的标志而产生的溢价
不要贪迷于技术的变革
重要的事情是不讲效率的。
阮云:什么叫重要,其实就是产出投入比高
自我实现的预言
阮云:有了锤子,看啥都像钉子
动机的不对称,混饭吃和搏命的区别
]]>精深练习:朝着既定目标挣扎前进,挑战自己的能力极限,不断放错,这让你更聪明
设定一个稍微超过自己现有能力的目标,盲目受挫毫无帮助,实现目标才能突破原有水平。
专业选手与普通人之间的差别是懂这种语言与不懂的区别
精深练习
练习就是每天弹奏同样的音符,人类基本的姿态 —— 为一个想法努力,为你渴望的伟大成就努力争取,然后感觉它与你失之交臂。
激情的工作原理:是那些让我们意识到“我就想成为那样的人”的时刻。
激情的原始信号:未来归属感;不活不再安全;稀缺性
激情不是为遵守规则而存在的,它的存在只是为了工作,为了给自己选择的任务提供能量。
人才温床:肯定努力的价值,以及缓慢的进步,而不是天生的才华和智慧。
良好的性格是在外而内的,而且可以由动机和练习综合创造。
不要期望一下子取得大幅度的进步,试着每天进步一小点,这是必经之路,而一旦开始进步,就会持续进步。
髓鞘质回路不仅需要精深练习,也需要激情。
]]>《传习录》是阳明先生的问答语录和论学书信集。是一部儒家简明而有代表性的哲学著作。
王阳明可贵的不是他的学说,而是他行动和思想一样地伟大,能够知行合一,这样三不朽是理所应当的。
感悟:心学的核心致良知,就是良知的自我表达,良知的自知,主体性的向外扩展,使主观世界和客观世界融为一体,而不是主体被客观世界异化。
愿永为阳明先生门下走狗
悬想何益?但不忘栽培之功,怕没有枝叶花实
私欲日生,如地上尘,一日不扫,便有一层
修行如行路,走得一段便识得一段。若心思不在上面,便是千遍,也未必能识。
专涵养者,日见其不足;专见识者,日见其有余
狗若不咬人而追石块,终将一无所得。
譬如以手指月,不看月而追指,终无所得
夫道必体而后见,非己见道而加体道之功也
夫,正心、诚意、致知、格物,皆所以修身
夫道,天下之公道也;学,天下之公学也。非朱子可得而私也,非孔子可得而私也
现今流传于世的儒家经典,都夹杂了很多人的私货,尤其是朱熹的。
勿忘勿助,必有事焉
不要懈怠也不要冒进,一直修持
后世学者博文多识,留滞胸中,皆伤食只病也
学习如吃饭,只一味增加知识而不理解化用,只会消化不良
人若矜持太过,终是有弊。人只有许多精神,若专在容貌上用功,则于中心照管不及者多矣
然则,今之世人,类此者不知凡几
物来则应,不要着一分意思
道家谓之自然,佛家谓之当下
读书作文,安能累人?人自累于得失耳。
孟子不论心之动与不动,只是集义(让行为符合义),所行无不是义,此心自然无可动处。
譬如救火,扬汤止沸和釜底抽薪的区别
无善无恶心之体,有善有恶意之动,知善知恶是良知,为善去恶是格物。
思与学作两事做,故有‘罔’和‘殆’之病
心犹镜也,圣人心如明镜,常人心如昏镜。近世‘格物’之说,如以镜照物,照上用功,不知镜尚昏在,何能照?先生之‘格物’,如磨镜而使之明,磨上用功,明了后亦未尝废照。
如何磨镜,于意动处磨镜,即佛家所说之关照己身
动念处,正心诚意
]]>譬如,道路弯了要修正,肯定得是在修路的时候去改方向,不然全无用处
如果你追求阳光,你就躲不开身后的阴影。
美国青年在刷盘子时不会因为自己屈才而痛感命运不公,怨天怨地
华裔看不起黑人,常常只是因为黑人比他们穷;讨厌犹太人,只是嫉妒犹太人比他们更有成就感。
政府的正当权利,是要经过被治理者的同意才产生的。
必须听那些听不下去的话,“这正是我们必须为自由支付的代价”。
如果你因为害怕一个不自由的时代,因此就不给他们言论自由的话,那么这个不自由的时代已经开始了。
秩序?(没有自由的秩序不是真正的秩序)
安全还是自由,美国每天都在面对新的选择
阮云:自由美利坚,枪击每一天
抢不是一种工具,枪是一种权利。
权力会导致腐败,绝对的权力导致绝对的腐败。
一个人的房子就像他的城堡,当他安安静静地等待在里头的时候,他就应该安全得像一个城堡里的王子。
美国最高法院裁定,根据种族而给予优惠,几乎是与宪法精神不符的。
]]>记得当时在学校图书馆借了这本书,对幼稚的三观产生了巨大冲击。
书中所记内容,像是渣男语录集结,而纯情少女们也正吃这套。为何呢?漂亮话总是容易说的,成本也小,当你说骚话撩妹的时候,肯定不会想着如何兑现,先撩到手再说。
然而承诺就截然不同了,每一个承诺都得考虑其可行性及付出的成本,自然是得审慎,也就没那么吸引人了。
最终,还是还是白给了渣男的的甜言蜜语。
把一党专政化一下妆,当做民主的代用品,方法虽然巧妙,然而和人民的愿望相去十万八千里。中国人民都在睁着眼睛看:不要拿民主的代用品来欺骗我们啊? – 《新华日报》1945年1月28日
阮云:政客的话听听就好,但不要往心里去,政客的一切行为都应以动机论来分析
阮云:历史,特别是中国近代史,很少能分出个是非曲直,基本上都是屁股决定脑袋的事。
学校受外力干涉,教学内容受党化思想的规范都是有害的事情。 – 《新华日报》1946年2月6日社论
阮云:当时国人并未真正了解民主与自由,或者不想去了解,他们只是为了实现自己的目标而谈民主自由,而这个目标恰恰需要一个响亮的口号而已。
英美民主国家的人民集会,结社,是无论性质、地点、参与者的职业、性别几何,事前均无须请求警察许可,亦无须报告警察。在我国,各种人民团体之成立,无论下级团体或者上级团体,均应先经政府许可。
除了汉奸和反动派,其他任何人,都有说话的自由。
阮云:汉奸和反动派难度就没有说话自由吗?又是由谁来给这两者盖棺定论呢
即使说错话了也是不要紧的,国事是国家的公事,不是一党一派的私事。 – 毛泽东《在陕甘宁边区参议会的演讲》
]]>心理变态定义所遵循的标准:
如果你的行为使你能够缓解压力,应对挑战者或者完成你的任务,可以把你的行为称为适应性或者功能性的行为。
人民该怎样做才能使自己的生活变得有意义
文化的重要特征:这种文化的多数成员对什么才是生命中最重要的东西所持有的共同看法,以及这种共同看法的美好生活具有的意义。
一个人是否患有某种精神障碍很大程度上取决于他所面临的社会环境,当他不能正确地适应时就容易发生精神障碍。
何为正常,这需要有在一定环境条件下的统计数据
行为主义者认为焦虑是因为他们把某个并不能引起焦虑的东西与恐惧联系起来
行为理论理解的强迫性行为:未知事物 -> 焦虑不安 -> 某种行动(已知结果) -> 焦虑减轻 -> 强迫形成并加强
人为障碍症:动机在于使病人承担责任,而这样做可以引起社会的关注,并改善病人的社会关系
马斯洛需求理论:生理 -> 安全 -> 社交 -> 尊重 -> 自我实现
]]>若奖励主要取决于绝对绩效,个人选择的确有极高效率,可是若奖励取决于相对绩效,看不见的手就会失效。
负外部性:你收益的成本由别人承担(常使看不见的手失效)
理性的绑匪的需求来自受害者家人的支付能力
倘若商品的价格低于成本,人们就会浪费。
一旦文化流行到某个限度,没有这件事会带来明显的社会成本。
数百万人口的国家平均分配国民收入,意味着一个人不工作生活水准不会下跌,最后所有人都不工作了。
人们觉得自己需要怎样的房子,取决于周围的人有什么样的房子
阮云:互联网拓宽了周围的范围
瀑布效应:自己有钱,显得别人穷了
]]>“让钱花得物有所值”,只应该发生在交易之前
只要边际收益大于边际成本,我们就应该提高进行此事的程度
自愿前提下发生的交换活动会让所有的参与者生活质量变好。
在经济活动中药充分利用信息不对称
人们对消费品的适应性比人生体验要快得多
阮云:失败是有概率的,但当失败发生在你身上时,对于你来说它就是唯一
锚定效应
阮云:其实就是被带到沟里了
光晕效应
阮云:情人眼里出西施
若其他投入固定,而不断增加一种可变投入的数量,则产出的增量最终必然递减。
确保分配给生成活动的下一单位投入有最高的边际产量,规模收益递增
完全竞争的四大条件:
面对需求的定价弹性
只有当买家之间无法进行交易时,价格歧视才可行。
]]>本文为整理以前的读书笔记
之前对于传教士无甚好感,觉得他们总是将自己的信仰推销给别人,然而信仰的好坏暂且不论,总归是要自由的。
对司徒雷登的第一印象应该是来自《别了,司徒雷登》一文,毛主席对美帝一通抨击,连带着对他观感也不好了。
读了这本书才发现,一个较为真实的司徒雷登,了解他当时所处的社会现状,以及他说做的一些事情。
最为佩服的还是他对当时中国教育的帮助,在燕京大学上投注的心血,这些都足以为其生平作注了。
彼拉多见说也无济于事,反要生乱,就拿水在众人面前洗手,说,流这义人的血,罪不在我,你们承当吧。
出自圣经
中国人的思维方式不同,善于察言观色,认清局势并从中牟利
认识可谓深刻
认识可谓深刻
一群美国人为我国的教育奔走呼告,如此高义着实令人佩服
拜服
拜服
宗教信仰的表达,应当只是私人的事情
乱世的思想发展较好
如,战国时期百家争鸣,应当是乱世大家生活不好,在思想上就会有所宣泄,不平则鸣嘛。而且乱世当权者对思想的管控也小,各种思想也得以发展。
汪精卫:革命投机者
国共内战,与毛蒋两人的特质有很大关系
苏联非共产主义而是修正,这在司徒雷登早已明了
战争中的悲剧是中产阶级的生命和财产毁灭殆尽
没有自由,就算赢了全世界也毫无意义
]]>本文为整理以前的读书笔记
《在细雨中呼喊》是一本关于记忆的书,是余华发表于1991年的第一部长篇小说。
作者以第一人称讲述了一个家庭的欢乐和苦痛,孙光林、孙光明、孙光平兄弟三人的际遇,及他们所处的家庭和社会,常常让读者有种无力感,又带有些微的庆幸。
还是最深的体会就是,为人父母者永远不要将自己的懦弱施加到孩子身上。世间苦难重重,孩子本无意来此世上,还遭受无谓之伤害,实在无辜。
这事给我哥哥留下了深刻的印象,有一次他神情黯然地说:“当我们想成为城里人时,城里人却在想成为歌唱家。
你拼命去追求的目标,到头来不过是别人一直想舍弃的
孙光明在临死的前一天,还坐在门槛上向孙光平打听村里谁快要结婚了,他发誓这次要吃十颗水果糖。他说这话时鼻涕都流进了嘴巴。
]]>无常啊无常,世间事常是如此,不管是如此简单的快乐,还是鲜花着锦的富贵,都不能长久
本文为整理以前的读书笔记
国史大纲是钱穆先生大作,本是民国大学生的历史教科书。
所谓大纲者,不在完备,而是有提纲振领的作用。从中更是能体会到文化的精神,不是史料堆砌,不是无感情的上帝视角。
国家财赋偏向南方,向宋始
阮云:明朝时,科举分南北榜,南远胜北,自是有其缘由。
定州何明远资财巨万,家有绞机百张 – 太平广记
阮云:所谓资本主义萌芽
石晋尚能岁输契丹绢三十万匹
阮云:可见工商业以及相当兴盛
运河的开浚,其目的既专在通漕,对于北方原来水利,亦有损无益。
淮为河夺,七百七十余年。
阮云:黄河决堤,夺淮河水道入海
至元(忽必烈)时,江南技艺之人,乎曰“巧儿”,其价甚贵,北人得之,虑其逃遁,或以药哑其口,以火烙其足。
阮云:故,庄子云,无用之用是为大用
义者,本心所当为而不能自已,非有所为而为之者。
世族门第消灭,社会间日趋于平等,而散漫无组织,社会一切公共事业,均有主持领导之人,若读书人不管社会事务应科举,做官,谋身价富贵,则政治社会事业,势必日趋腐败。
阮云:然而,还不是公务员,真香
民穷财尽,为蕴乱之源。
阮云:无恒产者无恒心。苟无恒心,放辟邪侈,无不为已。
世运物力,则实清不如明,康熙五十年所谓盛世人丁者,尚不及明万历之半数。
阮云:康乾盛世,一是人红利,二是番薯等高产粮食作物传入。
用邪教的煽动起事,用流动的骚扰展开,这是安静散漫的农民所以能走上长期叛变的两条路子。
]]>阮云:农民自有其根性所在,目光很难超出一季作物的生长周期之外
放弃人生的某些东西,一定会给心灵带来痛苦。
从生理上决定了人是目光短浅的,因为离你最近的东西看起来总是比较大。
完全接受痛苦,在某种意义上痛苦就不再存在。
在全知全能的状态下做决定,远比在一知半解的状态下,要更痛苦。
为了放弃,首先必须拥有某种事物。你不能放弃从来没有的事物。
自律的四种原则:推迟满足感,承担责任,尊重事实,保持平衡。
坠入情网,意味着自我界限的某一部分突然崩溃,使我们的“自我”与他人合而为一。
坠入情网是人类基因对于人类理性的征服。
真正爱的本质之一,就是希望对方拥有独立自主的人格。
因为缺乏安全感,不能推迟满足感
阮云:所以过度享乐,放纵,寻找作弊器
缺乏耐心,拖延症其实是同一样东西
问题没有消失,它们仍继续存在,它们是妨碍心灵成长的永远的障碍。
坚守过时的观念,对现实漠然处之,称之为“移情”。
生活在封闭的系统里 —— 就像是单间牢房,我们反复呼吸自己释放的恶臭空气。
]]>本书主要讲述的是,我们必须去理解我们的情绪,情绪只是外显的东西,我们需要了解为何在这个时刻会产生这种情绪,它表明了什么。
借用书中的一句话:
情绪或多或少地行使着相同的目的,那就是从一个艰难的世界中逃离。
被冒犯的人通过扭转局面,将冒犯者置于失利的位置,从而保全自己的面子。
阮云:所以弱者常常发怒。
愤怒与否首先取决于它是否符合个人的长期利益。
愤怒如果无人知晓,那么它也失去了它存在的意义。
愤怒是对外的,恐惧是对内的。
阮云:愤怒向外传递信息,恐惧让自己提高警惕
恐惧或多或少地与某些自动的,即本能的大脑反应有关。
情绪更倾向于有明确或者具体的对象,而心境更倾向于不确定的对象。
大多数人需要学习的窍门是,能够快速地将那些快乐的时刻扩散到我们对整个世界的感觉中去。
性冲动(libido)利比多
阮云:人之所以诉说就是渴望认同,故而倾听是极为重要的。
阿里斯托芬:旧时有人四手四腿两个脑袋,宙斯因为人类的傲慢而将他们劈成两半,其中一半在地球上流浪,寻找“他的另一半”
爱共有的结构,那就是一个人的自我与另一个人的自我缠绕融合在一起。
同情带有明显的行动倾向。
当悲伤欢笑幸福放在一起时,它们才是最有意义的。
悲伤涉及的是自我的缺失。
当我们感到羞愧的时候,我们所意识到的自我以及我们真实存在常常是最为痛苦的。
羞愧、内疚、窘迫 – 自我评价型的情绪
窘迫涉及复杂的自我认知,这个自我是身处众人中的自我,它会受到别人期待和判断的影响,而这种影响被自我内化。
有责任感的人才会懊悔。
骄傲和羞愧 – 自我评价型,带有部落特征
羡慕会使人有持续不断的挫败感,使人低估自己拥有的东西,挫败感超出了能承受的范围时,羡慕 -> 怨恨
最直接有效的情绪理解方式应该是将注意力集中在我们的个人体验上。
情绪或多或少地行使着相同的目的,那就是从一个艰难的世界中逃离。
一个人爱的根本就不是爱人本身,而是一种投射。或是一种满足愿望的幻影,这一投射或幻影可能和真实的血肉之躯根本就没有什么关系。
为什么我要这么做,我从中能够获得什么
]]>阮云:理解你的情绪
抽象类是自底向上抽象出来的,接口是自顶向下的设计,也就是接口是预设计,抽象类是后抽象。
所有设计模式都是为了维护的方便,如果你的代码确定不需复用,阅后即焚,则设计模式是多余的。
敏捷开发:通过重构改善既有代码的设计
单一职责原则:就一个类应该只有一个引起它改变的原因
开放-封闭原则:对于扩展开发,对于更改封闭
依赖倒转原则:高层模块不应该依赖底层模块,两个都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
里式代换原则:依赖父类则可用子类代替,反之则不可(子类型必需能够替换父类型)
迪米特法则:如果两个类不必彼此相互相互通信,那么这两个类就不应该发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
声明抽象产品父类,使用工厂生产具体的产品子类(根据条件进行判断)
context类聚合抽象策略基类,并且含有目标方法,使用时将具体的策略实例传人context的构造方法,然后调用context的目标方法
不向context传入具体策略实例,而是传入类型参数,然后在构造函数中根据参数新建相应的策略实例
装饰者和被装饰者继承同一基类,新建装饰者时将被装饰者作为参数传入,然后调用装饰者,关键是装饰者实现了被装饰者的功能。
代理和使用代理者实现同一接口,使用代理者的所有操作都由代理执行(代理有使用代理者的引用)
具体工厂都实现了工厂方法接口,每个工厂负责一类产品,由客户端(用户)选择实例化哪个工厂。
新实例是旧实例的一个复制,并不调用构造方法。试用于实例化开销比较大的场合。
抽象类定义了算法骨架,子类重写特定步骤。
子系统中有多个模块,定义外观类,整合了子系统中的方法,从而简化调用,类似点菜时直接点一个套餐。
抽象建造者定义建造各部分所需的方法,指挥者确定具体的建造步骤(引用抽象建造类),使用时传入具体建造类。
通知者持有每个观察者的引用,当被观察的资源或对象的状态发生改变时,通知者就通过notify方法调用每个观察者的相关方法通知观察者。
抽象工厂有多个工厂方法,用以新建一个产品序列的多个产品,若有多个系列则可新建多个工厂子类。
Context同组合state抽象类来表示各种状态,每个状态类都有相同方法的不同实现,这样可以通过改变state对象来改变操作。
适配器继承目标类,关联到被适配类,通过重新组合被适配类的方法来实现目标方法。
发起者将自身状态传入备忘录的构造函数,并将备忘录对象由管理者持有;恢复时则从管理者处取出备忘录对象,调用备忘录的相关方法恢复数据。
定义抽象类Composite,树叶和树枝都继承自Composite,树枝通过内部的list储存子节点,树叶和树枝(节点)实现相同的行为。
这样就可以将树叶和树枝都统一遍历。
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
使构造函数私有,提供一个getinstance方法获得实例,在getinstance方法内部判断是否新建实例。
桥的两侧都有很多地方,通过组合,实现多种多样的搭配。
命令对象将命令执行者封装起来,并通过命令管理者来处理命令队列。
各个处理者继承自同一基类,并且他们处于同一条链上,设定某个条件,满足条件的处理者处理,否则转下一级。
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用。
享元工厂flyWeightFactory使用Hashtable记录已生成的各类实例,以降低实例化的损耗。
有终结符表达式类、非终结符表达式类,两者都继承自抽象表达式类,并且都实现了一个解释方法。
访问者种类固定,业务多变,定义业务类,将访问者作为对象传入。
]]>发放高额奖金对简单机械操作类工作可以提高成绩, 如果需要动脑, 则适得其反.
如果你找的人喜欢某一事物,你把他们放在有意义的工作条件下,他们从这个活动中得到的快乐会成为影响他们努力程度的主要动力。
相反,如果你把有同样热情和欲望的人放到无意义的工作条件下,就非常容易扼杀这项活动在他们心中引发的快乐。
劳动异化:
“孩子是自己的好”效应: 如果不是我发明的, 那就没什么价值.
惩罚冲动: 这一切说明实行报复—即使需要付出代价也要报复,这种欲望是有生物学基础的,而且这种行为事实上能获得快感(或者起码引发类似快感的反应)
快感适应: 这种情绪上的逐渐稳固现象—原有的正面感觉淡化,负面感觉也减弱—我们把这一过程称做快感适应。
中断厌恶的体验感是有害的, 中断愉悦感的体验过程有益.
真正的进步, 与真正的愉悦一样, 来自冒险和对不同事物的尝试.
我们的适应过程, 一定程度上受周围环境影响.
我们一旦感情用事, 救会做出短期决定, 而它却会改变我们很多的长期决定.
禀赋效应: 是指当个人一旦拥有某项物品,那么他对该物品价值的评价要比未拥有之前大大增加。
损失厌恶是一个简单的概念,人们失去自认为是属于自己的东西时会产生痛苦—比如钱—而这一痛苦要大于得到同样数量的钱所产生的愉悦感。
]]>对抑郁的忧虑会使我们的抑郁更加严重
忧虑是一项认知任务,个体如果把心理资源用于忧虑,就分散了用于处理其他信息的心理资源。
社交 – 情绪同步性
就事论事 – 维护关系
阮云:单纯地发泄情绪对于关系是没有多大帮助的,你要表达出你的诉求。
暴力行为的入侵性记忆
创伤有应激障碍引发的大脑变化是可以消除的,可从最直接的情绪印记当中复原,治愈的途径就是再学习。
创伤复原:获得安全感,记住创伤细节并哀悼由此造成的损失,最有重新恢复正常的生活。
行为疗法与药物一样有效。
]]>泰勒斯: 世间万物来源于水
苏格拉底: 把大量的时间用在空洞的欢乐和虚无的胜利上使年华虚度, 这样的生活毫无意义; 为了满足自己小心的虚荣心和野心而肆意挥霍上帝赐予的一切.
在基督徒眼里, 这个世界仅是通往天堂的前厅
摩尼教: 3世纪前叶创始人摩尼出生
现代的不宽容可以分为出于懒惰的不宽容, 出于无知的, 出于自私自利的.
一些人靠某种以及建立的崇拜谋生, 另一些人却要把他们从一个寺庙引到另一个寺庙. 他们之间的战争一直是公开的.
今天的异教徒, 明天就是一切持不同意见的人的劲敌
世界上本来只有一个暴君, 激进派却带来了两个
]]>意志力训练的关键:专注于目标,合乎道德。
意志力需要渐渐积累的
增强意志力的心态:兴趣,充沛的精力,顺其自然,理解(对事情的理性分析与接受),正直,自尊。
意志力走偏的表现与诊治:
教育的关键是:让我们的内心成为我们忠诚的朋友,而不是对立的敌人
不要一味仿效他人,那样只会讲你变成他人的影子,难以有所突破。要想有自己的看法,他就要有意识地计费新想法,开拓新思维,从而找到自己的发展之路。
不要懈怠目前从事的任何工作。
如果头脑里同时在考虑许多事情,就不要在此时下决定,因为你不可能同时既钓鱼又打猎。
意志力训练方法
人们醒着的时候,把大约1/4的时间用来抵制欲望,每天至少4个小时。首先是食欲,其次睡欲,休闲欲,性欲,交往欲。
平均而言,用意志力抵制欲望,10次当中只有5次是成果的。
阮云:严重高估,我大概有一次成功
邓巴最后下结论,大脑进化得越来越大,并不是为了应付物理环境,而是为了应付社会环境。
意志力像肌肉一样,过度使用会疲劳,长期锻炼会增强。
自我损耗:人们对自己思维感受和行为调节能力减弱的过程。
压力真正做的是损耗意志力,而这会减弱情绪控制能力。
阮云:真是感同身受,以前有段时间压力大,常常发无明业火。
你的意志力是有限的,使用就会消耗;你从同一账户提取意志力用于各种不同任务。
为了保持自制力,吃转化为葡萄糖慢的食物(蔬果鱼肉蛋奶)
阮云:所以穷人普遍缺乏自制力,部分原因是高碳水饮食
互相冲突目标造成:1. 愁得多;2. 做得少; 3. 身心健康变差
为不太重要的熟人做决定消耗的意志力远少于为自己做决定。
意志力下降,偏爱做风险小的选择,比如说找借口避免、推迟决策。
折中是类别特别高级,特别困难的决策,折中能力是意志力耗尽之后第一个衰退的能力
阮云:所以推销员让顾客疲于选择也是一种策略
练习情绪控制不会增强你的意志力;意志力的“力量”不同于意志力的“耐力”
提升意志力的关键,集中精力改变一个习惯。
许可效应:做了好事就表现得好像有了做坏事的权利
阮云:做了运动就喝杯奶茶奖励自己
情绪温差:在理性,冷静的“冷温”状态,体会不到充满激情和欲望的“高温”状态有何表现。
阮云:人的感觉中的时间并不是均匀分布的,离你近的时间会变短,最后期限是做加速运动向人而来。
一旦用意志力把刮胡子变为习惯,刮胡子就变成了相对自动的心智过程,再也不用什么意志力。
棉花糖实验:一直盯着棉花糖的孩子迅速耗尽了意志力,分散注意力的孩子成功坚持下来。
宗教有利于增强自我控制的两个要素:意志力和行为监控。
建议无神论者寻找自己的一套神圣价值观。
零容忍是一条明线:完全戒除,任何时候都没例外,一旦遵守一条明线规则,你“现在自我”会相信“未来自我”也会遵守它。
高自尊好处:
惩罚有3个基本方向:严厉性,及时性,一致性(后两者更为重要)
费伯入眠法:忽略婴儿哭泣 – 安慰 – 走开 – 重复到入睡
进化喜欢那种能在饥荒中活下来的人,所以身体一旦有吃不饱的经历就会努力保存所有脂肪。
阮云:所以节食减肥容易反弹
反条件进食(去它的效应):超过限量后大吃大喝
阮云:破罐子破摔
实施意向(implement intention): 把行为自动化。实施意向要以“如果”…“那么”…形式出现, 如果x发生了,我一定会做y,利用这个技术把行为自动化,减少消费的努力
告诉自己“稍后再吃”,在心理上和现在就吃一样
损耗意志力的是斗争而不是结果
帕金森定律:有多少时间,工作就能做多久。
计划谬误:高估能力,低估所需时间。解决方法:强迫自己想过去的做法。
想做不好的事时,请尽量往后拖延。
如果你只用意志力拒绝东西,那么它就成了残忍讨厌的防守工具。但是当你用意志力获得东西,你就能从最枯燥的任务中体会到乐趣。
]]>阮云:所以做苦行僧不如,得到应得的奖励。
本文为整理以前的读书笔记
作者: 丹尼尔·夏克特 / Daniel L. Schacter
顾名思义,这是一本关于记忆的书,讲述了人脑记忆的机制。我们是如何储存,并提前记忆的。
阅读这本书,对理解记忆,寻找改善记忆的方式有很大帮助。
对往事的利用依赖于:
记忆活动的两种模式:
一个记忆的感情强度,部分地决定于你的回忆方式
一件事,我们仅仅是知道它发生过,还是记住了,取决于它发生时是如何加以注意的。
对某事记忆印象,一方面取决于该事在过去的发生,另一方面也取决于目前回忆时所发生的情况
短时时记忆,工作记忆系统。
类似电脑RAM
“深度加工”效应,要想建立长久的记忆,那就必须对所接受的信息进行更彻底或更深入的编码,即以一种有意义的方式将这一信息与已经存在于记忆之中的知识相联结。
将信息赋予意义,能改善记忆
记忆是大脑欲赋予环境以秩序的一种努力
精细编码为回忆提供看更多的线索
只有未消退的记忆在碰到适当的回忆线索时,才能被记起
提取线索具有的特征,会影响我们的回忆结果
新的编码和储存会干扰先前记忆的回忆能力
记忆歪曲在一点程度上是由编码过程引起的
提取记忆唤醒了沉睡的记忆影像,还有当时的主观体验。
海马系统,与近期经验的外显记忆相关
颞叶中央区系统损伤引起失忆症
注意力的集中是形成新的的外显记忆的必要条件
内隐记忆与外显记忆
启动效应:与事件相关的线索,有助于唤醒对该事件的记忆
启动效应的产生不依赖于有意识的记忆
启动效应依赖于某一以知觉为基础的记忆系统
闪光灯记忆(像拍照一样的记忆),比日常记忆保持得更好
某一事件发生之后,随时间的流逝,人们倾向于遗忘或者混淆该记忆的来源
换个角度理解,已于遗忘的记忆一般都是单一来源的。
回闪记忆:不由自主地产生、并具有强迫性质的种种回忆。
能引起情绪变动的材料记忆得更牢
情绪一致性提取:当你感到悲伤时,你似乎很容易产生各种消极的想法并记起痛苦的经历来。
杏仁核 – 情绪调节的关键结构
情绪之所以有助于记忆,是因为它引起了某些与应激性有关的激素释放
心因性失忆(因心理创伤而引起的暂时性记忆丧失) – 大脑损伤,情绪创伤的联合反应
]]>本文为整理以前的读书笔记
历史到底是什么样子的?很难确定。
我们所经历的生活,在若干年后就会是历史,但是我现在仍看不清现实。想必后来人看现在也是一样的,仿佛笼罩在烟尘之中。
有的台湾人对大陆建国之后的历史有着错误的认识,视我们为洪水猛兽,但反过来,我们对他们的了解又有多少呢?
这本书就是以台湾历史亲历者的角度,诉说进几十年的历史,普通人眼中台湾的过往。
浮生六记算是沈复的自传吧,其人才情虽好,德行却是没什么称道的地方。当然他前半生神仙眷侣般的生活,也不知惹多少人羡慕,红袖添香夜读书大概就是如此吧。
名为六记,只余其四,也是人生一大憾事
其形削肩长项,瘦不露骨,眉弯目秀,顾盼神飞,唯两齿微露,似非佳相。
阮云:一会儿就该说真香了
余镌“愿生生世世为夫妇”图章二方,余执朱文,芸执白文,以为往来书信之用。
阮云:此生能得一良侣已是奢望,又怎么敢求生生世世。
山果收获,必借猴力,果然。
余调其言,如蟋蟀之用纤草,渐能发议。其每日饭必用茶泡,喜食芥卤乳腐,吴俗呼为臭乳腐,又喜食虾卤瓜。
阮云:喜欢饭后喝茶,腌制食品,早夭有因
情之所钟,虽丑不嫌。
其癖好与余同,且能察眼意,懂眉语,一举一动,示之以色,无不头头是道。
于是相挽登舟,返棹至万年桥下,阳乌犹未落山。舟窗尽落,清风徐来,绒扇罗衫,剖瓜解暑。
贪此生涯,卵为蚯蚓所哈(吴俗称阳曰卵),肿不能便,捉鸭开口哈之,婢妪偶释手,鸭颠其颈作吞噬状,惊而大哭,传为语柄。此皆幼时闲情也。
人生坎坷何为乎来哉?往往皆自作孽耳,余则非也,多情重诺,爽直不羁,转因之为累。
况锦衣玉食者,未必能安于荆钗布裙也,与其后悔,莫若无成。
儿女之事粗能了了,但分离至此,令人终觉惨伤耳。
阮云:为人父母者,如此大大不该。虽未抛妻,也算弃子,而后其子早夭。然为钱财所困,也可理解。
总因君太多情,妾生薄命耳
阮云:爱别离
茫茫沧海,不知此生再遇知己如鸿干者否?
吾乡之虎丘山,终日目所见者妖童艳妓,耳所听者弦索笙歌,鼻所闻者佳肴美酒,安得身如枯木、心如死灰哉?”
阮云:当今之世,声色犬马几倍于其时,无怪乎人心散乱
及终席,有卧吃鸦片烟者
阮云:乾隆时期,虽然称为盛世,但衰弱的根子己经埋下了
剪烛絮谈,始悉翠籍湖南,喜亦豫产,本姓欧阳,父亡母醮,为恶叔所卖
阮云:流落烟花者,必有不幸之过往
秀峰今翠明红,俗谓之跳槽,甚至一招两妓;
阮云:跳槽甚是快意
余四月在彼处,共费百余金,得尝荔枝鲜果,亦生平快事。
阮云:回顾前文之困窘 一饮一啄自有定数
本文为整理以前的读书笔记
潜规则应该是对三观冲击最大的书之一,也是作者在读遍历史的总结。对于政权的更迭,剖析其原因,以及背后的潜规则。
此书可以和《血酬定律》搭配阅读,效果更佳。
一个变质的政府,一个剥削性越来越强,服务型越来越弱的政府,自然也需要变质的官员,需要他们泯灭良心心狠手辣,否则就要请你走人。
阮云:或者是说变质的官员构成了这样的政府,不容于这样环境的官员只能走人。
袁宏道言官:如小儿看见了蜡糖人,啼哭不已非要吃,又唯恐唾只不尽,做官的滋味就是这样。
阮云:形象形象,官迷的形状跃然纸上,现在这种人照样多得是,只是一些出现在职场里了。
现代民谣:村骗乡,乡骗县,一级一级往上骗,一直骗到国务院。
新官堕落定律
阮云:如同大学生进入社会。
晏子转型:明规则向潜规则转变。
征税的压力越大,反叛的规模越大,帝国新增的暴力敌不过新生的反叛暴力。
阮云:现代政府稳定的一大原因,低成本高效的征税
中国地主历代的田租常规是产量的50%
阮云:读史常使人心中不忍
古时人相食:一个人单身走路经常失踪,被人像偷鸡摸狗一样悄悄杀了吃掉。
阮云:所谓偷人
当一个社会,当了强盗土匪的生存的可能性更大时,它的灭亡就不可避免了。
阮云:类似癌症
田地负担越重,苛捐杂税越多,田地就越不值钱。
]]>阮云:然后失地农民越多,土地兼并越严重,这时闯王就越有可能出现
作者,埃里克·霍弗。本书主要讲述处于群众运动中的个体的心理状态。作者亲身参与过群众运动,发现这些运动的参与者主要是现实生活的失意者。他们抛弃自己本已无可救药的生活,投身于“神圣的”群众运动,化身为伟大集体的一份子,号召自我牺牲以获救赎。阅读本书或许会对中国的革命年代多一些不一样的理解。
愈做不好一般事情是人愈的胆大妄为。
希望并不是生命可以寄托的东西。
阮云:除了精进,没有什么可以寄托生命。
当你做不好分内的事时,别人会耻笑你,但当你帮助别人时,没人会耻笑你。
有成就感的人会把世界当成一个友好的世界,失意者则乐于看到世界急遽改变。
任何教义如果想要成为一种力量的来源,它就必须宣扬自己是打开未来之书(唯一)的钥匙。
无私者的虚荣心是无边界的。
一个群体的性格和命运,往往是由其最低等的成员决定。
悲愤会在它几乎得到补偿的时候最为蚀骨。
看它的组织能不能迅速将所有失意者集合起来。
君主专制政体,它们最危险的时刻就是开始改革的时刻,也就是开始流露自由倾向的时刻。
对于不是我们真正想要的东西,得到再多也不会满足。
得为三餐糊口的人不会有烦闷感。
阮云:空闲时间多的人才会无聊
群众运动的策略是要把一种病传染给人,然后又把自己说成是救命的良方。
逃避自我让人易于自我牺牲。
通过认同,个人不再是他自己,而成了某种永恒物的一部分。
没有对美好的未来抱深信不疑的信仰,把“现在”的贬抑就不够彻底,就没有办法让人投身到那“伟大而神圣”的事业当中去。
阮云:现在的保守派以前必定是激进派,地位不同而已。
渴望而非拥有,才是人们赴汤蹈火在所不辞的动力
教义不是让人去理解的,而是让人是去信仰的。
阮云:因此,去证明马克思主义的合理与否不是很可笑吗。
宗教狂热者的对立面不是狂热的无神论者,而是温和的愤世嫉俗者。
在所有团结的催化剂中,最容易应用的一项是仇恨。
阮云:爱同一个对象时大家是情敌,恨同一个对象时大家是战友。因为吃的时候总希望独食,付钱是时候最好平分。自有世界以来从未有过慈悲的宗教或革命党。
匆匆忙忙的生活更容易制造齐一性。
失意者乐于以独立性来交换免于做决定的自由,
阮云:因为决定意味着责任。
耶稣并不是基督徒,一如马克思不是马克思主义者。
]]>阮云:言辞者如马克思,只提供了一种构思;狂热者作为文章的载体——草稿,被行动者利用而实现这种构思。
文革十年甚于崖山者百倍
视其平居无以异于俗人,临大节而不可夺,此不俗人也
人民,人民,有多少罪恶借你的名义施行
殷鉴不远,多行不义必自毙
]]>人是生而自由的,却无往不在枷锁之中。有些人自以为是一切的主人,但他们其实只是一切的奴隶。
博主云:所谓心为形役
一开始没有人愿意做奴隶的,实力还是使有些人成为了第一批奴隶,这些人之所以永远当奴隶是因为他们自己的怯懦。
最强的人除非能把自己的实力转换为权利,把服从转化为义务,否则的话,最强的人也不可能永远强下去,更不会永远做主人。
每个人在这个社会公约遭到破坏时立刻会恢复他原来的权利,在丧失约定自由时又重新获得了他为了约定的自由而放弃的自己的天然的自由。
严格定义社会公约:我们在共同体中接纳每一个成员,使之成为全体不可分割的一部分,在公意的最高指导下,我们每个人以及自身全部的力量贡献出来。
博主云:所谓的社会约定,就是每个人把自己献给全体,以获得保护自身的更大的力量。因为这个约定,个人放弃了与生俱来的无限的天然的自由,获得了社会秩序的保护,并且享有有限的自由。
唯有服从人们自己所规定的法律,才是自由,仅只有嗜欲的冲动仍然还是奴隶状态
在力量和才智上,人们虽然是不同的,但由于社会契约所规定的权利,从来都是自由和平等的。
一国之内,不能有派系存在,并且每个公民只能是表示自己的意见,才能很好地表达公意。如果有了派系存在,就应该在增加一个派系,让两个派系之间的势力保持平衡。
博主云:不能有派系,也就是直接民主,像古希腊那样,适合小国;两个势均力敌的派系,也就是代议制,适合大国。至于大国,宣称唯有一个派系最合适,未之闻也。
为了保存自己的生命,每个人都有权冒着生命的危险。
博主云:民不畏死,奈何以死惧之。
如果你能缓慢地行走,没有任何想要获得什么的念头,便已经是一名优秀的禅门弟子。
入道之门屏息外界的诸多攀援之物,并止内在的情绪与妄念,当你变成一块砖或一面石墙,你便入了道。
虽然由你的推想,你可以有一种试验性的理解;然而,你应该由经验而理解空性。
叙述出来的并不是真正是实相,而当你认为它是实相时,已经包含你个人的观念和想法,那是一个自我的概念。
大小方圆,并不属于现实实相,它们仅仅是观念,这就是空去水杯的含义。(忘记已有的观念)
当我们分析一己的经验时,就产生了时间或空间大或小、重或轻的概念;某种的衡量尺度是必要的;以我们心中的各色各样的衡量尺度,我们经历着事物。然而事物本身并不存在衡量尺度,那是我们强加到实相上的。
阮云:先生未生之前各种尺度何在,又何必头上安头。
我们有无限的痛苦,因为我们有无限的欲望。
阮云:因欲生贪,因欲生嗔,贪嗔既久,陈陈相因,愚痴无救。
戒律不是用来约束你的,而是来支援你的修行。
如果你费很大力气去持戒,那不是真正的持戒。
真正的自由是穿着这套麻烦的正式禅袍时,不觉得被约束。
阮云:人生而自由却不往不在枷锁之中,这些枷锁往往是自己造就。
当你以全副的身心探究某事时,会生出直接的经验。
如果没有一根柱子让你攀爬,你将很难体会有朝一日从顶端跳下的经验。
你持守戒律,不是因为遵从佛陀的话语,而是要延伸真实的修行到每一天的生活中,或者在自己身上安顿自己。
阮云:当你玩游戏时,是你在玩游戏,还是游戏在玩你。
当我们说“精神的”,意指非物质的,但是根据佛教,即使是精神的,仍然是属于现象界的现实这一面。
道元禅师说,若你要离于生死,不要尝试离于生死,生死是我们这一生的配备,若无生死,我们不能生存。
有一些问题是好的,没有问题我们不能存活,但是不需要太多,你不须为自己创造更多的问题,你已经有足够的问题了。
阮云:问题和观念一样,不要人为地制造过多。
一些感受,不一定对
]]>百尺竿头与十尺竿头乃至一尺竿头并无甚么区别,因为都已经到了尽头,不能有所进步。
其实真正的竿头并不存在,只要你放下了自己。
只管打坐即是立足当下
大欲小欲就节欲来说是同一的
数息的目的并不是数息,方便法门而已。
本书讲的的是一些程序员必须了解的数学知识,有些知识点挺有意义的,特别是对于非科班出身的程序员来说。
就我的观点来说,一般的程序员需要的数学水平大概是高中就够了,当然要做算法研究大学数学专业水平也不一定够。
下文记录本书一些有用知识点,包括一些算法的数学思路,还有有趣的逻辑问题。
对于一个问题的分析要注意每种情况的排他性和全体的完整性,也就是不能遗漏也不能重复。
逻辑蕴含(A => B)
: 若(存在)A,则(判断)B。A为真,整个式子的值由B决定,A为假,整个式子为真。A => B
与 ¬A ∨ B
等价。
逆否命题的真值等价与原命题。
摩根定律: (¬A) ∨ (¬B) = ¬(A ∧ B)
余数问题一般存在周期。比如今天是周日,问第10∧2,10∧3,10∧4 … 10**n天是星期几。只要将天数对7取余数,就可得知,余数为3、2、6、4、5、1不断循环。
奇偶校验:就是判断奇数和偶数的个数是否符合一点的比例,比如相等。奇偶校验可以用于解决,草席是否能铺满房间的问题,信息传输过程中的完整性校验。
一笔画问题:其实用到了‘图’的数据结构,有n个点用线段互相连接,一个点有几根线连着,‘度’就为几。一笔画问题,如果终点和起点相同,则每个点的度都应该为偶数;如果终点和起点不同,则除起点和终点之外,每个点的度都应该为偶数;
排列组合中,不区分某些元素,即是除以这些元素的全排列。排列组合应该以有独一性的作为基准。
斐波那契数列: 今天的总数 = 昨天的所有 + 今天生的 = 昨天的所有 + 前天的所有。 鹦鹉螺内壁、葵花种子的摆法、植物叶子的长法、爬楼梯问题都可简化为斐波那契数列。
摆砖头问题: 由于每个砖头没有区别,所以它在哪个位置没有影响。
组合问题: n 取 k = (n-1) 取 (k-1) + (n-1) 取 (k)。即是组合中不包含新元素、组合中包含新元素两种情况。
发现递归结构的要领:
不可解问题是原则上不能用程序解决的问题。
]]>如果你失去自足的本心,就会无戒不犯。当你的心变得苛求,当你汲汲于想要得到什么,到头来你就会违反自己誓守过的戒律,包括不妄语、不偷盗、不杀生、不邪淫等等。
博主云:不忘初心,方得彼岸。
我们都会试着让自己以外的东西变得恰如其分,而不是让我们自己变得恰如其分。但是如果你自己不是恰如其分的话,也就不可能让任何东西恰如其分。
当你下定决心要以佛陀的伟大心灵来禅修时,你就会发现,最下等的马才是最有价值的。在你自身的不完美中,你会为你坚定的求道之心找到基础。那些轻轻松松就能把打坐练好的人,通常都要花更多时间才能掌握到禅的真实感和禅的精髓。但那些觉得禅修极为困难的人,却会在其中找到更多意义。所以我认为,最上等的马有时就是最下等的马,而最下等的马有时就是最上等的马。
当你说“做什么都无妨”时,实际上你是在为你做的事情找借口。
博主云:大善。说“无妨”时,你已知晓所做事不好,但未能改变,故说“无妨”,给内心安慰。
修行之初,你会碰到各式各样的困难,这时你有必要做一些努力来让修行贯彻下去。对初学者而言,不需要努力的修行并非真正的修行,因为初学者的修行是需要花大力气的。尤其是对年轻人来说,必须非常刻苦耐劳才能略有所成,你必须竭尽全力。色即是色。你应该忠于自己的感觉,直到你完全忘掉你自己为止。
我们有必要记得自己做过些什么,但却不该执著于这些做过的事。
博主云:不要执着,即是当下心里不要装着过去的事,不要装着未来的事。
如果你打坐累了,或者对打坐产生厌烦的感觉,就应该知道这是一个警告。那表示你的修行太理想主义,表示你有贪念,修行不够淸净。要是修行时太贪心,你就会容易气馁。
最好的一种修行,是没有快乐的感觉的(包括精神上的快乐),修行的人只管修行,忘掉了肉体与心灵的感觉,也忘掉了自身的存在,这是第四个阶段的修行,也是最高的层次。
哪怕是错误的修行,只要你知道它是错误的并持续修行下去,自然而然就会变成正确的修行。我们的修行是不可能完美的,但不必为此气馁,应该持续下去,这就是修行的秘诀所在。
要想让修行不带任何目的,有一个方法,就是限制你的活动,或者说专注于你当下的活动。
博主云:如此则妄念不起,凡心不乱。
所以,我们不崇拜某种对象,而只是专注于每一个当下的活动上。叩头时叩头,打坐时打坐,吃饭时吃饭,不作他想。只要这样做,法性自然会在其中。这个在日文里称为“一修定”。“一修”是指一次的修行,“定”(即三摩地)则是专注的意思。
金刚经:应如是住,如是降伏其心。
当然,有时候,某些激励是必要的,但激励只是激励,并非修行的真正目的,它只是一帖药。
金刚经:知我说法如筏喻者。法尚应舍何况非法。
你应该只管磨砖,别管磨的结果,这就是我们的修行。
有什么东西梗在你的意识里头时,你就无法获得真正的从容自若。想要获得完全的从容自若,最好的方式是忘掉一切。
博主云:所谓“梗”即是执着。
各位知道怎样才能带给身体休息,却不知道怎样才能带给心灵休息。哪怕是躺在床上,各位的心仍然异常忙碌,哪怕是睡着,各位的心仍忙于做梦。
当你能说出“这是虚妄”这样的话时,虚妄就会无地自容,自己走开。所以,“当在虚妄中建立修行”,不因虚妄而有所挂碍,就是修行,而即使你自己没有意识到,但这就是开悟。
佛陀说:“奇哉!奇哉!一切众生悉有如来智慧德相,唯因妄执未证。
没有刻意成为佛的时候,你就会是佛,这就是我们寻求开悟的方法。
有一种东西比公开的丑陋和混乱还要恶劣,那就是戴着一副虚伪面具,假装秩序井然,其实质是视而不见或压抑在挣扎中的要求给予关注的真实的秩序。
城市公共区域的安宁,不是主要由警察来维持的,它主要是由一个互相关联的,非正式的网络来维持的,这是一个有自觉的抑止手段的标准网络,由人们自行产生,也由其强制执行。
保证陌生人安全的成功街区,必须具备的三个条件:
缺少人际交流的城市街道上会出现来历不明的陌生人
如果公共人物被赋予过多的负担,他们发挥的效用会急剧下降。
我们在理解城市的行为和了解相关城市的有用信息时,应该观察实际发生的事情,而不是进行虚无缥缈的遐想。
一般街区公园如果其周边环境从任何形式上都是功能单一,那么它在一天的大部分时间里不可避免地要成为真空区。
随着城市化进程的加快,大企业越来越大,小企业在数量上越来越多
就像一个街道不可能是自给自足的一样。一个地区也不可能是另一个地区的翻版;一个城市不是一些重复类似的城镇的集合体,一个有吸引力的地区应有自己的特性和特长。
]]>自我实现:充分利用和开发天资,能力,潜能。较少地受愿望、欲望、焦虑、恐惧的影响;或较少地受性格决定的乐观或悲观倾向的影响,对未来的预测更加准确。
自我实现者更倾向于领悟实际的存在而不是他们所属文化群的愿望、恐惧以及理论或信仰。
自我实现者的动作是发展个性,成熟发展。
自我实现者受自己的个性原则,而不是社会原则支配。被视为道德、伦理和价值的许多东西,可能是一般人普遍心理病态的产物。
两类自我实现者:
无条件的积极关怀——有效心理治疗的必要先决条
处于高峰体验的人更受精神规律支配
迈向自我实现:当面对一杯酒,不要在意它的价值抑或是标志仅以它作为酒自身来评判它,倾听你自身的声音。
拿不准时要诚实,对自己诚实比对他人诚实重要得多。
每一次承担责任,都是一次自我实现。
自我实现是努力做好自己想做的东西。
识别防卫心理,寻求勇气来抛弃它们。
在创造力激发阶段,创造者只生活在此时此刻。
阮云:这就是佛家所说的当下,或者说心流。
忘我精神是发现人之真正本体,自我以及真实深刻的本质的途径。
约拿情节:害怕成功,对自己成长的逃避。敬慕成功的人,同时感到自惭形秽,嫉妒。
投射心理:我们感到别人有意使我们难堪,似乎我们成为靶子。
对于最高和最好事物的畏惧是固有的,是合理的。
阮云:所以女神求而不得。
谦卑与骄傲之间恰如其分的整合对于创造性工作是绝对必要的。
任何需要只要是真正地被满足,就会有助于性格的形成。
满足的层次:生存 - 安全感 - 归属 - 爱 - 自尊
越是高级的需要,对于维持纯粹的生存也就越不迫切。其满足越能更长久地推迟,并且,这种需要也越容易永远地消失。
高级需要的满足能引起更深刻的幸福感。
心理治疗不能止住饥饿(低级需要)
牢骚的水平可以用来表示他的生活水平的动机层次。
我决不应期待牢骚的停止,只应期待它们会变得越来越高级。
日常生活中的一般欲望,通常是达到目的的手段而非目的本身。
阮云:比如想要一个游戏机,其实要的是玩游戏带来的满足感
神经病症两种:
治疗:帮助人们回到自我实现的轨道上来
提供所有必要的原料,退至一边,让机体自己表达愿望、要求,自己进行选择。
健康的人靠内在的法则而不是外界的压力生活。
成长性动机:为了长远目标保持平衡
匮乏性动机:降低竞争恢复平衡
本文为整理以前的读书笔记
所谓血酬,即流血拼命所得的酬报,体现着生命与生存资源的交换关系。从晚清到民国,吃这碗饭的人比产业工人多得多。血酬的价值,取决于所拼抢的东西,这就是“血酬定律”。
血酬定律其实描述的是分配权力的权力是如何获取的,当然是通过暴力,或者祖上的暴力。
这书第一次读带来很大震撼,有种过于真实引起不适的感觉,慎读,慎读,慎读。
“帮闲”二字道出多少人情事理,他人日子过得行清闲了你便上去帮一把,忙时你道不管。
白员何以得之,正员于事不想出力,初找人顶替,后终成一制。
阮云: 今谓之临时工。潜规则亦是如此,有些人觉得明规则不利于他,所以在台面下又整了一套规则出来。
合法伤害权:即低风险伤害能力,它们价值由避免伤害的费用决定。
阮云: 类似勒索的收益
无恒产者无恒心,做坏事的集合成本很低。
低成本伤害能力,合法伤害权之类的东西,就好比是一个利薮,一块培养基,一个生态位,白员就是这个生态位的必然产物。
资源和劳动力总要无孔不入地流向收益比较高的领域,假如官员执法有对自己有利,这个法律就不好贯彻。
淘汰良民定律:富裕,狡猾的百姓得以逃脱劳役,良民或死于沟壑,或挤入白员队伍,或沦为盗贼。
“合法伤害权”是官家安身立命的本钱。
在一个秩序缺乏的社会里,对获利能力的幻想,不如加害能力的幻想,那么具有根本性。
阮云: 所以官本位有其道理在
施恩能不能得到回报,取决于受益者的良心,而施恩者无法控制受益者的良心;加害者可以单方面控制局面,因为加害只需依赖对方的恐惧。
重视意识形态和人心控制,不过是暴力赋敛集团在和平时期选择的一种低成本的统治手段。
潜规则体系对正规道德体系的偏离,源于从皇帝到官吏的真实行为对正式角色规定的偏离。
]]>欲望和干劲是两回事,干劲反而因为欲望的压力而消耗。
博主云:你的欲望和使欲望满足的方法往往是两个东西,所以欲望越多,越不知道该干什么。欲望想要的是一件事的结果,而我们能做的却仅仅是每一个具体的过程。
难以抉择会给心灵带来极大负担。
博主云:人在一段时间内的自制力是有限的,一个选择在内心停留越久,对自制力就越大损耗,自制力耗尽就表现为什么都不想做。
欲望最原生的材料就是压力,即是苦,所以说将我们驱逐至欲望的正是(痛)苦。
一直待在痛苦的世界是不好的。
我们的欲望并不是自己的欲望,它是你前一段时间所有行为对当下心灵的残留影响。
往往看起来高尚伟大的“xxx主义”,大致上总说着一些漂亮的场面话,实际上只是执着于想要增加自己所属团体的利益。
觉得快乐并不是什么坏事,但是这往往会导致想要追求更多的快乐,就是扩大欲望。
博主云:因快乐而生欲望,因欲望而生执着,因执着而造业。
倾听对方的苦闷时,不要将自己的观点带入(无我相)。
所有与当下情景无关的思想念头都是杂念。心与当下该做的事情合二为一。
金刚经云:应如是住,如是降服其心。
一段时间内,内心积极的能量是一定的,所有消极的想法情绪都会消耗它。
切勿缅怀过去,亦不可期望将来的欣喜。因为过往已如云烟消逝,未来尚未发生,对当下的事物不起执着,深入观察眼前的一切,内心不受动摇,有智慧的人应该如此修行,就从今天开始努力精进。
刹那——一个心念启动的瞬间。
行动因欲望而起,过程中却也因欲望而受干扰,只要着手进行,想做的事就应该变为应做的事,从而忘掉欲望。
灭心中之苦即为修行。
在行动之前花三秒钟审视欲望。
当观知诸所以受,若过去若现在,若内若外,若粗若细,若好若丑,若远若近,悉皆非我亦非本我,以此如实正观。
对内心进行现行犯的机会教育。
博主云:即是在起心动念的刹那观察念头
如果仔细凝视自己内心的状态,会发现无论绝对非做不可、一定要做的这种念头有多强烈,其实一定还隐藏着相反的念头,只是碰巧两端之一浮出水面而已。
色受想行识
我们的心情并不是以自己的力量创造的,只是任意来去的过客罢了。
人类所谓的活动,是潜在中憎恶对方的快乐和希望对方痛苦两种组合而成的。
人的生存,是不断输入心情好、很痛苦、模棱两可这三种刺激而生存。
博主云:感觉心情好生贪念,感觉痛苦生嗔念,感觉模棱两可即是痴亦为无明。
用戒(自律)定(专注)慧(观察力)破贪嗔痴。
]]>博主云:戒而后定,定而能生慧,循序渐进,方能成就。
生命意义因人而异,但每一种意义或多或少都含有错误的成分在里头。个体心理学发现生活中的每一个问题几乎都可以归到:职业、社会和性这三个主要问题之下。
人类的重要性是依他们对别人生活所做贡献而定的。
意义不是被环境所规定的,我们以我们赋予环境的意义决定了我们自己。器官缺陷、被娇纵、被忽视,最容易使人将错误的意义赋予生活。
所有心理上的错误,都是选择动作方向时的错误。
博主云:举例说明,逃避。这种快速而又毫无意义的行为。当你遇到一件难事或者一件令你害怕的东西,它令你不安,这时我们就会逃避,这样仿佛这不安就消失了。然而你没有成长,不安的源头就一直存在,等待一个合适的时候让你触发,超出你的心理承受能力之外。为释放不安与随之而来的压力,大脑会引发一些行为进行代偿,这就是各种心理疾患的萌芽了。
人类对其环境所做的改变,我们称之为文化。
感情绝不会和生活方式相对立,目标一旦订下,感情就会为了实现它而调整自身。
博主云:如果你的做事目标仅仅是为了证明自己的能力,那么你就要小心了,因为拖延症是让一个人不无能的最快方法。
愤怒是控制一个人或者一种情境的工具之一。
用有计划的打击,我们可以把任何一个儿童都打击成神经质类型的人。
博主云:可怕的是,有些愚昧的父母,经常性地无意识地对儿童的打击,简直比计划的还可怕。
倘若我们发现某种情绪很明显地引起了困难,而且违背了个人的利益,那么只想改变掉这种情绪是徒劳无益的,因为它是个人生活样式的正常表现,只有改变他的生活方式,才能斩草除根。
自卑者,我们从他的行为里可以看出他是采用什么狡诈的方法来向自己保证他的重要性
自卑者不是锻炼得更加强壮,而是使自己在自己眼中显得强壮。
博主云:毕竟这还算得上是最快的方式,不是吗?心理问题一般是遇到问题时,我们采取了直接了当而错误的方法,直接的方法不费力,但同样没什么效果,可能还有反效果。
自卑情结:当个人面对一个他无法适当应付的问题时,他表示自己跟本无法解决这个问题,此时出现的就是自卑情结的表现。
博主云:也就是在无确实根据的情况下判断自己不行
自卑感总是引起紧张,必然出现争取优越感的补偿作用,煞费苦心地避免失败,而不是追求成功。
博主云:所以自卑加上完美主义的话,简直只有拖延症一条路可走。对个人成长来说,其实都是自我设限。
没有哪个人会发现自己所处的地位已经接近最终目标——能够完全控制其所处的环境。
博主云:无论你年龄多大阅历多么丰富,在某些事情上你都是新手。连行将就木的老人,也有他没做过的事情,就是面对死亡经历死亡。
我们生活中的乐趣,主要来自生活的不确定性。
博主云:一成不变的人生有何意义。
他们(这里指有心理障碍的人)选用的方法都是正确的,没什么错误——它们都是无可厚非的。他们必须要改进的是他们的具体目标。
在卷入爱的漩涡时,有许多人觉得自己很软弱。
博主默默看着右手。。。
优越感目标,对优越感的追求是所有人类的共性。
真正能够面对并且主宰生活问题的人,是那些在奋斗过程中也能表现出利他倾向的人。
记忆绝非出自偶然,个人所记忆的是他从他接受到的无数个印象中选择出来的,那些他认为对他又重要意义的东西。
如果一个人面临的问题是他不希望用常识来解决的,那么他就可以用梦所引起的感觉,来坚持他的态度。
假如一个人无法与人交友,最大的原因可能是他想驾驭别人,事实上,他只对自己感兴趣,他的目标就是表现他个人的优越感。
博主云:阿德勒与弗洛伊德着眼点不同,一个是社会合作,一个是个体性。
在女孩子中长大的男孩子,如果不是变得非常强壮,就会变得非常软弱。
博主云:所谓物极必反
兴趣是心理功能发展的最大因素。
青春期固然存在很多危险,但它并不能真正地改变人格。
三类儿童容易产生特别困难:第一,身体有缺陷的儿童;第二,被宠坏的儿童;第三,受忽视的儿童。
在我们的文化里,大部分人在他们的困难超出某个限度后,他们的合作能力就消失殆尽了。
在16岁高中毕业时,仍然对自己未来的职业举棋不定,这些孩子常常是品学兼优的学生,但是对以后的生活没有一点点主意,这些孩子大多野心勃勃,不过却不肯真正与人合作。当我们看到这样的孩子在以后的生活中躲避困难时,我们必须以科学的方法找出他们错误的原因,并采用科学的方法纠正过来。
人类的一个非常沉重的负担就是社会上还有许多不学无术和对共同利益不感兴趣的人,这些人总觉得自己屈居人下,不如别人。
以我的观点来看,任何人的努力,只要他是以合作为最高目标的,我就完全赞同他。
强迫性的脸红,口吃,阳痿,早泄,都是对他人缺乏兴趣造成的。
假如他能成为所有人的好朋友,并以美满的婚姻和有用的工作对这些有所贡献,他就不会有自己不如别人,或是被别人击败的感觉。
博主云:其实就是将优越感的来源转移至对他人的贡献上来,而不是以地位能力凌驾于他人之上。
人类保存生命的主要方法,就是通过我们的生殖力和对肉体吸引力的不断追求,来使后代得以繁衍。
生活中的任何一种严肃而重要的工作,都是不可以先替自己安排脱身之计的。
博主云:破釜沉舟,百二秦关终属楚。
这本书应该是对三观冲击最大的一本,在大学时第一次读,先是震惊,再是愤怒,然后是害怕。
当然现在看来,又有一些不一样的见解。一直自认为,思维深刻,三观稳定,然而也不可避免地被各种书籍媒体塑造。
想要保持独立思考是何其之难,再者,就算能独立思考,人们接受的信息都是片面的,有怎能确保得出正确的结果呢?
如果从长远考虑,我们是自己命运的创造者,那么,从短期着眼,我们就是我们所造的观念的俘虏。
阮云:有一些事情没什么特别的,当加上想象的动机,就变得非常可怕了。所以唯心主义也是有意义的,你的世界是有你的思想观念重构的。
个人主义的基本特征,就是把个人当做个人尊重,就是在他自己的范围内承认他的看法和趣味是至高无上的。
阮云:其实就是不应该承认有思想教皇的存在
在安排我们的事务时,应该尽可能多地运用自发的社会力量,而尽可能少地借助于强制
“西方的”就是指自由主义与民族主义,资本主义与个人主义,自由贸易与任何形式的国际主义或对和平的热爱
总是使一个国家变成人间地狱的东西,恰恰是人们视图将其变成天堂。 – F·荷尔德林。
社会主义从一开始便直接了当地具有独裁主义的性质,是通过等级制度的路线审慎地改革社会,并强加一种强制性的“精神力量”,以此“终结革命的一种尝试”。
民主在自由之中寻找平等,而社会主义则在约数和奴役之中寻求平等。
对更大自由的许诺已经成为社会主义宣传最有效的武器之一。
一个名副其实的计划经济,必定有一个单一的观念。
防止权力专断的不是它的来源,而是对他的限制。
法治的基本要点:留给执掌强制权力的权行机构的行动自由,应当减少到最低程度。
计划必然要涉及对不同的人们的具体要求给予有意识的差别对待,并允许这个人做一定要禁止另一个人做的事情。
任何旨在实现公平分配的重大理想政策,必定会导致法治的破坏。
阮云:分配的权力该如何分配
如果我们力求获取金钱,那是因为金钱能够提供我们最广泛的选择机会去享受我们努力的结果。
私有制是自由的最重要保障。
富人得势的世界,得势的人才能致富的世界。
所有权力都易腐坏,绝对的权力则绝对地会腐化。
西方世界大多数人所想象的那种“自由社会主义”何以是纯理论的,而世界各处实行的社会主义为什么却是极权主义的。
目的说明手段的正当性这个原则,在集体主义道德里是至高无上的准则。
国家社会主义或者共产主义这一类活动背后所蕴含的道德情感的强度,也许只有历史上的伟大的宗教运动能与之相比。
不喜欢掌握权力的人能够当权的可能性是和一个心地非常善良的人在奴隶种植园里担任监工的工作的可能性是一样的。
斥责任何只为活动而活动,没有远大目标的人类行为,这是完全符合极权主义的整个精神的。
只有当我们对自己的利害关系负责并且只有牺牲他们的自由时,我们的决定才能有道德价值,而我们要是在没有选择自由的情况下做到了无私,在道德上也微不足道。
]]>本文为整理以前的读书笔记
书如其名,是作者对与道德或者说真理的思索,内容多的短小的片段。
虽然连贯性略微缺失,但是思想的光芒无法掩盖,适合睡前阅读。
从文字间能看出,作者尚未能与道融而为一,尚未知行合一,故常有无力与痛苦之感,作者难道也是个悲观主义者吗?
激情常常走向自己的反面
阮云:那是因为得不到正确的发泄通路
节制产生之因,不过害怕遭受他人的嫉妒和轻蔑
阮云:其实更深切地是对自己所拥有的东西有不安定感
凡物之不足大抵与过之同,但若过之而到一极限则也不存在这个问题了
阮云:所谓过犹不及,若超越极限则是超脱,也叫能放下
承担好运所需要的德行,要多于承担厄运所需。
如果我们不怀骄傲之心,我们就不会怨恨他人之骄傲。
阮云:说明人们骄傲不能独立存在,是在与他人的比较中产生,也是着相吧
利益使一些人盲目,使另一些人眼明
阮云:可以用辩证法来这样造句,用马克思的话来说,是一分为二地看。这是一个万金油,随便一个名词都可以造句。
幸福得之于体验,而非事物本身所系。
阮云:所以啊,人生最大的痛苦莫过于,得到了想要的事物,却成为了你所讨厌的人
真理在世间之善,并没有假冒它的伪真理所行之恶那么多
阮云:伪真理一来于蠢,一来于坏
优雅之于身体,犹如良知之于善良
没有什么东西能像“建议”那样被我们如此慷慨地送出
阮云:戒之,戒之,好为人师
我们太习惯于向别人伪装自己,以致最后我们向自己装自己
阮云:这有时候也不是坏事
人们的背叛行为更多地是出于软弱,而不是出于某种既定的背叛动机。
]]>阮云:所以气量是种难得的东西
一旦压力减轻,人就容易维持现状。然而,如果压力没有在抱怨中流失,它就会推积起来,到达一个极限,迫使你采取行动变现状。
其实,压力产生的后果取决与你的器量,如果你能承受那便是动力,否则只会使你崩坏。
当遇到事情时,理智的孩子让血液进入大脑,能聪明地思考问题;野蛮的孩子让血液进入四肢,大脑空虚,疯狂冲动。
理智虽好,但若时时都是,未免略为无趣。
毁灭一个生命,和毁灭全世界同等罪恶;拯救一个生命,和拯救全世界同等荣耀。
就逃跑来说,五十步和百步其实没有区别。
当人们开始对某人进行人身攻击的时候,他才能知道自己赢得了争论。
俗话说,君子动口不动手。
外人只能看到事物的外表,即使内心充满担忧与恐惧,每个人都会在脸上装出沉着冷静的样子。
当然你如果内心冷静,外表恐惧,别人也是看不出的。
如果我们没有受到惩罚就意味肴我们做得正确,那么我们每天不知道要干多少坏事!
惩罚和一件事是不是坏事没有关系,好或是坏存乎一心。
年轻的时候决定离开一个人,就像背上行囊浪迹天涯,从形式到心情都感到特别悲壮,等到自己真正成熟的时候,才发觉离开时让人痛心的并不是某个形式,而是那种无奈的心情。
女人受情绪左右,男人靠欲望支配;而情绪又受环境影响,欲望则指向具体目标。
对于不懂女人的男人,只有先征服了世界才能再征服女人;而对于了解女人的男人,骑着自行车就直捣芳心了。
很好的概括,然而我估计两者都没可能了,悲乎!!
女人跟男人的对话只是一种自言自语,没有经验的男人却总是规规矩矩地去寻求客观答案。其实,二人世界里没有正确和错误,只有应该和不该。那些要坚持真理的,你不如直接去当尼采。
有些对话只是表达情绪而不是事实,不要对情绪进行理性分析,如果能分析那便不是情绪。如果女人纯是理性,想来这世界也有些可怕。
这个国家最让我心悸的,不是国家机器任意妄为地作恶,而是占据主流力量的普通人纷纷告诉你:这个国家就是这样的,你改变不了的,习惯了就行。
人不能独自生存,极致的自由意味着极致的孤独。
弱者不能独自生存,但强者可以。
儒以文乱法,侠以武犯禁。
古人早就想得很明白,知识分子只能写写文章发牢骚,让他们不敢或是不能发表自己的看法,比如太祖“百花齐放”这招用得就很好。至于那些想动手的,不给他们工具就好了。
计划经济让人体会到权力的美好。
所谓计划,肯定要有一个实体来定,那么这里面很多活动空间了。
人用什么方法夺得一件事物,他便极端害怕以同样的方式失去它。人因某一原因失去一件事物,他也极端害怕继续因此失去其他事物。
]]>因为了解,所以才更加害怕。
随风而行 是伊朗作家阿巴斯·基阿鲁斯达米的一本诗集。阿巴斯·基阿鲁斯达米不仅仅是为诗人,同时他也是一位电影导演。
因此他的短诗有着极强的画面感,诗句隽永富含哲思,使人如临其境。
下面是本人从中摘录的一些喜欢的。
春风不识字
却翻作业本
孩子趴在小手上
睡得正香
飞起再落下
落下又飞走
蚂蚱的方向
只有它知道
羽绒枕
屠害千只小鸟梦
海一片漆黑
岸也一片漆黑
我该等太阳
还是月亮
沉睡的男人身侧
女人醒着
指望不上一只爱抚的手
秋日午后
无花果树叶
轻轻落下
停在
自己的影子上
风起时
轮到那片叶子
飘落呢
旅人疲惫啊
踽踽行
还有七里呢
是终站
索桥垂垂
擦破水面
弄皱了
月的光
东边
浓雾低垂
太阳
一轮苍白
修女们的辩论
没有一点儿结果
渐渐地
到了该睡觉的时候
狗儿在巷尾
埋伏
候着下一个要饭的
碧空
樱花似雨
染上
春日夕照
蜘蛛啊
两天的收获
让老管家的笤帚
全毁了
雨滴
顺玻璃滑落
染墨的小手
从窗户脸上
抹掉它
雪色茫茫
冒出矿井的工厂
刺痛了眼睛
越想
越不明白
为何一无所获者
手上尽是老茧
最后一片树叶在枝头坚持
守着承诺
要看一眼春天的新芽
火车嘶鸣着
停住
蝴蝶再铁轨上酣睡
明月目中疑惑
今天看她的人
是否
还是千年前的那些
区分与联系。
所谓区分也就是一事物仅仅是它本身而不被认为是他物,有了区分才有了物与无之间的界线,没有和其他的“区分”就可以说该事物就不能存在。
然后是联系,这体现了该事物存在的意义。如果该事物与其他事物之间完全没有关系,那么它也没有存在的必要了,因为有你没你并没有什么不同。
包括以下两者:1、定义;2、作用。
在世俗,作用才是最重要的,因为你与他物的联系是由作用实现。
这世界就是这么势利,它第一句话总是问你:“你能为我做什么,你有什么用”。经济学告诉我们,价值是由别人对该事物未来的预期决定的,而不是它的成本。
而预期从何而来,根据就是一个事物的作用,一定意义上说也就是你所能带来的收益。
我觉得记忆(我认为学习的本质就是记忆,所谓的理解、融汇贯通),不过是构筑你脑中的世界,本质与现实世界的组织是一致的,让脑中概念各得其位。
]]>由于最近做Python的linux服务器脚本,经常要同步代码到测试服务器,又不想直接ssh登录到服务器,所以萌生了用git自动同步代码的想法。
具体的要求是:测试机是服务器,需要本地一push代码,测试机能实时更新。
查阅相关资料发现可以用git的hooks来实现。
说明:
mkdir git-transfer |
git remote add transfer ssh://r@192.168.157.129/home/r/git-transfer/
cd /home/r/git-transfer/hooks/ |
将 post-update 的内容修改为如下!/bin/sh
An example hook script to prepare a packed repository for use over
dumb transports.
To enable this hook, rename this file to "post-update".
unset GIT_DIR
DeployPath=/home/r/git-dest
cd $DeployPath
git add . -A && git stash
git pull origin master
git clone /home/r/git-transfer/ /home/r/git-dest/
或者cd git-dest
git init
git remote add origin /home/r/git-transfer/
这样在本地项目一push,目的项目就会马上更新
]]>童话里经常会看到英雄打败恶人的故事,而且故事里总会有一个类似黑暗森林的场景——要么是一个山洞,要么是一篇森林,要么是另一个星球,反正是英雄不该去的某个地方。
当然,一旦反面角色在剧情中出现,你就会发现英雄非得去那片破森林去杀掉坏人。当英雄的总是不得不冒着生命危险进到邪恶森林中去。
在面向对象编程中,“继承”就是那片邪恶森林。
有经验的程序员知道如何躲开这个恶魔,因为他们知道,在丛林深处的“继承”,其实是邪恶女皇“多重继承”。她喜欢用自己的巨口尖牙吃掉程序员和软件,咀嚼这些堕落者的血肉。
不过这片丛林的吸引力是如此的强大,几乎每一个程序员都会进去探险,梦想着提着邪恶女皇的头颅走出丛林,从而声称自己是真正的程序员。你就是无法阻止丛林的魔力,于是你深入其中,而等你冒险结束,九死一生之后,你唯一学到的,就是远远躲开这片破森林,而如果你不得不再进去一次,你会带一支军队。
有的程序员现在正在丛林里跟邪恶女皇作战,他会对你说你必须进到森林里去。他们这样说其实是因为他们需要你的帮助,因为他们已经无法承受他们自己创建的东西了。
而对于你来说,你只要记住这一条:
大部分使用继承的场合都可以用合成取代,而多级继承则需要不惜一切地避免之。
继承的用处,就是用来指明一个类的大部分或全部功能,都是从一个父类中获得的。当你写class Foo(Bar)
时,代码就发生了继承效果,这句代码的意思是“创建一个叫 Foo 的类,并让他继承 Bar”。
继承的时候,父类和子类有三种交互方式:
当你在父类里定义了一个函数,但没有在子类中定义的例子,这时候会发生隐式继承。
class Parent(object): |
有时候你需要让子类里的函数有一个不同的行为,这种情况下隐式继承是做不到的,而你需要覆写子类中的函数。
class Parent(object): |
父类中定义的内容运行之前或者之后再修改行为。
class Parent(object): |
合成就是不通过继承直接引用所需要类的方法。
继承是一种有用的技术,不过还有一种实现相同功能的方法,就是直接使用别的类和模块,而非依赖于继承。
如果你回头看的话,我们有三种继承的方式,但有两种会通过新代码取代或者修改父类的功能。这其实可以很容易地用调用模块里的函数来实现。
此处要注意所需类的实例化参数class Other(object):
def override(self):
print "OTHER override()"
def implicit(self):
print "OTHER implicit()"
def altered(self):
print "OTHER altered()"
class Child(object):
def __init__(self):
self.other = Other()
def implicit(self):
self.other.implicit()
def override(self):
print "CHILD override()"
def altered(self):
print "CHILD, BEFORE OTHER altered()"
self.other.altered()
print "CHILD, AFTER OTHER altered()"
son = Child()
son.implicit()
son.override()
son.altered()
“继承 vs 合成”的问题说到底还是关于代码重用的问题。
你不想到处都是重复的代码,这样既难看又没效率。继承可以让你在基类里隐含父类的功能,从而解决了这个问题。而合成则是利用模块和别的类中的函数调用实现了相同的目的。
如果两种方案都能解决重用的问题,那什么时候该用哪个呢?这个问题答案其实是非常主观的,不过我可以给你三个大体的指引方案:
然而,不要成为这些规则的奴隶。
面向对象编程中要记住的一点是,程序员创建软件包,共享代码,这些都是一种社交习俗。
由于这是一种社交习俗,有时可能因为你的工作同事的原因,你需要打破这些规则。这时候,你就需要去观察别人的工作方式,然后去适应这种场合。
人生在世,无论理想如何远大,终须为稻粱谋。陶渊明不为五斗米折腰,前提没这点俸禄也不至于饿死。若是形式迫人,虽嗟来之食亦受之。一旦不为生计发愁,迎接你的必是诗与远方。故我先欲求之财务自由,后践吾心之所向。
不够强大的理想主义者,一大部分矮化成了现实主义者,另一部分成了随波逐流的躯壳,余下的抛弃了这个娑婆世界。
而我,先欲求安身立命之所,后追寻最初的梦想,希望能走得更远。
下面描述下这个梦
在梦中,我被一阵持续的噪音惊醒,声音类似老式空调压缩机的声音,嘈杂而持久,整个房间都有一点震动的感觉。我在房间里仔细搜寻噪音的源头,好让自己再次入眠。
找来找去,发现我的冲牙器在不停震动,声音也很是类似,还以为忘记关开关了。于是准备把开关关了,一阵手忙脚乱开关还是没能关掉(梦中这种细致的操作不好实现?)。于是我把冲牙器的插头拔下了,但是它好像没有停下来的意思,还在不停震动着,而且冲牙器的位置也不是现实中的位置,我好似也坦然接受了这无须电源的震动(这好像不是重点),但是又无法确定噪音的源头就是它。
然后,富有逻辑的操作来了,我找了个桶把冲牙器盖起来,发现噪音并没有减弱,于是我排除了它的嫌疑。
接着就发现应该是空调的压缩机在响,我就认定是它了,然后空调的指示灯变清晰了,原来是我误开了空调,我笃定开得是制热,只有制热需要这么大的功率,空气也燥热起来,一切是那么符合逻辑。
心满意足找到答案之后,我醒了,发现噪音来源是外面修路的水泵。
]]> 凡是条件好一点的图书馆,只要工作人员不禁止,必定有一部分座位因占座党而闲置。
举我最近去的广州图书馆为例,我早上9点开始自习,到下午5点,占座的人始终没来,而且不是一两次。几乎每次有占座的,很大可能一天都不来。
这也很好理解,既然需要占座自然是来的意愿不是很强,不来自然是情理之中。人皆有惰性,无可厚非,但座位有限,还是留给需要的人为好。
除占座党之外,手机党和电脑党也是图书馆一大特色,并且与年龄无关。
今天坐我对面的是个大概60岁的大爷,先是早上来没找到位置坐,把我对面的占座党呵斥了一通,抢了她占的座位。然后等我下午从图书馆出来时,他手上拿的手机还没有放下。既然玩手机,在家里不是更舒服吗,何必呢?学得下就学,不行就回去休息,在这磨洋工除了求个心安之外毫无裨益。
另外,品行、习惯与年龄没多大关系,不好的不改正,年纪大了还是不好,不过是习惯了而已。俗语说三岁看老
,未免夸张了点,十三、二十三看老估计是十拿九稳。
人呐,还是要对自己有所要求,若是随着年岁的增长而没有丝毫长进,多么可悲,多么可怕。年少时不学好有人教育你,年老了还无长进,无需教育也无人可教育你了。
在卫生间,洗完手,有一人擦手用了接近2米的纸,很大一堆,不知道什么心态。如果你平时在家都是这样使用,我无话可说,但仅因为免费就滥用,未免太下作了。
免费的东西并不是没有成本,而是一些心存善念的人承担了,因此我们在使用它的时候因心存感激。
一切免费的物品,如图书馆的座位,如纸巾,如果不能在使用价值或者获取代价上和通常物品加以区分,就不免会被滥用,导致真正需要的人不能得到。
而这正是这个世界的可悲之处,所有事物并不是让需要的人得到,而是让有取得优势的人得到。
从14年开始,晚上时不时看一点,到现在看完,正好六年。
人生又有多少个六年呢
第一个主题,就是人生要“具见”,见地具备,就是普通讲的见解,再普通一点讲,就是眼光、思想。
物与物之间互相在变化,所以叫“物化”
阮云:庄周化蝶,就是物化
“野马”不是一匹马喔,“野马”就是佛经上讲的“阳焰”,太阳光的幻影,古书叫做“海市蜃楼”。
人类的见解、知识和生活经验都是“比量”,不是真实的。同样一个气候,同样一个空间,一个时间,一个颜色,因人而产生的感受各异。
阮云:因为人和真实的世界,中间总是隔着一层感官,所以每个人的感受是不一样的。
“循业发现”。每一个人根据他自己的生活经历、思想见解、智慧境界等,看一个东西的观念都不同。
阮云:这也是为什么人们很难相互理解
世味尝来浑是蜡,莫教开口向人提。
阮云:大抵是真实而又乏味吧,而且生活的苦不要对他人讲,因为别人体会不了,徒增笑料
依他而起,属于“比量”的境界,通过比较而产生的,都是“依他而起”
阮云:人类的见解、知识和生活经验都是“依他而起”,这个“他”,类似佛家的本性
以神仙丹道家学说来讲,认为生时魄在肉体生命活力中普遍存在。不经过修炼,不能和魂凝聚为一,死后魄就归沉于地。因此,魂就是鬼影,魄是鬼形。
人世间哪个是真理?哪个是是?哪个是非?哪个是黑?哪个是白?其实对与不对,都是人的“师心自用”。就是说一个人有“成见”,有主观的观念,自以为对就对,叫“师心自用”。
阮云:人总以为自己的经验,能够放之四海,可笑啊
会万物于己者,其惟圣人乎。
阮云:所谓物我混而为一
庄子说:是非观念所产生可以不可以,是从我们的主观来的,我们的认识,你认为可以就可以,你认为不可以就不可以,宇宙间没有一个真正的离开身心以外的是非观念。
阮云:所以说,真正的客观是不存在的。
“物固有所然,”天地万物它有它的所以然,既然宇宙形成了万物,电就是电,电通过灯时,它发亮;通过录音机收音机时,它发声。这个物体有它所以然的特别的性能。“物固有所可。”所以万物有它适宜应该的本位,有它适宜应该的立场。
阮云:一个物有造就它的原因,自然也限制了它能适用的地方。如果这些变了,这物就变为他物了吧
“不用而寓诸庸”呢?“庸”不是马虎,不是差不多,是“得其环中”,
阮云:无用之用是谓大用,道家来讲是无为,佛家是随缘
把自己的精神、聪明向一点上钻。这个“劳神明为一”的“一”,不是“道通为一”的“一”的意思,不要搞错了。只向一点上钻牛角尖,他认为自己最高明,不晓得向大同方面钻,
形而上之道无是也无非,无善也无恶,形而下之道,有是非,有善恶。
阮云:世界万物本来应该是中性的,因为人的观念介入,而有了高下,有了分别
“未始有封也”,并没有界线。
阮云:物于物之间不存在界限,一直在转化
物质文明越发达,社会越复杂,思想之混乱,是非善恶观念之复杂,都是障道的因缘
阮云:这些就是五蕴,色受想行识。
我再提一下全篇的宗旨,道体,宇宙万有的本体,本来是绝对、同一的。当道体起作用的时候,一切万类的现象不同,作用不同,但道体是一样的。
阮云:一以贯之的就是道体吧
道体是“一”的。因为大家自己的观念不同,被现象骗了,所以各家有各家的看法,儒家有儒家的看法,墨家有墨家的看法 道家有道家的看法,各种说法都不同,应用的方法也不同。因此,被现象迷住了,忘记了本来。
人要晓得“知几”,把握自己生命的重点,不“知几”,对于自己是在开玩笑,没有用
阮云:几通机,因为物化,事物总在变动之中,所以要知晓关键时机
不管是宗教的,哲学的、科学的、古代诸子百家的,现在的科学分门别类,都有一个大原则,一切学问与人身心性命没有关系的,它不会成立不会存在。
天地是与我同存的,万物是与我同一的,我们跟万物同样都是那个东西的一份子,并非天地就是我,也不是我就是天地,物是物,我是我,天还是天,地还是地。
佛学里的有一句名言“如人饮水,冷暖自知”,就是“圣人怀之”,到了那个程度,那个境界,“圣人”只有自己知道
“是非以不辩为解脱”,禅宗注重的行为,不完全是打坐,所以百丈禅师讲“疾病以减食为汤药”,有了病最好少吃东西,肠胃清理一下,
上古我们的老祖宗是吃狗肉的,现在广东人保持了这一习俗,上古祭祖宗都要用狗肉来祭,大约到了商、周以后,在祭祀中,才渐渐免除了狗肉这项祭品,但在某些祀典中,仍然须用草扎一个象形的狗,替代一头真的狗,这就是刍狗的来源。
阮云:以万物为刍狗,把万物当成草扎的狗,在祭祀的时候,很庄重,把草狗当成真狗,当祭祀完,就可以扔掉了。
“大廉不嗛”的道理,我经常说一个笑话,拿什么来比呢?拿猪来比,实际世界上最爱干净的是猪,研究生物学的都懂。你看猪一天到晚用嘴东拱西拱,人们以为猪脏,其实它最爱清洁了,脏东西一点都看不惯,看到脏东西就把它拱开,结果是越拱越脏。
阮云:嗛通谦,真正的大廉没有谦让,并不是不谈钱。
啮缺问:你知不知道,天地万物有一个到了最高处基本是相同的,绝对的,同一的那个东西?王倪答复:我哪里知道?换一句话说,我不知道。啮缺又问:你为什么不知道?你知不知道你那个时候你不知道的?王倪说我也不知道,我也不懂。那么啮缺就问:既然这样,宇宙万物的最高处是无知吗?王倪又说,那我也不知道。我们中国文化有一个成语,叫“一问三不知”
“吊诡”就是佛家禅宗所谓“机锋”。中国学武的有一句话:“弓在弦上,不得不发”,弓拉满了,箭在弦上,不得不发,这是“机”。彼此两个机关相对,非常锋利,很快,不可以用思想,来不及用思想
阮云:所谓势,物理中的势能就是来自此处
“不可思议”,最高的真理就这四个字。不可以用思想知识去推测,不可用逻辑思辩来断定。
知其不可奈何而安之若命,德之至也。
凡交近则必相靡以信,远则必忠之以言。言必或传之。
夫两喜必多溢美之言,两怒必多溢恶之言。凡溢之类妄,妄则其信之也莫,莫则传言者殃。
阮云:所以君子之交淡如水
克核太至,则必有不肖之心应之,而不知其然也。
我们常常看到办事的,做公务员的“迁令”。譬如我发现有跟我做事的同学,我说:“请你帮我把下面那一本书拿上来。”结果他到了下面对另一人说,“某某人,老师叫你把那本书拿上去。”这就叫“迁令”,已经不对了。做人要“不迁令”。
阮云:然而现在企业在中层都是这种人,可笑可笑
“美成在久,”就是我们平时所讲的,好事不在忙。成就好的事情,不是一时做得到的。坏的事情却容易成就,一成就了以往,来不及改正。所以作人处事要慎重地考虑。
“有人于此,其德天杀。与之为无方,则危吾国,与之为有方,则危吾身。
阮云:与暴君相处,如果任由放纵他,危害国家,如果约束他,危害自身性命
形就而入,且为颠为灭,为崩为蹶;心和而出,且为声为名,为妖为孽。
阮云:讲得是要和光同尘,如果顺从本心,反而被认为妖孽
汝不知夫螳螂乎?怒其臂以当车辙,不知其不胜任也,是其才之美者也。戒之,慎之,积伐而美者以犯之,几矣!
故未终其天年而中道之夭于斧斤,此材之患也。
支离其德
阮云:支离是古人叫支离疏,长得奇形怪状的,然而却免遭各种劳役。这里讲的还是无用之用
福轻乎羽,莫之知载。
阮云:幸福这东西比羽毛还轻,不知道拿什么把它装起来。
《人间世》全篇的宗旨:“世路难行”。并不是世路是不可行的,是可行的。人生要你自己善于处。那么归结起来告诉我们什么东西呢?三个字:守本份。人要守本份,在什么立场就做什么事,处什么态度。大家进了歌厅就要跟着唱歌,进了舞厅就要跟着跳舞,大家喝醉了你就要装醉,大家清醒起来你也要跟着清醒,大家都在做工你却在睡觉,那就不是疯而是蠢到极点了。
我们知道,春秋战国的文化,道跟德是分开的,道是体,就是内涵,是每个人修养学问的内涵;德是用,得了道体就能起用,即用世之道。
自其异者视之,肝胆楚越也;自其同者视之,万物皆一也。
吾所谓无情者,言人之不以好恶内伤其身,常因自然而不益生也。
尤其这一篇我们要了解什么是“命”,这个命不是算八字那个命,它在哲学的理论上叫天命,在实际的修证就是认清生命的来源。
这个“命”相当于佛学中讲的业,善的是善业,恶的是恶业,不善不恶的是无记业。
古书上的“天”字,大约概括了五类内涵:(一)天文学上物理世界的天体之天,如《周易》乾卦卦辞“天行健”的“天”。 (二)具有宗教色彩,信仰上的主宰之天,如《左传》所说的“昊天不吊”。(三)理性上的天,如《诗经》小节的“苍天苍天”。(四)心理性情上的天,如《泰誓》和《孟子》的“天视自我民视,天听自我民听”。(五)形而上的天,如《中庸》所谓“天命之谓性”。
那么这个道怎么来的呢?两个路线:一是抛弃了你的小聪明,而求那个“无知之知”的大道;另一个路线,把世间的聪明学问都通到了极点,最后归到“一无所知而无不知”,也就得道了。
阮云:和禅宗所谓顿悟和渐修一个意思
不要弄得像现在大学的史学系一样,自己好像比历史还高明,然后去分析历史批判历史,结果你不是历史,你是书呆子。现在研究历史同我们过去不同,我们过去研究历史,是使自己懂得如何作人做事,现在不然,现在是比历史都还要高。
阮云:我们的历史课本常犯这种毛病,脱离时代来谈局限性
“未死先学死,有生即杀生”,“生”就是心念一动,就要把心念通通去掉;这个“死”,不是自己吃安眠药去死,是要烦恼杂念妄想通通死光,就是杀的作用。也就是说,心中的烦恼杂念通通死光,生命的本能才会恢复,才会长生不死。
孔子讲得很彻底:“徒善不足以为敬,徒法不足以自刑”。
汉家自有制度,本以霸王道杂之
泉涸,鱼相与处于陆,相呴以湿,相濡以沫,不如相忘于江湖。与其誉尧而非桀也,不如两忘而化其道。
杀生者不死,生生者不生。其为物,无不将也,无不迎也,无不毁也,无不成也,其名为撄宁。撄宁也者,撄而后成者也。
孔子儒家所标榜的圣王之道,得了道才可以入世,“终日挥形”,他们虽然一天到晚看起来忙死了,但“神气无变”,内在修养神与气,并没有受忙碌的外界所影响。
“各有前因莫羡人”,每一个人都有各自的前因后果,你不要嫉妒羡慕人家。这些都是人生哲学的问题。
认为时代是进步的,这是站在物质文明立场上来讲。今后的人在物质的享受上,比我们现在还要进步,最后的形态,是物质文明一切一切都在进步;认为时代是退化退步的,这是站在精神文明来讲,这两种观念,必须要推论到宗教上面去。
你看其它国家的人,标榜人道,可见很不人道,所以才需要人道。
“其卧徐徐,其觉于于”,这两句话代表佛学禅宗讲的“梦觉一如”,人没有昏迷过,无所谓睡眠,睡眠也是清醒,醒了以后,也没有昏迷过,在清醒中“人生如梦”,本来是梦境,这没有什么两样。
阮云:所谓色即是空,空即是色。
“以己出经”,拿自己推理别人,就是儒家讲的推己及人的忠恕之道。
圣帝明王,就是动物园的园长,就养一些高明的动物。
阮云:所以领导者并不是自己多强,是要能招到厉害的人,管好厉害的人。
这个道理只可以悟不可以讲,讲出来就很讨厌的。
阮云:就如鲁迅说的,“这人终究会死的”,虽然是实话,但是难听
没有一个人会在妈妈肚子里问:我为什么要生出来?我生出来的目的是什么?没有一个人是问明白了才生出来的。所以人生就以人生为目的,本来如此,这个题目本身就是答案,还有什么好讲的!
我们人生只有十二个字:“看得破,忍不过;想得到,做不来。”
]]> 可能你认为重于生命的东西,于他人不过徒增笑谈尔。比如信教者的斋戒等行为,对无神论者来说是多余的,而对信徒来说却是可能比生命更重要。
又如,你还在大学的时候,这时的时间对你来说十分廉价,所以你愿意去发8元每小时的宣传单;等你事业有成或是垂垂老矣,你还愿意花这一小时去干这个吗,也许只会感叹自己当时太年轻吧。一个人尚且不能理解年轻时的自己,又怎能奢望他理解别人呢?
真正的相互理解是要认同他人对事物价值的判定,但一个事物对他们的价值有天壤之别,这样你叫他们如何相互理解呢?
那么怎么解决这种情况呢?也就是怎么寻找一种相对普适的价值衡量方法呢?
我的一个浅薄的思路是,衡量一个事物的价值可以通过这个人的付出相对自身的稀缺性,这种付出包括物资的付出和精神上承受的压力。
比如说,一个乞丐付出了仅有的100元买了个表,和一个百万富豪付出1000元买了个表。对乞丐来说这表是全部,而对富豪来说表是1/1000。因此乞丐对表爱护有加,富豪对表弃之如弊履,就完全可以理解了。也可以说乞丐的表价值大于富豪的表。
虽然绝对价值不可判定,并且造成很多纷争。但是,同一件事对不同的人价值不同也不是全然没有好处,至少在市场经济里,你们完全可以交换这些东西嘛。
要之,于娑婆世界中,众生皆苦,要以事物相对他人的稀缺性来看他人的付出与承受,如此理解他人才成为可能。
]]>文如其名,是由91篇关系不是很紧密的python文章集合而成
这本书和国内很多技术类书籍一样,排版的代码缩进很有问题,还有文章有些观点并不正确。
除此之外,还是有很多有用的知识点,需要读者自己辨别。
结构日益规范化。现在的库或框架跟随了以下潮流:
__init__
.py文件。Python表达式计算的顺序说起。
一般情况下Python表达式的计算顺序是从左到右,但遇到表达式赋值的时候表达式右边的操作数先于左边的操作数计算,因此表达式expr3, expr4=exprl, expr2
的计算顺序是exprl, expr2 —> expr3, expr4
。
因此对于表达式x, y=y, x
,其在内存中执行的顺序如下:
y,x
,因此先在内存中创建元组(y,x)
,其标示符和值分别为y、x及其对应的值,其中y和x是在初始化时已经存在于内存中的对象。python的and、or表达式并不会将每个值都算出,一旦整个表达式的值已知,其他部分就不会被计算,并且返回最后计算的那个值。
因此,在编程过程中,如果对于or条件表达式应该将值为真可能性较高的变量
写在or的前面;
而对于and则相反,应该推后。
().__class__.__bases__[0].__subclasses__
可返回当前解释器进程中的所有类对象。__import__("os").system("dir")
使用__import__
可以导入模块,并且返回这个模块因为Python解释器会将++i
操作解释为+(+i)
,其中+
表示正数符号。对于--i
操作也是类似。
循环如果正常结束(没有break),则执行else子句。for i in range(10):
if i < 4:
print i
else:
print 'for loop completed'
0
1
2
3
for loop completed
__nonzero__()
方法约定了如何判断对象的真假该内部方法用于对自身对象迸行空值测试,返回0/1
或True/False
。
如果一个对象没有定义该方法,Python将获取__len__()
方法调用的结果来进行判断。__len__()
返回值为0则表示为空。
如果一个类中既没有定义__len__()
方法也没有定义__nonzero__()
方法,该类的实例用if判断的结果都为True。
对于不可变对象来说,当我们对其进行相关操作的时候,Python实际上仍然保持原来的值而是重新创建一个新的对象,所以字符串对象不允许以索引的方式进行賦值.
当有两个对象同时指向一个字符串对象的时候,对其中一个对象的操作并不会影响另一个对象。
*args
与**kwargs
与默认参数首先满足普通参数,然后是默认参数;
如果剩余的参数个数能够覆盖所有的默认参数,则默认参数会使用传递时候的值;
如果剩余参数个数不够,则尽最大可能满足默认参数的值(从前往后)。
除此之外其余的参数除了键值对以外所有的参数都将作为args的可变参数,kwargs则与键值对对应。
若kwargs中有键与默认参数重复,会抛出TypeError
。
sorted()
与sort()
从函数的定义形式可以看出,sorted()接收任意可迭代的对象作为参数,返回排序后对象,而sort()—般作用于列表(是bound method),原位操作。针对元组使用sort()方法会抛出AttributeError
,而使用sorted()函数则没有这个问题。
dict或者嵌套list排序,注意itemgetter
用法
from operator import itemgetter |
使用collections.Counter来对可迭代对象统计各元素出现次数,Counter类是dict的子类。from collections import Counter
'qqqaaaasdfsdf') Counter(
Counter({'a': 4, 'q': 3, 's': 2, 'd': 2, 'f': 2})
使用pandas处理大型CSV文件。
Pandas即PythonDataAnalysisLibrary,是为了解决数据分析而创建的第三方工具,它不仅提供了丰富的数据模型,而且支持多种文件格式处理,包括CSV、HDF5、HTML等,能够提供高效的大型数据处理。
一般情况使用ElementTree解析XML。
cElementTree是ElementTree的Cython实现,速度更快,消耗内存更少,性能上更占优势,在实际使用过程中应该尽量优先使用cElementTree。一般情况指的是XML文件大小适中,对性能要求并非非常严格。
如果在实际过程中需要处理的XML文件大小在GB或近似GB级别,第三方模块lxml会获得较优的处理结果。
pickle估计是最通用的序列化模块了。
它还有个C语言的实现cPickle,相比pickle来说具有较好的性能,其速度大概是pickle的1000倍,因此在大多数应用程序中应该优先使用cPickle(注:cPickle除了不能被继承之外,它们两者的使用基本上区别不大)。
pickle中最主要的两个函数对为dump()和load(),分别用来进行对象的序列化和反序列化。
Logging只是线程安全的,不支持多进程写人同一个日子文件.
因此对于多个进程,需要配置不同的日志文件,否则会出现log覆盖。
用mixin模式让程序更加灵活.,每个类都有__bases__
属性,它是一个元组,用来存放所有的基类。
与其他静态语言不同,Python语言中的基类在运行中可以动态改变。所以当我们向其中增加新的基类时,这个类就拥有了新的方法,也就是所谓的混人(mixin)。这种动态性的好处在于代码获得了更丰富的扩展功能。
__init__()
与__new__()
哪个才是构造方法__init__()
并不是真正意义上的构造方法,__init__()
方法所做的工作是在类的对象创建好之后进行变量的初始化。
__new__()
方法才会真正创建实例,是类的构造方法。__new__()
方法是静态方法,而__init__()
为实例方法。__new__()
方法一般需要返回类的对象,当返回类的对象时将会自动调用__init__()
方法进行初始化,如果没有对象返回,则__init__()
方法不会被调用。__init__()
方法不需要显式返回,默认为None
,否则会在运行时抛出TypeError
。(新式类)
class MyC(object): |
__getattr__()
和__getattribute__()
的注意事项__getattr__()
和__getattribute__()
方法的时候需要特别小心,否则可能出现无穷递归。__getattribute__()
方法之后,任何属性的访问都会调用用户定义的__getattribute__()
方法,性能上会有所损耗,比使用默认的方法要慢。__getattr__()
方法如果能够动态处理事先未定义的属性,可以更好地实现数据隐藏。因为dir()通常只显示正常的属性和方法,因此不会将该属性列为可用属性__metaclass__
与元编程在新式类中当一个类未设置__metaclass__
属性的时候,它将使用默认的type元类来生成类。而当该属性被设置时查找规则如下:
__dict__['__metadass__']
,则使用对应的值来构建类;否则使用其父类__dict__['__metaclass__']
中所指定的元类来构建类,当父类中也不存在指定的metadass的情形下使用默认元类type。__metaclass__
,则使用该变量所对应的元类来构建类;否则使用types.ClassType
。元方法可以从元类或者类中调用,而不能从类的实例中调用;但类方法可以从类中调用,也可以从类的实例中调用。__hash__()
与可哈希可哈希对象,它是通过__hash__()
这个内置函数的,这在创建自己的类型时非常有用,因为只有支持可哈希协议的类型才能作为dict的键类型(不过只要继承自object的新式类默认就支持了)。
from contextlib import contextmanager |
症状
原因
作者认为某些内容没有说清楚
措施
收益
可以增强表述能力,有可能暴露出重复性
例外
有些注释是必要的,比如需求文档的链接
症状
原因
没有及时分解代码块,只是一味地在原来的位置增加代码
措施
收益
可以增强表述能力,有可能暴露出重复性,通常有助于建立新的类和抽象。
例外
有些复杂的算法需求,天然需求很多行
症状
原因
没有及时根据职责拆分类,只是一味地在原来的类上增加代码。
措施
收益
可以增强表述能力,有可能暴露出重复性
例外
无
症状
原因
可能是为了尽量减少对象之间的耦合。这样做不是由被调用对象来了解类之间的关系,而是让调用者来确定所有一切。
也有可能是程序员对例程进行了通用化
措施
收益
可以增强表述能力,有可能暴露出重复性。通常可以缩小规模。
例外
症状
原因
措施
收益
可以增强表述能力,有可能暴露出重复性
例外
有些场景命名中的类型是有帮助的,如sql的字段
症状
原因
取名没有规范,过于随意
措施
收益
可以增强表述能力
例外
症状
原因
不同的人会在不同时刻创建类, 但作用是相同的, 导致名称不一样
措施
收益
可以增强表述能力, 有可能暴露出重复性
例外
无
症状
原因
措施
收益
降低规模。可以增强表述能力,代码更简单。
例外
症状
原因
措施
收益
降低规模。可以增强表述能力,代码更简单。
例外
症状
原因
措施
收益
减少重复。可以增强表述能力,代码更简单。
例外
症状
原因
措施
收益
减少重复, 降低规模,代码更简单。
例外
症状
原因
措施
协调各个类, 使他们一致, 从而去除其中一个
收益
减少重复, 降低规模,可能增强表述能力。
例外
症状
原因
措施
收益
减少重复, 减少逻辑错误。
例外
症状
原因
措施
收益
可能增强表述能力
例外
症状
原因
没有对要判断的对象进行很好的分析和抽象
措施
收益
可以增强表述能力, 可能暴露重复性问题
例外
症状
原因
懒得引入类型
措施
相同条件的switch语句多处出现
如果条件式出现在一个单独的类中,可以通过将参数替换为显式方法或引入Null对象来取代条件逻辑。
收益
可以增强表述能力, 可能暴露重复性问题
例外
症状
原因
当ArrayList(或其他一些通用结构)被滥用时,会出现这类紧密相关问题。
措施
对于缺失对象
对于模拟类型,一个整型类型码对应一个类
对于模拟字段访问函数
收益
可以增强表述能力, 可能暴露重复性问题, 通常能表明还需要使用其他重构技术。
例外
症状
原因
类还未重复开发, 还没有抽象出行为
措施
收益
可以增强表述能力,可能会暴露重复性问题
例外
症状
原因
这些字段和方法, 往往应该属于另一个类, 但是没有人发现类缺失
措施
收益
可以增强表述能力,可能会暴露重复性问题, 通常会降低规模
例外
症状
原因
通过字段而不是参数来传递信息
措施
收益
增强了表述能力并提高了清晰性。可能会减少重复,特别是在其他位置可以使用新类时。
例外
症状
原因
某个类之所以继承自另一个类,可能只是为了实现方便,而不是真的想用这个类来取代其父类。
措施
收益
可增强表述能力,能改善可测试性
例外
症状
原因
子类过分依赖了父类的一些信息, 过度耦合
措施
收益
可以减少重复。通常能够增强表述能力,还可能会降低规模。
例外
无
症状
原因
措施
收益
可降低规模。能增强表述能力,代码更简单
例外
症状
原因
常见的现象, 一般是因为代码的迭代, 类的职责发生了便宜
措施
收益
可减少重复。通常会增强表述能力,暴露需重构的问题
例外
说明
区分依恋情结和不当的紧密性有时并不简单。
依恋情结是指,一个类自身所做甚少,需要借助大量其他类才能完成自己的工作。
不当的紧密性则是指,一个类为了访问某些本不该访问的内容,过于深入到其他类中。
症状
原因
两个类之间有时可能会稍有关联。等你意识到存在问题时,这两个类已经过于耦合了
措施
收益
可以减少重复。通常能够增强表述能力,还可能会降低规模。
例外
无
症状
原因
一个对象必须与其他对象协作才能完成工作,这是自然,问题在于这不仅会使对象相互耦合,还会使获得这些对象的路径存在耦合。
方法不应与“陌生人”说话,也就是说,它应当只与其自身、其参数、它自己的字段或者它创建的对象有信息传递。
措施
收益
可以减少重复或者暴露重复性问题
例外
如果过分应用隐藏委托,那么对象都去忙着委托,就会没有一个真正做实事的
症状
原因
可能是因为应用了隐藏委托来解决消息链引起的。
可能在此之后其他一些特性已经被移除了,剩下的主要就是委托方法了。
措施
收益
可降低规模,还可能增强表述能力
例外
症状
原因
类在发展过程中会承担越来越多的职责,但没有人注意到这会涉及两种截然不同的决策
措施
收益
可增强表述能力(更好地传达意图),还可以提高健壮性以备将来修改。
例外
无
症状
原因
措施
收益
可以减少重复,增强表达能力,并能改进可维护性(将来的修改将更为本地化)。
例外
无
症状
原因
这样两个类并不是无关的, 而是体现了同一决策的不同方面(维度)。
措施
收益
可以减少重复。可能会增强表述能力,也可能会降低规模
例外
无
症状
原因
这与并行继承体系相关,但是所有内容都折叠到了一个继承体系中 。
原本应当是独立的决策却通过一个继承体系实现了。
措施
收益
可以减少重复, 降低规模
例外
无
症状
原因
库类的作者未能满足你的要求
措施
收益
可以减少重复
例外
如果有多个项目,每个项目都使用不兼容的方式来扩展一个类,那么在改变库时就会导致额外的工作
beforevoid printOwing() {
printBanner();
// Print details.
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
aftervoid printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
beforeclass PizzaDelivery {
// ...
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
}
afterclass PizzaDelivery {
// ...
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
}
beforevoid renderBanner() {
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
}
aftervoid renderBanner() {
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIE && wasInitialized() && wasResized) {
// do something
}
}
beforeboolean hasDiscount(Order order) {
double basePrice = order.basePrice();
return basePrice > 1000;
}
afterboolean hasDiscount(Order order) {
return order.basePrice() > 1000;
}
beforedouble calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
afterdouble calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98;
}
}
double basePrice() {
return quantity * itemPrice;
}
beforedouble temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
afterfinal double perimeter = 2 * (height + width);
System.out.println(perimeter);
final double area = height * width;
System.out.println(area);
beforeint discount(int inputVal, int quantity) {
if (inputVal > 50) {
inputVal -= 2;
}
// ...
}
afterint discount(int inputVal, int quantity) {
int result = inputVal;
if (inputVal > 50) {
result -= 2;
}
// ...
}
beforeclass Order {
// ...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// Perform long computation.
}
}
afterclass Order {
// ...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// Copy relevant information from the
// order object.
}
public double compute() {
// Perform long computation.
}
}
beforeString foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")){
return "Don";
}
if (people[i].equals("John")){
return "John";
}
if (people[i].equals("Kent")){
return "Kent";
}
}
return "";
}
afterString foundPerson(String[] people){
List candidates =
Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i < people.length; i++) {
if (candidates.contains(people[i])) {
return people[i];
}
}
return "";
}
beforeclass Report {
// ...
void sendReport() {
Date nextDay = new Date(previousEnd.getYear(),
previousEnd.getMonth(), previousEnd.getDate() + 1);
// ...
}
}
afterclass Report {
// ...
void sendReport() {
Date newStart = nextDay(previousEnd);
// ...
}
private static Date nextDay(Date arg) {
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
}
beforeclass Range {
private int low, high;
boolean includes(int arg) {
return arg >= low && arg <= high;
}
}
afterclass Range {
private int low, high;
boolean includes(int arg) {
return arg >= getLow() && arg <= getHigh();
}
int getLow() {
return low;
}
int getHigh() {
return high;
}
}
beforeString[] row = new String[2];
row[0] = "Liverpool";
row[1] = "15";
afterPerformance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
beforeclass Person {
public String name;
}
afterclass Person {
private String name;
public String getName() {
return name;
}
public void setName(String arg) {
name = arg;
}
}
beforedouble potentialEnergy(double mass, double height) {
return mass * height * 9.81;
}
afterstatic final double GRAVITATIONAL_CONSTANT = 9.81;
double potentialEnergy(double mass, double height) {
return mass * height * GRAVITATIONAL_CONSTANT;
}
beforedouble disabilityAmount() {
if (seniority < 2) {
return 0;
}
if (monthsDisabled > 12) {
return 0;
}
if (isPartTime) {
return 0;
}
// Compute the disability amount.
// ...
}
afterdouble disabilityAmount() {
if (isNotEligibleForDisability()) {
return 0;
}
// Compute the disability amount.
// ...
}
beforeif (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}
afterif (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();
beforeif (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
afterif (isSummer(date)) {
charge = summerCharge(quantity);
}
else {
charge = winterCharge(quantity);
}
beforeclass Bird {
// ...
double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new RuntimeException("Should be unreachable");
}
}
afterabstract class Bird {
// ...
abstract double getSpeed();
}
class European extends Bird {
double getSpeed() {
return getBaseSpeed();
}
}
class African extends Bird {
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
double getSpeed() {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.getSpeed();
beforeString foundMiscreant(String[] people){
String found = "";
for (int i = 0; i < people.length; i++) {
if (found.equals("")) {
if (people[i].equals ("John")){
sendAlert();
found = "John";
}
}
}
return found;
}
afterString foundMiscreant(String[] people){
for (int i = 0; i < people.length; i++) {
if (found.equals("")) {
if (people[i].equals ("John")){
sendAlert();
return "John";
}
}
}
return "";
}
beforepublic double getPayAmount() {
double result;
if (isDead){
result = deadAmount();
}
else {
if (isSeparated){
result = separatedAmount();
}
else {
if (isRetired){
result = retiredAmount();
}
else{
result = normalPayAmount();
}
}
}
return result;
}
afterpublic double getPayAmount() {
if (isDead){
return deadAmount();
}
if (isSeparated){
return separatedAmount();
}
if (isRetired){
return retiredAmount();
}
return normalPayAmount();
}
beforeif (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
afterclass NullCustomer extends Customer {
boolean isNull() {
return true;
}
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ?
order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
beforedouble getExpenseLimit() {
// Should have either expense limit or
// a primary project.
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit :
primaryProject.getMemberExpenseLimit();
}
afterdouble getExpenseLimit() {
Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}
beforeint low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
afterboolean withinPlan = plan.withinRange(daysTempRange);
beforevoid setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
aftervoid setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
beforeint basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
afterint basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
beforeclass Employee {
Employee(int type) {
this.type = type;
}
// ...
}
afterclass Employee {
static Employee create(int type) {
employee = new Employee(type);
// do some heavy lifting.
return employee;
}
// ...
}
beforeint withdraw(int amount) {
if (amount > _balance) {
return -1;
}
else {
balance -= amount;
return 0;
}
}
aftervoid withdraw(int amount) throws BalanceException {
if (amount > _balance) {
throw new BalanceException();
}
balance -= amount;
}
beforedouble getValueForPeriod(int periodNumber) {
try {
return values[periodNumber];
} catch (ArrayIndexOutOfBoundsException e) {
return 0;
}
}
afterdouble getValueForPeriod(int periodNumber) {
if (periodNumber >= values.length) {
return 0;
}
return values[periodNumber];
}
beforeclass Manager extends Employee {
public Manager(String name, String id, int grade) {
this.name = name;
this.id = id;
this.grade = grade;
}
// ...
}
afterclass Manager extends Employee {
public Manager(String name, String id, int grade) {
super(name, id);
this.grade = grade;
}
// ...
}
随着年岁渐长, 看着亲人离自己远去, 真是很难接受, 恨自己的无力, 不能为他们做些什么.
或许这就是佛家说的求不得吧, 不喜欢的事物它还是会来, 喜欢的事物也还是会走, 来或是不来都难以求得, 父亲那是也是这样.
死生事大, 无常迅速.
无法, 唯有精进.
]]>早起唯有床狼藉
]]>每个人心中都有把青冥剑
,而我想说每个人心中都有个小王子
。写给曾经是孩子的成人的童话
。所以,不论文章还是电影,都比较有深度,适合我们这些曾经是孩子的人观看。关于电影情节就介绍到这里,不然就有剧透之嫌。下面要讲的主要还是对小王子原本故事的解读,可以放心阅读。
所谓文学作品就是作者的梦,里面是各种元素的杂糅,各个人物和情节都有现实的影子,但有不等同现实。文中,飞行员,毫无疑问代表作者;小王子,就比较复杂了,姑且可以认为是童真
,并且融入了一些作者珍视的东西,比如对玫瑰的爱;玫瑰,作者的爱人,也是让作者有所愧疚的人。小王子因为和玫瑰闹了别扭,离开他居住的那房子般大小的小行星,路上经过了几颗325、326、等等小行星,每颗小行星上都居住了一个大人——滑稽的大人,小王子与他们交谈后来到了地球。
其中酒鬼所说的话极具代表性。
小王子:你为什么要喝酒?
酒鬼:为了忘记羞愧的事。
小王子:羞愧什么呢?
酒鬼:喝酒。
我们这些大人很多时候就是这么奇怪,好像自己每天做的东西都是理所应当,‘我们都是正经人,都做正经事。’,却离真正想要的东西越来越远。
小王子就是小时候的我们,小孩子眼中的世界和小王子的星球一样,虽然小但是有他喜爱的东西就够了。而这些大人,每人忙忙碌碌,追求着自己不需要的东西。
每个人心中都有个小王子,那个我们曾经拥有,却终将逝去的小王子。
年轻的时候如果不好好去爱,等到老去的时候就一定会因为曾经的轻狂而感到后悔。
他的脸色气得发红,接着说道:“若有人爱上了开在亿万星星上的一株花,那么看着星星就足以使他感到幸福。”
我本该猜出她那站不住脚的花招后面的温情。花儿是多么自相矛盾!可我太小,不懂得如何去爱她……”
“人都在什么地方?”小王子终于又开了口,“沙漠里真有点孤独。” “在人群里照样孤独。”蛇说
狐狸说,“人类是不再有空去了解任何东西的,他们到商店去购买现成的东西”。
只有用心才能看到本质的东西,精髓是眼睛看不见的。
“只有孩子知道自己在找什么,”小王子说,“他们为一个布娃娃花费时间,布娃娃就变得很重要,如果布娃娃被夺走,他们就哭泣……”“他们真幸运。”
“你这里的人在同一个花园种植 5000朵玫瑰……”小王子说,“却不能从中找到自己要找的东西……”
“有一天,我看见过四十三次日落。”
“你知道,当人们感到非常苦闷时,总是喜欢日落的。”
“一天四十三次,你怎么会这么苦闷?”
小王子没有回答。
no man is a island.
西谚有云“没有人是个孤岛”,人是社会性动物,很多自我价值都在社交中实现,自是离不开沟通。
但真正的沟通又是何其难?某种意义上,你永远都不能知道对方真正的意思。当然,有时你连自己是什么意思都不甚了解。
在聊天中经常出现的是,表面上相谈甚欢,背地里很可能是呵呵一笑。毕竟不同价值观,你不能奢望别人能完全理解你,不然怎么老说知音难觅呢?
叔本华曾说
人就像寒冬里的刺猬,互相靠得太近,会觉得刺痛;彼此离得太远,却又会感觉寒冷.
大师所言简直入木三分,而且这些刺猬靠得太近的时候,会说“这个距离太热了”,而不是“你刺到我了”。人与人之间的沟通乃至交往,正是寻找这个合适的距离,这个对双方都合适的距离。
人人都有刺,你的刺一方面是你防卫的武器,另一方面阻止距离更近一步的枷锁。没安全感的人,他的刺就特别长,这样虽然安全了,但别人永远无法接近你。所以我们往往躲在自己厚重的防卫之下,同时感叹着“怎么没有真正理解我的人呢?”。
但其实这些刺都是无用的累赘,你以为它能保护你,其实它把所有人都隔离在外。其实能伤害你的只有你自己,敞开你的心扉吧,你会找到真正的知音,就算不幸有人要辱骂你伤害你,你不放在心上,他就无的放矢。
放下无谓的防备,追求无碍的沟通。
]]> 也曾想,自己的生命有多长,然后就是一阵惊惧。
生命实在是太过短暂,而且很可能在你不经意时终结。
生死事大,无常迅速。
唉!无法,徒增忧伤而已。
]]>人生苦短,及时行乐。
有愿立偿,不余遗憾。
番茄工作法(英语:Pomodoro Technique)是一种时间管理法方法,在上世纪八十年代由Francesco Cirillo创立。 该方法使用一个定时器来分割出一个一般为25分钟的工作时间和5分钟的休息时间,而那些时间段被称为pomodori,为意大利语单词 pomodoro(中文:番茄)之复数。
番茄工作法有五个基本步骤:
番茄工作法的关键是规划,追踪,记录,处理,以及可视化。在规划阶段,任务被根据优先级排入”To Do Today” list。 这允许用户预计每个任务的工作量。当每个番茄时结束后,成果会被记录下来以提高参与者的成就感并为未来的自我观察和改进提供原始数据。
番茄工作法一般与GTD
相结合,能取得事半功倍的效果。
滴答清单
或Doit.im
小米手环本来没有番茄钟的功能,通过Mi Band Tools
和极简番茄
这两个应用,在米环工具的“应用”中添加 极简番茄,可以实现手环震动提醒番茄钟。
当然你也可以直接使用极简番茄
,通过手机震动或是响铃提醒,或者买一个计时器。
在米环工具的“应用”中添加 极简番茄。
具体的应用配置可以参考截图,一般只修改震动次数、忽略不可清除的通知,其他默认即可
下面就简单地总结下,如何双标。
双标的核心是,定性的看问题,不要就事论事具体分析。
双标的要点之一,选取部分真相进行概括定性。
这里的选取非常关键,我们应该选择性地看见我们想看见的东西。假如B捡了地上的一块钱并据为己有,贪小便宜不是君子所为,这人肯定是品行不端。假如A也这么干了,对不起我没有看见。假如A捡了垃圾放垃圾桶了,这时我们就可以说,从一件微不足道的事情上就可以看出A真是道德高尚。
双标的要点之二,定性之后的上纲上线。
由于第一步我们已经定性了A和B,以后再发生事情就很好处理了。假如都做了好事,A就是好人性质的体现,B因为他是坏人肯定是另有目的的,是伪装的。假如都做了坏事,反过来处理就好了。比如,你认定A是好人,B是坏人,假设他们都随地吐痰了。这时就可以说A肯定是不小心的,大善人怎么可能做这种事;可以说B真是狗改不了吃屎,连基本的公德心都没有。假设都做了好事,扶老奶奶过马路,A就是一以贯之的道德楷模,值得学习;B肯定是装的,为了方便自己做坏事。
实用主义, 实为 “实验主义”
苏格拉底: 天下任何事物和概念都有其 “普遍界说”, 比如说,猫的“普遍界说”就是“捉老鼠”。
胡适有好疑问的信条, 唯有对这一信条不疑问
诸公茶余溺后, 伸缩乎竹椅之上, 打桥牌则 “金刚钻”, “克鲁伯”, 纸声飕飕, 下象棋则过河卒子, 拼命向前, 无牌无棋, 则张家山前, 李家山后, 饮食男女, 政治时事, 粪土当朝万户侯! 乖乖, 真是身在茶馆, 心有邦国, 眼观世界, 牛皮无边
阮云: 怎一个颓废了得
我们政学两界都害了过分依赖权威的毛病.
阮云: 让人个个人又想成为权威
阮云: 革命一定要年轻而冲动, 你想革他人之命, 须先将已命悬于腰间, 故革命言论一定要偏激而富有煽动性. 过于理性的人做不出革命之举(后又读吴思的书, 又有些不同看法)
朱熹: “格物”是, “格”, 至也, “物”, 犹事也.
]]>举最熟悉的编程来说,它应该是最适合自学的领域了。网络上相关的学习资源也数不胜数,但是为何很多人最后进步很慢或者是干脆“从入门到放弃了”,究其原因大抵是走了错误的道路。
打开知乎,类似“如何学习编程”的问题数不胜数,也有很多人善意地给了自己的回答。记得我当年也问过类似的问题,有人说先从SICP入手吧,有人说直接LeetCode刷题,有人说学C,有人说学Python。或许一千个人就有一千种答案,但是到底哪个能帮助到你呢?每个人的答案,其实都是他过往经验的总结。
由于每个人的资质、禀赋、基础(下面统称为资质)并不相同,每个方法也有其适应人群和局限,照搬别人的路子只能是失败。越高资质的人(利根人),对于各种方法的兼容性越好,当然效果是有区别的。低资质的人(钝根人),越发体现方法的重要性。这里的方法就是学习的路径,佛家称之为法门。一切法门都是手段而已,都是为了最终能学到东西。
譬如爬山,有盘山公路,也有直接的楼梯,也有索道,并无绝对高下之分,只有合适与不合适。
体力不同能承受的坡度就不同,超出限度最后只能半途而废,所以得从这些路径上选择自己恰好能接受的那条路。当自学的时候,由于没有老师,我们很容易步入错误的道路,从而落入陷阱。
那么,如何适合自己的道路呢?首先要了解自己的的优劣势,明确目标,然后了解每条路径的特点(要求、效率、产出等)。
具体来说,逐条尝试路径,评估投入产出,做记录,并对比当前的目标(为了欣赏风景,还是锻炼身体),选择相对合适的路径。而且选择不应该是僵化的,应该持续地评估,因为的你会变、路会变、你和路之间还会变。当然不要去尝试所有的路径,一则时间精力有限,二则我们也不是路径测评师。
有朝一日学有所得,能够为人导师时,记得随人讲法。
最后,学习使我快乐。
]]>独处之时意淫幻想,骤然临之慌乱畏惧不前。平生如遇佳人则豫惧无措,欲与之亲近而不得。而后偶一回想,怅然颓首而无所解。如此于人于己有何益处,空留念想,只招惆怅而已。
呜呼,面皮之为物当得了几两干饭,胆气之所缺非得几斤豹胆所能解。
为文以纪,聊为自勉,愿余能一扫前非。
]]>既不若向之所求,何须用心添烦恼。
不谏已悟,来者可追。
当效拂袖,心役即脱。
]]>当你维护旧项目的时候,可能要优化一些原有的代码,又不想大改特改。毕竟设计模式的原则是对更改关闭,对扩展开放。假如大改特改势必会引入新的bug,增加测试的工作量。优化代码的目的是提高稳定性和效率,如果引入了新bug就得不偿失了。
这时我们可使用适配器或装饰器,在不改变调用的情况下,优化代码。
比如下面这代码段,封装了一个MySQL的连接,但是只返回了游标对象(cursor),没有返回连接对象(connnection)。这样每次调用完这个函数之后,数据库连接不能关闭,很可能会导致连接量到达上限,引起性能问题。import MySQLdb
def mysql_connect():
conn = MySQLdb.connect(host='localhost',
port=3306,
user='root',
passwd='123456',
db='db',
charset='utf8',
use_unicode=False,
unix_socket='/tmp/mysql3306.sock')
cur = conn.cursor()
return cur
所以我写了一个适配器类,通过适配器对象包装游标和连接,并且使mysql_connect
函数返回适配器对象,以解决连接不能关闭的问题。
MySQLConnectAdapter
适配器组合了cursor
和connnection
,调用close
方法的时候会同时关闭cursor
和connnection
。重写__getattribute__
方法,将除close
之外的属性和方法重定向的self._cur
对象上,实现对原有调用的兼容。需要注意,一旦重写__getattribute__
方法,所以属性和方法查找都会通过自定义的__getattribute__
,注意防止无穷递归。
import MySQLdb |
这篇讲代码的坏味道,学习它是为了提升我们的眼力,为找到对应的重构方法做准备
症状: 全局作用域的变量, 类变量, 单例
分析: 从代码库的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出了修改。最刺鼻的坏味道之一。
症状: 将可变对象作为函数参数
分析: 难以追踪数据变更, 定位故障
症状: 函数需要的参数很多, 如4个以上的必填参数
分析: 额外损耗调用者的心智, 也是函数违背了单一职责的征兆.
症状: 用基本类型来代表业务对象, 如钱、坐标、范围、人名等;
分析: 缺少必要的抽象, 容易诱发数据泥团, 也容易产生重复代码.
症状: 超长的链式调用, 或者一长串取值函数, 或一长串临时变量.
分析: 系统难理解和维护, 客户端代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。
症状: 一处代码包含了多个上下文, 也就是承担了多个职责.
分析: 难以测试; 增大修改的难度, 因为要关注多个上下文.
症状: 一个上下文, 被分散在多处, 每遇到某种变化,你都必须在许多不同的类\函数\模块内做出许多小修改.
分析: 与发散式变化类似, 难以测试, 难以修改.
症状: 一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流.
分析: 错误的抽象, 导致不同上下文的交汇, 难以测试.
症状: 不同模块之间互相调用过多
分析: 说明这两个模块的封装不恰当, 模块的函数或者数据放错地方了.
症状: 在一个以上的地点看到相同的代码结构
分析: 影响可读性, 阅读时需要花额外的精力,比较多个地方是否存在差异。
症状: 函数很长(超过100行), 大量的参数和临时变量, 复杂的循环和分支
分析: 影响可读性, 逻辑复杂难以测试
症状: 扎堆出现的多个数据项, 删掉众多数据中的一项, 其他数据就失去了意义; 例如: 多个类的相似属性, 多个函数的多个相同参数.
分析: 缺少必要的抽象, 开发者关注了过多细节; 难以修改
症状: 出现在多个地方的相同switch语句, 或者连续的if\else.
分析: 是重复代码的一种, 也会引发霰弹式修改; 容易引发bug
症状: 过多的循环, 嵌套的循环
分析: 难以一眼看出循环代码的意图, 常演变成在一个循环里做多件事情.
症状: 单个类做太多事情, 有些字段或者函数关联性不高
分析: 与过长函数类似, 违背了单一职责原则.
症状: 可有可无的代码元素; 如: 只有一个方法的类, 只有一个子类的父类.
分析: 过度设计的典型, 难以应对变化. 如无必要, 勿增实体.
症状: 为了通用性而预先设计的代码结构, 实际上去很少用到; 如: 预留许多钩子处理各种非必要的事情, 无实际意义的多层继承体系.
分析: 过度设计, 系统更难理解和维护。
症状: 预留的字段或者变量, 仅为某种特定情况而设.
分析: 过度设计, 代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有字段。
症状: 没有必要的中间人对象, 过度使用委托. 如: 某个类的接口有一半的函数都委托给其他类.
分析: 过度设计
症状: 在不同继承体系的类, 做的事情却大同小异
分析: 重复代码的一种类型
症状: 只有数据字段和读写函数的类
分析: 纯数据类常常意味着行为被放在了错误的地方, 使得难以追踪数据的修改.
症状: 子类和父类的相似度很低; 如: 子类只需要父类的一个方法, 却继承了整个类.
分析: 意味着继承体系设计错误
症状: 不能清晰地表明自己的功能和用法, 或者随意地取无意义的名称.
分析: 对可读性有很大影响, 如果你想不出一个好名字,说明背后很可能潜藏着更深的设计问题。
症状: 超长的的注释
分析: 意味着相关代码实现难以理解
坏味道(英文) | 坏味道(中文) | 原书页码 | 常用重构 |
---|---|---|---|
Alternative Classes with Different Interfaces | 异曲同工的类 | 83 | 改变函数声明(124),搬移函数(198),提炼超类(375) |
Comments | 注释 | 84 | 提炼函数(106),改变函数声明(124),引入断言(302) |
Data Class | 纯数据类 | 83 | 封装记录(162),移除设值函数(331),搬移函数(198),提炼函数(106),拆分阶段(154) |
Data Clumps | 数据泥团 | 78 | 提炼类(182),引入参数对象(140),保持对象完整(319) |
Divergent Change | 发散式变化 | 76 | 拆分阶段(154),搬移函数(198),提炼函数(106),提炼类(182) |
Duplicated Code | 重复代码 | 72 | 提炼函数(106),移动语句(223),函数上移(350) |
Feature Envy | 依恋情结 | 77 | 搬移函数(198),提炼函数(106) |
Global Data | 全局数据 | 74 | 封装变量(132) |
Insider Trading | 内幕交易 | 82 | 搬移函数(198),搬移字段(207),隐藏委托关系(189),以委托取代子类(381),以委托取代超类(399) |
Large Class | 过大的类 | 82 | 提炼类(182),提炼超类(375),以子类取代类型码(362) |
Lazy Element | 冗赘的元素 | 80 | 内联函数(115),内联类(186),折叠继承体系(380) |
Long Function | 过长函数 | 73 | 提炼函数(106),以查询取代临时变量(178),引入参数对象(140),保持对象完整(319),以命令取代函数(337),分解条件表达式(260),以多态取代条件表达式(272),拆分循环(227) |
Long Parameter List | 过长参数列 | 74 | 以查询取代参数(324),保持对象完整(319),引入参数对象(140),移除标记参数(314),函数组合成类(144) |
Loops | 循环语句 | 79 | 以管道取代循环(231) |
Message Chains | 过长的消息链 | 81 | 隐藏委托关系(189),提炼函数(106),搬移函数(198) |
Middle Man | 中间人 | 81 | 移除中间人(192),内联函数(115),以委托取代超类(399),以委托取代子类(381) |
Mutable Data | 可变数据 | 75 | 封装变量(132),拆分变量(240),移动语句(223),提炼函数(106),将查询函数和修改函数分离(306),移除设值函数(331),以查询取代派生变量(248),函数组合成类(144),函数组合成变换(149),将引用对象改为值对象(252) |
Mysterious Name | 神秘命名 | 72 | 改变函数声明(124),变量改名(137),字段改名(244) |
Primitive Obsession | 基本类型偏执 | 78 | 以对象取代基本类型(174),以子类取代类型码(362),以多态取代条件表达式(272),提炼类(182),引入参数对象(140) |
Refused Bequest | 被拒绝的遗赠 | 83 | 函数下移(359),字段下移(361),以委托取代子类(381),以委托取代超类(399) |
Repeated Switches | 重复的switch | 79 | 以多态取代条件表达式(272) |
Shotgun Surgery | 霰弹式修改 | 76 | 搬移函数(198),搬移字段(207),函数组合成类(144),函数组合成变换(149),拆分阶段(154),内联函数(115),内联类(186) |
Speculative Generality | 夸夸其谈通用性 | 80 | 折叠继承体系(380),内联函数(115),内联类(186),改变函数声明(124),移除死代码(237) |
Temporary Field | 临时字段 | 80 | 提炼类(182),搬移函数(198),引入特例(289) |
from __future__ import division
就可以使用python3的除法。/
与操作数有关,x / y
中x、y都为整型的话,为floor除法
,否则为true除法
也是日常的除法。/
为true除法
, 与操作数无关。//
在 Python2 与 Python3 中并无差别, 都代表floor除法
-5/3 |
-5/3 |