Python学习tips

  最近毕设要做机器学习相关,考虑到Python这门语言在web里也经常用,后面也会接触大数据,所以借此机会将Python完完整整地过一遍,这篇文章主要记录一下自己在看书和官方文档时对一些需要注意的东西的理解,学习阶段主要参考书为《Python基础教程(第2版)》、《Python核心编程(第3版)》以及官方提供的“Python 2.7.12 documentation”(因为我以前下的是2.7.12版本Python),学习中会不断更新。


1. 字典的copy()方法

  copy()方法返回一个具有相同键值对的新字典,但是实现的是浅复制(shallow copy),也就是说值本身是相同的(is级别的相同),而不是副本。

In [1]: x = {'username': 'admin', 'machine': ['yyy', 'baz']}

In [2]: y = x.copy()

In [3]: z = {'username': 'admin', 'machine': ['yyy', 'baz']}

In [4]: x['machine'] is y['machine']
Out[4]: True
In [5]: x['machine'] is z['machine']
Out[5]: False
In [6]: x['machine'] == z['machine']
Out[6]: True
In [7]: y['machine'].remove('yyy')

In [8]: x
Out[8]: {'machine': ['baz'], 'username': 'admin'}
In [9]: z
Out[9]: {'machine': ['yyy', 'baz'], 'username': 'admin'}
In [10]: del y['machine']

In [11]: y
Out[11]: {'username': 'admin'}

In [12]: x
Out[12]: {'machine': ['baz'], 'username': 'admin'}

  这里,copy()只满足相等性,不满足同一性,x[‘machine’] is y[‘machine’]返回True,而x[‘machine’] is z[‘machine’]返回False,也就是说y对键machine所映射的值做任何更改都会影响x[‘machine’],因为本质上x、y这两个对象的’machine’指向了同一块内存区域(即同一个对象)。
  具体分析原因:这里machine键映射的是list的地址,copy()复制的是这种映射关系,也就是说y的键是新键(删除y的machine项不影响x),但映射的仍然是x创建的list对象。
  避免映射关系为地址这个问题的方法是使用深复制(deep copy),此时键所对应的值都是一个新的对象,互不干扰(下面的代码所示),函数为copy模块里的deepcopy()。

In [15]: from copy import deepcopy

In [16]: d = deepcopy(x)

In [17]: d['machine'] is x['machine']
Out[17]: False
In [18]: x['machine'][0] = 'new'

In [19]: x
Out[19]: {'machine': ['new'], 'username': 'admin'}
In [20]: d
Out[20]: {'machine': ['baz'], 'username': 'admin'}

2. None

  这类数据只有一类类型,即NoneType类型,NoneType类型只有一个值——就是None。某些场景下逻辑上需要一个值,但这个值物理上并不存在,就可以赋值为None,比如说函数逻辑上需要返回值,一个没有显式定义返回值的函数不符合逻辑,为了符合函数的定义,这个函数被调用时就返回了None值,None的真值为False。


3. Python函数的参数的区分

  Python作为一门脚本语言,其函数的参数在定义函数和调用函数时变化很多,关于各类参数的叫法,网上和书本上的资料很杂乱,我这里结合“Python 2.7.12 documentation”Glossary里关于parameter和argument的解释给出我自己的理解。
  由于parameter和argument都被翻译做参数,所以有些中文命名就混淆了,实际上,parameter指函数定义里的参数,也就是形参;而argument指函数调用时传给函数的实际参数,也就是实参
####关于argument,文档里提到有两种实参:####

  • 关键字参数(keyword argument,翻译成实参会好一点?):也就是调用函数时,采用键值对方式传值的参数,这种传递参数的方式叫做关键字参数,如下所示;
# complex定义原型
class complex([real[, imag]]) 
complex(real=3, imag=5)
complex(**{'real': 3, 'imag': 5})
#使用**号将字典解包裹,**{'real': 3, 'imag': 5}等价于real=3, imag=5
  • 位置参数(positional argument,同上翻译成位置实参会清晰一些):除开键值对方式传值的参数,都叫位置参数,如下所示。
