美文网首页Head First Python
《Head First Python》Ch4:代码重用:函数与模

《Head First Python》Ch4:代码重用:函数与模

作者: 老A不加V | 来源:发表于2021-02-25 22:01 被阅读0次

    版权声明:本文为CSDN博主「一笑照夜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/erwugumo/article/details/91385059

    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)
    

    效果如下:


    image

    可以看得出来,犯了很多愚蠢的错误。首先,最重要的是,虽然没有为函数的参数指定类型,但函数参数必须有类型,像我第一次那样,参数直接是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))
    

    效果如下:


    image

    注意,当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时有区别:


    image

    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进入命令行


    image

    那么这里的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文件可以为空。

    如图,文件夹中有三个文件:


    image

    ②生成一个发布文件

    这一步要使用命令行来完成。

    在当前工作目录移到setup文件所在的文件夹内后,使用命令

    py -3 setup.py sdist

    来生成发布文件。发布文件是一个压缩包,保存在工作路径下的一个叫disk的文件夹中。

    ③安装发布文件

    py3.4包含了一个名为pip的工具,可以用来安装包。将工作目录设置为disk后,按下图输入命令。


    image

    可以看出,已经成功安装。

    需要验证是否安装成功,只靠上面的显示是不行的。要用import来验证。


    image

    如图可以看出,在我把setup文件改好之后,模块真正安装成功了。

    注意,site-package实际上是一个文件夹,位于...\python\Lib\site-packages,真正安装好后,会有一个文件夹,名字中带有安装的模块,另外,模块中函数的源码也存在该文件夹中,如下:


    image

    如图,可以看见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是一个整数,属于不变的值,应按值调用,实际也是这样:


    image

    但是当arg为一个列表时,按原来的想法,参数为b,是一个列表,列表属于可变的值,因此b应该翻倍,因为是按引用调用,实际却不是这样:


    image
    可以看出,arg的确翻倍了,但是b没有翻倍。

    这是python中=的特性决定的。

    在python中,=是赋值操作,在赋值时,首先执行=右边的代码,创建一个新的对象,然后将这个新的对象赋给=左边的变量。也就是说,在上面的代码中,是对b的内容进行*2的操作,形成一个新的对象,赋给arg,然而b本身一点变化都没有。因此b不会变化。

    其实还有一个好玩的事,前面说过,python中变量都是指针,那么参见下图:


    image

    我们将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进行安装。

    按如下执行代码:


    image

    如下图表示安装成功。


    image
    接下来按照同样方法安装pep8。

    安装成功之后,前往需要检查的代码文件所在的文件夹,此处就是vsearch.py所在的文件夹,执行如下命令:

    py.test --pep8 vsearch.py

    效果如下:


    image

    可以看出,有五处不合格,三处是代码中间的冒号:后面没有加空格,一处是逗号,后面没有加空格,一处是等号=左右没有加空格。更改。然后再次检查。如下:


    image
    右侧的100%和1 passed说明已经没有错误。
    ————————————————

    相关文章

      网友评论

        本文标题:《Head First Python》Ch4:代码重用:函数与模

        本文链接:https://www.haomeiwen.com/subject/qhhpfltx.html