i += 1 与 i = i + 1在可变数据类型的不同
def assignment_heighten():
"""增强赋值"""
list_one = [2, 3, 4]
list_two = list_one
list_two += list_one
print("l ist_two", list_two)
print("list_one", list_one)
print("list_one_id", id(list_one), "list_two_id", id(list_two))
def assignment_general():
"""普通赋值"""
list_one = [2, 3, 4]
list_two = list_one
list_two = list_one + list_two
print("list_two", list_two)
print("list_one", list_one)
print("list_one_id", id(list_one), "list_two_id", id(list_two))
assignment_heighten()
print("---------------------------")
assignment_general()
运行结果:
list_two [2, 3, 4, 2, 3, 4] list_one [2, 3, 4, 2, 3, 4] list_one_id 1725183689352 list_two_id 1725183689352
list_two [2, 3, 4, 2, 3, 4]
list_one [2, 3, 4]
list_one_id 1725183689352 list_two_id 1725183689416
a、b 在进行运算后依旧指向了同一个内存对象。例二则相反,a、b 分别指向了不同的内存对象,也就是说在例二中隐式的新建了一个内存对象。
这是一个值得注意的坑,警惕我们在使用增量赋值运算符来操作可变对象(如列表)
时可能会产生不可预测的结果。要解释这个问题,首先需要了解「Python共享引用」
的概念:在Python中,允许若干个不同的变量引用指向同一个内存对象。同时在前文
中也提到,增强赋值语句比普通赋值语句的效率更高,这是因为在 Python 源码中,
增强赋值比普通赋值多实现了“写回”的功能,也就是说增强赋值在条件符合的情况下
(例如:操作数是一个可变类型对象)会以追加的方式来进行处理,而普通赋值则会
以新建的方式进行处理。这一特点导致了增强赋值语句中的变量对象始终只有一个,
Python解析器解析该语句时不会额外创建出新的内存对象。所以例一中变量 a、b
的引用在最后依旧指向了同一个内存对象;相反,对于普通赋值运算语句,Python解
析器无法分辨语句中的两个同名变量
(例如:b = b +1)是否应该为同一内存对象,所以干脆再创建出一个新的内存对象
用来存放最后的运算结果,所以例二中的a、b,从原来指向同一内存对象,到最后分
别指向了个不同的内存对象。
这是一个不为人所熟知的问题,我们能得到的结论就是:尽量不要使用
增量赋值运算符来处理任何可变类型对象,除非你对上述问题有了足够
的了解。
多态性和鸭子类型
多态:父类的引用指向子类的对象
class Animal(object):
def running(self):
print("Animal is running")
class Dog(Animal):
def running(self):
print("Dog is running")
class Cat(Animal):
def running(self):
print("Cat is running")
class Sb:
def running(self):
print("Sb is running")
def use_method(obj):
obj.running()
use_method(Dog())
use_method(Sb())
>
结果 : Dog is running
Sb is running
你会发现,新增一个Animal的子类,不必对use_method()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态的作用:
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用running()方法,而具体调用的running()方法是作用在Animal、Dog、Cat还是Dog对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保running()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的use_method等函数。
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用running()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个running()方法就可以了这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
线程和进程实现多任务
定义
- 进程是资源的分配单位
- 线程只是CPU轮流调度单位
- 概括:进程和线程都是一个时间段的描述,是CPU工作时间段的描述。
进程和线程的深入理解
一个必须知道的事实:执行一段程序代码,实现一个功能的过程介绍 ,当得到CPU的时候,相关的资源必须也已经就位,就是显卡啊,环境变量,有内存,打开的文件,映射的网络端口须就位,然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被CPU临幸的运行环境,必须保存。串联起来的事实:前面讲过在CPU看来所有的任务都是一个一个的轮流执行的,具体的轮流方法就是:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文
========= 重要的东西出现了========
进程和线程就是这样的背景出来的,两个名词不过是对应的CPU时间段的描述,名词就是这样的功能。进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文线程是什么呢?进程的颗粒度太大,每次都要有上下的调入,保存,调出。如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:程序A得到CPU =》CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。
多任务设计
要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。
如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。
多进程的优点
多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
多进程的缺点
多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程缺点
任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程
管于多进程全局共享的一个小延伸
-- coding:utf-8 --
author = ‘bobby’
date = ‘2018/4/6 9:56’
title = ‘prodcons_queue’
from random import randint
from time import ctime, sleep
from multiprocessing import Queue,Pool,Process
# queue1 = Queue(32)
#多进程进程间不共享全局变量限于不可变类型,可变类型全局共享
#根本原因在于共享变量的是否可以追加性
a=[]
# 创建写/读入队列的方法
# -*- coding:utf-8 -*-
__author__ = 'bobby'
__date__ = '2018/4/6 9:56'
__title__ = 'prodcons_queue'
from random import randint
from time import ctime, sleep
from multiprocessing import Queue,Pool,Process
# queue1 = Queue(32)
#多进程进程间不共享全局变量限于不可变类型,可变类型全局共享
a=[] #创建了空队列
# 创建写/读入队列的方法
def wirte_queue():
# queue1.put('xxx', 1)
a.append(1)
print('写入到队列,当前队列长度为:', len(a))
def read_queue():
print('开始写出队列')
a.pop(a[0])
print(a)
print('写出后当前队列长度为:', len(a))
def writer(loops):
for i in range(loops):
wirte_queue()
# sleep(randint(1, 3))
def reader(loops):
for i in range(loops):
read_queue()
# sleep(randint(2, 5))
funcs = [writer, reader] #线程函数列表
nfuncs = len(funcs)
nloops = [randint(5, 8),randint(3, 5)]
# 实例化队列,创建一个FIFO的队列,最大容量为32个
def main():
po = Pool(2)
# for i in range(nfuncs):
# p=Process(target=funcs[i],args=(q,nloops[i]))
# p.start()
for i in range(nfuncs):
po.apply_async(funcs[i], (nloops[i],))
print('---------------------开始---------------------')
po.close()
po.join()
print('---------------------结束---------------------')
if __name__ == '__main__':
main()
结果:
---------------------开始---------------------
写入到队列,当前队列长度为: 1
写入到队列,当前队列长度为: 2
写入到队列,当前队列长度为: 3
写入到队列,当前队列长度为: 4
写入到队列,当前队列长度为: 5
写入到队列,当前队列长度为: 6
写入到队列,当前队列长度为: 7
写入到队列,当前队列长度为: 8
开始写出队列
[1, 1, 1, 1, 1, 1, 1]
写出后当前队列长度为: 7
开始写出队列
[1, 1, 1, 1, 1, 1]
写出后当前队列长度为: 6
开始写出队列
[1, 1, 1, 1, 1]
写出后当前队列长度为: 5
---------------------结束---------------------