目标

  1. 补充基础阶段相关内容
  2. 回顾基础系列之数据类型

第一部分

有了前一部分的铺垫,相信大家已经明白变量名只是对内存空间的映射,那么

  1. 共享变量是怎么回事?
  2. 深浅拷贝又是是什么?

1. 赋值与深浅拷贝

🤔 思考如下代码,a、b、c、x四者在x = x + [3, 4]前后的地址是如何变化的?

a = 1
b = 2
c = 1
# print(" a: %s \n b: %s \n c: %s" % (id(a), id(b), id(c)))
# print(" -----")
x = [a, b, c]
# print(" x: %s \n ------ \n x[0]: %s \n x[1]: %s \n x[2]: %s" % (id(x), id(x[0]), id(x[1]), id(x[2])))
x = x + [3, 4]
# print(" -----")
# print(" x: %s \n ------ \n x[0]: %s \n x[1]: %s \n x[2]: %s" % (id(x), id(x[0]), id(x[1]), id(x[2])))
a = 3
# print(" a: %s" % (id(a)))

打印结果如下

a: 4323184656
b: 4323184688
c: 4323184656
-----
x: 4324441928
------
x[0]: 4323184656
x[1]: 4323184688
x[2]: 4323184656
-----
x: 4324441928, y: 4324441928
-----
x: 4326634696
------
x[0]: 4323184656
x[1]: 4323184688
x[2]: 4323184656
a: 4323184720

四种情况

  1. 尽量用+=,-=之类复合符号的,因为无需开辟新的,+=会自动调用 extend 方法进行合并运算, 共享引用;
  2. 【不可变对象】:如打印结果中的开始和结束a,地址是不同的,重新申请地址;
  3. 【可变对象】:无需重新申请地址,但区域可长可短;

2. 浅拷贝

不管多么复杂的数据结构,浅拷贝都只会 copy 一层;如需完整拷贝,请参考深拷贝,还记得这个图么?

浅复制

3. 深拷贝

深拷贝——即 python 的 copy 模块提供的另一个 deepcopy 方法。深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,在这个过程中我们对这两个变量中的一个进行任意修改都不会影响其他变量

深复制

第二部分

1. 打印 Print

日常调试,还是离不开打印的,大概了解下如何打印,有两种:

1. %
2. format

举几个简单的例子

print(" x: %s, y: %s" % (id(x), id(y)))

print("{} {}".format("hello", "world"))
print("{1} {0}".format("hello", "world"))

表格

%s    字符串 (采用str()的显示)
%r 字符串 (采用repr()的显示)
%c 单个字符
%b 二进制整数
%d 十进制整数
%i 十进制整数
%o 八进制整数
%x 十六进制整数
%e 指数 (基底写为e)
%E 指数 (基底写为E)
%f 浮点数
%F 浮点数,与上相同
%g 指数(e)或浮点数 (根据显示长度)
%G 指数(E)或浮点数 (根据显示长度)

第三部分 前期知识汇总

Python基础数据类型

目标

  1. 了解变量名与变量的关系
  2. 理解引用、拷贝、赋值等概念与含义

1 前提概况

我们接触最多的就是变量,究竟变量名变量值 关系是啥?如何存储?

首先看一个最简单的

a = 100

上侧是一个赋值操作,其中a是变量名100是变量的值。编程语言的编译器(或者解释器)处理到这一句代码的时候,一般语言会这样做

  1. 在内存中开辟一个内存空间, 地址0x4377878280;
  2. 存放100进第一步的空间中;
  3. 取值100时,找到地址为0x4377878280即可。

但是
但是
但是

这么长除了计算机能记住,我是记不住。正如ip地址与域名的关系,我们只需要记住taobao.com即可,所以,编译器给我们做了个表,在这个表中,它将内存地址和变量名做了个映射

变量名 内存地址
a 0x4377878280
b 0x4377878281
c 0x4377878282
….

变量名完全可以看成一个内存地址的别名(只是方便我们记忆),真正的数据是存在这个内存地址的存储空间上的。变量名在运行的时候,没有任何用处。

赋值

2. 深入一些

奔着:以问题来解决疑问题,先看这个代码,思考一下结果

>>> values = [0, 1, 2]
>>> values[1] = values

请认真思考此时 values 是多少?
请认真思考此时 values 是多少?
请认真思考此时 values 是多少?

也许你认为是[0, [0, 1, 2], 2],原因可能是:

  1. list 可变;
  2. 把list[1]的位置直接添加一个list;

代码如下,完全符合预期.

>>> a = [1, 2, 3]
>>> a[1] = [1, 2, 3]
>>> a
[1, [1, 2, 3], 3]

但是结果却是 无限循环

>>> values
[0, [...], 2]

❌就❎在第二步中
其中涉及两个概念:引用、拷贝

第一部分已经说过,变量名是内存的别称

a=10的含义:内存中有个地址0x1234567的空间存了10,然后a指向这块内存。

变量多次'赋值‘

上图中很好的证明了,变量‘赋不同值’,只是引用不同而已

values[1] = values只是把自身指向自身。如图:

自身引用自身

如需达到预期效果,只需要类似于你的逻辑,指向一份值相同但内存不同的值

值相同但内存不同

代码如下:

# 拷贝生成一份新的数据,
>>> values[1] = values[:]
>>> values
[0, [0, 1, 2], 2]

