1、 http://webdemo.myscript.com/views/math/index.html
2、https://latex.codecogs.com/eqneditor/editor.php
3、http://detexify.kirelabs.org/symbols.html
4、顶级神器: mathpix 截图转latex
这段时间写了几篇machine learning的文章,需要用到很多数学公式,这些文章里所有的数学公式基本都是贴图的,其实这样并不方便,而且很不美观。主要是自己懒,觉得能截图就截图了,感觉在wordpress里编辑数学公式肯定很麻烦,但实际上在wordpress里显示数学公式是一件非常简单的事,出乎意料的简单,连插件都不需要装。我采用的方案是一个基于LaTeX显示数学公式的JavaScript引擎-MathJax-https://www.mathjax.org/,这个JS引擎的优点是全浏览器支持,不需要额外插件设置,非常方便。具体使用步骤如下:在header.php文件里添加JS引用,具体步骤:登陆wordpress,进入外观->编辑->主题页眉 (header.php)。在head标签里添加一行代码引入MathJax:
1 | <script src=’https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML’></script> |
(值得注意的是,这行代码必须要放到 <?php wp_head(); ?>之前,否则不生效)这里的JS地址用的是MathJax的官方CDN,其实也完全可以把这个MathJax.js文件下载放到本地,然后配置成本地地址。但是呢,这个CDN地址在墙内的访问速度还可以,所以还是建议大家直接使用官方的CDN吧,省事、方便。
完成上面的步骤就完成了MathJax的配置,怎么样,很简单吧。那配置好了之后该如何显示数学公式呢?我们知道MathJax是基于LaTeX的,所以首先要用LaTeX语法把数学公式写出来。这里推荐一个非常好用的在线的LaTeX编辑数学公式的网站:Oneline LaTex Equation Editor
这个网站提供图形化编辑工具,能实时显示数学公式。LaTeX编辑数学公式的语法学习成本基本为零,很简单,写得多了都不需要图形化工具了。用LaTex写好了数学公式之后在博客里把它加进去,这里要使用某些特定的分隔符以方便被MathJax识别。具体地,有两种显示方式:
$$\sqrt{a^2+b^2}$$
或者 \[\sqrt{a^2+b^2}\]
,显示效果就是这样的:行内显示(in-line mathematics),它的分割符号是 \(...\)
,行内显示的格式: \(\sqrt{a^2+b^2}\
)
今天遇到一个问题,在linux里有好几个python解释器
/bin/python3
/usr/bin/python
/bin/python
查看python解释器路径:
通过脚本查看
import sys
import os
print('当前 Python 解释器路径:')
print(sys.executable)
如果需要修改默认Python解释器:
默认的python解释器是python3.6或者python2.7,反正不是我们刚下载下来的python3.7。为把默认的python解释器改为python3.7,我们进行如下操作:
cd /usr/bin
sudo rm /usr/bin/python #删除原有的python连接文件
sudo ln -s /usr/bin/python3.7 python #建立指向python3.7的连接
用pip命令把python包安装到指定目录:
sudo pip install numpy –target=/usr/local/lib/python2.7/site-packages
参考文章:支持向量机通俗导论
https://blog.csdn.net/v_july_v/article/details/7624837
线性分类
在训练数据中,每个数据都有n个的属性和一个二类类别标志,我们可以认为这些数据在一个n维空间里。我们的目标是找到一个n-1维的超平面(hyperplane),这个超平面可以将数据分成两部分,每部分数据都属于同一个类别。其实这样的超平面有很多,我们要找到一个最佳的。因此,增加一个约束条件:这个超平面到每边最近数据点的距离是最大的。也成为最大间隔超平面(maximum-margin hyperplane)。这个分类器也成为最大间隔分类器(maximum-margin classifier)。支持向量机是一个二类分类器。
非线性分类
SVM的一个优势是支持非线性分类。它结合使用拉格朗日乘子法和KKT条件,以及核函数可以产生非线性分类器。
以下摘自 支持向量机通俗导论 https://blog.csdn.net/v_july_v/article/details/7624837
支持向量机,因其英文名为 Support Vector Machine,故一般简称 SVM,通俗
来讲,它是一种二类分类模型,其基本模型定义为特征空间上的间隔最大的线
性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。
凸二次优化:https://zhuanlan.zhihu.com/p/100041443
凸函数:直观来讲就是形状看上去“凹”下去的函数,注意可不是看上去“凸”的函数
凸优化的形式化定义:
其中, x 为决策变量, f和g 均为凸函数, h为仿射(线性)函数。
https://www.cnblogs.com/90zeng/p/Lagrange_duality.html
KKT条件
同时包含等式约束和不等式约束:
定义Lagrange函数:
此时
满足的必要条件为:
大名鼎鼎的SVM算法。机器学习发展历史上一颗璀璨的明珠。相信很多接触机器学习的同学都是从SVM开始的。
SVM是为小样本学习设计的,而工业界(尤其是互联网领域)不缺少数据,同时SVM训练效率较低且不容易调试,同时不如LR模型可解释行强。所以SVM常见于实验室而在互联网领域鲜有应用。不过SVM将问题建模为“有约束凸二次优化问题”,其求解过程非常具有代表性。
上述过程将原有问题通过拉格朗日乘子法转换为对偶问题。
上述过程通过各种消元trick将问题转换成只有一类对偶变量 的形式,减小了求解难度。在实际SVM工具包中通常采用SMO算法。
如果所有变量的解都满足此最优化问题的KKT条件(Karush-Kuhn-Tucker conditions),那么这个最优化问题的解就得到了。因为KKT条件是该最优化问题的充分必要条件。否则,选择两个变量,固定其他变量,针对这两个变量构建一个二次规划问题。这个二次规划问题关于这两个变量的解应该更接近原始二次规划问题的解,因为这会使得原始二次规划问题的目标函数值变得更小。重要的是,这时子问题可以通过解析方法求解,这样就可以大大提高整个算法的计算速度。子问题有两个变量,一个是违反KKT条件最严重的那一个,另一个由约束条件自动确定。如此,SMO算法将原问题不断分解为子问题并对子问题求解,进而达到求解原问题的目的。
之前只是在实验室的服务器上跑代码,但因为将anaconda文件夹放置在了系统盘110G,所以系统爆满,想要解决,却无从下手,因此将linux中涉及文件挂载分区的知识进行整理总结,以备不时之需。
Linux 物理磁盘管理常用三个命令为 df、du 和 fdisk。
挂载的概念
当要使用某个设备时,例如要读取硬盘中的一个格式化好的分区、光盘或软件等设备时,必须先把这些设备对应到某个目录(这个目录可以不为空,但挂载后这个目录下以前的内容将不可用),而这个目录就称为“挂载点(mount point)”,这样才可以读取这些设备,而这些对应的动作就是“挂载”。
需要理解的是:
linux操作系统将所有的设备都看作文件,
它将整个计算机的资源都整合成一个大的文件目录。
我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上,
然后通过访问这个目录来访问存储设备
挂载分区:挂载的目录必须为空
mkdir /d1
mkdir /d2
mount /dev/sdc1 /d1
mount /dev/sdc2 /d2
查看文件(夹)所在分区(挂载点):
1、最简单的,直接 df <文件(夹)路径>
2、用df 或 fdisk -l查看分区挂载情况,直接输入mount或者也可以用cat /etc/mtab,然后pwd找最接近的挂载点信息
#df -h(查看分区情况及数据盘名称)
# mkdir /data(如果没有data目录就创建,否则此步跳过)
# umount /home(卸载硬盘已挂载的home目录)
# mount /dev/sdb3 /data (挂载到data目录)
# vi /etc/fstab (编辑fstab文件修改或添加,使重启后可以自动挂载)
/dev/sdb3 /data ext3 auto 0 0
数据盘 新挂载目录
编辑/etc/fstab里面的/home为/data, 或创建让系统启动的时候自动挂载到/data
ln -s
你可以将链接简单地理解为 Windows 中常见的快捷方式(或是 OS X 中的替身),Linux 中常用它来解决一些库版本的问题,通常也会将一些目录层次较深的文件链接到一个更易访问的目录中。在这些用途上,我们通常会使用到软链接(也称符号链接)。
我为什么要用:有时候服务器的home目录下的磁盘容量不足,而用户的文件都在 /home/用户名下面,这样的话无法满足用户的使用,就需要ln软链接了
建立软连接
ln [参数][源文件或目录][目标文件或目录] 常用:软链接命令 ln -s 源文件或目录 目标文件或目录 (将源文件或目录 连接到 目标文件或目录) 其中: 源文件或目录:文件原始地址 目标文件或目录:文件想要建立链接的位置
Linux ln(英文全拼:link files)命令是一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接。
当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。
命令功能 :
Linux文件系统中,有所谓的链接(link),我们可以将其视为档案的别名,而链接又可分为两种 : 硬链接(hard link)与软链接(symbolic link),硬链接的意思是一个档案可以有多个名称,而软链接的方式则是产生一个特殊的档案,该档案的内容是指向另一个档案的位置。硬链接是存在同一个文件系统中,而软链接却可以跨越不同的文件系统。
不论是硬链接或软链接都不会将原本的档案复制一份,只会占用非常少量的磁碟空间。
软链接:
硬链接:
必要参数:
这里有两点要注意:
第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化;
第二,ln的链接又分软链接和硬链接两种,软链接就是ln –s 源文件 目标文件,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接 ln 源文件 目标文件,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。
ln指令用在链接文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则会把前面指定的所有文件或目录复制到该目录中。若同时指定多个文件或目录,且最后的目的地并非是一个已存在的目录,则会出现错误信息。
删除软连接的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 以下这样的删除都没问题 ~ unlink link ~ rm link ~ rm -r link # 这里的参数 r 其实是没有意义的,因为link是一个软连接 不是目录 ~ rm -rf link # 这里的 rf 同样没有意义,只是rm 命令忽略了这里的参数 ~ ~ # 这样删除就会造成灾难 ~ rm -rf link/ # 这个时候你发现软连接并没有删除,但是 origin 目录下的文件是全部没删除了 ==! # 这些罪魁祸首是 参数 f,如果你没有使用f参数 这一切还可以挽回 ~ rm link/ rm: cannot remove `link/’: Is a directory # 这里 rm 通过你的参数 link/ 发现是要删除一个目录,这时候需要你添加参数 r ~ rm -r link/ rm: cannot remove `link’: Not a directory # 这里你添加了 r 参数,但是并不能找到目录 link/ 因为link并不是一个目录,他是一个软连接,只不过有些shell在补全的时候会将 `/` 补全上去 |
collections模块实现一些特定的数据类型,可以替代Python中常用的内置数据类型如dict, list, set, tuple,简单说就是对基本数据类型做了更上一层的处理。
https://github.com/python/cpython/blob/3.10/Lib/collections/init.py
https://docs.python.org/zh-cn/3/library/collections.html?highlight=deque#collections.namedtuple
init.py:
'''This module implements specialized container datatypes providing
alternatives to Python's general purpose built-in containers, dict,
list, set, and tuple.
* namedtuple factory function for creating tuple subclasses with named fields
* deque list-like container with fast appends and pops on either end
* ChainMap dict-like class for creating a single view of multiple mappings
* Counter dict subclass for counting hashable objects
* OrderedDict dict subclass that remembers the order entries were added
* defaultdict dict subclass that calls a factory function to supply missing values
* UserDict wrapper around dictionary objects for easier dict subclassing
* UserList wrapper around list objects for easier list subclassing
* UserString wrapper around string objects for easier string subclassing
'''
__all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList',
'UserString', 'Counter', 'OrderedDict', 'ChainMap']
用途:双端队列,头部和尾部都能以O(1)时间复杂度插入和删除元素。类似于列表的容器
所谓双端队列
,就是两端都能操作,与Python内置的list区别在于:头部插入与删除的时间复杂度为O(1),来个栗子感受一下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = 'liao gao xiang'
"""
保留最后n个元素
"""
from collections import deque
def search(file, pattern, history=5):
previous_lines = deque(maxlen=history)
for l in file:
if pattern in l:
yield l, previous_lines # 使用yield表达式的生成器函数,将搜索过程的代码和搜索结果的代码解耦
previous_lines.append(l)
with open(b'file.txt', mode='r', encoding='utf-8') as f:
for line, prevlines in search(f, 'Python', 5):
for pline in prevlines:
print(pline, end='')
print(line, end='')
d = deque()
d.append(1)
d.append("2")
print(len(d))
print(d[0], d[1])
d.extendleft([0])
print(d)
d.extend([6, 7, 8])
print(d)
d2 = deque('12345')
print(len(d2))
d2.popleft()
print(d2)
d2.pop()
print(d2)
# 在队列两端插入或删除元素时间复杂度都是 O(1) ,区别于列表,在列表的开头插入或删除元素的时间复杂度为 O(N)
d3 = deque(maxlen=2)
d3.append(1)
d3.append(2)
print(d3)
d3.append(3)
print(d3)
输出结果如下
人生苦短
我用Python
2
1 2
deque([0, 1, '2'])
deque([0, 1, '2', 6, 7, 8])
5
deque(['2', '3', '4', '5'])
deque(['2', '3', '4'])
deque([1, 2], maxlen=2)
deque([2, 3], maxlen=2)
因此,如果你遇到经常操作列表头的场景,使用deque最好。deque类的所有方法,自行操作一遍就知道了。
class deque(object):
"""
deque([iterable[, maxlen]]) --> deque object
A list-like sequence optimized for data accesses near its endpoints.
"""
def append(self, *args, **kwargs): # real signature unknown
""" Add an element to the right side of the deque. """
pass
def appendleft(self, *args, **kwargs): # real signature unknown
""" Add an element to the left side of the deque. """
pass
def clear(self, *args, **kwargs): # real signature unknown
""" Remove all elements from the deque. """
pass
def copy(self, *args, **kwargs): # real signature unknown
""" Return a shallow copy of a deque. """
pass
def count(self, value): # real signature unknown; restored from __doc__
""" D.count(value) -> integer -- return number of occurrences of value """
return 0
def extend(self, *args, **kwargs): # real signature unknown
""" Extend the right side of the deque with elements from the iterable """
pass
def extendleft(self, *args, **kwargs): # real signature unknown
""" Extend the left side of the deque with elements from the iterable """
pass
def index(self, value, start=None, stop=None): # real signature unknown; restored from __doc__
"""
D.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.
"""
return 0
def insert(self, index, p_object): # real signature unknown; restored from __doc__
""" D.insert(index, object) -- insert object before index """
pass
def pop(self, *args, **kwargs): # real signature unknown
""" Remove and return the rightmost element. """
pass
def popleft(self, *args, **kwargs): # real signature unknown
""" Remove and return the leftmost element. """
pass
def remove(self, value): # real signature unknown; restored from __doc__
""" D.remove(value) -- remove first occurrence of value. """
pass
def reverse(self): # real signature unknown; restored from __doc__
""" D.reverse() -- reverse *IN PLACE* """
pass
def rotate(self, *args, **kwargs): # real signature unknown
""" Rotate the deque n steps to the right (default n=1). If n is negative, rotates left. """
pass
这里提示一下,有些函数对队列进行操作,但返回值是None,比如reverse()
反转队列,rotate(1)
将队列中元素向右移1位,尾部的元素移到头部。
用途:带有默认值的字典。父类为Python内置的dict
字典带默认值有啥好处?举个栗子,一般来讲,创建一个多值映射字典是很简单的。但是,如果你选择自己实现的话, 那么对于值的初始化可能会有点麻烦,你可能会像下面这样来实现:
d = {}
for key, value in pairs:
if key not in d:
d[key] = []
d[key].append(value)
如果使用 defaultdict 的话代码就更加简洁了:
d = defaultdict(list)
for key, value in pairs:
d[key].append(value)
defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以你只需要 关注添加元素操作了。比如:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = 'liao gao xiang'
# 字典中的键映射多个值
from collections import defaultdict
d = defaultdict(list)
print(d)
d['a'].append([1, 2, 3])
d['b'].append(2)
d['c'].append(3)
print(d)
d = defaultdict(set)
print(d)
d['a'].add(1)
d['a'].add(2)
d['b'].add(4)
print(d)
输出结果如下:
defaultdict(<class 'list'>, {})
defaultdict(<class 'list'>, {'a': [[1, 2, 3]], 'b': [2], 'c': [3]})
defaultdict(<class 'set'>, {})
defaultdict(<class 'set'>, {'a': {1, 2}, 'b': {4}})
用途:创建命名字段的元组。工厂函数
namedtuple主要用来产生可以使用名称来访问元素的数据对象,通常用来增强代码的可读性, 在访问一些tuple类型的数据时尤其好用。
比如我们用户拥有一个这样的数据结构,每一个对象是拥有三个元素的tuple。使用namedtuple方法就可以方便的通过tuple来生成可读性更高也更好用的数据结构。
from collections import namedtuple
websites = [
('Sohu', 'http://www.sohu.com/', u'张朝阳'),
('Sina', 'http://www.sina.com.cn/', u'王志东'),
('163', 'http://www.163.com/', u'丁磊')
]
Website = namedtuple('Website', ['name', 'url', 'founder'])
for website in websites:
website = Website._make(website)
print website
# 输出结果:
Website(name='Sohu', url='http://www.sohu.com/', founder=u'\u5f20\u671d\u9633')
Website(name='Sina', url='http://www.sina.com.cn/', founder=u'\u738b\u5fd7\u4e1c')
Website(name='163', url='http://www.163.com/', founder=u'\u4e01\u78ca')
注意,namedtuple是函数,不是类。
用途:统计可哈希的对象。父类为Python内置的dict
寻找序列中出现次数最多的元素。假设你有一个单词列表并且想找出哪个单词出现频率最高:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = 'liao gao xiang'
from collections import Counter
words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
]
word_counts = Counter(words)
# 出现频率最高的三个单词
top_three = word_counts.most_common(3)
print(top_three)
# Outputs [('eyes', 8), ('the', 5), ('look', 4)]
print(word_counts['eyes'])
morewords = ['why', 'are', 'you', 'not', 'looking', 'in', 'my', 'eyes']
# 如果你想手动增加计数,可以简单的用加法:
for word in morewords:
print(word)
word_counts[word] += 1
print(word_counts['eyes'])
结果如下:
[('eyes', 8), ('the', 5), ('look', 4)]
8
why
are
you
not
looking
in
my
eyes
9
因为Counter继承自dict,所有dict有的方法它都有(defaultdict和OrderedDict也是的),Counter自己实现或重写了6个方法:
most_common(self, n=None)
,elements(self)
fromkeys(cls, iterable, v=None)
update(*args, **kwds)
subtract(*args, **kwds)
copy(self)
用途:排序的字段。父类为Python内置的dict
OrderedDict在迭代操作的时候会保持元素被插入时的顺序,OrderedDict内部维护着一个根据键插入顺序排序的双向链表。每次当一个新的元素插入进来的时候,它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。
需要注意的是,一个OrderedDict的大小是一个普通字典的两倍,因为它内部维护着另外一个链表。 所以如果你要构建一个需要大量OrderedDict 实例的数据结构的时候(比如读取100,000行CSV数据到一个 OrderedDict 列表中去),那么你就得仔细权衡一下是否使用 OrderedDict带来的好处要大过额外内存消耗的影响。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = 'liao gao xiang'
from collections import OrderedDict
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
# d['bar'] = 22 #对于一个已经存在的键,重复赋值不会改变键的顺序
for key in d:
print(key, d[key])
print(d)
import json
print(json.dumps(d))
结果如下:
foo 1
bar 2
spam 3
grok 4
OrderedDict([('foo', 1), ('bar', 2), ('spam', 3), ('grok', 4)])
{"foo": 1, "bar": 2, "spam": 3, "grok": 4}
OrderDict实现或重写了如下方法。都是干嘛的?这个留给大家当课后作业了^_^
clear(self)
popitem(self, last=True)
move_to_end(self, key, last=True)
keys(self)
items(self)
values(self)
pop(self, key, default=__marker)
setdefault(self, key, default=None)
copy(self)
fromkeys(cls, iterable, value=None)
用途:创建多个可迭代对象的集合。类字典类型
很简单,如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = 'liao gao xiang'
from collections import ChainMap
from itertools import chain
# 不同集合上元素的迭代
a = [1, 2, 3, 4]
b = ('x', 'y', 'z')
c = {1, 'a'}
# 方法一,使用chain
for i in chain(a, b, c):
print(i)
print('--------------')
# 方法二,使用chainmap
for j in ChainMap(a, b, c):
print(j)
# 这两种均为节省内存,效率更高的迭代方式
一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。然后,这些字典并不是真的合并在一起了,ChainMap 类只是在内部创建了一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的,比如:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __author__ = 'liao gao xiang'
# 合并多个字典和映射
a = {'x': 1, 'z': 3}
b = {'y': 2, 'z': 4}
# 现在假设你必须在两个字典中执行查找操作
# (比如先从 a 中找,如果找不到再在 b 中找)。
# 一个非常简单的解决方案就是使用collections模块中的ChainMap类
from collections import ChainMap
c = ChainMap(a, b)
print(c)
a['x'] = 11 # 使用ChainMap时,原字典做了更新,这种更新会合并到新的字典中去
print(c) # 按顺序合并两个字典
print(c['x'])
print(c['y'])
print(c['z'])
# 对于字典的更新或删除操作影响的总是列中的第一个字典。
c['z'] = 10
c['w'] = 40
del c['x']
print(a)
# del c['y']将出现报错
# ChainMap对于编程语言中的作用范围变量(比如globals,locals等)
# 是非常有用的。事实上,有一些方法可以使它变得简单:
values = ChainMap() # 默认会创建一个空字典
print('\t', values)
values['x'] = 1
values = values.new_child() # 添加一个空字典
values['x'] = 2
values = values.new_child()
values['x'] = 30
# values = values.new_child()
print(values, values['x']) # values['x']输出最后一次添加的值
values = values.parents # 删除上一次添加的字典
print(values['x'])
values = values.parents
print(values)
a = {'x': 1, 'y': 2}
b = {'y': 2, 'z': 3}
merge = dict(b)
merge.update(a)
print(merge['x'], merge['y'], merge['z'])
a['x'] = 11
print(merge['x'])
输出结果如下:
ChainMap({'x': 1, 'z': 3}, {'y': 2, 'z': 4})
ChainMap({'x': 11, 'z': 3}, {'y': 2, 'z': 4})
11
2
3
{'z': 10, 'w': 40}
ChainMap({})
ChainMap({'x': 30}, {'x': 2}, {'x': 1}) 30
2
ChainMap({'x': 1})
1 2 3
1
作为ChainMap的替代,你可能会考虑使用 update() 方法将两个字典合并。这样也能行得通,但是它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。同时,如果原字典做了更新,这种改变不会反应到新的合并字典中去。
ChainMap实现或重写了如下方法:
get(self, key, default=None)
fromkeys(cls, iterable, *args)
copy(self)
new_child(self, m=None)
parents(self)
popitem(self)
pop(self, key, *args)
clear(self)
这三个类是分别对 dict、list、str 三种数据类型的包装,其主要是为方便用户实现自己的数据类型。在 Python2 之前,这三个类分别位于 UserDict、UserList、UserString 三个模块中,需要用类似于 from UserDict import UserDict
的方式导入。在 Python3 之后则被挪到了 collections 模块中。这三个类都是基类,如果用户要扩展这三种类型,只需继承这三个类即可。
摘自:https://www.cnblogs.com/xzkzzz/p/11378842.html
在 Python 中定义函数非常简单,像这样:
def say(name): return f'Hello {name}!'
但是,有时候也会看到这样的代码:
def say_hi(name: str) -> str: return f'Hello {name}!'
函数定义似乎变得复杂些了:多出来这些 str
、 ->
都是什么意思?有什么作用?
本文将由浅入深,好好聊聊 Python 3.5 之后的类型注解。理解它将非常有益于优化你的代码。
Python 是动态语言,其显著特点是在声明变量时,你不需要显式声明它的类型。
比如这个:
age = 20 print('The age is: ', age + 1) # Output: # The age is: 21
你看,虽然代码里没有明确指定 age
的类型,但是程序运行时隐式推断出它是 int
类型,因此可以顺利执行 age + 1
的动作。
除此之外,已经确定类型的变量,可以随时更改其类型,比如:
age = 20 print(type(age)) # Output: <class 'int'> age = '20' print(type(age)) # Output: <class 'str'>
Python 这种动态特性的好处是它非常的自由,大部分时候你不用纠结类型声明、类型转化等麻烦事,可以用很少的代码完成各种骚操作。但是缺点也在这里:如果你代码某些变量的类型有错,编辑器、IDE等工具无法在早期替你纠错,只能在程序运行阶段才能够暴露问题。
比如下面这个例子:
age = 20 # ... # 这里进行了一大串的其他指令 # 然后你忘记了 age 应该是 int # 错误地将其赋值为字符串 age = '20' print('The age is: ', age + 1) # Output: TypeError: can only concatenate str (not "int") to str
在项目代码逐渐膨胀之后,上面这种看似弱智的情况可能会经常发生。
因此,Python 3.5 之后引入了类型注解,其作用就是让你可以明确的声明变量的类型,使代码不再那么的自由(放飞自我)。
类型注解还在快速发展中,因此尽量用较新的 Python 版本去尝试它。
比如上面的代码,就可以用类型注解改写了:
age: int = 20
看似多此一举,但是编辑器可以凭借此,找出你那些错误的骚操作。
比如笔者用的 VS Code,安装好类型注解插件 Pylance 后,如果写出下面的代码:
age: int = 20 age = '20'
那么编辑器会用醒目的方式告诉你:孙子,你这里的类型写错了!(见下图)
Pylance 默认关闭了类型检查,你得在设置中手动打开。其他的编辑器/IDE(比如 Pycharm)也都提供了类似的类型检查,放心用吧。
很简单,但却带来了巨大的好处:
注意,类型注解仅仅是提供给编辑器进行类型检查的机会,也就是起提示的作用,对 Python 程序的运行不会产生任何影响。也就是说,Python 跟以前一样自由,即使你进行了错误的类型赋值,只要不直接引发错误,程序依旧可以运行。
最后,Python 中几种基本的变量类型都得到了支持:
a: int = 3 b: float = 3.14 c: str = 'abc' d: bool = False
很简单吧。让我们继续。
文章开头提到的那个例子,就是简单的函数类型注解:
def say_hi(name: str) -> str: return f'Hello {name}!'
你可以很清楚的知道,这个函数应该接收一个字符串参数 name
,并且返回值应该也是字符串。
带默认值的函数像这样书写:
def add(first: int = 10, second: float = 5.5) -> float: return first + second
如果函数没有返回值,那么下面两种写法都可以:
def foo(): pass def bar() -> None: pass
自定义的对象也没问题,像下面这样:
class Person: def __init__(self, name: str): self.name = name def hello(p: Person) -> str: return f'Hello, {p.name}'
如果要避免循环导入或者注解早于对象定义的情况,可以用字符串代替类型:
def hello(p: 'Person') -> str: return f'Hello, {p.name}' class Person: def __init__(self, name: str): self.name = name
效果是相同的。
相比变量类型注解,函数里的类型注解更加有用,并且可能是你最频繁用到注解的地方了。
列表、字典、元组等包含元素的复合类型,用简单的 list,dict,tuple 不能够明确说明内部元素的具体类型。
因此要用到 typing
模块提供的复合注解功能:
from typing import List, Dict, Tuple # 参数1: 元素为 int 的列表 # 参数2: 键为字符串,值为 int 的字典 # 返回值: 包含两个元素的元组 def mix(scores: List[int], ages: Dict[str, int]) -> Tuple[int, int]: return (0, 0)
如果你用的是 Python 3.9+ 版本,甚至连 typing
模块都不需要了,内置的容器类型就支持了复合注解:
def mix(scores: list[int], ages: dict[str, int]) -> tuple[int, int]: return (0, 0)
在某些情况下,不需要严格区分参数到底是列表还是元组(这种情况还蛮多的)。这时候就可以将它们的特征抽象为更泛化的类型(泛型),比如 Sequence(序列)。
下面是例子:
# Python 3.8 之前的版本 from typing import Sequence as Seq1 def foo(seq: Seq1[str]): for item in seq: print(item) # Python 3.9+ 也可以这么写 from collections.abc import Sequence as Seq2 def bar(seq: Seq2[str]): for item in seq: print(item)
例子中函数的参数不对容器的类型做具体要求,只要它是个序列(比如列表和元组)就可以。
有时候对象的类型可能会非常复杂,或者你希望给类型赋予一个有意义的名称,那么你可以这样定义类型的别名:
from typing import Tuple # 类型别名 Vector2D = Tuple[int, int] def foo(vector: Vector2D): print(vector) foo(vector=(1, 2)) # Output: (1, 2)
Vector2D
这个名称清晰的表达了这个对象是一个二维的向量。
与类型别名有点类似的,是用 NewType
创建自定义类型:
from typing import NewType from typing import Tuple # 创建新类型 Vector3D = NewType('Vector3D', Tuple[int, int, int]) def bar(vector: Vector3D): print(vector)
乍一眼看起来与前面的类型别名功能差不多,但不同的是 NewType
创建了原始类型的“子类”:
# 类型检查成功 # 类型别名和原始类型是等价的 foo(vector=(1, 2)) # 类型检查失败 # NewType创建的是原始类型的“子类” bar(vector=(1, 2, 3)) # 类型检查成功 # 传入参数必须是 Vector3D 的“实例” v_3d = Vector3D((4, 5, 6)) bar(vector=v_3d)
具体用哪种,得根据情况而定。
如果函数没有返回值,那么可以这样写:
from typing import NoReturn def hello() -> NoReturn: raise RuntimeError('oh no')
注意下面这样写是错误的:
def hello() -> NoReturn: pass
因为 Python 的函数运行结束时隐式返回 None
,这和真正的无返回值是有区别的。
猜猜下面的类型注解错在哪里:
def foo(a: int = 0) -> str: if a == 1: return 'Yeah'
答案:函数既有可能返回 None
,也有可能返回 str
。单凭返回值注解为 str
是不能准确表达此情况的。
这种“可能有也可能没有”的状态被称为可选值,在某些项目中非常常见。比如 web 应用中某个函数接受账号和密码作为参数,如果匹配则返回用户对象,若不匹配则返回 None
。
因此,有专门的可选值类型注解可以处理这种情况:
from typing import Optional def foo(a: int = 0) -> Optional[str]: if a == 1: return 'Yeah'
比 Optional
涵盖面更广的是 Union
。
如果函数的返回值是多种类型中的一种时,可以这样写:
from typing import Union def foo() -> Union[str, int, float]: # .... # some code here
上面这个函数可以返回字符串、整型、浮点型中的任意一种类型。
可以发现 Optional
实际上是 Union
的特例:Optional[X]
和 Union[X, None]
是等价的。
我们知道, Python 中的函数和类的区别并不明显。只要实现了对应的接口,类实例也可以是可调用的。
如果不关心对象的具体类型,只要求是可调用的,那么可以这样写:
from typing import Callable class Foo: def __call__(self): print('I am foo') def bar(): print('I am bar') def hello(a: Callable): a() # 类型检查通过 hello(Foo()) # 同样通过 hello(bar)
即字面量。它在定义简单的枚举值时非常好用,比如:
from typing import Literal MODE = Literal['r', 'rb', 'w', 'wb'] def open_helper(file: str, mode: MODE) -> str: ... open_helper('/some/path', 'r') # 成功 open_helper('/other/path', 'typo') # 失败
即协议。我们通常说一个对象遵守了某个”协议“,意思是这个对象实现了”协议“中规定的属性或者方法。
比如下面这个例子:
from typing import Protocol class Proto(Protocol): def foo(self): print('I am proto') class A: def foo(self): print('I am A') class B: def bar(self): print('I am B') def yeah(a: Proto): pass # 通过,A 实现了协议中的 foo() 方法 yeah(A()) # 不通过,B 未实现 foo() yeah(B())
如果你实在不知道某个类型注解应该怎么写时,这里还有个最后的逃生通道:
from typing import Any def foo() -> Any: pass
任何类型都与 Any
兼容。当然如果你把所有的类型都注解为 Any
将毫无意义,因此 Any
应当尽量少使用。
要理解泛型,首先得知道没有它时所遇到的麻烦。
假设有一个函数,要求它既能够处理字符串,又能够处理数字。那么你可能很自然地想到了 Union
。
于是写出第一版代码:
from typing import Union, List U = Union[str, int] def foo(a: U, b: U) -> List[U]: return [a, b]
这样写有个很大的弊端,就是参数的类型可以混着用(比如 a: int
且 b:str
),即便你并不想具有这样的特性。看下面这个就明白了:
# 类型检查通过 # 因为 Union[str, int] 可以是其中任意一种类型 # 即便你并不想将 str 和 int 混用 foo('Joe', 19) # 通过 foo(19, 21) # 通过 foo('Joe', 'David')
泛型就可以解决此问题。来看第二版代码:
from typing import TypeVar, List # 定义泛型 T # T 必须是 str 或 int 其中一种 T = TypeVar('T', str, int) def bar(a: T, b: T) -> List[T]: return [a, b] # 类型检查不通过 # 函数的参数必须为同一个类型"T" bar('Joe', 19) # 通过 bar(19, 21) # 通过 bar('Joe', 'David')
可以看出,泛型类似于某种模板(或者占位符),它可以很精确地将对象限定在你真正需要的类型。
让我们再看看下面这个对泛型的应用:
from typing import Dict, TypeVar # 定义泛型 K 和 V # K 和 V 的具体类型没有限制 K = TypeVar("K") V = TypeVar("V") def get_item(key: K, container: Dict[K, V]) -> V: return container[key] dict_1 = {"age": 10} dict_2 = {99: "dusai"} print(get_item("age", dict_1)) # 例1 # 类型检查通过,输出: 10 print(get_item(99, dict_2)) # 例2 # 类型检查通过,输出: dusai print(get_item("name", dict_2)) # 例3 # 类型检查失败 # 因为"name"是字符串,而dict_2的键为整型
get_item()
接受两个参数。这个函数不关心参数 container
字典的键是什么类型,或者字典的值是什么类型;但它的参数 container
必须是字典,参数 key
必须与字典的键为同类型,并且返回值和字典的值必须为同类型。仅仅通过查看函数的类型注解,就可以获得所有这些信息。重点来看下例3的类型检查为什么会失败:
dict_2
定义时,其键被定义为整型。get_item("name", dict_2)
调用时,"name"
为字符串,而 dict_2
的键为整型,类型不一致。而类型注解中清楚表明它两应该为同一个类型 K
,产生冲突。泛型很巧妙地对类型进行了参数化,同时又保留了函数处理不同类型时的灵活性。
再回过头来看看类型注解的作用:
def get_item(key: K, container: Dict[K, V]) -> V: # ... def get_item(key, container): # ...
上面两个函数功能完全相同,但是没有类型注解的那个,显然需要花更多的时间阅读函数内部的代码,去确认函数到底干了什么。并且它也无法利用编辑器的类型检查,在早期帮助排除一些低级错误。
最后再总结下,Python 社区为什么花了很大力气,去实现了类型注解这个仅仅起”提示作用“的功能: