python垃圾回收机制

垃圾回收机制:python采⽤的是以引用计数为主,隔代回收机制为辅的策略

小整数对象池(Python运行之前就已经创建好了),常驻内存

Python对小整数的定义是[-5,257)这些整数对象是提前建立好的,不会被垃圾回收.在一个Python的程序中,所有位于这个范围内的整数使用的都是同一个对象.
同理,单个字母也是这样的

1
2
3
a ,b= 100,100
id(a) == id(b)
# True

intern机制尽量地共享一份内存地址,靠引用计数去维护何时释放

但如果是包含有特殊字符的就不会共享了

1
2
3
4
5
6
# 共享
a = "hello"
b = "hello"
# 不共享
a = "hello world"
b = "hello world"

引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
# 引用计数 解决不了 变量的循环引用或互相引用
# 才有分代收集机制才会减一来辅助完成回收
def f2():
while True:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
del c1
del c2
# 如果没有下面一行代码,则内存使用快速上升
gc.collect() # 手动启动垃圾回收
gc.disable()
# 隔(零)代回收:promote
f2()

引用计数的优点是

  1. 简单性
  2. 实时性:一旦没有引用,内存就直接释放了.不用像其他机制等到特定
    时机.实时性还带来一个好处:处理回收内存的时间分摊到了平时.

缺点是

  1. 维护引用计数消耗资源
  2. 循环引用或相互引用,不能完成回收
GC(Garbage Collection)垃圾回收机制

gc模块唯一处理不了的是循环引用的类都有del方法,所以项目中要避免
重写del方法

触发垃圾回收的情况:

  1. 调用gc.collect(),
  2. 当gc模块的计数器达到阀值的时候.
  3. 程序退出的时
  4. 引用计数为0时
Share Comments

python 装饰器

装饰器(decorator):它能对任何可调用的对象进行包装,既能够用于方法也能够用于函数,想要对一个已有的模块做一些”修饰工作”,但又不让这个小装饰(小功能)侵入到原有的模块中的代码里去,

函数名是某个函数的引用(reference),所以,我们可以对同一个函数设置不同的函数名.(可以理解为,函数也是对象,可以通过赋值来设置不同的对象名)

1
2
3
4
5
6
7
8
9
10
11
12
>>> def succ(x):
return x+1
>>> successor = succ
>>> successor(10) # 11
>>> succ(10) # 11
# successor和succ都指向同一个函数(应该是对象),都指向同一个内存区域.
>>> id(successor) # 139701625513368
>>> id(succ) # 139701625513368
>>> del succ
>>> successor(10) # 11
>>> type(successor) # function

写代码要遵循开放封闭原则

虽然在这个原则是⽤的⾯向对象开发,但是也适⽤于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现的功能代码块 开放:对扩展开发

装饰器什么时候进行装饰

python解释器对函数默认是不执行的,只是知道这块有这些个函数,在调用的时候才执行
@der # 但只要是python解释器执行到这个代码了,那么就会自动的进行装饰,而不是等到调用的时候才装饰的
装饰器:装饰时由内往外装,调用时由外向内

结果如下

1
2
3
4
5
---正在装饰2----
---正在装饰1----
---正在验证权限1----
---正在验证权限2----
---f1---

使用装饰器对有返回值的函数进行装饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def func(functionName):
print("---func---1---")
def func_in():
print("---func_in---1---")
ret = functionName()
print("---func_in---2---")
return ret
print("---func---2---")
return func_in
@func
def test():
print("----test----")
return "haha"
ret = test()
print("test return value is %s"%ret)

装饰器(decorator)功能

1.引⼊⽇志 2.函数执⾏时间统计 3.执⾏函数前预备处理 4.执⾏函数后清理功能 5.权限校验等场景 6.缓存

装饰器的两种类型:函数装饰器和类装饰器

函数装饰器

无参数装饰器-包装无参数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def decorator(func):
# 装饰函数
print('hello')
return func
@decorator
def foo():
# 被装饰函数 回调函数 闭包函数(当有自由变量)
print('foo end')
# 其解释器将此方法解释为
# foo = decorator(foo)
# 把一个函数当参数传到另一个函数中,然后再回调
if __name__ == '__main__':
foo
# hello
foo()
# hello
# foo end
foo_text = decorator(foo)
print(type(foo_text))
# hello
# hello
# <class 'function'>
foo_text()
# foo end

装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的(被装饰)的函数

下面这个例子向我们展示了decorator的本质:hello(foo)返回了wrapper()函数,所以,foo其实变成了wrapper的一个变量,而后面的foo()执行其实变成了wrapper()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def hello(fn):
def wrapper():
print("hello, %s" % fn.__name__)
fn()
print("goodby, %s" % fn.__name__)
return wrapper
@hello
def foo():
print("i am foo")
# foo = hello(foo)
if __name__ == '__main__':
foo()
# hello, foo
# i am foo
# goodby, foo

类似地

1
2
3
4
5
6
7
8
9
10
11
12
13
# 多个decorator
@decorator_one
@decorator_two
def func():
pass
# func = decorator_one(decorator_two(func))
# 带参数的decorator
@decorator(arg1, arg2)
def func():
pass
# func = decorator(arg1,arg2)(func)
# 这意味着decorator(arg1, arg2)这个函数需要返回一个"真正的decorator"

无参数装饰器-包装带参数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def decorator_func_args(func):
def handle_args(*args, **kwargs):
print("handle args start")
func(*args, **kwargs)
print("handle args end")
return handle_args
@decorator_func_args
def foo2(a, b=2):
print(a,b)
if __name__ == '__main__':
foo2(1)
# handle args start
# 1 2
# handle args end
# 先传递函数名,再传递参数
foo2_text = decorator_func_args(foo2)
print('--')
foo2_text(1)
# --
# handle args start
# handle args start
# 1 2
# handle args end
# handle args end

带参数装饰器 – 包装无参数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def decorator_with_params(arg_of_decorator):
print(arg_of_decorator)
def newDecorator(func):
print(func)
return func
return newDecorator
@decorator_with_params("deco_args")
def foo3():
pass
if __name__ == '__main__':
foo3()
# deco_args
# <function foo3 at 0x7fba7bfcf7b8>
# 先传递参数,再传递函数名
foo3_text = decorator_with_params("deco_args")
print('--')
foo3 = foo3_text(foo3)
# deco_args
# <function foo3 at 0x7fba7bfcf7b8>
# deco_args
# --
# <function foo3 at 0x7fba7bfcf7b8>
func = decorator_with_params('1')(foo3)
# deco_args
# <function foo3 at 0x7ff913a6f730>
# 1
# <function foo3 at 0x7ff913a6f730>

带参数装饰器– 包装带参数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def decorator_with_params_and_func_args(arg_of_decorator):
def handle_func(func):
def handle_args(*args, **kwargs):
print("begin")
func(*args, **kwargs)
print("end")
print(arg_of_decorator, func, args, kwargs)
return handle_args
return handle_func
@decorator_with_params_and_func_args("123")
def foo4(a, b=2):
print("content")
if __name__ == '__main__':
foo4(1, b=3)
# begin
# content
# end
# 123 <function foo4 at 0x7f4f0f056730> (1,) {'b': 3}
foo4_dec = decorator_with_params_and_func_args("123")
print('**')
foo4_func = foo4_dec(foo4)
print('@@')
foo4_text = foo4_func(1, b=3)
# **
# @@
# begin
# begin
# content
# end
# 123 <function foo4 at 0x7f663fd82730> (1,) {'b': 3}
# end
# 123 <function decorator_with_params_and_func_args.<locals>.handle_func.
# <locals>.handle_args at 0x7f663fd827b8> (1,) {'b': 3}

给decorator参数赋值,返回handle_func对象
handle_func()函数接收foo4对象,返回handle_args对象
handle_args()函数接收传递给foo4的参数.
一个有点意义的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def makeHtmlTag(tag, *args, **kwds):
def real_decorator(fn):
css_class = " class='{0}'".format(kwds["css_class"]) \
if "css_class" in kwds else ""
def wrapped(*args, **kwds):
return "<" + tag + css_class + ">" + fn(*args,**kwds) + "</" + tag + ">"
return wrapped
return real_decorator
@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello(name):
return "hello world {0}".format(name)
if __name__ == '__main__':
print(hello('femn'))
# <b class='bold_css'><i class='italic_css'>hello world femn</i></b>

makeHtmlTag有两个参数.所以,为了让 hello = makeHtmlTag(arg1, arg2)(hello) 成功,
makeHtmlTag 必需返回一个decorator(这就是为什么我们在makeHtmlTag中加入了real_decorator()的原因),
这样一来,我们就可以进入到 decorator 的逻辑中去了——
decorator得返回一个wrapper,wrapper里回调hello. “

函数的元数据

函数的一些属性:

  1. name(函数名),
  2. doc(描述)和
  3. module(模块位置)
    在使用装饰器的时候,函数会丢失这些元数据.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def greeting(func):
    def function_wrapper(x):
    """function_wrapper of greeting"""
    print("Hi, " + func.__name__ + " returns:")
    return func(x)
    return function_wrapper
    @greeting
    def f(x):
    """just some silly function"""
    return x + 4
    if __name__ == '__main__':
    f(10)
    print("function name: " + f.__name__)
    print("docstring: " + f.__doc__)
    print("module name:" + f.__module__) ")

运行结果如下

1
2
3
4
Hi, f returns:
function name: function_wrapper
docstring: function_wrapper of greeting
module name:__main__