往更深处说,values[:] 复制操作是所谓的「浅复制」(shallow copy),当列表对象有嵌套的时候也会产生出乎意料的错误,比如

a = [0, [1, 2], 3]
b = a[:]
a[0] = 8
a[1][1] = 9
# 正确答案
>>> print(a)
[8, [1, 9], 3]
>>> print(b)
[0, [1, 9], 3]
# a 与 b 的地址确实不同
>>> print(id(a))
4355761096
>>> print(id(b))
4355761416
# 但是内部list的地址确实相同的,也就是共同指向了同一块内存地址
>>> print(id(a[1]))
4354260680
>>> print(id(b[1]))
4354260680

看完上图的打印结果,请看下图;

浅复制

重点: values[:] 只是浅层复制
重点: values[:] 只是浅层复制
重点: values[:] 只是浅层复制

正确的复制嵌套元素的方法是进行「深复制」(deep copy),方法是

import copy

a = [0, [1, 2], 3]
b = copy.deepcopy(a)
a[0] = 8
a[1][1] = 9

深复制

3 引用 VS 拷贝:

  1. 没有限制条件的分片表达式(L[:])能够复制序列,但此法只能浅层复制
  2. 字典 copy 方法,D.copy() 能够复制字典,但此法只能浅层复制
  3. 有些内置函数,例如 list,能够生成拷贝 list(L)
  4. copy 标准库模块能够生成完整拷贝:deepcopy 本质上是递归 copy

参考链接

python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域 原

目标

  1. 掌握解析式的使用
  • liet 解析式
  • dict 解析式
  1. 深入理解迭代器
  2. 深入理解生成器

第一部分 迭代器、生成器

参考完全理解Python迭代对象、迭代器、生成器, 完整实例请看原文。

在了解Python的数据结构时,容器(container)可迭代对象(iterable)迭代器(iterator)生成器(generator)列表/集合/字典推导式(list,set,dict comprehension)众多概念参杂在一起,难免让初学者一头雾水,我将用一篇文章试图将这些概念以及它们之间的关系捋清楚。

关系图

请仔细看上图
请仔细看上图
请仔细看上图

1 容器(container)

像列表(list)、集合(set)、序列(tuple)、字典(dict)都是容器。简单的说,容器是一种把多个元素组织在一起的数据结构, 【可以逐个迭代获取其中的元素。】但是,但是,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。

'a' in {'a', 'b', 'c'} # 输出 True
'a' in {'a': 1, 'b': 2} # 输出 True
'a' in set(['a', 'b', 'c']) # 输出 True

2 可迭代对象(iterable)

凡是可以返回一个迭代器的对象都可称之为可迭代对象

>>> x = [1, 2, 3]
>>> y = iter(x)
>>> z = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(z)
1
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>

这里x是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象
y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。
迭代器有一种具体的迭代器类型,比如list_iterator,set_iterator。
可迭代对象实现了iter方法,该方法返回一个迭代器对象。

3 迭代器(iterator)

那么什么迭代器呢?它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了iternext()(python2中实现next())方法的对象都是迭代器
iter返回迭代器自身,
next返回容器中的下一个值,
如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。

所以,迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。

迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。

>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'

4 生成器(generator)

生成器算得上是Python语言中最吸引人的特性之一生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。
它不需要再像上面的类一样写iter()和next()方法了,只需要一个yiled关键字
生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。用生成器来实现斐波那契数列的例子是:

def fib():
prev, curr = 0, 1
while True:
yield curr
prev, curr = curr, curr + prev

>>> f = fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

说实在的没看懂,后续章节补充这个。。。

总结:

  1. 容器是一系列元素的集合,str、list、set、dict、file、sockets对象都可以看作是容器
    容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。
  2. 可迭代对象实现了iter方法,该方法返回一个迭代器对象。
  3. 迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了nextiter方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。
  4. 生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield。

第二部分 解析式

1 什么是列表解析式?

列表解析式是将一个列表(实际上适用于任何可迭代对象(iterable))转换成另一个列表的工具。在转换过程中,可以指定元素必须符合一定的条件,才能添加至新的列表中,这样每个元素都可以按需要进行转换。参考

如果看过图就明白了,就结束啦

2 Show Code

需求: 遍历数组,将满足条件的数据放入新的数组中

单层嵌套

# 一般的写法
numbers = [1, 2, 3, 4, 5]

doubled_odds = []
for n in numbers:
if n % 2 == 1:
doubled_odds.append(n)

*解析式表达如下,分三部分

doubled_odds = [
n,
for n in numbers
f n % 2 == 1
]

说明

基本语法: [ expr for item in iterable judge ]

1. expr 待操作元素
2. for item in iterable 循环语句【可嵌套,从后往前】
3. judge 判断条件 【可选】

双层嵌套

tmpList = [ [1, 2, 3], [4, 5, 6]  ]
newList =[
n
# 先后再前
for inNum in inList for inList in tmpList
# 判断语句可选
]

参照list解析式,记住语法,其实也不难

flipped = {
value: key
for key, value in original.items()
}

目标

  1. 掌握字典的常用操作
  2. 掌握集合的常用操作

整体

第一部分 字典

字典就是一个个的键值对 {key: value},实现原理:根据key 计算 hash, 速度快。
注意事项

  1. key: 任意不可变对象,但不能是list;
  2. value: 可以是任意对象。