complex(3, 5)
complex(*(3, 5))#使用*号将元组解包裹,*(3, 5)等价于3, 5
tlp = (3, 5)
complex(*tlp)#注意,使用complex(tlp)会出错!!

  需要注意的是字典形式的传值属于关键字参数,而以元组传值的方式属于位置参数,这两种形式的传值方式又叫做解包裹(unpacking)参数。所以关键字参数或位置参数都是属于函数调用时的定义,并且,位置参数必须出现在关键字参数前面,也就是说一旦采用键值对方式传值后,该键值对后面出现了非键值对,那么调用失败!

关于parameter,文档里提到了四种形参:
  • 位置或关键字(positional-or-keyword):定义时混用位置和关键字,如下所示;
 def func(foo, bar=None): ...
  • 只有位置(positional-only):定义时只有形参,没有默认值;
  • 可变数目位置(var-positional):定义函数时,参数的个数可变,但均不使用关键字形式定义形参,如下args形参;
def func(*args, **kwargs): ...
  • 可变数目关键字(var-keyword):定义函数时,参数的个数可变,但是为关键字形式定义形参,如上的kwargs形参。

  官方文档里对形参的划分不是很友好,我根据网上出现的各种名词加以自己的理解做了一个总结:

形参类型例子备注
必选参数def func(x)x在调用函数时必须按位置或以关键字参数指定,x是必选参数
默认参数def default_func(key=value)定义函数时参数key使用了键值对指定默认值,key就是默认参数
包裹(packing)位置参数def packing_positional_func(*args)定义函数时使用了*,此时将传入的零个或多个值放置在一个元组args内,args就是包裹位置参数
包裹(packing)关键字参数def packing_keyword_func(**kwargs)定义函数时使用了**,此时将传入的零个或多个键值对放置在一个字典kwargs内,kwargs就是包裹关键字参数

  注意:
  (1)定义函数时,必须按照必选参数、默认参数、包裹位置参数和包裹关键字参数的顺序定义形参列表;
  (2)包裹位置参数和包裹关键字参数可合称为可变参数或收集参数;
  (3)必选参数依然可以使用关键字参数形式传参,而默认参数也可以使用位置参数形式传参;
  (4)如果一个函数在定义里同时使用了默认参数及包裹位置参数,那么默认参数只能采用位置参数形式传值,这是由于位置参数必须出现在关键字参数前面,所以默认参数就不能使用关键字参数传值了,如下所示:

def func(param1='param1', *args):
    print param1
    print args

func(12, 1, 2, 3)
# 12
# (1, 2, 3)

# func(param1=12, 1, 2, 3)
# SyntaxError: non-keyword arg after keyword arg

4. Python2和Python3共存

  有时候需要同时使用Python2和Python3,在windows下同时安装Python2和3时,怎么启动两个编译器,pip又怎么区分是个问题,我在网上看了一些资料后,这里做一个小结。
  在windows上安装好两版的Python后,进入cmd,此时Python3会覆盖掉Python2的设置,也就是说,使用Python进入Python界面或者直接使用pip install命令都是在Python3下工作,这是由于安装Python3,安装器会自动在C盘的Windows文件夹下生成一个py.exe和pyw.exe的文件,这两个文件可以区分Python2和Python3,但默认是Python3,启动Python2,就需要使用命令"py -2"了。

动作Python2Python3
cmd启动编译器py -2py -3或python

pip安装包
pip2 install package-name
py -2 -m pip install package-name
pip3 install package-name
pip install package-name
py -3 -m pip install package-name

