1、函数
相比于C++,python中写一个函数需要一个关键字def。其主要结构如下:
def 函数名(函数参数):
"""函数文档,docstring"""
函数体
return
如果一个函数是一个类的一部分,它就称为一个方法。
注意函数没有类型信息,无论是函数参数还是返回值,都不需要类型信息,当然为了便于阅读,可以使用函数注解。
函数注解是py3新增的一项功能,类似注释,它们只负责提高程序的可读性,而不会对程序功能做出任何改变。
推荐python代码为了可读性,遵循PEP8的规范。以下是一个例程:
def search4vowels(word):
"""Display any vowels found in an asked-for word."""
vowels=set('aeiou')
found=vowels.intersection(set(word))
for vowel in found:
print(vowel)
效果如下:
可以看得出来,犯了很多愚蠢的错误。首先,最重要的是,虽然没有为函数的参数指定类型,但函数参数必须有类型,像我第一次那样,参数直接是chy是肯定不行的,单独的chy会被编译器认为是一个变量而不是一个字符串;另外,第二次没有输入参数,会报缺少参数的错误,第三次直接把函数名打错了,最后一次,加了单引号,得到正确结果。
在python中,每一个对象都有一个与之关联的真值,表示这个对象为True或者False。如果该对象计算为0、值为None、是一个空串或一个空的数据结构,那么就是False,否则是True。使用函数bool来计算对象的真值。严格来说,所有的非空数据结构都计算为True。
默认来说,函数使用return关键字来返回值,只能返回一个值,若想返回多个值,就需要使用数据结构,这和C++中类似。
将上面的函数改为返回word中的元音,如下:
def search4vowels(word):
"""Display any vowels found in an asked-for word."""
vowels=set('aeiou')
return vowels.intersection(set(word))
效果如下:
注意,当word中不含元音时,应该返回一个空集合。由于集合使用{}包围,那么空集合应该返回{}。但是返回的是set()。这是因为,{}没法区分是空集合还是空字典,因此用{}表示空字典,用set()表示空集合。
上述代码加入注解后的效果如下:
def search4vowels(word:str)->set:
"""Display any vowels found in an asked-for word."""
vowels=set('aeiou')
return vowels.intersection(set(word))
在运行时,加不加注解没有任何区别,只有在使用help时有区别:
help会把注解显示,还会把docstring也显示。
接下来,定义一个更泛用的函数,用于找到两个字符串中所有相同字符的集合,代码如下:
def search4vowels(phrase:str)->set:
"""Display any vowels found in a supplied phrase."""
vowels=set('aeiou')
return vowels.intersection(set(word))
def search4letters(phrase:str,letters:str)->set:
"""Return a aet of the 'letters' found in 'phrase'."""
return set(letters).intersection(set(phrase))
可以在一个文件中定义多个函数,注意,根据PEP8,两个函数间要空两行。这里的第二个函数search4letters用于实现找到两个字符串的交集。注意,第一个函数是第二个函数在letters=‘aeiou’情况下的特例,因此,如果能够让letters默认为aeiou,那么就可以实现函数二对函数一的完全代替。这样就可以实现函数的可重用。
如何实现呢?很简单,在参数注解的后面加入默认值即可,如下:
def search4letters(phrase:str,letters:str='aeiou')->set:
"""Return a aet of the 'letters' found in 'phrase'."""
return set(letters).intersection(set(phrase))
这样,当我们在调用search4letters函数时,若只用了一个参数,那么就默认第二个参数是aeiou;若用了两个参数,那么letters就不再是默认值。默认值与初值不同,不要搞混。默认值只有在def行才能修改。
与C++不同的是,python提供了两种参数赋值方法,位置赋值与关键字赋值。其中,位置赋值的情况下,对函数的调用与C++中相同,即函数f(A,B,C)调用时就是f(1,2,3),其中A=1,B=2,C=3。然而,关键字赋值的情况下,参数顺序就没用了,调用f可以是f(C=3,A=1,B=2),即点名某个参数等于多少。这样就不用记住参数顺序了,当然缺点就是要多打一些字。
2、模块
好,既然在1中,我们已经写好了函数,那么,如何实现它的可重用呢?
如果调用函数的代码就在函数文件里,那直接用就好了,但是如果多个文件都要用这个函数,那每个文件里都把这个函数重新写一遍,那就太蠢了。要使用import。
我们已经知道了,import是用来引入模块的,也就是说,已经有了函数,还要把它打包成一个模块,才能在其他文件中调用这个函数。
首先来看import如何引入模块。
使用import A引入一个名为A的模块,编译器会去三个地方找这个叫A的模块:首先在当前工作目录找,然后去解释器的site-package找,最后去标准库位置找。找到了的话,直接引入,没找到就报错。
当前工作目录,这个概念我是在linux中了解到的。在win中,用cmd进入命令行
那么这里的C:\Users\Amarantine就是当前工作目录,可以用cd来换工作目录。
如果我们import的模块不在工作目录中,那就会报错。
因此,要想使用一个模块,可以把它放在我们的当前工作目录里,但是这样太麻烦了。更好的方法是放在site-package里,这里放着的都是第三方的python模块。标准库模块不要想了,那不是我们能染指的。
在py3.4中,引入了一个新的模块,setuptools,使用这个模块可以较为方便的在site-package中加入我们自己的模块,仅需三步:
①、创建一个发布描述
至少需要两个描述文件,一个是setup.py安装文件,一个是README.txt解释文件。这两个文件必须放在和函数源码文件相同的文件夹中。setup文件中的代码几乎是定死的,以发布vsearch这个模块为例,其代码如下:
from setuptools import setup
setup(
name='vsearch',
version='1.0',
description='The Head First Python Search Tools',
author='Osiris',
author_email='123456@qq.com',
url='baidu.com',
py_modules=['vsearch'],
)
重要的参数有两个,一个是name,其值是发布的包的名字;另一个是py_modules,其值是文件夹中所有py文件的列表。注意,这里py_modules即使填写错误,也不会报错。我第一次就是把vsearch打成了vresearch导致安装成功却import失败。很是蛋疼。
txt文件可以为空。
如图,文件夹中有三个文件:
②、生成一个发布文件
这一步要使用命令行来完成。
在当前工作目录移到setup文件所在的文件夹内后,使用命令
py -3 setup.py sdist
来生成发布文件。发布文件是一个压缩包,保存在工作路径下的一个叫disk的文件夹中。
③、安装发布文件
py3.4包含了一个名为pip的工具,可以用来安装包。将工作目录设置为disk后,按下图输入命令。
可以看出,已经成功安装。
需要验证是否安装成功,只靠上面的显示是不行的。要用import来验证。
如图可以看出,在我把setup文件改好之后,模块真正安装成功了。
注意,site-package实际上是一个文件夹,位于...\python\Lib\site-packages,真正安装好后,会有一个文件夹,名字中带有安装的模块,另外,模块中函数的源码也存在该文件夹中,如下:
如图,可以看见vsearch-1.0-py3.7.egg-info文件夹和vsearch.py文件。
3、函数的按值调用与按引用调用
在学C的时候,我们已经清楚了按值调用和按引用调用的区别,这是一个重要考点。
按值调用不会改变函数参数的值,但按引用调用会改变。
在python中,函数既支持按值调用,又支持按引用调用。也就是说,在某些情况下,函数的行为看起来像按值调用,在另外一些情况下,则是按引用调用。其本质则是因为,python中变量都是对象引用,变量中存储的是值的内存地址,而不是真正的值,从这一点看,所有的变量都是指针。
当变量指向的值是可变的值:列表、字典、集合时,按引用调用来行为;
当变量指向的值是不变的值:字符串、正数、元组时,按值调用来行为。
然而有一点要注意,参见下面两个函数:
#按值调用
def double(arg):
print('Before: ',arg)
arg=arg*2
print('After: ',arg)
#按引用调用
def change(arg):
print('Before: ',arg)
arg.append('More data')
print('After: ',arg)
当arg为整数时,调用double函数,参数为a,看上去a会不变,因为a是一个整数,属于不变的值,应按值调用,实际也是这样:
但是当arg为一个列表时,按原来的想法,参数为b,是一个列表,列表属于可变的值,因此b应该翻倍,因为是按引用调用,实际却不是这样:
可以看出,arg的确翻倍了,但是b没有翻倍。
这是python中=的特性决定的。
在python中,=是赋值操作,在赋值时,首先执行=右边的代码,创建一个新的对象,然后将这个新的对象赋给=左边的变量。也就是说,在上面的代码中,是对b的内容进行*2的操作,形成一个新的对象,赋给arg,然而b本身一点变化都没有。因此b不会变化。
其实还有一个好玩的事,前面说过,python中变量都是指针,那么参见下图:
我们将a初始化为一个整数1,b初始化为2,然后观察a和b的地址,它们是不同的;
然后将a加1,现在a=2,按C的理解,应该是140734582055568这个地址中的内容从1变成了2,但实际不是这样,是a指向了b的地址,原因是b=2。这也可以看出,对于不能改变的量,python赋值采用的是改变指向的操作完成的。这也导致了在C中很容易做的数字处理变得麻烦。
4、检查PEP8的兼容性
若想检查代码是否符合PEP8的规范,需要使用pytest框架和pep8插件。同样的,我们使用pip进行安装。
按如下执行代码:
如下图表示安装成功。
接下来按照同样方法安装pep8。
安装成功之后,前往需要检查的代码文件所在的文件夹,此处就是vsearch.py所在的文件夹,执行如下命令:
py.test --pep8 vsearch.py
效果如下:
可以看出,有五处不合格,三处是代码中间的冒号:后面没有加空格,一处是逗号,后面没有加空格,一处是等号=左右没有加空格。更改。然后再次检查。如下:
右侧的100%和1 passed说明已经没有错误。