字典是有序的

思考:为何字典的key不可以是list类型?

1. 三种创建方式

dic1 = {"sex": 1}
dic2 = dict([('name', 'simuty'), ("age", 1)])
dic3 = dict(age=2, name='python', sex=1)
print(dic1)
print(dic2)
print(dic3)

'''
{'sex': 1}
{'name': 'simuty'}
{'age': 2}
'''

2. 删除指定元素

del dic3['age']
dic3.pop('name')
print(dic3)

# 清空字典
dic3.clear()
print(dic3)

del dic3
print(dic3)

'''
{'age': 2, 'name': 'python', 'sex': 1}
{'sex': 1}
{}
Traceback (most recent call last):
File "dic.py", line 28, in <module>
print(dic3)
NameError: name 'dic3' is not defined
'''

3. 合并


# 合并
a_dic = {"k": 1}
b_dic = {'k': 2, "x": 3}
a_dic.update(b_dic)
print(a_dic)

# 删除最后一个并返回
print(b_dic.popitem())
print(b_dic)

dic = {'k': 2, "x": 3}
# 接遍历字典获取键,根据键取值
for key in dic:
print(key, dic[key])

字典

第二部分 集合

set集合是一个无序不重复元素的集,基本功能包括关系测试和消除重复元素。

集合使用大括号({})框定元素,并以逗号进行分隔。
创建一个空集合,必须用 set() 而不是 {} ,因为后者创建的是一个空字典。

1. 集合操作

set1 = {1, 2, 3}
set2 = {5, 4, 3}

print(set1 ^ set2)
print(set1 - set2)
print(set2 - set1)
print(set1 & set2)
print(set1 | set2)

a = {1, 2, 3, 4}
print(a)
b = a.add(5)
print(b)

实例

set

目标

  1. 序列对象有整体的认识;
  2. 掌握list的常用操作
  3. 掌握tuple的常用操作
  4. 理解深浅拷贝问题

第一部分 序列对象

1. 了解序列对象

Python一切皆对象。

  1. 序列对象包含str、list、tuple
  2. 序列成员属性:有序,可通过下标访问

先大致预览下列表(list)、元组(tuple)的异同点,后续详细梳理

差异点 列表 元组
表示方法 空列表:[]
单元素列表:[1]
多元素列表[1, ‘a’]
空元组:()
单元素元组:(1,)
多元素元组:(1, ‘a’)
可变性 可变 不可变
可操作性 支持丰富的操作 仅支持序列操作
可哈希性 不可哈希,不能作为字典的关键字 可哈希,可以作为字典的关键字

整体图

第二部分 List

Python的列表是一个有序可重复的元素集合,可嵌套、迭代、修改、分片、追加、删除,成员判断。
从数据结构角度看,Python的列表是一个可变长度的顺序存储结构,每一个位置存放的都是对象的指针

比如,对于这个列表 alist = [1, “a”, [11,22], {“k1”:”v1”}],其在内存内的存储方式是这样的:

内存地址

1. 什么是list?

概念: 逗号分隔的不同的数据项使用方括号括起来即是list;
性质: list元素可变,改变的是原对象

>>> tmpList = []  # 创建一个空列表
>>> numList = [1, 2, 3, 4]
>>> allList = [1, 'a', [11,22], {'k1':'v1'}]

2. 增删改

以xmind的方式呈现

第三部分 Tuple

类似于list的对象,

类似于list, 相对不可更改

重要的实例

alist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# stop_index 逻辑上要大于 start_index
# 正整数stop_index>start_index
# 负数:stop_index<start_index & step<0

print(alist[1:8:3])
# 全部,正向取值
print(alist[:])
# 全部,反向取值
print(alist[-1:-11:-1])
# stop_index > len(list), 不报错
print(alist[-1:-12:-1])

类似于list, 相对不可更改

参考:廖雪峰老师博客–使用list和tuple的最后来看一个“可变的”tuple:

>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个tuple定义的时候有3个元素,分别是’a’,’b’和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?

别急,我们先看看定义的时候tuple包含的3个元素:

初始化

当我们把list的元素’A’和’B’修改为’X’和’Y’后,tuple变为:

改变

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向’a’,就不能改成指向’b’,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

绝对不变的元组:必须保证tuple的每一个元素本身也不能变。

第三部分 深浅拷贝

对列表和元组进行拷贝时,默认进行的是 浅拷贝只拷贝成员对象的引用,而不会拷贝引用指向的成员对象本身。借助于 copy模块 的deepcopy方法,可以实现深拷贝

深拷贝:既拷贝成员对象的引用,又会拷贝引用指向的成员对象本身。
浅拷贝:只拷贝成员对象的引用,而不会拷贝引用指向的成员对象本身

值得注意的是,上述说法并不完全正确如果成员对象本身是原子类型的(数值、字符串,或者只包含数值或字符串的元组),那么对该成员不会发生真正的深拷贝,即便执行深拷贝动作,内部也只会进行浅拷贝。

浅拷贝和深拷贝的示意图如下:

关于浅拷贝和深拷贝的实际案例,可以参考 《Python核心编程(第二版)》 中的 『6.20』 一节:『*拷贝Python对象、浅拷贝和深拷贝』。

参考链接

使用list和tuple

列表

Python基础:序列(列表、元组)

字符串是编程必然面临的类型,常用指数之高,不得不先了解下Python中字符串的概念与常用方法

