python学习笔记
变量
关于for循环的循环变量i
python中for循环的循环变量i比较特殊,在执行时进程独立,且会被加mutex锁。除了让i自增,你无法对它进行修改。
还有个关于i的坑:
- def count():
- fs = []
- for i in range(1,4):
- def f():
- return i*i
- fs.append(f)
- return fs
- f1,f2,f3 = count()
- print(f1(),f2(),f3())
输出是9,9,9。
原因是,for循环首次执行时,遇到def f()
时,跳过f()内部(未执行任何语句,只是将语句复制,注意此时引用了i),将此时的f()
的内存地址添加到fs中。第二次循环时,同理,又定义了一个新的f()添加到了fs中,也对i进行了引用....第三次...
所以f1,f2,f3 = count()
就是将返回的列表中的元素挨个赋值给f1,f2,f3。但值得注意的是,此时这三个元素中存储的是三个f()
的内存地址,而且这三个f()
中都引用了i,而此时的i由于循环结束,所以值为3。
那么,如何得到结果1,4,9呢?
改成这样:
- def count():
- fs = []
- a = 0
- for i in range(1,4):
- def f():
- nonlocal a
- a += 1
- return a*a
- fs.append(f)
- return fs
- f1,f2,f3 = count()
- print(f1(),f2(),f3())
还是一样的过程。
一种省去中间变量的简化赋值方式
示例:
a, b = b, a + b
相当于:HHJJKLKH
- t = (b, a + b) # t是一个tuple
- a = t[0]
- b = t[1]
赋值、浅拷贝、深拷贝的坑
- 赋值
python中,赋值都是对对象内存地址的引用](),所以如果我们修改了A的内容,则B的内容也将被修改。我称之为引用依赖。为了解决这个依赖带来的问题,就有了下面的浅拷贝和深拷贝。 - 浅拷贝
浅拷贝在拷贝对象A时会创建一个新的对象B,也即分配新的内存空间。
但B中依然使用原对象A中的元素的引用。所以如果修改了A中的元素,则B中的元素也被修改了。
总结一下,当我们使用下面的操作的时候,会产生浅拷贝的效果:- 使用切片[:]操作
- 使用工厂函数(如list/dir/set)
- 使用copy模块中的copy()函数
- import copy
- will = ["Will", 28, ["Python", "C#", "JavaScript"]]
- wilber = copy.copy(will)
- 深拷贝
深拷贝,顾名思义,就是拷贝前后的A和B之间不存在引用依赖。通常是将原对象A中所有内容都分配新的内存空间以创建新的对象B。为什么是通常呢?因为对于A中的不可变类型元素,B直接引用也不会产生引用依赖,所以没必要申请新的内存。
通常我们使用copy模块的deepcopy()进行深拷贝。
总而言之:
- Python中对象的赋值都是进行对象引用(内存地址)传递
- 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
- 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
- 对于非容器类型(如数字、字符串、和其他'原子'类型的对象)没有被拷贝一说
- 如果元祖变量只包含原子类型对象,则不能深拷贝
列表和tuple
tuple定义的坑
只有1个元素的tuple定义时必须加一个逗号,
,来消除歧义:
- >>> t = (1,)
- >>> t
- (1,)
常见错误:
- >>> t = (1)
- >>> t
- 1
这是因为括号()
既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1
tuple元素可被修改的坑
我们一般将tuple视为不可变列表,其实,tuple本质上是个不可变容器,它内部引用的元素内存地址不可变。
所以是存在这么一种尴尬的坑:如果元组内部的元素是可变类型数据,而这个元素又被修改了,则元组的内容也发生了变化。
关于参数
参数的设置
函数的参数可分为位置参数、默认参数、可变参数、命名关键字参数、关键字参数。设置时也要按照这个顺序。
参数的传递
函数通常都有参数,用于将外部的实际数据传入函数内部进行处理。但是,在处理不同数据类型的参数时,会有不同的情况发生。这一切都是因为以下两点。
- Python的数据类型分可变数据类型和不可变数据类型。
- 参数如果是不可变数据类型,参数传递的是实际对象的内存地址。如果是可变数据类型,则传递的是对象的内存地址。注意这里的“实际”两个字。
参数为不可变类型,如数字,元组
- a = 1
- def func(a):
- print("在函数内部修改之前,变量a的内存地址为: %s" % id(a))
- a = 2
- print("在函数内部修改之后,变量a的内存地址为: %s" % id(a))
- print("函数内部的a为: %s" % a)
-
-
- print("调用函数之前,变量a的内存地址为: %s" % id(a))
- func(a)
- print("函数外部的a为:%s" % a)
打印结果为:
- 调用函数之前,变量a的内存地址为: 1401140288
- 在函数内部修改之前,变量a的内存地址为: 1401140288
- 在函数内部修改之后,变量a的内存地址为: 1401140320
- 函数内部的a为: 2
- 函数外部的a为:1
参数为可变类型,如列表、字典
- a = [1, 2, 3]
-
- def func(b):
- print("在函数内部修改之前,变量b的内存地址为: %s" % id(b))
- b.append(4)
- print("在函数内部修改之后,变量b的内存地址为: %s" % id(b))
- print("函数内部的b为: %s" % b)
-
-
- print("调用函数之前,变量a的内存地址为: %s" % id(a))
- func(a)
- print("函数外部的a为:%s" % a)
输出为:
- 调用函数之前,变量a的内存地址为: 34875720
- 在函数内部修改之前,变量b的内存地址为: 34875720
- 在函数内部修改之后,变量b的内存地址为: 34875720
- 函数内部的b为: [1, 2, 3, 4]
- 函数外部的a为:[1, 2, 3, 4]
参数的作用域
先来看一个典型的坑:
- >>> x = 10
- >>> def foo():
- ... x += 1
- ... print x
- ...
- >>> foo()
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "<stdin>", line 2, in foo
- UnboundLocalError: local variable 'x' referenced before assignment
这是因为,在一个作用域里面给一个变量赋值的时候,Python自动认为这个变量是这个作用域的本地变量,并屏蔽作用域外的同名的变量。很多时候可能在一个函数里添加一个赋值的语句会让你从前本来工作的代码得到一个UnboundLocalError
。
作用域的分类
Python的作用域(scope)一共有4种,分别是:
- L (Local) 局部作用域
- E (Enclosing) 闭包函数外的函数中
- G (Global) 全局作用域
- B (Built-in) 内建作用域
程序执行时以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
Python除了def/class/lambda 外,其他如: if/elif/else/ try/except for/while并不能改变其作用域。定义在他们之内的变量,外部还是可以访问。
- >>> if True:
- ... a = 'I am A'
- ...
- >>> a
- 'I am A'
- # 定义在if语言中的变量a,外部还是可以访问的。
- # 但是需要注意如果if被 def/class/lambda 包裹,在内部赋值,就变成了此 函数/类/lambda 的局部作用域。
在 def/class/lambda
内进行赋值,就变成了其局部的作用域,局部作用域会覆盖全局作用域,但不会影响全局作用域。
- g = 1 #全局的
- def fun():
- g = 2 #局部的
- return g
-
- print fun()
- # 结果为2
- print g
- # 结果为1
闭包与nonlocal
闭包的定义:如果在一个内部函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。需要注意的是,闭包是以函数定义的位置来识别的。比如说,将下方internal的函数体定义拿到external函数体的外面,那么也就不存在闭包了。
函数嵌套/闭包中的作用域:
- a = 1
- def external():
- global a
- a = 200
- print a
-
- b = 100
- def internal():
- # nonlocal b
- print b
- b = 200
- return b
-
- internal()
- print b
-
- print external()
上面的代码一样会报错:“引用在赋值之前”,Python3有个关键字nonlocal
可以解决这个问题,但在Python2中还是不要尝试修改闭包中的变量。
关于闭包中还有一个坑:
- from functools import wraps
-
- def wrapper(log):
- def external(F):
- @wraps(F)
- def internal(**kw):
- if False:
- log = 'modified'
- print log
- return internal
- return external
-
- @wrapper('first')
- def abc():
- pass
-
- print abc()
也会出现 引用在赋值之前 的错误,原因是解释器探测到了 if False 中的重新赋值,所以不会去闭包的外部函数(Enclosing)中找变量,但 if Flase 不成立没有执行,所以便会出现此错误。除非你还需要else: log='var' 或者 if True 但这样添加逻辑语句就没了意义,所以尽量不要修改闭包中的变量。
locals()和globals()
global
和 globals()
是不同的,global
是关键字用来声明一个局部变量为全局变量。globals() 和 locals() 提供了基于字典的访问全局和局部变量的方式。
默认参数的坑
默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:
先定义一个函数,传入一个list,添加一个END
再返回:
- def add_end(L=[]):
- L.append('END')
- return L
当你正常调用时,结果似乎不错:
- >>> add_end([1, 2, 3])
- [1, 2, 3, 'END']
- >>> add_end(['x', 'y', 'z'])
- ['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的:
- >>> add_end()
- ['END']
但是,再次调用add_end()
时,结果就不对了:
- >>> add_end()
- ['END', 'END']
- >>> add_end()
- ['END', 'END', 'END']
很多初学者很疑惑,默认参数是[]
,但是函数似乎每次都“记住了”上次添加了'END'
后的list。
原因解释如下:
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None
这个不变对象来实现:
- def add_end(L=None):
- if L is None:
- L = []
- L.append('END')
- return L
返回函数/闭包
一个函数A可以返回一个计算结果,也可以返回一个内部函数B。
代码:
- def createCounter():
- i = 0
- def counter():
- nonlocal i
- i += 1
- return i
- return counter
- f1 = createCounter()
- f2 = createCounter()
输出:
- >>>f1()
- 1
- >>>f1()
- 2
- >>>f2()
- 1
- >>>f2()
- 2
在执行第8行的赋值语句时,将先执行第2行的i=0再执行第7行的return语句,函数counter()并未执行,但counter的函数体地址通过return语句被赋予了f1。同时counter()将与createCounter中的变量i引用构成一个称之为“闭包”的数据结构,在第10行的再次调用时将直接调用f1指向的counter(),并从闭包中取得局部变量i进行计算,计算完成后更新的i将继续保存在闭包中等待下次调用。所以在第12行的调用中将打印出2。在第12行的赋值语句中,将重复这个过程,创建第二个闭包。
关于生成器
关于生成器与迭代器
可以理解为,迭代器就是由生成器返回生成的.
- import sys
-
- def fibonacci(n): # 生成器函数 - 斐波那契
- a, b, counter = 0, 1, 0
- while True:
- if (counter > n):
- return
- yield a
- a, b = b, a + b
- counter += 1
- f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
-
- while True:
- try:
- print (next(f), end=" ")
- except StopIteration:
- sys.exit()
next()/send()
生成器有两个主要的方法,一个是next(),一个是send().
下面以一个例子来理解:
- def gen(n=None):
- for i in range(n):
- c = yield i
- print(c)
- g = gen(3)
- >>>next(g)
- 0
- >>>g.send(10)
- 10
- 1
在第6行的next执行时,程序跳转到第2行,执行到第3行时,按照从右到左的顺序先执行yield i,然后就跳出返回第6行了,c并没有被赋值.(如果此时再次执行next,程序将跳转到第4行执行,c的赋值部分就被跳过了.)
在第8行的send执行时,由于next和send调用都使用了对象g的栈,所以这次程序接着上次跳转到第3行,将10赋值给c,然后往下接着执行直到又到了第3行,执行yield i后也跳出返回了.
需要注意的是,第一次启动生成器时,如果用send,那么一定得是send(None),因为此时并没有yield语句来接收参数完成赋值.
yield/yield from
- yield
- def fun_inner(arg):
- i = 0
- while True:
- i = yield i
- a = fun_inner()
- >>>a.send(None)
- >>>a.send(5)
- <<<0
- <<<5
本质上yield是一个函数,它有两个作用:
一个是返回后面表达式的值,相当于return i,返回后继续等待下次迭代.
第二个作用是当程序继续迭代而跳转回yield处,yield接收函数收到的参数作为自己表达式的返回值,在这里就是arg,(要使用这个作用,必须首先完成一次迭代,不然就没有yield来接收函数,程序将报错.这也是为什么通过send完成生成器对象的第一次迭代时必须是send(None))
所以在上例中,先执行send(None),程序跳转到fun_inner()内执行,由于跳转的初始位置没有yield,所以参数None并未传递给yield.当程序执行到第4行时,执行完yield i就跳出生成器.并未完成改行的赋值语句.
执行send(5)时,程序跳转到上次退出的位置,也即第4行yield处,同时5作为参数被传递给yield语句,并作为yield函数的返回值赋值给i,然后程序循环一遍又来到第4行,执行yield i后退出程序.
- yield from
- def fun_inner():
- i = 0
- while True:
- i = yield i
-
- def fun_outer():
- a = 0
- b = 1
- inner = fun_inner()
- inner.send(None)
- while True:
- a = inner.send(b)
- b = yield a
-
- if __name__ == '__main__':
- outer = fun_outer()
- outer.send(None)
- for i in range(5):
- print(outer.send(i))
在两层嵌套的情况下,值的传递方式是,先把值传递给外层生成器,外层生成器再将值传递给外层生成器,内层生成器在将值反向传递给外层生成器,最终yield出结果。如果嵌套的层次更多,传递将会越麻烦。
下面是yield from的实现方式:
- def fun_inner():
- i = 0
- while True:
- i = yield i
-
- def fun_outer():
- yield from fun_inner()
-
- if __name__ == '__main__':
- outer = fun_outer()
- outer.send(None)
- for i in range(5):
- print(outer.send(i))
所以yield from相当于是根据后面的生成器方法生成了一个专用的对象,并中断进入该对象迭代一次,迭代完成返回yield from处,与yield不同的是,yield from并不会return结果.上面打印出的结果是fun_inner()中yield的产出.这与上面的代码效果是一样的,但是明显的代码量减少了,嵌套传值的时候,并不需要我们手动实现。当执行到yield from时,将根据后面的生成器生成
装饰器/执行顺序
- 一般用法
- 代码
- def log(func):
- print('xiaxi')
- def wrapper(*args, **kw):
- print('call %s():' % func.__name__)
- return func(*args, **kw)
- return wrapper
- @log
- def now():
- print('2015-3-25')
- >>> now()
- 输出
- <<<xiaxi
- <<<call now():
- <<<2015-3-25
解释器遇到第一个log()函数时将整体读入内存,遇到第8行的@log时等效于执行now=log(now),于是跳转到log()内,同时将now的函数内存地址赋给func,之后执行print('xiaxi'),之后遇到wrapper的函数体,不执行而将其加载入内存,之后遇到return语句,将wrapper的函数地址返回给now。也就是说解释器在读取到第9行时就已经打印了“xiaxi”。在第11行执行now()时,由于now已经指向wrapper,将跳转到wrapper内,先打印“call now()”,再执行return语句的func(),由于func指向的是now原先的函数地址,所以将跳转执行print(‘2015-3-25’)。
- 进阶用法
- 代码
- def log(text):
- def decorator(func):
- def wrapper(*args, **kw):
- print('%s %s():' % (text, func.__name__))
- return func(*args, **kw)
- return wrapper
- return decorator
- @log('execute')
- def now():
- print('2015-3-25')
- >>> now()
-
- 输出
- execute now():
- 2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
getattr/定制类实现类的链式调用
- 一般用法
- class Chain(object):
- def __init__(self, path=''):
- self._path = path
- def __getattr__(self, path):
- return Chain('%s/%s' % (self._path, path))
- def __str__(self):
- return self._path
- __repr__ = __str__
- >>> Chain().status.user.timeline.list
- '/status/user/timeline/list'
执行过程:
1、由于类中没有可以执行的普通语句,解释器直接扫描到第9行,首先执行Chain(),于是解释器分配内存空间,新建并初始化Chain对象,就记为Chain()。初始化完毕后,解释器将跳转回第9行。
2、如果Chain()之后没有了,由本节定制类的内容(见标题链接)可知,紧接着解释器将调用第8行实现对象的_repr_str_()来返回self._path(空)。
3、然而Chain()后还有很长的属性调用,所以解释器跳转回第9行后将继续调用Chain()的status属性,由于类中并没有status属性,于是解释器调用_getattr_str_()方法返回“/status”。
4、以此类推
- 进阶用法
上面的普通用法缺少实例方法的实现。
- class Chain(object):
- def __init__(self,path = ''):
- self._path = path
- def __str__(self):
- return self._path
- __repr__ = __str__
- def __getattr__(self,attr):
- return Chain('%s/%s' % (self._path,attr))
- def __call__(self,param):
- return Chain('%s/%s' % (self._path,param))
-
- chain = Chain('')
- >>>chain.users('michael').page(12).repos
- /users/michael/page/12/repos
执行过程:
与上面的一般用法基本相同。区别在于,当解释器执行到第13行的users('michael')
时先将users属性传入,传回对象Chain(‘/users’),再将'michael'作为对象的实例方法参数传入Chain(‘/users’)。也就是说chain.users('michael')=Chain(‘/users’)(‘michael’)。如果有a=Chain(‘/users’),则刚刚的式子可以写成chain.users('michael')=Chain(‘/users’)(‘michael’)=a(‘michael’)。
于是解释器调用对象a的_call()_str()_方法来返回字符串“/users/michael”.
以此类推。
with的用法
这个语法是用来代替传统的try...finally语法的。任何对象,只要正确实现了上下文管理,就可以用于with语句。
基本思想是with所求值的对象必须有一个enter()方法,一个exit()方法。
紧跟with后面的语句被求值后,返回对象的enter()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的exit()方法。
- #!/usr/bin/env python
- # with_example01.py
- class Sample:
- def __enter__(self):
- print "In __enter__()"
- return "Foo"
- def __exit__(self, type, value, trace):
- print "In __exit__()"
- def get_sample():
- return Sample()
- with get_sample() as sample:
- print "sample:", sample
- 输出:
- In __enter__()
- sample: Foo
- In __exit__()
分析:
__enter__()
方法被执行__enter__()
方法返回的值 - 这个例子中是"Foo",赋值给变量'sample'- 执行代码块,打印变量"sample"的值为 "Foo"
__exit__()
方法被调用
with真正强大之处是它可以处理异常。可能你已经注意到Sample类的__exit__
方法有三个参数- val, type 和 trace。这些参数在异常处理中相当有用。
常用函数
isinstance() 函数
内建函数sinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。
isinstance() 与 type() 区别:
- type() 不会认为子类是一种父类类型,不考虑继承关系。
- isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance()。
示例:
- 判断是否为可迭代对象,需要借助collection包
- >>> from collections import Iterable
- >>> isinstance('abc', Iterable) # str是否可迭代
- True
- >>> isinstance([1,2,3], Iterable) # list是否可迭代
- True
- >>> isinstance(123, Iterable) # 整数是否可迭代
- False
- 判断数据类型
- >>> x = 'abc'
- >>> y = 123
- >>> isinstance(x, str)
- True
- >>> isinstance (a,(str,int,list)) # 是元组中的一个返回 True
- True
- type() 与 isinstance()区别:
- class A:
- pass
- class B(A):
- pass
-
- isinstance(A(), A) # returns True
- type(A()) == A # returns True
- isinstance(B(), A) # returns True
- type(B()) == A # returns False
调用cmd
使用方法
常用的方式有os.system和os.popen两种,前者只执行传入的命令,返回值为布尔类型.后者可以获得回显值,更为方便.
- 示例
- os.system("ping 192.168.1.1")
- os.popen("ping 192.168.1.1")
- 一次调用中传入多条cmd语句
由于每次调用都是一个新的上下文环境,所以有时需要在一个cmd窗口中执行多条语句,这时只要在语句见加上&&就可以了,比如:
os.popen('chcp 65001&&netsh wlan show interface').read()
常见错误
当调用了cmd的py文件打包为exe时,执行时往往会遇到编码错误,只要在调用的cmd语句前通过chcp 65001
指定编码方式为unicode既可.具体可参考[报错]-pyinstaller.
打包为exe
主流的有py2exe 、cx_freeze和 pyInstaller等,本次主要使用pyinstaller.
PyInstaller 是一个十分有用的第三方库,可以用来打包 python 应用程序,打包完的程序就可以在没有安装 Python 解释器的机器上运行了。
它能够在 Windows、Linux、 Mac OS X 等操作系统下将 Python 源文件打包,通过对源文件打包, Python 程序可以在没有安装 Python 的环境中运行,也可以作为一个 独立文件方便传递和管理。
PyInstaller 支持 Python 2.7 / 3.4-3.7。可以在 Windows、Mac OS X 和 Linux 上使用,但是并不是跨平台的,而是说你要是希望打包成 .exe 文件,需要在 Windows 系统上运行 PyInstaller 进行打包工作。
下面我们以 Windows
为例来进行程序的打包工作。
安装
- pip install pyinstaller
- # 或者
- python -m pip install pyinstaller
安装成功:
使用
pyinstaller -F helloworld.py
其中,-F
表示打包成单独的 .exe 文件,这时生成的 .exe 文件会比较大,而且运行速度回较慢。仅仅一个 helloworld 程序,生成的文件就 5MB 大。
另外,使用 -i
还可以指定可执行文件的图标; -w
表示去掉控制台窗口,这在 GUI 界面时非常有用。不过如果是命令行程序的话那就把这个选项删除吧!
当输入多参数时,既可以-F -w
,也可以-Fw
一起输入.
PyInstaller 会对脚本进行解析,并做出如下动作:
1、在脚本目录生成 helloworld.spec 文件; 2、创建一个 build 目录; 3、写入一些日志文件和中间流程文件到 build 目录; 4、创建 dist 目录; 5、生成可执行文件到 dist 目录;
执行流程:
- $ pyinstaller -F helloworld.py
- 838 INFO: PyInstaller: 3.4
- 839 INFO: Python: 3.4.3
- 841 INFO: Platform: Windows-8-6.2.9200
- 842 INFO: wrote d:\code\Python\pyinstaller\helloworld.spec
- 858 INFO: UPX is not available.
- 885 INFO: Extending PYTHONPATH with paths
- ['d:\\code\\Python\\pyinstaller', 'd:\\code\\Python\\pyinstaller']
- 886 INFO: checking Analysis
- 887 INFO: Building Analysis because Analysis-00.toc is non existent
- 888 INFO: Initializing module dependency graph...
- 890 INFO: Initializing module graph hooks...
- 899 INFO: Analyzing base_library.zip ...
- 6225 INFO: Processing pre-find module path hook distutils
- 11387 INFO: running Analysis Analysis-00.toc
- 12012 INFO: Caching module hooks...
- 12022 INFO: Analyzing d:\code\Python\pyinstaller\helloworld.py
- 12027 INFO: Loading module hooks...
- 12028 INFO: Loading module hook "hook-encodings.py"...
- 12395 INFO: Loading module hook "hook-xml.py"...
- 13507 INFO: Loading module hook "hook-pydoc.py"...
- 13508 INFO: Loading module hook "hook-distutils.py"...
- 13606 INFO: Looking for ctypes DLLs
- 13662 INFO: Analyzing run-time hooks ...
- 13677 INFO: Looking for dynamic libraries
- 13894 INFO: Looking for eggs
- 13895 INFO: Using Python library C:\WINDOWS\system32\python34.dll
- 13895 INFO: Found binding redirects:
- []
- 13915 INFO: Warnings written to d:\code\Python\pyinstaller\build\helloworld\warn-helloworld.txt
- 14035 INFO: Graph cross-reference written to d:\code\Python\pyinstaller\build\helloworld\xref-helloworld.html
- 14287 INFO: checking PYZ
- 14287 INFO: Building PYZ because PYZ-00.toc is non existent
- 14288 INFO: Building PYZ (ZlibArchive) d:\code\Python\pyinstaller\build\helloworld\PYZ-00.pyz
- 15836 INFO: Building PYZ (ZlibArchive) d:\code\Python\pyinstaller\build\helloworld\PYZ-00.pyz completed successfully.
- 15883 INFO: checking PKG
- 15884 INFO: Building PKG because PKG-00.toc is non existent
- 15884 INFO: Building PKG (CArchive) PKG-00.pkg
- 18528 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
- 18536 INFO: Bootloader D:\program\Python34\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
- 18537 INFO: checking EXE
- 18537 INFO: Building EXE because EXE-00.toc is non existent
- 18538 INFO: Building EXE from EXE-00.toc
- 18538 INFO: Appending archive to EXE d:\code\Python\pyinstaller\dist\helloworld.exe
- 18548 INFO: Building EXE from EXE-00.toc completed successfully.
生成文件:
注意事项
1、直接运行最终的 .exe 程序,可能会出现一闪而过的情况,这种情况下要么是程序运行结束(比如直接打印的 helloWorld),要么程序出现错误退出了。
这种情况下,建议在命令行 cmd 下运行 .exe 文件,这时就会有文本输出到窗口;
2、-i
是改变图标的,但是我发现是有些 bug 的,客官请看:
放大过程中,图标才变成了我们设置的图标。
3、写代码的时候应当有个良好的习惯,用什么函数导什么函数,不要上来 import 整个库,最后你会发现你一个 100KB 的代码打包出来有 500MB;
4、当你的代码需要调用一些图片和资源文件的,这是不会自动导入的,需要你自己手动复制进去才行。不然 exe 文件运行时命令窗口会报错找不到这个文件。
导入方法:
假设程序中需要引入一个 test.txt
文件,首先我们运行:
pyi-makespec -F helloworld.py
此时会生成一个 .spec
文件,这个文件会告诉 pyinstaller 如何处理你的脚本,pyinstaller 创建一个 exe 的文件就是依靠它里面的内容进行执行的。
正常情况下你不需要去修改这个 spec 文件,除非你需要打包一个 dll 或者 so 文件或者其他数据文件。
那么我们就需要修改这个 spec 文件:
- a = Analysis(['helloworld.py'],
- pathex=['/home/test'],
- binaries=[],
- datas=[], ### <------- 改
修改为:
- a = Analysis(['helloworld.py'],
- pathex=['/home/test'],
- binaries=[],
- datas=[('test.txt','.')], ## <---- 修改此处添加外部文件
然后在生成 exe 文件:
pyinstaller helloworld.spec
然后生成的文件就可以正常引入外部文件了。
numpy的使用
关于numpy的练习,推荐实验楼的百题冲关
array的轴
与普通数组的理解是一样的。
先看一个简单的例子:
- import numpy as np
- a = np.array([[[1,4,3],[6,2,9],[4,7,2]],[[1,3],[3,4]]])
- a[0],a[0][1],a[0][1][2]
输出:
[[4, 7, 2], [6, 2, 9], [1, 4, 3]]
由此可见,numpy的数组的低维到高维,相对应的,在具体的数组中就是由大颗粒到小颗粒。在示例中,a数组由两个二维子数组构成:[[1,4,3],[6,2,9],[4,7,2]]和[[1,3],[3,4]],而这二维子数组的序号即对应第0维。二维子数组由一维子数组构成,而这一维子数组的序号即对应第1维。最后一维子数组中元素的序号即对应第2维。当然,这都是以默认行主序为前提的。
numpy的轴在某些方法中会有些难以理解,比如有:
- import numpy as np
- a=np.array([[1,4,3],[6,2,9],[4,7,2]])
- np.max(a,axis=0),np.max(a,axis=1)
输出:
(array([6, 7, 9]), array([4, 9, 7]))
可以从维度指定颗粒度来进行考虑,指定第0维,所以是在三个一维子数组“间”(不是“中”)进行比较,于是将各一维子数组的对应元素进行对比。以此类推,指定第1维,就是在各一维子数组内部中进行对比。
reshape
数组新的shape属性应该要与原来的配套,如果等于-1的话,那么Numpy会根据剩下的维度计算出数组的另外一个shape属性值.
例子:
- z = np.array([[1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
- [13, 14, 15, 16]])
- z.reshape(-1, 2)
结果是:
- array([[ 1, 2],
- [ 3, 4],
- [ 5, 6],
- [ 7, 8],
- [ 9, 10],
- [11, 12],
- [13, 14],
- [15, 16]])
当你指定第一维度为-1,第二维为2时,np将调整第一维使第二维为2.由于共有16个数,所以新的第一维就是16/2=8.然后按照行主序的方式reshape.
还可以这么用:
z.reshape(-1)
输出:
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
这么用:
z.reshape(-1,1)
输出:
- array([[ 1],
- [ 2],
- [ 3],
- [ 4],
- [ 5],
- [ 6],
- [ 7],
- [ 8],
- [ 9],
- [10],
- [11],
- [12],
- [13],
- [14],
- [15],
- [16]])
numpy.stack
numpy.stack(arrays, axis=0)
沿着新轴连接数组的序列。
_axis_参数指定新轴在结果尺寸中的索引。例如,如果axis=0
,它将是第一个维度,如果axis=-1
,它将是最后一个维度。
- 参数: 数组:array_like的序列每个数组必须具有相同的形状。axis:int,可选输入数组沿其堆叠的结果数组中的轴。
- 返回: 堆叠:ndarray堆叠数组比输入数组多一个维。
上面是官方给出的解释,很难理解。
我们先从增加维度说起。
- >>> a = np.array([1, 2, 3])
- >>> b = np.array([2, 3, 4])
- >>> a.shape
- (3,)
- >>> b.shape
- (3,)
- >>> np.stack((a, b), axis=0).shape
- (2, 3)
- >>> np.stack((a, b), axis=1).shape
- (3, 2)
- 12345678910
一言以蔽之:
堆叠函数中指定的维度0或者1是针对结果矩阵来说的,将a和b两个列表进行堆叠,这两个列表首先就是最大的颗粒度,也将构成一个范围为2的新维度。而这个新维度在新矩阵中的位置将由我们在函数中指定的值来确定。
多言以蔽之:
接着说说这个axis
参数的意义,我们可以理解这里的axis
就是要增加哪一个维度,比如说这里的axis=0
,就是增加第一维度,所以这里的(2,3)
中的2
在第一个位置。axis=1
,就是增加第二维度,所以这里的(3,2)
中的2
在第二个位置。
举一个稍微复杂的例子
- >>> a = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
- >>> b = np.array([[4, 5, 6], [4, 5, 6], [4, 5, 6]])
- >>> a.shape
- (3, 3)
- >>> b.shape
- (3, 3)
- >>> np.stack((a, b), axis=0).shape
- (2, 3, 3)
- >>> np.stack((a, b), axis=1).shape
- (3, 2, 3)
- >>> np.stack((a, b), axis=2).shape
- (3, 3, 2)
- 123456789101112
这里的2
就是指的a
和b
,而2
放在什么位置是根据axis
来确定的。
接着说一下矩阵的坐标
- a a的元素对应的坐标
- [1 2 3] (0,0) (0,1) (0,2)
- [1 2 3] (1,0) (1,1) (1,2)
- [1 2 3] (2,0) (2,1) (2,2)
- 1234
很好理解。
接着以np.stack((a, b), axis=1)
为例子
- >>> np.stack((a, b), axis=1).shape
- (3, 2, 3)
- >>> np.stack((a, b), axis=1)
- array([[[1, 2, 3],
- [4, 5, 6]],
-
- [[1, 2, 3],
- [4, 5, 6]],
-
- [[1, 2, 3],
- [4, 5, 6]]])
- 1234567891011
原来我们a[0][0]=1
,现在中间加上一个维度(因为这里axis=1
),就变成了a[0][0][0]=1
,注意这里为什么中间是0
,因为np.stack((a, b), axis=1)
中,a
在b
的前面。同理
- a a的元素对应的坐标
- [1 2 3] (0,0,0) (0,0,1) (0,0,2)
- [1 2 3] (1,0,0) (1,0,1) (1,0,2)
- [1 2 3] (2,0,0) (2,0,1) (2,0,2)
- 1234
那么b[0][0]=4
,因为np.stack((a, b), axis=1)
中,b
在a
的后面。所以b[0][1][0]=4
- b b的元素对应的坐标
- [4 5 6] (0,1,0) (0,1,1) (0,1,2)
- [4 5 6] (1,1,0) (1,1,1) (1,1,2)
- [4 5 6] (2,1,0) (2,1,1) (2,1,2)
- 1234
接着将对应坐标的数组合,就得到了新的array
- 元素对应的坐标
- array([[[1, 2, 3], (0,0,0) (0,0,1) (0,0,2)
- [4, 5, 6]], (0,1,0) (0,1,1) (0,1,2)
-
- [[1, 2, 3], (1,0,0) (1,0,1) (1,0,2)
- [4, 5, 6]], (1,1,0) (1,1,1) (1,1,2)
-
- [[1, 2, 3], (2,0,0) (2,0,1) (2,0,2)
- [4, 5, 6]]]) (2,1,0) (2,1,1) (2,1,2)
- 123456789
还有一个更加简单的理解方式(堆叠)
对于axis=1
,就是横着切开,对应行横着堆
对于axis=2
,就是横着切开,对应行竖着堆
对于axis=0
,就是不切开,两个堆一起。
numpy.pad()
一维版本:
- 基础版:
- import numpy as np
- a = np.ones((5))
- b = np.pad(a, ((1,2)), 'constant',constant_values = (5,6))
- b
输出:
array([5., 1., 1., 1., 1., 1., 6., 6.])
np.pad()的((1,2))意为在默认维度的前后分别加上一个、两个常量,这两个常量由后面的constant_values指定,当然你也可以不指定,默认为0。也可以只指定一个值。
- 进阶版:
- import numpy as np
- arr1D = np.array([1, 1, 2, 2, 3, 4])
-
- '''不同的填充方法'''
- print 'constant: ' + str(np.pad(arr1D, (2, 3), 'constant'))
- print 'edge: ' + str(np.pad(arr1D, (2, 3), 'edge'))
- print 'linear_ramp: ' + str(np.pad(arr1D, (2, 3), 'linear_ramp'))
- print 'maximum: ' + str(np.pad(arr1D, (2, 3), 'maximum'))
- print 'mean: ' + str(np.pad(arr1D, (2, 3), 'mean'))
- print 'median: ' + str(np.pad(arr1D, (2, 3), 'median'))
- print 'minimum: ' + str(np.pad(arr1D, (2, 3), 'minimum'))
- print 'reflect: ' + str(np.pad(arr1D, (2, 3), 'reflect'))
- print 'symmetric: ' + str(np.pad(arr1D, (2, 3), 'symmetric'))
- print 'wrap: ' + str(np.pad(arr1D, (2, 3), 'wrap'))
输出:
- 填充方法:
- constant连续一样的值填充,有关于其填充值的参数。constant_values=(x, y)时前面用x填充,后面用y填充。缺参数是为0000。。。
- edge用边缘值填充
- linear_ramp边缘递减的填充方式
- maximum, mean, median, minimum分别用最大值、均值、中位数和最小值填充
- reflect, symmetric都是对称填充。前一个是关于边缘对称,后一个是关于边缘外的空气对称╮(╯▽╰)╭
- wrap用原数组后面的值填充前面,前面的值填充后面
- 也可以有其他自定义的填充方法
- constant:[0 0 1 1 2 2 3 4 0 0 0]
- edge: [1 1 1 1 2 2 3 4 4 4 4]
- linear_ramp: [0 0 1 1 2 2 3 4 3 1 0]
- maximum: [4 4 1 1 2 2 3 4 4 4 4]
- mean: [2 2 1 1 2 2 3 4 2 2 2]
- median: [2 2 1 1 2 2 3 4 2 2 2]
- minimum: [1 1 1 1 2 2 3 4 1 1 1]
- reflect: [2 1 1 1 2 2 3 4 3 2 2]
- symmetric: [1 1 1 1 2 2 3 4 4 3 2]
- wrap: [3 4 1 1 2 2 3 4 1 1 2]
二维版本
- import numpy as np
- a = np.ones((5,5))
- b = np.pad(a, ((1,2), (3,4)), 'constant',constant_values = (5,6))
- b
输出:
- array([[5., 5., 5., 5., 5., 5., 5., 5., 6., 6., 6., 6.],
- [5., 5., 5., 1., 1., 1., 1., 1., 6., 6., 6., 6.],
- [5., 5., 5., 1., 1., 1., 1., 1., 6., 6., 6., 6.],
- [5., 5., 5., 1., 1., 1., 1., 1., 6., 6., 6., 6.],
- [5., 5., 5., 1., 1., 1., 1., 1., 6., 6., 6., 6.],
- [5., 5., 5., 1., 1., 1., 1., 1., 6., 6., 6., 6.],
- [5., 5., 5., 6., 6., 6., 6., 6., 6., 6., 6., 6.],
- [5., 5., 5., 6., 6., 6., 6., 6., 6., 6., 6., 6.]])
根据上面说的关于维度的理解,这里的(1,2)对应的是维度0,(3,4)对应的是维度1。维度0在矩阵a中用以区别不同的行向量,也即最大的颗粒度,所以在第1行前增加一行,值为5,在最后一行后增加两行,值为6。同理,维度1是在某个行向量中,用以区别行向量的不同元素。所以在每一个行的前面加上3个元素5,在后面加上4个元素6。
多维版本以此类推。