1.Python---copy()、deepcopy()与赋值的区别
https://blog.csdn.net/u011630575/article/details/78604226
创建一个对象,就是将这个对象指向内存中空间的一块地址,内存里面放着对象的值。
首先直接上结论:
—–深复制(deepcopy),即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响,同理也是没有影响。
—–而等于赋值,并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。
—–而浅复制(copy)要分两种情况进行讨论:
1)当浅复制的值是不可变对象(数值,字符串,元组)时和“等于赋值”的情况一样,对象的id值与浅复制原来的值相同(不可变对象改变的时候,标签也会改变,所以对浅复制的值没有影响)。
2)当浅复制的值是可变对象(列表和元组)时会产生一个“不是那么独立的对象”存在。有两种情况:
第一种情况:复制的 对象中无 复杂 子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。
第二种情况:复制的对象中有 复杂 子对象 (例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值 中的复杂子对象的值 会影响浅复制的值,同时改变浅复制中复杂子对象的值,也会影响原来值中的复杂子对象的值。主要原因是原来值中的复杂子对象和浅复制中的复杂子对象的地址是一样的。
对于简单的 object,例如不可变对象(数值,字符串,元组),用 shallow copy 和 deep copy 没区别
复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。
copy.copy()分为两种情况
1.无复杂子对象
a.原数据改变对copy后的数据没有影响
>>> import copy
>>> a = [1, 2, 3]
>>> b = copy.copy(a)
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3]
b.copy后的数据改变对原数据没有影响
>>> import copy
>>> a = [1, 2, 3]
>>> b = copy.copy(a)
>>> b.append(4)
>>> b
[1, 2, 3, 4]
>>> a
[1, 2, 3]
2.有复杂子对象,原数据的复杂子对象改变,会对copy后的数据也会跟着改变;copy后复杂子对象的数据改变,原数据也会跟着改变。
a.原数据改变对copy后的数据有影响
>>> import copy
>>> a = [[1], 2]
>>> b = copy.copy(a)
>>> a[0].append(3)
>>> a
[[1, 3], 2]
>>> b
[[1, 3], 2]
b.copy后的数据改变对原数据有影响
>>> import copy
>>> a = [[1], 2]
>>> b = copy.copy(a)
>>> b[0].append(3)
>>> b
[[1, 3], 2]
>>> a
[[1, 3], 2]
为什么会出现这种情况的呢,使用id方法查看一下复杂子对象的地址
>>> import copy
>>> a = [[1], 2]
>>> b = copy.copy(a)
>>> id(a)
140658135604872
>>> id(b)
140658135590920
>>> id(a[0])
140658135589576
>>> id(b[0])
140658135589576
2.有复杂子对象,原数据的子对象改变,copy后的数据不会改变;copy后子对象的数据改变,原数据不会随着改变。
>>> import copy
>>> a = [[1], 2]
>>> b = copy.copy(a)
>>> a.append(3)
>>> a
[[1], 2, 3]
>>> b
[[1], 2]
赋值
针对赋值对象的不同,会有两种不同的结果
- 可变对象
- 不可变对象(内存中的值不可改变)
在Python中,数字、float、 字符串str 、元祖tuple、boole 都是不可变对象
列表list、集合set、字典dict都是可变对象
#可变对象list
>>> a = [1, 2]
>>> id(a)
2937430095880
>>> a.append(3)
>>> a
[1, 2, 3]
>>> id(a)
2937430095880
#不可变对象
>>> b = 1
>>> id(b)
140705058431376
>>> b = b + 1
>>> id(b)
140705058431408
可变对象
这两个可变对象是等价的,修改其中任何一个都会影响到另一个。
>>> a = [1,2]
>>> b = a
>>> a.append(3)
>>> b.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> id(a)
1771873471048
>>> id(b)
1771873471048
不可变对象
>>> a = 2
>>> b = a
>>> a = 3
>>> a
3
>>> b
2
>>> id(a)
140705058431440
>>> id(b)
140705058431408
>>> id(2)
140705058431408
>>> id(3)
140705058431440
python可变对象和不可变对象
https://www.cnblogs.com/suxianglun/p/9013021.html
https://www.cnblogs.com/zf-blog/p/10613981.html
python列表的append和+的区别
列表是不可变对象,append只是在列表对象存储块添加元素,本身id值没有变化。列表进行+操作,列表对象变了,id值也变了。append的运行速度更快。
>>> nums = [1, 2]
>>> id(nums)
1899108578312
>>> nums.append(3)
>>> nums
[1, 2, 3]
>>> id(nums)
1899108578312
>>> id(nums + [4])
1899108611912
Python中List的复制(直接复制、浅拷贝、深拷贝)
https://blog.csdn.net/qq_24502469/article/details/104185122
如果list没有复杂子对象,可以选择使用切片[:]进行复制。通过使用 [ : ] 切片,可以浅拷贝整个列表,同样的,只对第一层实现深拷贝。
>>> nums = [1, 2]
>>> temp = nums[:]
>>> id(nums)
2432461058760
>>> id(temp)
2432461071432
python中+和join的区别
在用"+"连接字符串时,结果会生成新的对象
用join时结果只是将原列表中的元素拼接起来,所以join效率比较高
python多线程
https://blog.csdn.net/pursuit_zhangyu/article/details/97925622
总结一下:
线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。每一个进程都可以共享进程的资源,因为有的资源有限,所以需要锁保护资源。
多线程可以并发的处理数据,节省时间。Python 多线程适合用在 I/O 密集型任务中。I/O 密集型任务较少时间用在 CPU 计算上,较多时间用在 I/O 上,如文件读写,web 请求,数据库请求 等;而对于计算密集型任务,应该使用多进程。多进程一般使用多核 CPU,意味着可以有多个核并行完成计算,所以多核提升的是计算性能,但每个 CPU 一旦遇到 I/O 阻塞,仍然需要等待,所以多核对 I/O 密集型任务没什么太高提升。
说到 Python 中的多线程,一个绕不过去的话题就是全局锁 GIL(Global interpreter lock)。GIL 本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可迭代对象其实就是一种容器,容器里面存了很多数据,比如列表list,一个list就是一个容器,里面右很多数据,数据之间是有联系的,知道一个数据,怎么才能知道下一个数据呢。实现的方法是__next__方法,实现了这个方法的对象是迭代器。迭代器就是一个产生数据的方法,不仅产生了下一个数据的,还记录了数据的状态。生成器也是迭代器,也是产生数据的方法,更加简单方便。使用关键之yield的不同函数就可以了。
Python生成器
https://www.liaoxuefeng.com/wiki/897692888725344/923029685138624
什么是生成器?
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器
自己的总结:生成器(generator),看英文(or)就知道了。它更像是一个产生的数据的方法。
创建生成器的方法
- 生成器表达式
- 生成器函数:也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始
生成器表达式
第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator
#列表生成式
lis = [x*x for x in range(10)]
print(lis)
#生成器
generator_ex = (x*x for x in range(10))
print(generator_ex)
结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000002A4CBF9EBA0>
那么创建list和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object <genexpr> at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?
如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:
#生成器
generator_ex = (x*x for x in range(10))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
结果:
0
1
4
9
16
25
36
49
64
81
Traceback (most recent call last):
File "列表生成式.py", line 42, in <module>
print(next(generator_ex))
StopIteration
大家可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:
#生成器
generator_ex = (x*x for x in range(10))
for i in generator_ex:
print(i)
结果:
0
1
4
9
16
25
36
49
64
81
生成器函数
所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如著名的斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:
1,1,2,3,5,8,12,21,34.....
斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
#fibonacci数列
def fib(max):
n,a,b =0,0,1
while n < max:
a,b =b,a+b
n = n+1
print(a)
return 'done'
a = fib(10)
print(fib(10))
仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说上面的函数也可以用generator来实现,上面我们发现,print(b)每次函数运行都要打印,占内存,所以为了不占内存,我们也可以使用生成器,这里叫yield。如下:
def fib(max):
n,a,b =0,0,1
while n < max:
yield b
a,b =b,a+b
n = n+1
return 'done'
for i in fib(6):
print(i)
结果:
1
1
2
3
5
8
生成器的总结
https://blog.csdn.net/qq_26442553/article/details/82418257
生成器使用总结:
1.生成器的好处是可以一边循环一边进行计算,不用一下子就生成一个很大的集合,占用内存空间。生成器的使用节省内存空间。
2.生成器保存的是算法,而列表保存的计算后的内容,所以同样内容的话生成器占用内存小,而列表占用内存大。每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。
3.使用for 循环来遍历生成器内容,因为生成器也是可迭代对象。通过 for 循环来迭代它,不需要关心 StopIteration 异常。但是用for循环调用generator时,得不到generator的return语句的返回值。如果想要拿到返回值,必须用next()方法,且捕获StopIteration错误,返回值包含在StopIteration的value中。
4.在 Python 中,使用了 yield 的函数都可被称为生成器(generator)。生成器是一个返回迭代器的函数,只能用于迭代操作。更简单点理解生成器就是一个迭代器。
5.一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,保存当前所有的运行信息,并返回一个迭代值,下次执行next() 方法时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置。
迭代器,可迭代对象生成器
https://zhuanlan.zhihu.com/p/24376869
总结
容器是一系列元素的集合,在python中str,list,set,dict,file,sockets都可以看作是容器,容器都可以被迭代,因此他们也称为可迭代对象,可迭代对象实现了__iter__方法,迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果,生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield。
自己小结
可迭代对象
只要对象实现了 __iter__()
方法,并且返回的是一个迭代器,那么这个对象就是可迭代对象。可迭代对象有list,set,tuples,打开状态的files。可迭代对象包含了迭代器(可能不恰当),譬如for循环一个可以知道下一个值,就是迭代器实现的(__next__函数)
迭代器
那么什么迭代器呢?它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__next__()(python2中实现next())方法的对象都是迭代器。
迭代器就像是一个产生数据的方法,同时记录了状态。
为了更直观地感受迭代器内部的执行过程,我们自定义一个迭代器,以斐波那契数列为例:
class Fib:
def __init__(self):
self.prev = 0
self.curr = 1
def __iter__(self):
return self
def __next__(self):
value = self.curr
self.curr += self.prev
self.prev = value
return value
>>> f = Fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Fib既是一个可迭代对象(因为它实现了__iter__方法),又是一个迭代器(因为实现了__next__方法)。实例变量prev和curr用户维护迭代器内部的状态。每次调用next()方法的时候做两件事:
- 为下一次调用next()方法修改状态
- 为当前这次调用生成返回结果
迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
生成器
生成器是一种普通函数语法定义的迭代器,只需在普通函数里面添加yiled关键字
生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值
https://www.zhihu.com/question/26930016/answer/99243411
python 装饰器
https://segmentfault.com/a/1190000012315090
装饰器本质其实就是一个函数, 可以让其它函数不改动源代码的情况下增加其他新功能, 比如网站经常需要的权限校验等场景
python装饰器
预备知识
首先我们要知道在python,一切皆对象,函数也是一个对象
>>> def test():
... return "Hello World"
有自己的id值,有type,有自己的值
>>> id(test)
140155005410568
>>> type(test)
<class 'function'>
>>> test
<function test at 0x7f78614f9d08>
甚至可以赋值给其他变量
>>> test1 = test
>>> test1()
'Hello World'
哪怕是当做参数传递给别的函数,也可以当做函数的返回值
>>> def foo(func):
... print(func)
... return func
...
>>> test2 = foo(test)
<function test at 0x7f78614f9d08>
>>> test2()
'Hello World'
装饰器定义
装饰器本质其实就是一个函数, 可以让其它函数不改动源代码的情况下增加其他新功能, 比如网站经常需要的权限校验等场景
最初的函数
def add(x, y):
print(x+y)
add(1,2)
现在我们有一个新需求, 计算代码执行时间
import time
def add(x, y):
start_time = time.time
print(x+y)
stop_time = time.time
print("{func} spend {time} ".format(func = "add", time = stop_time-start_time))
add(1,2)
我们当然可以这么写, 但是一来修改了源代码可能会造成一些未知的错误, 二来如果我们有一百个函数, 这样写也不现实, 这就是我们装饰器出场的时候了.
创建一个装饰器
import time
def timmer(func):
"""
:param func: 被装饰的函数
:return: 一个计算函数运行时间的函数
"""
def wrapper(*args, **kwargs):
"""
:param args:收集被装饰函数的参数
:param kwargs:收集被装饰函数的关键字参数
:return:
"""
start_time = time.time()
# 让进程睡一秒
time.sleep(1)
# 调用被装饰的函数
result = func(*args, **kwargs)
stop_time = time.time()
print("{func} spend {time} ".format(func = "add", time = stop_time-start_time))
return result
return wrapper
使用装饰器
def add(x, y):
print(x,y)
# 因为timmer返回的是wrapper函数对象,所以执行add()相当于执行wrapper()
add = timmer(add)
add(1,2)
如果觉得还是麻烦那就通过一个语法糖@符号来使用装饰器
@timmer
def add(x, y):
print(x,y)
add(1,2)
这就是最基本的装饰器, 在不修改源代码的前提下为函数添加一个新功能, 调用时只需要在原函数上方添加一个 @deco_name , 在这里是@timmer
带参数的装饰器
python还允许我们给装饰器带上函数
import time
def timmer(flag):
"""
:param flag: 接收装饰器的参数
:return:
"""
def outer_wrapper(func):
"""
:param func: 接收被装饰的函数
:return:
"""
# 接收被装饰函数的参数
def wrapper(*args, **kwargs):
"""
:param args: 收集被装饰函数的参数
:param kwargs: 收集被装饰函数的关键字参数
:return:
"""
if flag == "true":
start_time = time.time()
# 调用被装饰的函数
result = func(*args, **kwargs)
# 让进程睡一秒
time.sleep(1)
stop_time = time.time()
print("{func} spend {time} ".format(func="add", time=stop_time - start_time))
return result
else:
print("Unexpected ending")
return wrapper
return outer_wrapper
通过一个语法糖@符号来使用装饰器
所谓的语法糖便是你不使用也可以完成任务,但是使用它可以让你的代码更简洁
@timmer(flag="false")
def add(x, y):
print(x, y)
add(1,2)
被多个装饰器装饰
当函数被多个装饰器装饰时,从里向外装饰
@a
@b
@c
def func():
pass
相当于
func = a(b(c(func)))
python2和python3有哪些区别
https://www.cnblogs.com/qjx-2016/p/7991956.html
https://www.jianshu.com/p/1a7147a7e93b
1.print
py2:print语句,语句就意味着可以直接跟要打印的东西,如果后面接的是一个元组对象,直接打印
py3:print函数,函数就以为这必须要加上括号才能调用,如果接元组对象,可以接收多个位置参数,并可以打印
2.输入函数
py2:input_raw()
py3:input()
3./和//
浮点数除法操作符/和//区别
- Python2:/是整数除法,//是小数除法
- Python3:/是小数除法,//是整数除法。
1/2的结果
py2:返回0
py3:返回0.5,没有了int和long的区别
4.
Python3中这些方法再不再返回list对象:dictionary关联的keys()、values()、items(),zip(),map(),filter(),但是可以通过list强行转换:
"__if__"=="__main__"的作用是什么
https://www.zhihu.com/question/49136398/answer/517623249
Python的问题
1. python2和python3有哪些区别
Python垃圾回收机制
Python多线程
Python深浅拷贝
Python函数作用域
Python生成器
Python装饰器