第一部分 基础

1. 不可变类型

作为不可变类型,主要涉及两个概念赋值、引用,先看三种定义字符串的方法

// 第一种
var1 = 'Hello world'
// 第二种
var1 = "Pyhoth"
// 第三种
var2 = """
>>> a = "asd"
>>> id(a)
4431000496
>>> a = "122"
>>> id(a)
4431000552
"""

上边的代码涉及个问题:

1.1 为何说是不可变类型,怎么var1还可以等于Python?

Python 没有赋值,只有引用。可以从代码中第三种可以看出,同样的字符串a, 内存地址是不一样的。原因在于,第二次的所谓赋值,其实是重新创建一块内存,然后将a指向新内存地址

第二部分 内建函数

1 填充

var3 = "1234"
# 填充字符串
print(var3.center(10, "*"))
print(var3.ljust(10, '^'))
print(var3.rjust(10, "^"))
print(var3.zfill(10))

# 返回值
'''
***1234***
1234^^^^^^
^^^^^^1234
0000001234
'''

2 删减

var4 = "55785"
print(var4.strip("5"))
print(var4.lstrip("5"))
print(var4.rstrip("5"))

返回值

‘’’
78
785
5578
‘’’

3 变形

var5 = "thank yoU"
print(var5.lower())
print(var5.upper())
print(var5.capitalize())
print(var5.swapcase())
print(var5.title())

'''
thank you
THANK YOU
Thank you
THANK YOu
Thank You
'''

4 切分

var6 = "7890"
# 有点像 find()和 split()的结合体,从 str 出现的第一个位置起,把 字 符 串 string 分 成 一 个 3 元 素 的 元 组 (string_pre_str,str,string_post_str),如果 string 中不包含str 则 string_pre_str == string.
print(var6.partition('9'))
print(var6.partition('2'))
print(var6.rpartition("0"))
var7 = "abz\nzxy"
print(var7.splitlines())
print(var7.split("z"))
print(var7.rsplit("z"))

返回值

('78', '9', '0')
('7890', '', '')
('789', '0', '')
['abz', 'zxy']
['ab', '\n', 'xy']
['ab', '\n', 'xy']

5 连接

var8 = "ikaf"
print(var8.join("0000"))

# 判定
var9 = "kj45"
# 长度>0 && 都是字母或都是数字 true 否则false
print(var9.isalnum())
# 长度>0 && 都是字母 true 否则false
print(var9.isalpha())
print(var9.isdigit())
print(var9.islower())
print(var9.isupper())
print(var9.isspace())
print(var9.istitle())
print(var9.startswith('k'))
print(var9.endswith('5'))

6 查找

var10 = "1234567890zxc123vbndfgh"
print(var10.count('123', 0, len(var10)))
# 返回第一个满足条件的位置
print(var10.find('3', 0, len(var10)))
#
print(var10.index('3', 0, len(var10)))
# 找不到返回-1
print(var10.rfind('mm', 0, len(var10)))
# 找不到报错
# print(var10.rindex('mm', 0, len(var10)))

返回值

2
2
2
-1

7 替换

var11 = "aaaa111222hhhjjjkkk"
print(var11.replace("a", "b", 2))
# print(var11.translate())
# translate(table[,deletechars])

8 编码解码

编码就是将字符串转换成字节码,涉及到字符串的内部表示。
解码就是将字节码转换为字符串,将比特位显示成字符。

var12 = "什么鬼"
print(var12.encode())
print(var12.encode().decode())

'''
b'\xe4\xbb\x80\xe4\xb9\x88\xe9\xac\xbc'
什么鬼
'''

总览 Python 数据类型

目标

    1. 了解Python有几种数据类型
    1. 认识Python的数字类型
    1. 重点掌握对数字类型的操作

Python数据类型

第一部分 数字类型

数字类型用于存储数学意义上的数值。

Python 支持三种不同的数字类型,整数、浮点数和复数

1. 整数

定义:正或负整数,不带小数点的数

Python 的整数长度为 32 位,并且通常是连续分配内存空间的。

>>> id(0)
4361838576
>>> id(1)
4361838608
>>>

小整数对象池

Python 初始化的时候会自动建立一个小整数对象池,方便我们调用,避免后期重复生成!这是一个包含262 个指向整数对象的指针数组,范围是-5 到 256

也就是说比如整数 10,即使我们在程序里没有创建它,其实在 Python 后台已经悄悄为我们创建了。

看下边的 id(-6)与 id(257)的地址,4364640112

>>> id(-6)
4364640112
>>> id(-5)
4361838416
>>> id(255)
4361846736
>>> id(256)
4361846768
>>> id(257)
4364640112
为什么存在小整数对象池?

像懒加载一样,创建一个一直存在,永不销毁,随用随拿的小整数对象池,减小开支。

整数缓冲区

刚被删除的整数,不会被真正立刻删除回收,而是在后台缓冲一段时间,等待下一次的可能调用。

>>> a = 100
>>> id(a)
4401859696
>>> del a
>>> b = 100
>>> id(b)
4401859696
>>>

把 a 删了,然后创建一个 b,地址是一样的。

2. 浮点数

浮点数也就是小数
对于很大或很小的浮点数,一般用科学计数法表示,把10用e替代,1.23x10^9就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5,等等。

3. 复数