在正常情况下,f.name应该为f,而通过装饰器之后,由于没有保存函数的元数据,所以变成了function_wrapper.
每当定义一个装饰器时,应该总是记得为底层的包装函数添加functools库中的@wraps装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import wraps
def greeting(func):
@wraps(func)
def function_wrapper(x):
"""function_wrapper of greeting"""
print("Hi, " + func.__name__ + " returns:")
return func(x)
return function_wrapper
@greeting
def f(x):
"""just some silly function"""
return x + 4
if __name__ == '__main__':
f(10)
print("function name: " + f.__name__)
print("docstring: " + f.__doc__)
print("module name:" + f.__module__) ")

运行结果如下

1
2
3
4
Hi, f returns:
function name: f
docstring: just some silly function
module name:__main__

在python中圆括号意味着调用函数.在没有圆括号的情况下,python会把函数当作普通对象
而想引用对象时则必须要使用圆括号,而且必须要重写call方法,才能调用

1
2
3
4
5
6
7
8
9
10
11
class Test():
def __call__(self, *args, **kwargs):
print('类对象的直接调用时,必须要重写的方法')
def fun():
print('方法的引用之后的直接调用')
t = Test()
t()
f = fun
f()

类装饰器

类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class myDecorator(object):
def __init__(self, fn):
print( "inside myDecorator.__init__()")
# 在我们给某个函数decorator时被调用,所以
# 需要有一个fn的参数,也就是被decorator的函数
self.fn = fn
# 调用被decorator函数时被调用的
# 也可以在这个传入fn __call__(self.fn)
def __call__(self):
self.fn()
print( "inside myDecorator.__call__()")
@myDecorator
def aFunction():
print( "inside aFunction()")
print( "Finished decorating aFunction()")
if __name__ == '__main__':
aFunction()
# inside myDecorator.__init__()
# Finished decorating aFunction()
# inside aFunction()
# inside myDecorator.__call__()

下面这个示例展示了通过URL的路由来调用相关注册的函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MyApp():
def __init__(self):
self.func_map = {}
# decorator类中没有__call__(),但是wrapper返回了原函数
def register(self, name):
def func_wrapper(func):
self.func_map[name] = func
return func
return func_wrapper
def call_method(self, name=None):
func = self.func_map.get(name, None)
if func is None:
raise Exception("No function registered against - " + str(name))
return func()
app = MyApp()
@app.register('/')
def main_page_func():
return "This is the main page."
@app.register('/next_page')
def next_page_func():
return "This is the next page."
print(app.call_method('/'))
print(app.call_method('/next_page'))
# This is the main page.
# This is the next page.

Share Comments

linux 基本

shell:是用户与kernal 通信的桥梁.它分为GUI:GNOME CLI:BASH,一种是图形界面的终端.是多任务,多进程的终端.firefox &
hostname
uname -r or -a参数 –all

/bin:常用的可执行的二进制文件,终端命令,所有用户都有权限去执行
/sbin:只有root才能运行.比较危险的命令,fdisk
/boot:引导启动目录,操作系统内核(vmlinux-4.4.0)
/dev:硬件设备:硬盘sda1分区,终端tty1,网卡,声卡.所有的设备都被抽象成一个文件./dev/sd:SATA,SCSI(服务器硬盘),SAS,USB,a:是第几块硬盘,1:分区情况.有一个磁盘文件/dev/sda 每个分区也有个文件:/dev/sda1
/etc:配置文件,基本上是纯文本的 xx.conf
/home:除了root的,其它用户的私有目录.此目录与用户名同名.
/libabry:Liunx运行需要的库文件,.sh,所有用户都有权限.

/opt:安装一些大的软件
/proc:不存在在硬盘上,系统运行时 的实时信息,保存在内存中,是一个虚拟的一个文件系统.meminfo cpuinfo,interrupts终端信息,iomem,uptime启动时间,vmstat虚存信息.acpi电源信息,每个进程,会对应一个数字的目录.echo 1 > /proc/sys/net/ipv4/ip_forward 为LVS服务器开通路由功能(打开两个网卡之间的路由通道) 网卡充当路由器,也可以充当主机.
/sys:底层硬件信息.
/usr:应用软件 /usr/local/包.bin or /usr/share/包 .Sbin
/var:经常变化的东西 email,log操作系统的所有log信息

/usr/share/doc:所有程序的文档 都会以TXT,HTML,PDF等方式保存在/usr/share/doc目录下

/etc/skel/:当创建新用户时,这里面的文件会复制到新用户的家目录中
.bash_logout当用户关机时,执行的文件

Share Comments

linux 常用命令

安装 Ubuntu Desktop

注意事项一:进入BIOS

最近在lenovo电脑上安装,连进BIOS都是那么的费劲,别人的差不多都是F12,而你看lenovo的,进入BIOS之后还得,关闭安全启动模式,设置开机启动的传统模式

注意事项二:分区的设置

如果是为了省心的话,直接弄个4G的swap(交换分区),然后将所有的空间给/(根目录)
但这次我给/boot可以单独分成一个区,大小为200M

注意事项三; /boot空间不足

/boot文件里面存放的是系统引导文件和内核的一些东西,这些东西100M是足够容纳的。而大家都知道linux内核一直在更新,跟新后,旧的内核就不在使用,但旧的内核文件还在boot里面,占据着空间,更新几次过后boot文件就会被占满,显示boot磁盘空间不足。这时为了更新需要将不用的内核文件删除,释放空间

1
2
3
4
5
6
df -h
uname -a # (查看当前使用的内核版本)
sudo apt-get remove linux-image- (按两次tab键)# 删除旧版本内核
df -h # 查看硬盘的使用情况
sudo /usr/sbin/update-grub
# 解决you may need to re-run your boot loader[grub]

注意事项四:设置主屏和副屏
1
2
3
4
5
6
7
8
xrandr # (查看和设置显示屏)
xrandr --output HDMI2 --auto --primary
# HDMI2 是显示屏的名字
# auto 是自动分辨率
# primary 是主屏
xrandr --output eDP1 --right-of HDMI2 --auto
# eDP1 是笔记本显示屏的名字
# --right-of HDMI2 就是放在HDMI2 显示器的右边

不需要记住所有的命令,使用man info查看帮助文档:

1
2
3
4
5
6
ls --help
man ls # 帮助手册
man -k passwd
man -k nautilus # 可以用来查询包含该关键字命令的文档
info ls # 帮助文档(书)
# 所有程序的文档 都会以TXT,HTML,PDF等方式保存在/usr/share/doc目录下

用户与权限

用户:是限制使用者或进程可以使用,不可以使用哪些资源.
用户组:用来方便组织管理用户GID
UID:每个用户拥有一个,操作系统实际使用的是UID,而不是用户名.每个用户属于一个主组,属于一个或多个附属组(最多有31个),每个可登陆用户拥有一个指定的shell.
特别注意:每个进程以一个用户身份运行,并受到该用户可访问的资源限制.所以进程的权限与该用户的权限一样.
UID:用32位的二进制,用十进制查看,但系统为了兼容老式系统,用户ID限制在16位(65535个用户上限).root用户:ID为0,系统用户(1~499):没有一个登陆的shell,为某些服务创建的,比如说WEB服务,共享服务,ftp服务打印服务,这些用户仅仅是做为 这些进程去使用的,普通用户(500以上)

Linux权限基于UGO模型进行控制,权限rwx对目录的影响:
r:可列出目录内容,w:可在目录中创建删除文件,
x:可访问目录内容(进入目录),目录必须拥有X权限,否则无法进入目录
rwx(4+2+1)

默认权限:umask的值会从对象的全权限值中减掉
普通用户的umask:0002,root用户的为0022
目录的全权限 777-002
文件的全权限 666-002
umask: 显示 12位长的权限 UGO占用9bite
umask 0022 : 去设置默认权限

但一个目录,只能属于一个组,当多个组需要共同使用这个目录时,UGO模型的权限就不行了.
ACL(Access Control List)是一种高级权限机制,允许我们对一个文件或目录进行灵活的复杂的权限设置.
允许针对不同用户,不同组对一个目标文件或目录进行权限设置,不受UGO模型限制.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
who # 显示哪些用户已经登陆系统
whoami # 显示当前用户
id # 查看当前用户信息
passwd username # 修改用户密码,默认是修改当前用户
# 用户组 /etc/group
groupadd femnGroup
groupdel femnGroup
# 用户 /etc/shadow
useradd -G femnGroup femn1
# 此femn1用户主属组是femn1,附属组是femnGroup
useradd -d /home/femn1 -u 1000 femn1 # femn1是用户名 -u用户ID
sudo usermod -d /home/femn1 -s /bin/bash femn1 # -s默认bash
userdel femn1
userdel -r femn1 # 同时删除用户的家目录
# 改文件的UGO权限
chmod -R 777 文件名
chmod u+rw,g+o
chmod go+rw
chmod a-x # a代指ugo
# chown改所属用户 -R
chown -R name:name 文件
chown femn(用户名)learn.txt(文件名,如果是目录,则加上 -R参数)
# chgrp改所属组 -R递归修改目录下的所有文件的所属组
chgrp -R femnGroup learn.txt
# umask的最前面的那位,是用来保留特殊权限的.
chmod u+s 文件:
# 特殊权限一般是给可执行文件去用的.1770
chmod g+s 目录:
# 在此目录下生成的文件或目录,都会继承此目录的用户组 2770
chmod o+t 目录:
# 只能是本人可以删除自己的文件,即使是本组中的人员也不能删除别人的文件.4770
# ACL需要在挂载文件的时候打开ACL功能:
mount -o acl /dev/sda5 /mnt
# 针对一个用户对文件进行ACL设置:
setfacl -m u:name:rwx linux
setfacl -m g:name:rw linux
setfacl -x u:name linux
# 只要我们的文件系统打开了ACL功能,就可以查看一个文件或目录的ACL设置:
getfacl linux

