本笔记供自己复习时查看
浅复制问题深复制
python万物皆对象
首先python有两种对象:可变对象,不可变对象
-
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
-
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
需要注意不可变对象每一次赋值比如 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。(a=b这种还是赋值指针)
浅复制问题经常发生在可变对象上比如:列表、字典、集合,以及我们自定义的对象,此时是新建的引用指向了之前已经存在的内存,两个指向了同一个地方。浅复制存在于函数传参(其实也是赋值),和等号赋值
a=3
def f( a):
print(id(a))
f(a)
print(id(a))
a=[1,2,3]
f(a)
print(id(a))
结果
3022012875056
3022012875056
3022014266816
3022014266816
可以看出函数传参是个赋值的过程,在python中任何变量的赋值实际上都是是直接赋予地址,由于不可变对象你如果要再修改
结论:
python 函数的参数传递也就是参数传递到函数中:
-
不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
-
可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
id()函数究竟是什么含义?
a=[1,2,3]
print(id(a))
print(id(a[0]))
print(id(a[1]))
print(id(1))
print(id(2))
139730379775264
7394656
73946887394656
7394688
可以看出id的真正含义是如果是
a=[1,2,3],id取的是list[1,2,3]的地址
a[0]=1,id取的是1的地址
非(int char np.float...)就是找出其指向的地址,type是找出其指向的元素
int char就是找出其自身的地址,type是找自身的元素
如
a=np.array([np.float(1),np.float(2),np.float(3)])
b=np.array([np.float(4),np.float(5),np.float(6)])
b[...]=a[...]
print(id(a))
print(id(b))
print(id(a[0]))
print(id(a[1]))
print(id(b[0]))
print(id(b[1]))
139707398802192
139707398802864
139705004653168
139705004653296
139705004653168
139705004653296
可以看到a和b的值一样了,且相同的位置有了相同的引用(a[0]=b[0]),但a和b的地址还是不一样的,仅此而已,说明...确实是逐元素赋值。
结论:
引用赋值,如果引用指向的是可变对象(即被赋值了可变对象 ,如x=【1,2,3】),那么对其的修改将会作用于原引用的值。(修改后还是指向同一个空间)
如果引用指向的是不可变对象(即被赋值了不可变对象 ,如x=1),那么对其的修改将不会作用于原引用的值。(修改后不指向同一个空间)
比如x,y=[1,2,3]
x[...] = var[...]
相当于x[0]=var[0]=1 x[1]=var[1]=2(不可变对象赋值)
x=[1,2,3],x修改y不会变。
若x=y=[1,2,3](可变对象赋值)
x=[1,2,3],x修改y会变。
命名空间和作用域问题
参考:作用域
问题经常出现在:PYTHON的作用域由def、class、lambda(也就是接口和类)等语句产生,if、try、for (python条件语句与上级同作用域)等语句并不会产生新的作用域。变量名引用分为三个作用域进行查找:首先是本地,然后是函数内(如果有的话),之后是全局,最后是内置。
也很好理解,接口和类是需要封装内部的,因此会需要产生作用域来屏蔽外界访问内部的东西,当然可以选择性的暴露一部分。
局部作用域如何产生?
局部作用域和封装有密不可分的联系,所以局部作用域出现在函数和类的里面,因为这二者通常要内部变量和外部进行区分。
指明全局变量
1。直接在函数最外部定义变量
global
2.使用global可以调用外部的全局变量,如果有的话会调用外部的全局变量
如果没有的话就新建该变量为全局变量
比如
main(){
global a
a=10
}
如果全局还没有定义a,那么相当于,也就是说在此之后其他的函数也是可以调用到全局变量a的。
a
main(){
global a
a=10
}
global的赋值问题
a=3
class b:
par1=3
b()
def main():
a=8
b.par1=7
if __name__ == '__main__':
main()
print(a)
print(b.par1)
我在main中给a和b.par1赋值
3
7
虽然都是赋值,但结果是全局a没有被修改成功,但全局b.par1修改成功了,这是因为a=8是新建一个局部对象,指向8(没有涉及查询操作,就是单纯的创建一个a),而 b.par1=7因为b并没有被创建,我这里是查询b,查询到了全局的b,因此对全局b进行赋值(涉及到了查询操作)
作用域查找范围:
Python以L –> E –> G –>B
的规则查找变量
当前局部-》父作用域-》全局-》内置(就是像系统定义好的那种)
父作用域的值我们可访问可修改,就是自己人
a=1
def main():
a=3
def b():
a+=3
b()
print(a)
if __name__ == '__main__':
main()
比如这样就会在B中报错,并不是因为上面的原因,而是b中声明了变量a,a=a+3此时右边的a并不会找b外边的a,因为他自己有个a,但是a还未定义值,所以报错。
a=1
def main():
a=[3,3]
def b():
a.append(5)
b()
print(a)
if __name__ == '__main__':
main()
结果[3, 3, 5],可以看到内部确实被改变了
结论:因此内置的函数还是很好用的,可以直接修改外部的值,而不需要将外部的所有东西都传进来,非常好用
list,Tensor,tuple等在赋值时的拆分性质
a=[1,2,3]
a,b,c=a
print(a,b,c)
1 2 3
可以看到列表在等式右边时是可以拆解开的,一个列表可以由多个数进行接收
l=[[torch.Tensor([1,2]),3]]
for (x1,x2),z in l:
print(x1,x2,z)
输出
tensor(1.) tensor(2.) 3
for调用后实际上是
(x1,x2),z=[torch.Tensor([1,2]),3] #1
x1,x2=[1,2] #2
z=3
也是运用了赋值拆分的性质
索引命名规则
信息命名
我有每个room的点信息,我应该定义列表名为rooms_points,room_labels
要加s因为由多个room组成列表,有多个room,且一个room点也有多个
room_coord_max,每一个房间坐标最大的三个,最好是coord加s,也可不加
索引命名
rooms_idxs,或room idx都行,可以索引任何以room开头的列表,就是上面的所有
其他举例
信息:selected_points
索引:selected_points_idxs
numpy
索引
...表示所有维度 的所有
:表示当前维度 的所有
None也叫new axis指的是新的维度,且大小为1
**与*的索引,与列表元组的拆分
zip的教程也是这个,详细见下面
总体来说*与**只能用在函数的参数列表中,写在定义处就是打包(将参数打包成元组),比如这种
def f(*args): # 可以接受任意长度的参数,把所有参数打包后给args
print(args)
f(1,2,3)
args=(1,2,3)
写在调用处就是拆解,将元组、列表。。变为参数如下图
a=[1,2,3]
zip(*a)=zip(1,2,3)
因此可以得出:**与*利用了列表元组字典等的可拆分性,将参数更方便地加入到函数当中,也更容易提取出出来了。
def add(a, b):
return a + b
data = [7, 8]
print(add(*data)) # 15
*data=7,8
def add(a, b):
return a + b
data = {'a':7, 'b':8}
print(add(**data)) # 15
**data=a:7,b:8
a={#将二者合起来,形成临时字典
**data_dict,
'gt_boxes_mask': gt_boxes_mask
}
a={a:7,b:8,gt_boxes_mask': gt_boxes_mask}
partial
简化函数的参数,形成新的函数变体
from functools import partial
# 同样是刚刚求和的代码,不同的是加入的关键字参数
def add(*args, **kwargs):
# 打印位置参数
for n in args:
print(n)
print("-"*20)
# 打印关键字参数
for k, v in kwargs.items():
print('%s:%s' % (k, v))
# 暂不做返回,只看下参数效果,理解 partial 用法
add_partial = partial(add, 10, k1=10, k2=20,k3=40)
add_partial(1, 2, 3, k3=20)
结果
10
1
2
3
--------------------
k1:10
k2:20
k3:20
from functools import partial
# 同样是刚刚求和的代码,不同的是加入的关键字参数
def add(*args, **kwargs):
# 打印位置参数
for n in args:
print(n)
print("-"*20)
# 打印关键字参数
for k, v in kwargs.items():
print('%s:%s' % (k, v))
# 暂不做返回,只看下参数效果,理解 partial 用法
add_partial = partial(add, 10, k1=10, k2=20,k3=40) 相当于add(10, k1=10, k2=20,k3=40)
add_partial(1, 2, 3)
结果
10
1
2
3
--------------------
k1:10
k2:20
k3:40
结论:起到一个简化参数的作用,很多参数不需要我自己去一个一个输入了,固定下函数参数列表的一些参数,形成原函数的变种就是偏函数(实际上也就是为参数表中的参数设置了默认值,且默认值的优先级比在def参数列表中定义的优先级更高), 并且这些参数可以被覆盖的,就是比参数列表默认值高一级的默认值,其他的完全没有区别。
好处:可以保证强封装性和复用性,比如我要为函数设置config,我可以提前设置好用partial保存,不必再将这些配置保存在self里面,也不用每次调用函数都将config传进去,而且还能根据config创建新的函数。
python万物皆对象以及type函数
类也是对象(实例),是type类的实例
type函数就是查看他是谁的实例,如下图type(a)即a是A的实例,a的所属类是A。
print(a)列出了是谁的实例,名称,地址
__call__函数
就是对实例调用()会进入call,通常我们必须调用实例中的方法来调用函数,但该函数是直接调用实例就能使用,实际也还是一个名字比较怪的函数呗
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")
调用__call__()方法 C语言中文网 http://c.biancheng.net
对函数传入可变对象,那个对象就相当于进入了函数,因为拿到的是引用也就是指针,因为拿到的是指针,所以在函数内部对该变量做的任何操作都会对传入函数的函数外层的变量生效
装饰器wrapper
https://www.bilibili.com/video/BV1Vv411x7hj?p=4&vd_source=6346698154746eb5026e16499e253fe8
就类似java中工厂的一种东西,应该也可以说和java中的注解是一个道理,就是对所有函数进行统一的一种处理,包装,得到新的函数(注意wrapper返回的是新函数)。
比如
我想为所有函数前加一个输出begin,函数执行完加一个end,那么这刚好就是对函数进行统一的一个处理,就可以用wrapper
def outer(func):
def inner(*args,**kwargs):
print('=====begin===')
restult=func(*args,**kwargs)
print('=====end===')
return restult
return inner
@outer
def sayHello():
print('hello world')
@outer
def add(a,b):
return a+b
sayHello()
print(add(1,2))
构建思路wrapper:从个例去推最后的模版
我想函数前加一个输出begin,函数执行完加一个end,可以理解为长这样
print('=====begin===')
result=add(a,b):
print('=====end===')
return result
print('=====begin===')
result=sayHello():
print('=====end===')
return result
此时返回值和原函数对应上了,一个为int,一个为None。
那就可以定义一个inner,将不同的地方进行替换,函数名不同,统一设置为func,参数不同,改为(*args,**kwargs),就形成了上面的outer函数。
zip命令
实际上就可以理解为将以行为一整个元素的对象变成一个以列为一整个元素的对象,
对于a=[1,2,3]
b=[4,5,6]
变换为
[1,4]
[2,5]
[3,6]
再比如batch中
[
[coord1,feat1,label]
[coord2,feat,2label]
]
我想整合coord,feat,那么就可以
coord, feat, label = list(zip(*batch))
结果coord=(coord1,coord2)
map函数
让list的各个元素都执行相同的操作,返回map对象,可以转换为list等其他结构
def sq(x):
return x*x
print(list(map(sq,[y for y in range(10)])))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
list为二维时传入x的是数组。
lambda函数
就是一个函数,只不过简化了写法
lambda x: x*x
:之前是传入值,:之后是返回值,写不了太复杂的逻辑,多配合map进行合作
__getitem__和[]的关系
[]就是在调用__getitem__,
a[1]=a.__getitem__(1)
dict
key只能是数字或字符串
查找元素
dict={0:'zero',1:'one',2:'two',3:'three'}
print(dict[0])
实际上是调用了dict的__getitem
dict使用列表进行索引
正常情况下比如
dict={0:'zero',1:'one',2:'two',3:'three'}
a=[1,2,3]
dict(a)
是会报错的,因为dict不支持传入列表
方案1:因为对a中的元素都是相同操作,可以使用map
dict={0:'zero',1:'one',2:'two',3:'three'}
a=[1,2,3]
result=list(map(lambda key:dict[key],a))
print(result)
方案2:使用np.vectorize
就像 python map 函数一样,除了它使用 numpy 的广播规则。
参考https://mp.csdn.net/mp_blog/creation/editor/new/128705425
如何使用
args=(i,)
传入参数时经常看到对args的赋值是这样的,这里一定要加一个逗号,表示传给args的是元组
>>> type( (1,) )
<class 'tuple'>
>>> type( (1) )
<class 'int'>
>>> type( (1,2))
<class 'tuple'>
>>> type( () )
<class 'tuple'>
可以看到如果不加逗号,(1)会被认为是数学计算里的优先级符号,所以(1)并不是在定义元组,而是int 1.但(1,)就可以正确地被认为是元组。