复数由实数部分和虚数部分构成,可以用a + bj,或者complex(a,b)表示,复数的实部a和虚部b都是浮点型。关于复数,不做科学计算或其它特殊需要,通常很难遇到。

第二部分 操作数字类型

数字类型操作

1. 简单常用的计算

import math
# print(dir(math))
# print(abs(-10))
# print(cmp(1, 3))
# print(math.exp(2))
# print(math.ceil(1.2), math.ceil(-1.2))
# print(math.floor(1.2))
# print(max([12, 1]))

2. 随机操作

主要还没涉及更复杂的计算,只是罗列一些可能常用的方法;
真值分部相关的操作,如正态分部啥的,因为暂无需求,

我希望: 有朝一日能用的上那些高大上的。

# coding=utf-8
import random

print("---基本---")
# 基本
# 返回当前生成器的内部状态
print(random.getstate())
# 不大于K位的Python整数,结果是0~2^10之间的整数
print(random.getrandbits(10))

print("---整数---")
# 整数
# 0-9的整数:
print(random.randrange(10))
# 0-100的偶数
print(random.randrange(0, 101, 2))
# 返回 a <= N <= b, 等同于randrange(a, b+1)。
print(random.randint(1, 9))

print("---list---")
# list
# 从序列随机选择一个元素
print(random.choice(['python', 'node', '种地']))


print("---真值分布---")
# 随机浮点数: 0.0 <= x < 1.0
print(random.random())
# 随机浮点数: 1.1 <= x < 11.1
print(random.uniform(1.1, 11.1))


# 对序列进行洗牌,改变原序列
deck = 'one two three four'.split()
random.shuffle(deck)
print(deck)

还记得之前说,NodeJs 拥有丰富的第三方模块吗?

如何利用现有的模块,快速、高效的实现需求呢?

第一部分 模块管理工具

几个关键字

1. nvm:主要管理nodejs版本;
2. npm:nodejs自带模块管理工具;

1. NPM 是什么?

npm 之于 Node.js ,就像 pip 之于 Python, gem 之于 Ruby, pear 之于 PHP 。

NPM 官网给出解释如下:

  1. npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。
  2. 它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。

npm 由三个独立的部分组成:

  1. 网站:开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径;
  2. 注册表(registry):巨大的数据库,保存了每个包(package)的信息;
  3. 命令行工具 (CLI): 开发者通过 CLI 与 npm 打交道。

总之一句话,想简单高效的完成任务么?NPM 你值得拥有

接下来主要介绍使用

至于自我创建模块,暂不讨论

2. 安装配置

NodeJs 自带 npm, 故无需安装。
罗列几个常用的命令

# 查看 npm 命令列表
$ npm help

# 查看各个命令的简单用法
$ npm -l

# 查看 npm 的版本
$ npm -v

# 查看 npm 的配置
$ npm config list -l

$ npm list

# 加上 global 参数,会列出全局安装的模块
$ npm list -global

# 搜索模块
$ npm search <关键字>

# 本地安装
$ npm install <package name>

# 全局安装
$ sudo npm install -global <package name>
$ sudo npm install -g <package name>
实例一 眼见为实

下载一个常用的框架—ExpressJs,借此展示一下效果,并熟悉一个 ExpressJs

$ mkdir first
$ cd first
$ npm install express
$ node app.js
Example app listening on port 3000!

效果如图

实例二 初识 package.json 文件

简单说就是,记录文件,记录项目依赖的模块。

# 初始化一个package.json文件
$ npm init

实例说明

{
// 名称
"name": "npm",
// 版本
"version": "1.0.0",
// 描述
"description": "study npm",
// 入口文件
"main": "app.js",
// 模块依赖
"dependencies": {
"express": "^4.16.4"
},
"devDependencies": {},
// 执行脚本
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
// 关键词
"keywords": [
"npm"
],
// 作者
"author": "howard",
// 协议
"license": "ISC"
}

如我们需要下载ExpressJs,

# 一下命令即可将Express依赖加入到上边的文件中,偷个懒,不在重负写了
$ npm install express --save

实例三 更改npm源

可能你发现下载东西简直如龟速,这个你得感谢防火墙之父

为了更快更好的完成工作,我们可以用国内的淘宝、清华等源;

淘宝镜像说明:这是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10分钟 一次以保证尽量与官方服务同步。

$ npm install -g cnpm --registry=https://registry.npm.taobao.org

使用方法类似,用cnpm即可

$ cnpm install [name]

第二部分

Node.js 最大的特点就是异步式 I/O(或者非阻塞I/O)与事件紧密结合的编程模式。

1.阻塞I/O与非阻塞I/O概念

1.1 阻塞I/O(同步I/O)

线程在执行中如果遇到磁盘读写或网络通信(统称为I/O 操作), 通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为阻塞,当I/O 操作完成时,操作系统将这个线程的阻塞解除,恢复其对CPU的控制 ,令其继续运行。这种 I/O 模式就是通常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。

1.2 非阻塞I/O(异步I/O)

非阻塞I/O是针对所有的I/O不采用阻塞的策略, 当线程遇到I/O 操作时, 不会阻塞等待完成, 而是将I/O 操作发送给操作系统, 继续执行下一个语句, 等操作系统完成I/O 操作以后, 会以事件的形式发送通知执行I/O 操作的线程, 线程会在特定的时候处理这个事件; 也就是线程中会不停的监听时间循环, 看是否有未处理的事件, 并以此处理.