磁盘和挂载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
df -h # 设备文件名称,本系统中的磁盘的使用大小情况
df -at # 文件系统, 磁盘的状态
du -sh # 显示此目录总所占空间
du -sh ~/* | sort -nr
# 参数让结果按降序输出 管道命令将du命令的输出重定向到sort命令
fdisk -l # 查看本机的所有磁盘,以及磁盘的分区情况
fdisk /dev/sda # 查看,创建,删除某个磁盘 分区
dumpe2fs -h /dev/sdb3 # 得到这个磁盘分区的所有信息
# 格式化之前先不要挂载
mkfs -t ext4 /dev/sda4
mkfs.ext4 /dev/sda4
# 将这个分区(或硬盘)格式化为ext4文件系统,格式化磁盘
mke2fs -j -L "partition-label-name" -b 4096 -i -c 128 /dev/sda4
# 这样就可以传4.3G的大文件到U盘上了,只是这样格式,在Win不能用
# 挂载
mount /dev/sdc1 /home/name/usb
mount -o remount,rw,auto /dev/sdc1
# 将/dev/sdc1重新挂载,并加入参数rw,auto
umount /home/name/usb or /dev/sdc1 挂载点或设备文件名
# -l参数是强制
lsof |grep /dev/sdc1
# 当挂载不掉时,用下面的命令 ,会得到进程号
# 特殊设备loop挂载(镜像文件不刻录就挂载使用)
mount -o loop /home/name/ubuntu-16.04-desktop-amd64.iso \
/hame/name/usb
#使用 dd 命令写入下载好的 ISO 镜像到 U 盘中
sudo dd if=xxxxx.iso of=/dev/sdc && sync
nautilus . # 默认是打开home目录

传统的磁盘管理问题:
当分区大小不够用时无法扩展其大小,只能通过添加硬盘,创建新分区来扩充空间,但是新添加进来的硬盘是作为独立文件系统存在的,原有的文件系统并未得到扩充,上层应用很多时候只能访问一个文件系统,只能让现有磁盘下线,换上新的磁盘之后,再将原始数据导入.

LVM逻辑卷管理(Logical volume Manager)

PE(physical extend)物理扩展:逻辑卷空间管理的最基本单位,默认是4M
PV(physica volume)物理卷:将底层磁盘格式化成物理卷,可以是物理硬盘上的分区,也可以是整块物理硬盘
VG(volume group)卷组:空间池是给来装PE的,我们可以将一个或多个PV,加入到VG当中,当创建好了VG会有/dev/vgname的目录
LV(logical volume)逻辑卷(操作系统最终使用的是逻辑卷格式化之后的数据)/dev/vgname/lvname的目录,每个的逻辑卷空间有可以来自不同的物理磁盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1. 创建PV
# 整块物理硬盘创建PV
sudo pvcreate /dev/sd[b-c]
# 物理硬盘上的分区创建PV
fdisk /dev/sdb1 使用t将分区类型改成8e(LVM)
partprobe # 刷新分区表
sudo pvcreate /dev/sdb1
sudo pvs # 查看创建好的PV
2. PV创建好了就可以创建VG了
sudo vgcreate vg0 /dev/sdb1
sudo vgs # 查看创建好的VG
sudo pvs # sdb1这个PV就属于vg0这个卷组池了
3. 创建好卷组池就能从池中划分容量给逻辑卷(LV)了
sudo lvcreate -n lv0 -L 10G vg0
# 从卷组vg0中拿出10G,分配给lv0,其中-n后面是lv自定义名称,
# -L后面跟需要从vg0中分配给lv0的大小以及从哪个VG分配
4. 创建好lv后就可以格式化它,成为文件系统使用
sudo mkfs.ext4 /dev/vg0/lv0
# 创建好的lv所在目录是"/dev/卷组名称/逻辑卷名称",
# 所以本例就是/dev/vg0/lv0,将它格式化成ext4文件系统
5. 挂载和使用
sudo mount /dev/vg0/lv0 /mnt
6. 扩充VG
sudo pvcreate /dev/sdd
#新增加一块硬盘sdd(20G),将它创建成PV后划分到vg0下,
# 此时vg0的容量将增加sdd硬盘的大小
sudo vgextend vg0 /dev/sdd
7. 扩充LV
sudo vgs # 先确保VG池中有足够的VFree空间可供使用
sudo lvextend -L +5G /dev/vg0/lv0
sudo resize2fs /dev/vg0/lv0 # 更新

安装和下载

源的选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ubuntu官方有自身的软件源,也可以通过software&update的设置中的other去选择Best server去选择源
# 会自动的改变/etc/apt/sources.list的内容
# 也就是说选择哪种源的服务器,再(proxychains4) apt-get update就可以了
# 这样通过也可以解决 哪些包是在本地不能使用的(不能使用的一般是自己手动加入的)
我选择的是Main server 软件包会比较全(国内的选择清华镜像)
# 修改DNS
sudo vim /etc/resolvconf/resolv.conf.d/base
sudo vim /etc/resolvconf/resolv.conf.d/head
nameserver 8.8.8.8
nameserver 8.8.4.4
sudo resolvconf -u
# 重启网络
sudo service networking restart
sudo rm -r /var/lib/apt/lists/
sudo proxychains4 apt-get update

apt-get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 更新源(更新本地软件包索引), 更新软件仓库:/etc/apt/sources.list
sudo apt-get -y update
# sudo apt-key update # 更新密钥
# 升级系统上可以升级的软件包
sudo apt-get -y upgrade
# 尝试修正系统依赖损坏处
sudo apt-get -f install # --fix-missing
# 安装软件包, apt-get会从软件仓库中下载deb软件安装包 再进行安装
sudo apt-get install chromium-browser
# 删除系统上的软件包,不包含配置文件
sudo apt-get remove firefox
# 删除系统上的软件包以及其相关配置
sudo apt-get purge firefox
# 清除下载的软件包
sudo apt-get clean
# 当apt-get安装或升级软件包时,它会将deb安装包下载到文件系统的/var/cache/apt/archives目录下
# apt-get clean命令可以帮你删除这些deb安装包
# 删除不需要的依赖软件包
apt-get autoremove
# 搜索软件包, 查看对应版本进行安装
apt-cache search chromium
# 查看软件包的信息
apt-cache show chromium-browser
# 检查是否有损坏的依赖
sudo apt-get check
# 通过 PPA 源来安装软件
# 查看系统版本
cat /etc/*release
sudo lsb_release -a
# 通过搜索软件包关键字 + PPA 或者在 https://launchpad.net/中搜索软件包(区分大小写)
# 使用命令安装(在标题栏显示内存、CPU、网速)
sudo add-apt-repository ppa:fossfreedom/indicator-sysmonitor
sudo apt update
sudo apt install indicator-sysmonitor
# indicator-sysmonitor & 启动
# 或者手动安装下载好的Deb软件包(当无法使用命令安装时)
# https://launchpad.net/ubuntu/artful/amd64/libaio1/0.3.110-4
sudo dpkg -i libaio1_0.3.110-4_amd64.deb
dpkg -L libaio1
# https://launchpad.net/ubuntu/artful/amd64/libmecab2/0.996-3.1 # libmecab2
sudo dpkg -i **.deb # 安装 查看-l
dpkg -l |grep minitube # 查看安装好的包
dpkg -L minitube # 查看是否安装
sudo dpkg --purge minitube
# gdebi可以帮助我们自动安装依赖包
# https://launchpad.net/ubuntu/artful/amd64/gdebi-core/0.9.5.7+nmu1ubuntu3
# https://launchpad.net/ubuntu/bionic/amd64/gdebi/0.9.5.7+nmu1ubuntu3
sudo dpkg -i gdebi-core_0.9.5.7+nmu1ubuntu3_all.deb
sudo dpkg -i gdebi_0.9.5.7+nmu1ubuntu3_all.deb
gdebi --version
sudo gdebi *.deb

apt-get的代理问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
### 使用 http 代理
apt-get -o Acquire::http::proxy="http://127.0.0.1:8087" update
### 使用 socket 代理, 使用 proxychains4
sudo vim /etc/apt/sources.list
deb http://linux-packages.resilio.com/resilio-sync/deb resilio-sync non-free
# 要关联google,还需要改变此文件并重启网络
sudo vim /etc/resolv.conf
nameserver 8.8.8.8
sudo service networking restart
# 加密钥
proxychains4 wget -qO - https://linux-packages.resilio.com/resilio-sync/key.asc | sudo apt-key add -
sudo proxychains4 apt-get update
sudo apt-get -f install
sudo proxychains4 apt-get install resilio-sync

其它方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
vim /proc/meminfo 文件来观察LINux系统上虚拟内存的当前状态.
curl https://wordpress.org/latest.tar.gz | tar -zxvf -
curl url/install.sh | sh
wget -O redis.tar.gz \
http://download.redis.io/releases/redis-3.2.0.tar.gz
tar -zxvf redis.tar.gz -C /usr/src
make -C /usr/src/redis \
&& make -C /usr/src/redis/src install
# tar解压重命名
mkdir ./jdk8 && tar -xzvf jdk8.tar.gz -C ./jdk8 --strip-components 1
wget https://ghost.org/zip/ghost-latest.zip
unzip -uo ghost-latest.zip -d /var/www/ghost
wget -qO- url/install.sh | sh
wget --no-check-certificate url/install.sh -O ./install.sh
curl -LOk https://ghost.org/zip/ghost-latest.zip
unzip ghost-latest.zip -d ghost-temp
curl http://localhost:8000/wrap?name=femn& get()
http://localhost:8000/wrap -d name=femn post()方法请求
# Siege对我们的应用在10秒内执行大约10个并发请求
siege http://localhost:8000/?q=pants -c10 -t10s

文件和目录操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ll linux.md
# ll 查看的信息 第二列是链接数量
ll |grep -v linux.md # 取反
history | grep -c rar # 计算匹配的数量
touch file1 # 创建文件
file file1 # 查看文件类型
cp -Rf source destination
# -R 允许你通过命令递归地复制整个目录的内容.
# -f 强制覆盖已经存在的目标文件,不提示
# -l 硬链接,它们是同一个文件,索引节点是相同的
\cp -R dir1 dir2 # 同上
\cp -R content/themes/casper ../ghost/content/themes
rm -rf file1
tail -f -n 20 /etc/passwd
# 软连接可以不用事先创建目录,硬连接必须先创建目录
sudo ln -s /home/femn/file /usr/share/nginx/html/file
mkdir ~/file
# mv排除文件
# 将本路径下所有的文件移动到本路径下vimrc_config目录下,
# 但vimrc_config本身不动
mv !(vimrc_config) vimrc_config
# 但此命令不包括mv 隐藏文件
rm命令类似
sort -t ':' -k 3 -n /etc/passwd
# 按字段分隔数据,按分隔后的第几个进行排序
scp -r -P 22 root@ip:/server/app/main/handlers \
./main/handlers/
scp -P 22 ./1.txt python@ip:/usr/share/nginx/html/file/
# 只需要file目录有写权限,普通用户就可以用了
rsync -av -e "ssh -p 22" /home/python/app1.6/main/ \
root@ip:/server/app1.6/main/
rsync -av -e "ssh -p 22" /home/python/oldapp_8000/ \
root@ip:/server/app/main/
# 截图
#你将可以使用鼠标选取一个矩形框.在你放下鼠标左键的那一刻,
# 一个该矩形框的截屏会以import后面跟的文件名保存在当前目录下
import screenshot.jpg
gnome-screenshot -a
# 改变视频文件大小(分辨率)http://blog.topspeedsnail.com/archives/1699
ffmpeg -i source.mpg -s 960x540 -c:a copy destination.mp4
# Linux系统将每个对象当作文件来处理,这包括输入和输入的过程.Linux用文件描述符来标识每个文件对象.

解压缩

1
2
3
4
5
6
7
8
9
10
11
# tar
tar -zcvf /home/name/conclusion.tar.gz ./conclusion
tar -zxvf ./conclusion.tar.gz
tar -cvf /tmp/etc.tar /etc <==仅打包,不压缩
# rar
rar x file.rar # 解压
# zip
zip test.zip test
unzip test.zip

环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
printenv # 查看所有全局环境变量
set # 会显示为某个特定进程设置的所有环境变量
# 设置PATH环境变量
vim ~/.profile
export JAVA_HOME=/home/name/jdk1.8.0_92
export ANT_HOME=/home/name/apache-ant-1.9.7
PATH=${PATH}:/home/name/jdk1.8.0_92/bin:/home/name/apache/bin
# 设置局部变量(只能在定义的进程可见)
femn='hello,my name is femn'
# 设置全局变量
export femn # 记住不要再用美元符($)
# 删除环境变量
unset femn

ubuntu ip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 禁用IPV6
vim /etc/modprobe.d/blacklist.conf
#在文档最后添加 blacklist ipv6
# 设置一ip
sudo ifconfig enp0s3 192.168.1.101 netmask 255.255.255.0
sudo route add default gw 192.168.1.254
# 设置二ip
sudo vim /etc/network/interfaces
auto enp0s3
iface enp0s3 inet static # dhcp
address 192.168.1.101
gateway 192.168.1.254
netmask 255.255.255.0
#network 192.168.1.0
#broadcast 192.168.1.255
# 配置DNS
sudo vim/etc/resolv.conf
nameserver 127.0.1.1
# nameserver 8.8.8.8
search DHCP HOST
# 手动重启网络服务
sudo /etc/init.d/networking restart
#实在没有生效 reboot
# /etc/hosts:ip与域名的对应关系,DNS记录信息,
# /etc/hostname:存放的是主机名.域名与主机名没有任何关系
sudo hostname python-Vostro-3901 # 修改主机名 ubuntu
sudo hostnamectl set-hostname intl.aliyun.centos7
# 修改主机名 centos
# 永久修改主机名
vim /etc/hostname # ubuntu centos

centos7 ip

1
2
3
4
5
6
7
vim /etc/sysconfig/network-scripts/ifcfg-网卡名字
BOOTPROTO=static #dhcp改为static
ONBOOT=yes #开机启用本配置
IPADDR=192.168.1.107 #静态IP
GATEWAY=192.168.1.254 # 默认网关
NETMASK=255.255.255.0 #子网掩码
service network restart
查看开启端口
1
2
3
4
5
6
# 查看本机的端口开启情况
sudo nmap -sTU localhost
# 查看局域网的端口开启情况
sudo nmap -PS 192.168.1.222
# 查看远程服务器的端口开启情况
nc -zv 45.76.0.178 22 22334
Share Comments

常用软件安装

install python

win:选择Windows x86-64 executable installer(带有pip)
linux

1
sudo apt-get install -y python3-pip

mac

1
2
# 先安装homebrew工具,就像Ubuntu的apt-get一样
brew install python3

install mongodb

win: 选择https://www.mongodb.com/dr/fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-3.4.9-signed.msi/download

1
2
3
4
# 用系统命令运行,再以服务的方式运行
mongod --bind_ip 0.0.0.0 --logpath D:\data\log\mongo.log --logappend --dbpath D:\data --port 27017 --serviceName "MongoDB" --serviceDisplayName "MongoDB" --install
mongo

linux

1
2
sudo apt-get install -y mongodb
mongo

mac

1
2
3
brew install mongodb
mongod
mongo

mongodb图形界面

install redis

win:选择 https://github.com/MicrosoftArchive/redis/releases
linux

1
2
3
4
5
6
7
sudo apt-get install -y redis-server
redis-cli
vim /etc/redis/redis.conf
# bind 127.0.0.1 #远程可以访问redis,不然就只能本地访问
requirepass passwd # 设置密码
sudo service redis restart
redis-cli -a passwd

mac

1
2
3
4
5
6
7
brew install redis
redis-cli
vim /usr/local/etc/redis.conf
# bind 127.0.0.1
requirepass passwd
brew services start redis
brew services restart redis

win install redis desktop

install mysql

win(32/64一样): https://dev.mysql.com/downloads/installer/
mysql-front(mysql可视化客户端)

linux

1
2
3
4
5
sudo apt-get install -y mysql-server mysql-client
mysql -uroot -p
vim /etc/mysql/mysql.conf.d/mysql.conf
# bind-address = 127.0.0.1 #注释掉就可以远程访问了
sudo service mysql restart

mac

1
brew install mysql

install anaconda

安装好了Anaconda就相当于安装好了python和pip,并且里面还集成了很多关于python科学计算的第三方库

Anaconda 安装包可以到 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下载
安装好Anaconda之后,可以用conda install numpy来管理python包,就像pip一样

python多版本共存配置

1
ln -s /usr/bin/python3.5 /usr/bin/python3

Share Comments

python 对象

面向对象编程:Object Oriented Programming,简称OOP

把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),
通过类的封装(encapsulation)隐藏内部细节
通过继承(inheritance)实现类的特化(specialization)(重写)/泛化(generalization)
通过多态(polymorphism)实现基于对象类型的动态分派(dynamic dispatch)

多态:不同对象的相同方法的不同行为

在JAVA在继承-重写才能体现多态,在python中,不知道对象到底是什么类型,但是又要对对象做点什么的时候,都会用到多态.很多的函数和运算符都是多态.唯一能毁掉多态的就是显式的检查类型

1
2
3
4
5
1+2 # 3
'fe'+'mn' # femn
'abc'.count('a') # 1
[1,2,'a'].count('a') # 1

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class A(object):
def __init__(self):
print( "A.__init__" )
class B(A):
def __init__(self):
super().__init__()
print("B.__init__" )
class C(B):
def __init__(self):
super().__init__()
print("C.__init__" )
class D(A):
def __init__(self):
super().__init__()
print("D.__init__" )
class E(D):
def __init__(self):
super().__init__()
print("E.__init__" )
class F(C,E):# C,E 相当于是子 父关系
def __init__(self):
super().__init__()
print("F.__init__" )
if __name__ == '__main__':
f = F()
# A.__init__
# D.__init__
# E.__init__
# B.__init__
# C.__init__
# F.__init__

对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表. 这个MRO列表就是一个简单的所有基类的线性顺序表.例如

1
2
3
4
5
>>> F.__mro__
(class'__main__.F'>,<class'__main__.C'>,<class'__main__.B'>,
<class'__main__.E'>,<class'__main__.D'>,<class'__main__.A'>,
<class'object'>)

为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止
而这个MRO列表的构造是通过一个C3线性化算法来实现的,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  1. 子类会先于父类被检查
  2. 多个父类会根据它们在列表中的顺序被检查
  3. 如果对下一个类存在两个合法的选择,选择第一个父类

C3算法特点

  1. 本地优先级:指声明时父类的顺序,比如F(C,E),如果访问F类对象属性时,应该根据声明顺序,优先查找C类,然后再查找E类.

  2. 单调性:如果在F的解析顺序中,C排在E的前面,那么在F的所有子类里,也必须满足这个顺序

类(Class):

用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.
类创建后,类命名空间中所有的命名都是有效属性名

对象:类的实例化

通过类定义的数据结构实例.对象包括两个数据成员(类变量和实例变量)和方法.
对象基本上可以看作数据(特征)以及由一系列可以存取,操作这些数据的方法所组成的集合.
使用对象替代全局变量和函数的原因可以有很多.多态(鸭子类型),封装,继承
封装和继承是被用作现实世界中对象的模型

1
2
3
4
5
6
7
8
9
10
11
12
class Person(dict):
def __init__(self,name):
super(Person,self).__init__()
self.name = name
def get_name(self):
return self.name
if __name__ == '__main__':
p = Person('femn')
print(p) # {} self本身为空 {} ,但其属性不为空
print(p.get_name()) # femn
# 如果知道p是Person类的实例的话,
# 就可以把p.get_name()看作是Person.get_name(p)的简写.

属性:域,字段,成员变量,全局变量,数据成员(数据),私有成员,成员对象

属性:这个词来称呼任何点后面跟的名称 — 比如,在表达式z.real中,real就是对象z的属性.
更直接的说,对模块中名称的引用就是属性引用:在表达式 modname.funcname 中,
modname是模块对象而funcname是它的一个属性.
在这种情况下模块的属性和它里面所定义的全局名称之间就刚好有一个直接的映射关系:他们共享同一个命名空间

类变量:在整个实例化的对象中是公用的.定义在类中且在函数体之外,通常不作为实例变量使用.

实例变量:定义在方法中的变量,只作用于当前实例的类

property()方法实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self, size):
"""访问器方法,把所有的属性都放到访问器方法中"""
self.width, self.height = size
def getSize(self):
return self.width, self.height
size = property(getSize, setSize)
# 类属性,其中的访问器方法被用作参数(先取值,然后是赋值)
r = Rectangle()
r.width = 10
r.height = 5
r.size # (10, 5)
r.size = 150, 100
r.width # 150

@property 装饰器实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Person(object):
def __init__(self,name,age):
"""Constructor"""
self.name = ''
self.age = 0
# 修改属性的值 一
def getName(self):
print('property()')
return self._name
def setName(self,name):
""" 要使用_name 将实例属性name标记成protected变量
不然就将name类属性,改个名字,
只要满足类属性和实例属性不同名,否则会进入无限的递归"""
self._name = name # protected实例变量:_name
name = property(getName,setName) # publie类变量:name
__aim = 'become stronger' # private 类变量:__aim
# @property装饰器:
# 将方法变成了属性,我们可以使用正常的点符号访问它,但无法对属性值直接修改
@property
def info(self):
""" Return my info """
return "my name is %s, age is %s" % (self.name, self.age)
# 修改属性的值 二
#一个property其实是 getter、setter 和 deleter 方法的集合,而不是单个方法
# 访问它的时候会自动触发 getter 、setter 和 deleter 方法
@property
def age(self):
print('@property装饰器')
return self._age
@age.setter
def age(self,age):
self._age = age
@age.deleter
def age(self):
raise AttributeError("Can't delete attribute")
if __name__ == '__main__':
p = Person('leipengkai',25)
# 方法一
p.set_name = 'femn1'
p.name = 'femn1.1'
print(p.name)
# 方法二
p.age = 18
print(p.age)
# 输出如下
# property()
# femn1.1
# @property生成器
# 18

方法(绑定方法,动作)

  1. 绑定方法:将它们的第一参数绑定到所属的实例上,因此您无须显示提供该参数.当然也可以将特征绑定到一个普通的函数上,这样就不会有特殊的self参数了

  2. 函数:self参数是对象自身的引用.它正是方法和函数的区别.
    在java中,方法和函数是一样的.在python中,方法(self)必须在类中,而函数可以在任意位置定义

  3. 静态方法:无法访问类属性、实例属性,没有self参数.相当于一个相对独立的方法,
    跟类其实没什么关系,换个角度来讲,其实就是放在一个类的作用域里的函数而已.
    被装入staticmethod类型的对象中.

  4. 类成员方法:可以访问类属性,无法访问实例属性.
    需要名为cls的类似于self的参数,可以用类的具体对象调用.cls自动被绑定到类中
    被装入classmethod类型的对象中

    1
    2
    3
    4
    5
    class A():
    a =1 #类属性
    def __init__(self,b,c)
    self.b=b
    self.c=c#实例属性

参数

1
2
3
4
5
6
7
8
9
10
11
12
def print_params(x, y, z=3, *pospar, **keypar): #**代表命名参数
print(x,y,z)
print(pospar)
print(keypar)
print_params(1,2,3,5,6,7,foo=1, bar=2)
1 2 3
(5, 6, 7)
{'foo': 1, 'bar': 2}
print_params(1,2,foo=1, bar=2)
1 2 3
()
{'foo': 1, 'bar': 2}

Python 的作用域和命名空间

命名空间(namespace)是从名称(标识符)到对象的映射,可以将其理解为字典,各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响

如果狭义点的话,可以将命名空间简单的理解为是变量(包括类和内置类型等)的引用.

四个namespace:一个函数的所有local,一个类对象的所有属性(数据成员,成员函数),一个模块的global(这个模块定义的函数,类,变量)和built-in(包括内置函数,内置常量,内置类型)

命名空间都是有创建时间和生存期的:

  1. 对于Python built-in names组成的命名空间,它在Python解释器启动的时候被创建,
    在解释器退出的时候才被删除.内置名称实际上也存在于一个模块中; 这个模块叫 builtins.

  2. 对于一个Python模块的global namespace,它在这个module被import的时候创建,
    在解释器退出的时候退出.被最高级别的解释器调用的语句,不论是从脚本还是从交互读取的,
    都被认为是一个名叫main的模块的一部分,所以它们有自己的全局命名空间.

  3. 对于一个函数的local namespace,它在函数每次被调用的时候创建,
    函数返回的时候被删除.当然,递归调用会有它们自己的局部命名空间

总结来说:一个模块的引入,类的定义,函数的调用都会引入命名空间

在执行过程中遇到了某个标识符(名称)时,Python首先尝试在local命名空间中查找它,如果没有找到,
再在global命名空间中查找,如果还是没有找到,接着在built-in命名空间中查找.
如果都不存在,则被认为是一个错误,会抛出一个”NameError”异常

变量的作用域(scope):

是Python程序的文本区域,在该区域某个命名空间中的名字可以被直接引用.你所申明的变量(包括类和内置变量等)可以在哪些地方使用.

一个Python程序的几个作用域:

  1. 最里面的局部作用域,基本类型的作用域:(从变量定义处开始,以到结束此方法时截止)
  2. 外层函数的局部作用域:对象的作用域:(对象出了方法外后,只是引用消失了,但是对象本身还在堆中.资源回收站会定期回收垃圾对象)
  3. 模块的全局作用域
  4. 包含Python内置对象的最外层作用域

赋值(assignment):

赋值操作不会拷贝,只是把标识符和对象做一个绑定,也就是说赋值操作就是名字和对象的绑定或重绑定,也可以说赋值是把原来对象的引用传递给另一个引用!

global声明的变量会引用到当前模块的全局命名空间的变量

nonlocal:用于声明非全局的外层变量.这个声明会从声明处从里到外的namespace去搜寻这个变量,直到模块的全局域(不包括全局域)

找到了则引用这个命名空间的这个名字和对象,若作赋值操作,则直接改变外层域中的这个名字的绑定.如果在外层域中没有找到,则会报错

这是一个例子用于说明如何引用不同的作用域和命名空间, global 和 nonlocal 如何影响变量绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
# test spam
do_nonlocal()
print("After nonlocal assignment:", spam)
# nonlocal spam
do_global()
print("After global assignment:", spam)
# nonlocal spam
if __name__ == '__main__':
scope_test()
print("In global scope:", spam)
# global spam

注意局部的赋值(默认)并没有改变scope_test绑定的spam.
而nonlocal则改变了scope_test中的spam,而global则改变了模块级别的绑定.

你可以看到在global赋值之前并没有绑定spam的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def test():
def do_nonlocal():
nonlocal spam
print("asssignment:", spam)
def do_nonlocal2():
nonlocal spam
spam = "nonlocal2 spam" #修改了test()中spam的绑定
do_nonlocal2()
spam = "test spam"
do_nonlocal()
print("after nonlocal2 asssignment:", spam)
if __name__ == '__main__':
test()
# asssignment: test spam
# after nonlocal2 asssignment: nonlocal2 spam
# do_nonlocal2()中的spam引用的是其外面的二层,也就是test()函数域中的spam

总结:Python会按照从内层到外层的顺序逐个寻找作用域中的变量(局部到全局),在函数内引用全局变量不需要global,函数内修改全局变量要加global,nonlocal也是一样

Share Comments

多任务实现之线程-threads

多线程:共享内存空间,提高CPU使用效率

进程是资源分配的单位,线程是CPU调度的单位:启动一个进程之后,会有一个主线程去依次执行

进程号0:用来进程之间的切换,而进程号1:用来生成新进程(所有进程的父进程)

进程与线程创建多任务的区别

创建了5个线程,分别各个执行,不会相互干扰
同时即使主线程完成任务了,也不会退出,而是要等到子线程完成之后才退出,往往是为了收回子线程的资源

线程例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import threading
from time import ctime,sleep
def music(func):
for i in range(2):
print( "I was listening to %s. %s" %(func,ctime()))
sleep(1)
def move(func):
for i in range(2):
print( "I was at the %s! %s" %(func,ctime()))
sleep(5)
# 主线程中 加两个子线程
threads = [] # 装载多个线程的数组 看着像是同时进行的
t1 = threading.Thread(target=music,args=(u'爱情买卖',))
threads.append(t1)
t2 = threading.Thread(target=move,args=(u'阿凡达',))
threads.append(t2)
if __name__ == '__main__':
for t in threads:# 子线程
t.setDaemon(True)
""" 线程声明为守护线程,必须在start() 方法调用之前设置
如果不设置为守护线程程序会被无限挂起.
子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print "all over %s" %ctime()后
没有等待子线程,直接就退出了,同时子线程也一同结束"""
t.start()#开始线程活动
t.join()
""" 在子线程完成运行之前,这个子线程的父线程将一直被阻塞.join()实际上意味着等到队列为空,再执行别的操作
join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程 """
print( "all over %s" %ctime()) #主线程

线程例子2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import _thread
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
#继承父类threading.Thread
def __init__(self, delay):
threading.Thread.__init__(self)
self.delay = delay
def run(self):
#把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
print( "Starting " + self.name)
# name属性中保存的是当前线程的名字
print_time(threadName=self.name,delay=self.delay,counter=5)
print( "Exiting " + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
_thread.exit()
time.sleep(delay)
print( "%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
thread1 = myThread( delay=1)
thread2 = myThread( delay=2)
# 开启线程
thread1.start()
thread2.start()
print( "Exiting Main Thread")

多线程的复杂度

• 资源、数据的安全性:锁保护
• 原子性:数据操作是天然互斥的(不可分割,要不成功,要不失败)
• 同步等待:wait() notify() notifyAll()
• 死锁:多个线程对资源互锁,造成死锁
• 容灾:任何线程出现错误,整个进程都会停止

互斥问题

死锁:在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源,就会造成死锁

线程同进程的执行顺利一样都是不确定的,是由调度算法决定的

线程之间共享全局变量,进程不共享,所以才有进程间的通信,所以才有线程的资源竞争,互斥,死锁等问题
同时创建线程时,传递的参数是列表也是共享的

互斥锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from threading import Thread, Lock
import time
# 参数
l = [22,33]
#线程之间共享全局变量
g_num = 0
def test1(l):
global g_num
#这个线程和test2线程都在抢着对这个锁进行上锁,如果有1方成功的上锁,那么导致另外
#一方会堵塞(一直等待)到这个锁被解开为止
mutex.acquire()
for i in range(1000000):
g_num += 1
mutex.release()#用来对mutex指向的这个锁 进行解锁,,,只要开了锁,那么接下来会让所有因为
#这个锁 被上了锁 而堵塞的线程 进行抢着上锁
print("---test1---g_num=%d"%g_num)
l.append(11)
print(l)
def test2(l):
global g_num
mutex.acquire()
for i in range(1000000):
g_num += 1
mutex.release()
print("---test2---g_num=%d"%g_num)
l.append(44)
print(l)
#创建一把互斥锁,这个锁默认是没有上锁的
mutex = Lock()
p1 = Thread(target=test1,args=(l,))
p1.start()
#延时一会,保证p1线程中的事情做完
#time.sleep(3) #取消屏蔽之后 再次运行程序,结果会不一样
p2 = Thread(target=test2,args=(l,))
p2.start()
print("---g_num=%d---"%g_num)

全局变量在多个线程中共享,为了保证正确运行需要锁
非全局变量在每个线程中各有一份,不会共享,当然了不需要加锁

同步线程(继承类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import threading
from datetime import datetime
import time
import _thread
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, delay):
threading.Thread.__init__(self)
# self.threadID = threadID
self.delay = delay
def run(self):
print( "Starting " + self.name)
# 如果多个线程共同对某个数据修改,则可能出现不可预料的结果,
# 为了保证数据的正确性,需要对多个线程进行同步.
# 线程同步(为了确保数据的完整没有被其它的线程修改,必须要得锁和解锁
# 所以也导致了要一个线程全部结束之后,才能执行下一个的线程)
# 这会导致同步阻塞,也就是只有一个线程工作完之后,才能进行下一个线程
# 获得锁,成功获得锁定后返回True
# 可选的timeout参数不填时将一直阻塞直到获得锁定
# 否则超时后将返回False
threadLock.acquire()
print_time(threadName=self.name, delay=self.delay, counter=5)
# 释放锁
threadLock.release()
print( "Exiting " + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
_thread.exit()
time.sleep(delay)
print( "%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
# 线程同步
threadLock = threading.Lock()
threads = []
thread1 = myThread( delay=1)
thread2 = myThread(delay=2)
# 开启线程
n = datetime.now()
thread1.start()
thread1.join()#因为没有queue可继续执行下面的线程
thread2.start() #
# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)
# 等待所有线程完成
for t in threads:
t.join()
print("Exiting Main Thread")
print(datetime.now()-n)

线程quere用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import queue, time, threading, datetime
class Job:
def __init__(self, name):
self.name = name
def do(self):
time.sleep(2)
print("\t[Info] Job({0}) is done!".format(self.name))
que = queue.Queue()
#FIFO即First in First Out,先进先出 a[len(a):1]=[queue] 加在后面的
# lifoqueue = queue.LifoQueue()#LIFO即Last in First Out
# 后进先出 a[0:0]=[queue] 加在最前面的 入栈 出栈
for i in range(20):
# 保存要去工作的信息 在queue中
que.put(Job(str(i + 1)))
#调用队列对象的put()方法在队尾插入一个项目
# put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为1
# 如果队列当前满且block为1,put()方法就使调用线程暂停,直到空出一个数据单元.
# 如果block为0,put方法将引发Queue.Full异常
print("\t[Info] Queue size={0}...".format(que.qsize()))#返回队列的大小
# # Start activity to digest queue.
st = datetime.datetime.now()
#需要40秒
while que.qsize() > 0:
job = que.get()
job.do()
td = datetime.datetime.now() - st
print("\t[Info] Spending time={0}!".format(td))
#q.qsize() 返回队列的大小
# q.empty() 如果队列为空,返回True,反之False
# q.full() 如果队列满了,返回True,反之False
# q.full 与 maxsize 大小对应
# q.get([block[, timeout]]) 获取队列,timeout等待时间
# q.get_nowait() 相当q.get(False)
# 非阻塞 q.put(item) 写入队列,timeout等待时间
# q.put_nowait(item) 相当q.put(item, False)
# q.task_done() 在完成一项工作之后,q.task_done()函数向任务已经完成的队列发送一个信号
# q.join() 实际上意味着等到队列为空,再执行别的操作

线程quere

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import queue, time, threading, datetime
class Job:
def __init__(self, name):
self.name = name
def do(self):
time.sleep(2)
print("\t[Info] Job({0}) is done!".format(self.name))
que = queue.Queue()
for i in range(20):
que.put(Job(str(i + 1)))
print("\t[Info] Queue size={0}...".format(que.qsize()))
def doJob(*args):
queue = args[0]
while queue.qsize() > 0:
job = queue.get()
#调用队列对象的get()方法从队头删除并返回一个项目.可选参数为block,默认为True.
# 如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用.
# 如果队列为空且block为False,队列将引发Queue.Empty异常
job.do()
# Open three threads
thd1 = threading.Thread(target=doJob, name='Thd1', args=(que,))
thd2 = threading.Thread(target=doJob, name='Thd2', args=(que,))
thd3 = threading.Thread(target=doJob, name='Thd3', args=(que,))
# thd4 = threading.Thread(target=doJob, name='Thd3', args=(que,))
# thd5 = threading.Thread(target=doJob, name='Thd3', args=(que,))
# # Start activity to digest queue.
st = datetime.datetime.now()
thd1.start()
thd2.start()
thd3.start()
# thd1.join()
#在子线程完成运行之前,这个子线程的父线程将一直被阻塞.
# join() 实际上意味着等到队列为空,再执行别的操作
#這會讓呼叫 join() 方法的線程被 blocked, 一直到被呼叫 join() 的線程結束為止
#不会再进行下面的线程了
# thd4.start()
# thd5.start()
#首先當 Thread 類別被實例化, 你可以呼叫物件上面的方法 start() 來啟動該線程,
# 一旦線程啟動, 它的狀態會變成 "alive" ;
# Wait for all threads to terminate.
while thd1.is_alive() or thd2.is_alive() or thd3.is_alive():
time.sleep(1)
# 當執行完畢 run() 後狀態便不在是 "alive".
td = datetime.datetime.now() - st
print("\t[Info] Spending time={0}!".format(td))

推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import queue
import threading
from datetime import datetime
SHARE_Q = queue.Queue() # 构造一个不限制大小的的队列
_WORKER_THREAD_NUM = 4 # 设置线程的个数
def worker():
global SHARE_Q
while not SHARE_Q.empty():
one_dict = SHARE_Q.get() # 获得任务
# to worker ...
SHARE_Q.task_done()
def main():
a= datetime.now()
threads = []
global SHARE_Q
library_list =['9787550215184','9787550206267']
for i in library_list:
SHARE_Q.put(i)
for i in range(_WORKER_THREAD_NUM):
thr = threading.Thread(target=worker)
# 必须要有 target去指定函数
thr.start()
threads.append(thr)
for thread in threads:
thread.join()
SHARE_Q.join()
print(datetime.now() -a)#0:00:00.376745
print('success')
if __name__ == '__main__':
main()

Share Comments

进程与线程

性能:在其他同等条件下,高性能的程序应该可以等同于CPU的利用率,CPU的利用率越高(一直在工作,没有闲下来的时候),程序的性能越高.

体验:这里的体验不只是界面多么漂亮,功能多么顺手,这里的体验指程序的响应速度,响应速度越快,用户体验越好.

由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法.

另一种解决IO问题的方法是异步IO.当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了.
一段时间后,当IO返回结果时,再通知CPU进行处理,在处理 IO 的时候,阻塞和非阻塞都是同步 IO. 只有使用了特殊的 API 才是异步 IO.

首先对一些名词的理解

被调用方:同步与异步关乎做事情的方式.

同步:做完一件事再去做另一件. 异步:同时做多件事情,某个事情有结果了再去处理(又一个新事情)我的理解: 被调用方有没有能力同时处理问题的能力以及回调的功能

同步和异步关注的是消息通信机制(synchronous communication/ asynchronous communication):
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦调用返回,就得到返回值了
换句话说,就是由调用者主动等待这个调用的结果

而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果.换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果.
而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

典型的异步编程模型比如Node.js

1
2
3
4
5
6
7
举个通俗的例子: 你打电话问书店老板有没有《分布式系统》这本书
如果是同步通信机制,书店老板会说,你稍等,"我查一下",
然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果).
而异步通信机制,书店老板直接告诉你我查一下啊,
查好了打电话给你,然后直接挂电话了(不返回结果).
然后查好了,他会主动打电话给你.在这里老板通过"回电"这种方式来回调.

回调异步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import time
import threading
def long_io(callback):
"""将耗时的操作交给另一线程来处理"""
def fun(cb): # 回调函数作为参数
"""耗时操作"""
print( "开始执行IO操作")
time.sleep(5)
print("完成IO操作,并执行回调函数")
cb("io result") # 执行回调函数
threading._start_new_thread(fun, (callback,)) # 开启线程执行耗时操作
def on_finish(ret):
"""回调函数"""
print( "开始执行回调函数on_finish")
print( "ret: %s" % ret)
print( "完成执行回调函数on_finish")
# 可读性差,要结合多个函数
def req_a():
print( "开始处理请求req_a" )
long_io(on_finish)
print( "离开处理请求req_a")
def req_b():
print( "开始处理请求req_b")
time.sleep(2) # 添加此句来突出显示程序执行的过程
print( "完成处理请求req_b")
def main():
req_a()
req_b()
while 1: # 添加此句防止程序退出,保证线程可以执行完
pass
if __name__ == '__main__':
main()

调用方:阻塞与非阻塞关乎如何对待事情产生的结果.关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起.调用线程只有在得到结果之后才会返回. 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

阻塞:不等到想要的结果我就不走了. 非阻塞:有结果我就带走,没结果我就空手而回,总之一句话:爷等不起

1
2
3
4
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己"挂起"
直到得到这本书有没有的结果,如果是非阻塞式调用
你不管老板有没有告诉你,你自己先一边去玩了
当然你也要偶尔过几分钟check一下老板有没有返回结果
1
2
3
4
5
6
7
8
9
10
11
老张爱喝茶,废话不说,煮开水. 出场人物:
老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶).
1 老张把水壶放到火上,立等水开.(同步阻塞) 老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有.(同步非阻塞)
老张还是觉得自己有点傻,于是买了把会响笛的那种水壶.水开之后,能大声发出嘀~~~~的噪音.
3 老张把响水壶放到火上,立等水开.(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶.(异步非阻塞)

并发与并行

并发指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行(纳秒级),使得在宏观上具有多个进程同时执行的效果.
只能运行一个进程,CPU不断地在这些进程之间轮换执行,

并行指在同一时刻,有多条指令在多个处理器上同时执行;多个CPU处理器

进程(拥有独立的内存单元,chrome使用多进程)与线程(也被称作轻量级进程)

进程:程序的一次执行. 线程:CPU的基本调度单位 ,而在Linux系统里面,在最底层,线程和进程确实是不区分的.

进程是系统进行资源分配和调度的一个独立单位,线程是进程的执行单元,在进程中是独立的、并发的执行流线程的调度和管理,由进程本身负责完成.

当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程.当一个程序进入内存,运行后,即变成一个进程.

进程的三个特性

  1. 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间.在没有经过进程本身允许的情况下,一个用户进程不可以直接就访问其他进程的地址空间.

  2. 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合.在进程中加入了时间的概念.进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的.

  3. 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响

车间:

一个进程与线程的一个简单解释
程序(车间,有独立的内存空间)之间进行内存数据交换(通信)比较麻烦:每个程序的内存空间都是被保护的,不能被别的程序直接访问,所以要通过某种介质,管道或第三方工具,去通信.
nosql(第三方):将内存的数据缓存进来,供其它的程序调用,所有的程序都可以往里存数据,所有的程序都可以取数据.相当于实现了一个共享的内存空间

餐桌:

一群人(多个线程)在一个桌子(进程)上吃饭,他们会涉及到一些问题,比如多个人可能会夹一个菜(竞争)
A和B同时看到盘子里面有一块肉,同时伸出筷子去夹,A先夹走,B迟了一点伸到盘子的时候已经没了,只能缩回来(临界资源,互斥)
有一个点心需要用馍夹肉一起吃.A夹了肉,B夹了馍,A需要B的馍,B需要A的肉,他们僵持不下谁都不让步(死锁).
多线程之间的资源共享是非常方便的,因为他们共用进程的资源空间(在一个桌子上),但是需要注意一系列的问题,竞争,死锁,同步等

如果在旁边再开一个桌子(进程). 那么桌子之间讲话,递东西又不方便(进程间通信),而开一个桌子的开销比在一个桌子上多加一个人的开销要大.
另外一个桌子上的人数不可能无限制增加,桌子的容量有限也坐不下这么多人(进程的线程句柄是有限制的).
一个桌子坏了不会影响到另一个桌子上面人的就餐情况(进程间相互独立,一个进程崩溃不会影响另一个),而一个桌子上的某人喝挂了需要送医院,
估计这一桌人都要散了(线程挂掉会导致整个进程也挂掉).所以多线程与多进程是各有优缺点,不能一概而论

  1. 对于 Windows系统来说,【开桌子】的开销很大,因此Windows鼓励大家在一个桌子上吃菜.因此Windows多线程学习重点是要大量面对资源争抢与同步方面的问题.
  2. 对于Linux系统来说,【开桌子】的开销很小,因此Linux鼓励大家尽量每个人都开自己的桌子吃菜.这带来新的问题是:坐在两张不同的桌子上,说话不方便.
    因此,Linux下的学习重点大家要学习进程间通讯的方法.

单线程(单任务,按顺序去完成)和多线程

多线程则扩展了多进程的概念,使得同一个进程可以同时并发(轮换执行)处理多个任务.线程(Thread)也被称作轻量级进程(LightweightProcess),线程是进程的执行单元.

就像进程在操作系统中的地位一样,线程在进程中是独立的、并发的执行流.进程操作系统–线程进程,当线程被初始化后,主线程就被创建了对于应用程序而言,

通常至少有一个主线程,可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是相互独立的;

线程是进程的的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程,线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,
但是不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源.

因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,必须确保线程不会妨碍同一进程里的其他线程.线程的调度和管理由进程本身负责完成.

进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率

车间:

当要完成一项工作或多项工作时(但还是在一个主线程中),单线程就好比一个工人,而多线程就是招了多个工人一起干活,效率当然快了!
但管理就费劲了(死锁,竟争),允许单个任务分成不同的部分运行

餐桌:

单线程就是整个餐厅只有一个单人桌,这个人吃完了,下一个人轮上.
但大餐馆用的可能是八仙桌,同时能容纳八个人吃饭,这就是多线程:从一次一个变成了一次多个或者多次多个

多线程的作用

1.把程序细分成几个功能相对独立的模块,防止其中一个功能模块阻塞导致整个程序假死(GUI程序是典型).一个线程一件事,多线程做多件事

2.提高运行效率,比如多个核同时跑,或者单核里面,某个线程进行IO操作时,另一个线程可以同时执行
这样一条主线程,处理整个程序任务的主方向的链,而其链上又有许许多多的分支,就像树枝那样,
这样,既有了主线程去处理那些主要任务,又有了那些细小线程去处理耗时费力任务,从而让界面看起来更加流畅

多线程做同一件事

多线程使得程序内部可以分出多个线程来做多件事情,而不会造成程序界面卡死.比如迅雷等多线程下载工具就是典型的多线程.
一个下载任务进来,迅雷把文件平分成10份,然后开10个线程分别下载.这时主界面是一个单独的线程,并不会因为下载文件而卡死.
而且主线程可以控制下属线程,比如某个线程下载缓慢甚至停止,主线程可以把它强行关掉并重启另外一个线程.

多线程因为在同一个进程里,所以可以共享内存和其他资源,比如迅雷里10个线程一齐下载一个文件,这个文件是由进程打开的,
然后10个线程都可以往里写入东西.如果是10个进程就不行了,操作系统不允许一个文件由两个进程同时写入

python中使用 GIL(global interpreter lock)保证了线程安全(保证数据被安全读取),即同时只能有一个线程在CPU上运行,
这个线程也不能扩散到其它CPU上,GIL是以CPU为单位去控制这个锁,对于python的多线程,多核也没有实际的提速作用,

所以多线程在Python中只能跑在一个CPU上.如果想利用多CPU的话,就使用多进程(需要给独立的内存空间,所以比较占内存资源).

当每个CPU核心运行一个进程的时候,由于每个进程的资源都独立,所以CPU核心之间切换的时候无需考虑上下文.

当每个CPU核心运行一个线程的时候,由于每个线程需要共享资源,所以这些资源必须从CPU的一个核心被复制到另外一个核心,才能继续运算,这占用了额外的开销.
换句话说,在CPU为多核的情况下,多线程在性能上不如多进程.

(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源.

进程与线程的区别

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口.但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,
但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配

进程和线程的主要差别在于它们是不同的操作系统资源管理方式.进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径.线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮

参考于此

Share Comments

生成器与协程

生成器(generator):是一种⼀边循环⼀边计算的机制

是一种用普通函数语法定义的迭代器.生成器一定是迭代器(反之不成立)

创建生成器

第⼀种⽅法很简单,只要把⼀个列表⽣成式的[] 改成()

生成器推导式
1
2
3
4
d=(x*x for x in range(10))
sum(d)
# 285
d.next() # StopIteration sum(d)已经迭代完了
yield

yield 保持简洁性的同时获得了 iterable 的效果.把一个函数改写为一个 generator 就获得了迭代能力

yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器

yield 进程挂起 保持连接

使用生成器,定义斐波那契数列

1
2
3
4
5
6
7
8
9
10
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b #
# yield 的作用就是把一个函数变成一个 generator
# 调用 fab(5)不会执行fab函数,而是返回一个iterable对象
a, b = b, a + b
n = n + 1
# 如果没有return,则默认执行至函数完毕,如果在执行过程中return
#则直接抛出StopIteration终止迭代

1
for n in fab(5): print n

在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时
fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行
而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield
看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值.

1
2
f = fab(5)
print(f.next())

手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),
这样我们就可以更清楚地看到 fab 的执行流程

1
2
f1 = fab(5)
print(f1.next())

每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响

多个yield

1
2
3
4
5
6
7
8
9
def test_yield():
yield 1
yield 2
yield (1,2)
g = test_yield()
print(next(g))
print(next(g))
# print(next(g))

send()

1
2
3
4
5
6
7
8
9
10
11
12
13
def gen():
i = 0
while i < 5:
temp = yield i
# temp 并不是i的值
print( temp )
i+=1
f = gen() # 不是调用了函数,而是返回了生成器 生成器的值就是返回值
print(next(f)) # 0
print(next(f)) # None 1
print(f.send('femn')) # femn 2
# c.next()等价c.send(None)

⽣成器是这样⼀个函数,它记住上⼀次返回时在函数体中的位置.对⽣成器 函数的第⼆次(或第n次)调⽤跳转⾄该函数中间,⽽上次调⽤的所有局部变量都保持不变.

⽣成器不仅”记住”了它数据状态;⽣成器还”记住”了它在流控制构造(在命令 式编程中,这种构造不只是数据值)中的位置.

⽣成器的特点:节约内存. 迭代到下⼀次的调⽤时,所使⽤的参数都是第⼀次所保留下的,即是说,在整个所有函数调⽤的参数都是第⼀次所调⽤时保留的,⽽不是新创建的

yield控制进程的暂停与send又恢复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import time
import threading
gen = None # 全局生成器,供long_io使用
def long_io():
def fun():
print("开始执行IO操作")
global gen
time.sleep(5)
try:
print("完成IO操作,并send结果唤醒挂起程序继续执行")
gen.send("io result") # 使用send返回结果并唤醒程序继续执行
except StopIteration: # 捕获生成器完成迭代,防止程序退出
pass
threading._start_new_thread(fun, ())
def req_a():
print("开始处理请求req_a")
ret = yield long_io()
print("ret: %s" % ret)
print("完成处理请求req_a")
def req_b():
print("开始处理请求req_b")
time.sleep(2)
print( "完成处理请求req_b")
def main():
global gen
gen = req_a() # 得到生成器,不能像同步一样去输写
next(gen) # 开启生成器req_a的执行
req_b()
while 1:
pass
if __name__ == '__main__':
main()

yiled多任务(协程,进程,线程)

协程:单进程单线程的多任务执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def test1():
while True:
print("---1---")
yield None
def test2():
while True:
print("---2---")
yield None
t1 = test1()
t2 = test2()
while True:
next(t1)
next(t2)
# 暂停和启动之间的快速切换

yield ⼈⼯自动切换
而协程是由程序员自己决定怎样切换执行的,并且只能有一个执行路径

计算密集型需要占用大量的CPU资源,使用多进程
IO密集型需要网络功能,大量的时间都在等待网络数据的到来,使用多线程或线程
只切换了函数的调用,只要求保存函数的变量,而不像进程和线程的切换时,需要的其它内容的上下文
进程和线程的调用是由操作系统决定切换而执行的

gevent自动切换执行

1
2
3
4
5
6
7
8
9
10
11
12
import gevent
deff(n):
fori in range(n):
print(gevent.getcurrent(),i)
#用来模拟一个耗时操作,注意不是time模块中的sleep
gevent.sleep(1)
g1=gevent.spawn(f,5)
g2=gevent.spawn(f,5)
g3=gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()

当⼀个greenlet遇到IO(指的是input output 输⼊输出,⽐如⽹络、⽂件操作等)操作时,⽐如访问⽹络,就⾃动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执⾏
由于IO操作⾮常耗时,经常使程序处于等待状态,有了gevent为我们⾃动切换协程,就保证总有greenlet在运⾏,⽽不是等待IO

协程(又称微线程(coroutine),相当于单线程的能力)与子程序(函数调用是通过栈实现的,一个线程就是执行一个子程序,子程序就是协程的一种特例)

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕.
协程(协作完成任务),tornado好像就是用协程来实现异步的协程,协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行.

协程的特点在于是一个线程执行,所有协程有如下优势

最大的优势就是协程极高的执行效率.因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显.

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,
# 通过锁机制控制队列和等待,但一不小心就可能死锁.
# 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行
# 待消费者执行完毕后,切换回生产者继续生产,效率极高
import time
def consumer():
r = ''
while True:
n = yield r
# 3.consumer通过yield拿到消息,处理完又通过yield把结果传回
if not n: return print('[CONSUMER] Consuming %s...' % n)
print(n)
time.sleep(1)
r = '200 OK'
def produce(c):
next(c) # 不是值 1. 启用生成器
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
# 这里才是迭代器中的值 2. 一旦生产了东西,通过c.send(n)切换到consumer执行
print('[PRODUCER] Consumer return: %s' % r)
# 4. produce拿到consumer处理的结果,继续生产下一条消息
c.close()
# 5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束
if __name__ == '__main__':
c = consumer()
produce(c)

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为”协程”,而非线程的抢占式多任务. 子程序就是协程的一种特例.
线程确实比协程性能更好.因为线程能利用多核达到真正的并行计算,如果任务设计的好,线程能几乎成倍的提高你的计算能力,说线程性能不好的很多 是因为没有设计好导致大量的锁,切换,等待,这些很多都是应用层的问题.
而协程因为是非抢占式,所以需要用户自己释放使用权来切换到其它协程,因此 同一时间其实只有一个协程拥有运行权,相当于单线程的能力

Share Comments

迭代

为什么使用迭代

1
for i in range(1000): pass

会导致生成一个 1000 个元素的 List

1
for i in xrange(1000): pass

则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小.

因为 xrange 不返回 List,而是返回一个 iterable 对象

迭代

迭代是一个实现可迭代对象(实现的是 iter() 方法)和迭代器(实现的是 next() 方法)的过程.

可迭代对象(Iterable):可以直接作⽤于for循环的对象统称为可迭代对象

一个实现了 iter方法的对象是可迭代的对象,该方法返回一个迭代器对象

可迭代对象的三个类型

⼀类是集合数据类型,如list,tuple,dict,set,str等
⼀类是generator,包括⽣成器和带yield的generator function
一类是处于打开状态的files,sockets 也是可迭代对象

可以使⽤isinstance()判断⼀个对象是否是Iterable对象:

1
2
3
from collections import Iterable
isinstance([], Iterable) # True
isinstance((x for x in range(10)),Iterable) # True

迭代器(iterator): 一个实现了next方法的对象

迭代器是一个带状态的对象,返回容器中的下一个值,并为下一次调用next()方法修改状态

可迭代对象是你可以从其获取到一个迭代器的任一对象.迭代器是那些允许你迭代可迭代对象的对象

我们自定义一个迭代器,以斐波那契数列为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fab(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1
def __iter__(self):
return self
def __next__(self): # python2使用next()
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
>>> for n in Fab(5): print(n)

迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用.
迭代器是⼀个可以记住遍历的位置的对象

iter()函数

⽣成器都是Iterator对象,但list,dict,str虽然是Iterable,却不是Iterator
把list,dict,str等Iterable变成Iterator可以使⽤iter()函数

1
2
3
4
5
6
7
8
9
10
it=iter([1,2,3]) iterkeys({1:"1",2:"2"}) iteritems({}) # 可迭代对象
it.next() # python3.0之前 迭代器
next(it) # 之后
# itertools函数返回的都是迭代器对象
from itertools import cycle , islice
colors = cycle(['red', 'white', 'blue'])
next(colors)
limited = islice(colors, 0, 4)
for x in limited:print x
Share Comments