脚本执行
py -2 script-namepy -3 script-name
python script-name
py script-name

  注意:脚本执行还可以通过在脚本里加入一行注释也可实现区分,如果由python2解释运行,那么可以在脚本的第一行(一定要在脚本首行,即出现在”# -*- coding: utf-8 -*-“前面)加入

#!python2

如果是python3,那么在首行加入

#!python3

之后,运行脚本时,使用py script-name即可按希望的解释器运行,此时如果指定python2,则不能用python script-name运行脚本,如果是python3,则可以使用python script-name。


5. 推导式

  这部分内容参考python的各种推导式一文,Python的推导式一共有三种,Python2和Python3都支持,分别是:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式
5.1. 列表推导式

  列表推导式生成一个列表,语法(采用巴斯克范式)为:

list_display        ::=  "[" [expression_list | list_comprehension] "]"
expression_list ::=  expression ( "," expression )* [","]
list_comprehension  ::=  expression list_for
list_for            ::=  "for" target_list "in" old_expression_list [list_iter]
old_expression_list ::=  old_expression [("," old_expression)+ [","]]
old_expression      ::=  or_test | old_lambda_expr
list_iter           ::=  list_for | list_if
list_if             ::=  "if" old_expression [list_iter]

  其中,expression_list为直接创建列表的形式,list_comprehension是列表推导式,可以看出列表推导式for后面可以expression_list或者其他的for循环以及过滤条件if语句块。
  例1:此时的list_comprehension为expression for target_list in old_expression_list

>>> [i for i in 'asd', 'dsa']
['asd', 'dsa']

  例2:此时的list_comprehension含有if筛选符合条件的结果

>>> [i for i in [1, 2, 3, 4] if i != 3]
[1, 2, 4]
# 等价于
res = []
for i in [1, 2, 3, 4]:
	if i <> 3:
		res.append(i)
# res = [1, 2, 4]

  例3:此时的list_comprehension含有子for语句

>>> [ch for str in ['abc', 'pfg'] for ch in str]
['a', 'b', 'c', 'p', 'f', 'g']
# 等价于
res = []
for str in ['abc', 'pfg']:
	for ch in str:
		res.append(ch)
# res = ['a', 'b', 'c', 'p', 'f', 'g']

  **注意:**将列表推导式的[]改成()即可得到生成器,下一小节将讲生成器。

5.2. 字典推导式

  字典推导和列表推导的使用方法是类似的,只不过中括号该改成大括号,并且list_comprehension的expression部分改成键值对的形式。直接举例说明:
  例1

mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
mcase_frequency = {
    k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
    for k in mcase.keys()
    if k.lower() in ['a','b']
}
print mcase_frequency
#  Output: {'a': 17, 'b': 34}

  例2

mcase = {'a': 10, 'b': 34}
mcase_frequency = {v: k for k, v in mcase.items()}
print mcase_frequency
#  Output: {10: 'a', 34: 'b'}
5.3. 集合推导式

集合推导式跟列表推导式也是类似的, 唯一的区别在于它使用大括号{}。
  例1

squared = {x**2 for x in [1, 1, 2]}
print(squared)
# Output: set([1, 4])

6. 生成器(Generator)

  生成器本质上还是产生可迭代对象。生成器与前面提到的推导式的区别在于:生成器并不是一次性将数据(或项)加载进内存,也就是说待生成的可迭代对象并不是一次性生成的,而是每调用一次生成器,就生成一项数据。这么做的原因在于:如果需要创建一个长度为10,000的列表,一次性加载进内存,显然很浪费空间,如果能够把生成这些项的过程抽象并记录下来,那么每次需要数据时,只需要调用这个过程就可以了,这个过程就是生成器。
  生成器与推导式最直观的区别在于:调用生成器返回的一般是一个generator对象,而列表推导式返回的是一个列表(即每一项数据都可以直接获取),虽然列表也是对象,但是这里的对象之间的区别大家意会区别即可。如下所示:

# Python 2.7.12环境
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x000000000346E288>

  另外注意:我在这里表明了Python2.7环境,因为生成器的调用在Python2和Python3环境里有一点点差异,其他地方并没有差异。

6.1. 创建生成器
  1. 将列表推导式的[]改成()
      如上面生成g的代码所示,这种方式又叫做循环生成器。一定要注意这里的g是一个generator对象

  2. 使用yield语句
      将生成项的过程单独放在一个函数里,使用yield来返回产生的数据,一般将yield放在一个循环里(也可以在一个函数里使用多个yield语句来产生生成不同数据的效果),这个函数就叫做生成器。生成器在调用产生一个值后,将被挂起在yield处,下次再被调用时,从挂起处接着执行,生成器里也可以有return语句,但是return执行后,整个函数将会退出,所以可以作为有条件结束生成器的方法。下面是一个生成器的例子。

# Python 3.6.4rc1环境
def gene(lst):

    print('==========')

    for element in lst:
        yield element

    print('----------')
6.2. 调用生成器

  生成器在执行过程中,遇到yield就会中断执行并挂起,所以yield后面的代码有可能执行不到。另外,生成器只能next,不能prev。调用生成器的方式:

  1. next()方法(py2和py3有差异)
  2. 使用循环对生成器对象进行遍历(推荐)
  3. 调用生成器对象的send()方法
6.2.1. next()方法

  调用next()方法:注意Python3是将生成器对象单做参数传入内建函数next()获取值,该生成器对象仅有一个__next__()方法,而在Python2里面,每个生成器对象都有一个next()方法,可以直接调用。

# Python 3.6.4rc1环境
lst = [1, 2, 3, 4, 8, 9]
g = gene(lst)
print('start--2')
for num in range(len(lst)):
    print(next(g))     # 注意这里,next()方法的参数是生成器对象
print('end--2')

# 结果:
# start--2
# ==========
# 1
# 2
# 3
# 4
# 8
# 9
# end--2

  注意到yield后面的“print(’---------’)”并未“执行”,这是因为next()函数返回的是可迭代对象的数值,而不是这个过程,所以以next()方式调用,yield后面的语句有可能永远不会执行,因为最后一次调用生成器,生成器就挂在yield那里了,如果yield循环完了所有项,继续调用生成器,则会出现StopIteration异常。下面是Python2使用yield示例:

# Python 2.7.12环境
>>> g = (x * x for x in range(10))
>>> for i in range(10):
...     print g.next()
# 0
# 1
# 4
# 9
# 16
# 25
# 36
# 49
# 64
# 81
6.2.2. 循环调用生成器

  循环一个生成器对象,能够执行完生成器里yield语句之后的语句,并且一般不会出现StopIterator异常,py2和py3没有差异,这里仅以py3为例。

# Python 3.6.4rc1环境
print('start--1')
for i in gene(lst):   #注意:yield后面的语句也能够执行
    print(i)
print('end--1')
# 结果:
# start--1
# ==========
# 1
# 2
# 3
# 4
# 8
# 9
# ----------
# end--1
6.2.3. send()方法

  generator.send(value)方法为生成器对象的方法,可以直接调用,其可以传入参数到生成器里的变量,这个值最终成为yield表达式返回在函数内部的结果,注意不影响yield返回给外部调用处的值,py2和py3没有差异。

# Python 2.7.12环境
>>> def my_range(start, end):
...     for n in range(start, end):
...				#使用返回值的时候,安全起见,这里最好闭合yield表达式
...             ret = (yield 2*n + 1)
...             print(ret)#可以把这行注释掉后看看运行结果的差别
>>> g = my_range(3, 6)
>>> print(g.send(None))
7
>>> print(g.send('Hello01'))
Hello01
9

  这段代码里,yield用在ret那一行里,也就是说这个生成器返回给外部调用者的值是2*n+1,但是传给ret的值却是send的参数’Hello01’,另外使用send()方法时,第一次调用生成器,value只能是None,否则会报“TypeError: can’t send non-None value to a just-started generator”的问题。当然也可以在调用send()方法之前先调用一次next()方法,目的是让生成器先进入yield表达式


7. str和string

  Python由于是弱语言类型,所以没有类型之分,但是字符串作为很重要的一种数据类型,有字面量和字符串模块之分,Python文档里用str来表示字符串字面量,用string表示模块(内置一个string.py的模块),string模块里的函数都要通过string.function()来调用,而str的方法调用形式为"字符串".function(),这也从一定层面上反应了Python里一切皆对象的理念。


8.Python函数参数传递

  Python里,函数的参数传递类似于Java里面的值传递,也就说:

  • 对于不可变对象,如strings, tuples, 和 numbers,在函数传递过程中传递的是该对象的值的副本,此时函数内部对这个副本的修改不影响原对象;
  • 对于可变对象,如列表、字典等等,在函数传递过程中传递的是该对象的地址的副本,此时函数内部对这个地址副本操作是会影响到原对象的,如果在函数内部将形参指向了其他地址,之后对该参数的操作就和实参无关了。

  Java里也是这样,不过Java里只分基本类型、String和对象,其中基本类型和String对应着Python的不可变对象,而对象属于可变对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值