举个通俗的例子:
例子来自知乎网友

你打电话问书店老板有没有《分布式系统》这本书,如果是`同步通信机制`,书店老板会说,你稍等,”我查一下",
然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。

而`异步通信机制`,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。
然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
2.阻塞I/O与非阻塞I/O特点
同步I/O(阻塞式) 异步I/O(非阻塞式)
利用多线程提供吞吐量 单线程可实现高吞吐量
通过事件分割和线程调度利用多核CPU 通过功能划分利用多核CPU
需要由操作系统调度多线程使用多核CPU 可以将单进程绑定到单核 CPU
难以充分利用 CPU 资源 可以充分利用 CPU 资源
符合线性的编程思维 不符合传统编程思维

对比

第二部分 异步式编程(函数式编程)

有异步I/O,就必然有异步编程。
首先以同步式编程的语法读取一个文件:

创建readfilesync.js, 并执行

var fs = require('fs');
var data = fs.readFileSync('/Users/51testing/Desktop/file.txt', 'utf-8');
console.log(data);
console.log('end.');

打印的结果如下:

//执行
$ node /Users/51testing/Desktop/readfilesync.js
你好呀
end.

以上代码很容易理解, 自上而下的执行, 那么异步编程的做法呢?

创建readfileasync.js, 并执行

var fs = require('fs');
fs.readFile('/Users/51testing/Desktop/file.txt', 'utf-8', function(err, data) {
if (err){
console.error(err);
} else {
console.log(data);
}
});
console.log('end.');

打印的结果如下:

//执行
$ node /Users/51testing/Desktop/readfileasync.js
end.
你好呀
$

fs.readFile 调用时所做的工作只是将异步式 I/O 请求发给了操作系统, 然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。 fs 接到I/O 请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end.然后看到 file.txt 文件的内容。

2 NodeJs循环机制

循环

Node.js 在什么时候会进入事件循环呢?

生命周期为:Node.js 程序由事件循环环开始,到事件循环结束.

所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是 事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或直接发射 (emit)事件,执行完成后再返回事件 循环,事件循环会会检查循环中有没有 处理的事件,直到到程序结束。

小技巧

Node 版本区别:

从对 ES6 的支持来简单的区分是

0.x 完全不支持ES6
4.x 部分支持ES6特性,并处在LTS阶段
5.x 部分支持ES6特性(比4.x多些),属于过渡产品,现在来说应该没有什么理由去用这个了
6.x 支持98%的ES6特性

参考链接

Node.js开发指南
深入浅出Node.js

[toc]

认识 Python

人生苦短,我用 Python —— Life is short, you need Python

img

目标

  • Python 的起源
  • 为什么要用 Python?
  • Python 的特点
  • Python 的优缺点
  • Python 2.x3​​.x 版本简介
  • 执行 Python 程序的三种方式
    • 解释器 —— python / python3
    • 交互式 —— ipython
    • 我选择免费易扩展的VSCode,因为还得写别的语言,仅此而已

01. Python 的起源

Python 的创始人为吉多·范罗苏姆(Guido van Rossum)

Python之父是荷兰人Guido van Rossum,被誉为历史上最伟大的12名程序员之一。

e747899073c9b4416c91c2bb326f1549.jpeg

  1. 1989 年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的解释程序,作为 ABC 语言的一种继承(感觉下什么叫牛人
  2. ABC 是由吉多参加设计的一种教学语言,就吉多本人看来,ABC 这种语言非常优美和强大,是专门为非专业程序员设计的。但是 ABC 语言并没有成功,究其原因,吉多认为是非开放造成的。吉多决心在 Python 中避免这一错误,并获取了非常好的效果
  3. 之所以选中 Python(蟒蛇) 作为程序的名字,是因为他是 BBC 电视剧——蒙提·派森的飞行马戏团(Monty Python’s Flying Circus)的爱好者
  4. 1991 年,第一个 Python 解释器 诞生,它是用 C 语言实现的,并能够调用 C 语言的库文件

1.1 解释器(科普)

计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所写的程序语言翻译成机器语言,计算机才能执行程序。将其他语言翻译成机器语言的工具,被称为编译器

编译器翻译的方式有两种:一个是编译,另外一个是解释。两种方式之间的区别在于翻译时间点的不同。当编译器以解释方式运行的时候,也称之为解释器

12ae91928d3e790ef0b8cdf9ec299685.png

  • 编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如 C、C++
  • 解释型语言:解释型语言编写的程序不进行预先编译,以文本方式存储程序代码,会将代码一句一句直接运行。在发布程序时,看起来省了道编译工序,但是在运行程序的时候,必须先解释再运行

e56f0d1450ca0c514ba954ef640b4b4c.png

编译型语言和解释型语言对比

  • 速度 —— 编译型语言比解释型语言执行速度快
  • 跨平台性 —— 解释型语言比编译型语言跨平台性好

1.2 Python 的设计目标

1999 年,吉多·范罗苏姆向 DARPA 提交了一条名为 “Computer Programming for Everybody” 的资金申请,并在后来说明了他对 Python 的目标:

  • 一门简单直观的语言并与主要竞争者一样强大
  • 开源,以便任何人都可以为它做贡献
  • 代码像纯英语那样容易理解
  • 适用于短期开发的日常任务

这些想法中的基本都已经成为现实,Python 已经成为一门流行的编程语言

1.3 Python 的设计哲学

7ff693d9b86899489ddfc38bfe444bdc.png

  1. 优雅
  2. 明确
  3. 简单
  • Python 开发者的哲学是:用一种方法,最好是只有一种方法来做一件事
  • 如果面临多种选择,Python 开发者一般会拒绝花俏的语法,而选择明确没有或者很少有歧义的语法

在 Python 社区,吉多被称为“仁慈的独裁者”

02. 为什么选择 Python?

  • 代码量少
  • ……

同一样问题,用不同的语言解决,代码量差距还是很多的,一般情况下 PythonJava1/5,所以说 人生苦短,我用 Python

03. Python 特点

  • Python 是完全面向对象的语言
    • 函数模块数字字符串都是对象,在 Python 中一切皆对象
    • 完全支持继承、重载、多重继承
    • 支持重载运算符,也支持泛型设计
  • Python 拥有一个强大的标准库,Python 语言的核心只包含 数字字符串列表字典文件 等常见类型和函数,而由 Python 标准库提供了 系统管理网络通信文本处理数据库接口图形系统XML 处理 等额外的功能
  • Python 社区提供了大量的第三方模块,使用方式与标准库类似。它们的功能覆盖 科学计算人工智能机器学习Web 开发数据库接口图形系统 多个领域

面向对象的思维方式

  • 面向对象 是一种 思维方式,也是一门 程序设计技术
  • 要解决一个问题前,首先考虑 由谁 来做,怎么做事情是 的职责,最后把事情做好就行!
    • 对象 就是
  • 要解决复杂的问题,就可以找多个不同的对象各司其职,共同实现,最终完成需求

04. Python 的优缺点

4.1 优点

  • 简单、易学
  • 免费、开源
  • 面向对象
  • 丰富的库
  • 可扩展性
    • 如果需要一段关键代码运行得更快或者希望某些算法不公开,可以把这部分程序用 CC++ 编写,然后在 Python 程序中使用它们
  • ……

4.2 缺点

  • 运行速度
  • 国内市场较小
  • 中文资料匮乏

01. Python 2.x3​​.x 版本简介

目前市场上有两个 Python 的版本并存着,分别是 Python 2.xPython 3.x

新的 Python 程序建议使用 Python 3.0 版本的语法

  • Python 2.x 是 过去的版本
    • 解释器名称是 python
  • Python 3.x 是 现在和未来 主流的版本
    • 解释器名称是 python3
    • 相对于 Python 的早期版本,这是一个 较大的升级
    • 为了不带入过多的累赘,Python 3.0 在设计的时候 没有考虑向下兼容
      • 许多早期 Python 版本设计的程序都无法在 Python 3.0 上正常执行
    • Python 3.0 发布于 2008 年
    • 到目前为止,Python 3.0 的稳定版本已经有很多年了
      • Python 3.3 发布于 2012
      • Python 3.4 发布于 2014
      • Python 3.5 发布于 2015
      • Python 3.6 发布于 2016
  • 为了照顾现有的程序,官方提供了一个过渡版本 —— Python 2.6
    • 基本使用了 Python 2.x 的语法和库
    • 同时考虑了向 Python 3.0 的迁移,允许使用部分 Python 3.0 的语法与函数
    • 2010 年中推出的 Python 2.7 被确定为 最后一个Python 2.x 版本

提示:如果开发时,无法立即使用 Python 3.0(还有极少的第三方库不支持 3.0 的语法),建议

  • 先使用 Python 3.0 版本进行开发
  • 然后使用 Python 2.6Python 2.7 来执行,并且做一些兼容性的处理

03. 执行 Python 程序的三种方式

解释器 —— python / python3
交互式 —— ipython
集成开发环境 —— PyCharm / VsCode

3.1. 解释器 python / python3

Python 的解释器

# 使用 python 2.x 解释器
$ python xxx.py

# 使用 python 3.x 解释器
$ python3 xxx.py
其他解释器

Python 的解释器 如今有多个语言的实现,包括:

  • CPython —— 官方版本的 C 语言实现
  • Jython —— 可以运行在 Java 平台
  • IronPython —— 可以运行在 .NET 和 Mono 平台
  • PyPy —— Python 实现的,支持 JIT 即时编译

3.2. 交互式运行 Python 程序

  • 直接在终端中运行解释器,而不输入要执行的文件名
  • 在 Python 的 Shell 中直接输入 Python 的代码,会立即看到程序执行结果

1) 交互式运行 Python 的优缺点

优点
  • 适合于学习/验证 Python 语法或者局部代码
缺点
  • 代码不能保存
  • 不适合运行太大的程序

2) 退出 官方的解释器

1> 直接输入 exit()
>>> exit()
2> 使用热键退出

在 python 解释器中,按热键 ctrl + d 可以退出解释器

3> IPython

  • IPython 中 的 “I” 代表 交互 interactive
特点
  • IPython 是一个 python 的 交互式 shell,比默认的 python shell 好用得多
    • 支持自动补全
    • 自动缩进
    • 支持 bash shell 命令
    • 内置了许多很有用的功能和函数
  • IPython 是基于 BSD 开源的
版本
  • Python 2.x 使用的解释器是 ipython

  • Python 3.x 使用的解释器是 ipython3

  • 要退出解释器可以有以下两种方式:

1> 直接输入 exit
In [1]: exit
2> 使用热键退出

在 IPython 解释器中,按热键 ctrl + dIPython 会询问是否退出解释器

### 3.3 不过我选择vscode, 一个编辑器走天下!

工具顺手就行,只是为了提升开发效率,对Python的基本认识应该就到此位置了

第一部分 NodeJs 初识

不知了了几行代码是否可以引起你的兴趣

// service.js
var http = require("http");
http.createServer(function(req,res){
res.write("Hello NodeJs");
res.end();
}).listen(8888);

1 Nodejs是什么?

  • Node.js是一个让 JavaScript 运行在服务端的开发平台;

  • Node.js可以作为服务器向用户提供服务,它跳过了 Apache、Nginx 等 HTTP 服务器,直接面向前端开发。

  • 优秀的第三方包管理器(node package manager,npm)

2 Nodejs不是什么?

  1. Node.js 不是一种独立的语言
  2. Node.js 不是一个 JavaScript 框架 ;

3 Nodejs能做什么?

 具有复杂逻辑的网站;
 基于社交网络的大规模 Web 应用;
 Web Socket 服务器;
 TCP/UDP 套接字应用程序; 8  命令行工具;
 交互式终端程序;
 带有图形用户界面的本地应用程序;
 单元测试工具;
 客户端 JavaScript 编译器。

4 Nodejs 原理

Node.js 是基于Chrome V8引擎构建的,由事件循环(Event Loop)分发 I/O 任务,最终工作线程(Work Thread)将任务丢到线程池(Thread Pool)里去执行,而事件循环只要等待执行结果就可以了。

核心概念

* Chrome V8 是 Google 发布的开源 JavaScript 引擎,
采用 C/C++ 编写,在 Google 的 Chrome 浏览器中被使用。
Chrome V8 引擎可以独立运行,也可以用来嵌入到 C/C++ 应用程序中执行。
* Event Loop 事件循环(由 libuv 提供)
* Thread Pool 线程池(由 libuv 提供)

重点: 整体思路梳理

重点: 整体思路梳理

重点: 整体思路梳理

1. Chrome V8 是 JavaScript 引擎
2. Node.js 内置 Chrome V8 引擎,所以它使用的 JavaScript 语法
3. JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事
4. 单线程就意味着,所有任务需要排队,前一个任务结束,
才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
5. 如果排队是因为计算量大,CPU 忙不过来,倒也算了,
但是很多时候 CPU 是闲着的,因为 I/O 很慢,不得不等着结果出来,再往下执行。
6. CPU 完全可以不管 I/O 设备,挂起处于等待中的任务,先运行排在后面的任务,
将等待中的 I/O 任务放到 Event Loop 里,由 Event Loop 将 I/O 任务放到线程池里,
只要有资源,就尽力执行。

重点理解如下这句话

Nodejs接受任务是单线程,执行任务是多线程。

思路如下:

  1. Nodejs内置V8,采用Js语言,是单线程的,故接受任务是单线程,无需进程/线程切换,非常高效
  2. EventLoop派发任务给线程池里的I/O去执行,故是多线程执行任务。

第二部分 配置开发环境

1. 安装<官方文档>

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm

Mac安装后如果提示nvm: command not found, 是因为没有[.bash_profile file]文件,

第一种方法: 创建touch ~/.bash_profile, 然后重新执行curl操作;
第二种方法: 打开.bash_profile, 添加source ~/.bashrc

2 下载不同版本

                        now
(io.js) v2.0 : v2.x
| | : |
v0.10.x /--------------:-----------------\ Node.js 2.0
____|____/ : \______|_____
\ : /
\--------------:-----------------/
| | : | |
(node.js) v0.12.x : v0.13.x v0.14.x

对于nodejs与io.js的版本区别以及渊源,建议大家参考Node.js与io.js那些事儿

#罗列可以安装的版本
$ nvm ls-remote
.
.
.
v6.4.0
v6.5.0
v6.6.0

3 下载版本

#为了演示,选择了6.6.0
$ nvm install v6.6.0

$ nvm install v5.12.0
######################################################################## 100.0%
WARNING: checksums are currently disabled for node.js v4.0 and later
Now using node v5.12.0 (npm v3.8.6)

4 切换版本

$ nvm use v5
Now using node v5.12.0 (npm v3.8.6)

$ nvm use v6
Now using node v6.6.0 (npm v3.10.3)

5 常用命令

# 下载
$ nvm install ***
#切换版本
$ nvm use 版本号
#罗列下载的版本
$nvm ls
# 罗列远程版本
$nvm ls-remote
#设置默认的版本
$nvm alias default 版本

第三部分 注意事项

  1. 当关闭终端在此打开时, 输入 nvm会提示找不到, 解决办法:
    .bash_profile文件中添加source ~/.bashrc;即可;

  2. nvm安装的路径是: ~/.nvm;

  3. 使用nvm安装node, 会安装在该路径下: ~/.nvm/versions/node;

附加

Mac 环境变量配置

1./etc/profile: (不建议修改这个文件)全局(公有)配置,不管是哪个用户,登录时都会读取该文件。
2./etc/bashrc: 系统级环境变量. 全局(公有)配置,bash shell执行时,不管是何种方式,都会读取此文件。
3.~/.bash_profile: 用户级环境变量.每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!