Python基础入门知识(8)

接前面的文章:
Python基础入门知识(1)
Python基础入门知识(2)
Python基础入门知识(3)
Python基础入门知识(4)
Python基础入门知识(5)
Python基础入门知识(6)
Python基础入门知识(7)

2 Python的基础知识

2.13 函数

2.13.4 传递列表

2.13.4.2 禁止函数修改列表

有时候,需要禁止函数修改列表。例如,假设像前一个示例那样,我们有一个未打印的设计列表,并编写了一个将这些设计移到打印好的模型列表中的函数。我们可能会做出这样的决定:即便打印所有设计后,也要保留原来的未打印的设计列表,以供备案。但由于我们将所有的设计都移出了unprinted_designs,这个列表变成了空的,原来的列表没有了。为解决这个问题,可向函数传递列表的副本而不是原件;这样函数所做的任何修改都只影响副本,而丝毫不影响原件。
要将列表的副本传递给函数,可以像下面这样做:

function_name(list_name[:])

利用切片法[:]来创建列表的副本。如果不想清空未打印的设计列表,可像下面这样调用print_models():

print_models()unprinted_designs[:], completed_models)

2.13.5 传递任意数量的实参

有时候,我们预先不知道函数需要接受多少个实参,好在Python允许函数从调用语句中收集任意数量的实参。
例如,用一个我们之前写过的关于制作比萨的函数pizza.py,它需要接受很多配料,但我们无法预先确定顾客要多少种配料。下面的函数只有一个形参*toppings,但不管调用语句提供了多少实参,这个形参都将它们统统收入囊中:

def make_pizza(*toppings):
    """打印顾客点的所有配料"""
    print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms','green peppers','extra cheese')

输出:

('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')

现在,我们可以将这条print语句替换为一个循环,对配料列表进行遍历,并对顾客点的比萨进行描述:

def make_pizza(*toppings):
    """概述要制作的比萨"""
    print("\nMaking a pizza with the following toppings:")
    for topping in toppings:
        print("- "+topping)
make_pizza('pepperoni')
make_pizza('mushrooms','green peppers','extra cheese')

输出:


Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
2.13.5.1 结合使用位置实参和任意数量实参

如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。
例如,如果前面的函数还需要一个表示比萨尺寸的实参,必须将该形参放在形参*toppings的前面:

def make_pizza(size,*toppings):
    """概述要制作的比萨"""
    print("\nMaking a "+str(size)+"-inch pizza with the following toppings:")
    for topping in toppings:
        print("- "+topping)
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms','green peppers','extra cheese')

输出:

Making a 16-inch pizza with the following toppings:
- pepperoni

Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
2.13.5.2 使用任意数量的关键字实参

有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键—值对——调用语句提供了多少就接受多少。
有这样一个示例:要创建用户简介,我们知道我们将收到有关用户的信息,但不确定会是什么样的信息。在下面的示例中,函数build_profile()接受名和姓,同时还接受任意数量的关键字实参:

def build_profile(first, last, **user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切"""
    profile = {}
    profile['first_name'] = first
    profile['last_name'] = last
    for key, value in user_info.items():
        profile[key] = value
    return profile
user_profile = build_profile('albert','einstein',location='princeton',field='physics')
print(user_profile)

输出:

{'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'}

函数build_profile()的定义要求提供名和姓,同时允许用户根据需要提供任意数量的名称—值对。形参**user_info中的两个星号让Python创建一个名为user_info的空字典,并将收到的所有名称—值对都封装到这个字典中。在这个函数中,可以像访问其他字典那样访问user_info中的名称—值对。

2.13.6 将函数存储在模块中

函数的优点之一是,使用它们可将代码块与主程序分离。通过给函数指定描述性名称,可让主程序容易理解得多。我们可以更进一步,将函数存储在被称为模块的独立文件中,再将模块导入到主程序中。import语句允许在当前运行的程序文件中使用模块中的代码。
导入模块的方法有多种,下面对每种都作简要的介绍。

2.13.6.1 导入整个模块

是可导入的,得先创建模块。模块是扩展名为.py的文件,包含要导入到程序中的代码。下面来创建一个包含函数make_pizza()的模块。为此,我们将文件pizza.py中除函数make_pizza()之外的其他代码都删除:

def make_pizza(size,*toppings):
    """概述要制作的比萨"""
    print("\nMaking a "+str(size)+"-inch pizza with the following toppings:")
    for topping in toppings:
        print("- "+topping)

接下来,我们在文件所在的目录中创建另一个名为making_pizzas.py的文件,这个文件导入刚创建的模块,再调用make_pizza()两次:
making_pizzas.py

import pizza
pizza.make_pizza(16, 'pepperoni')
pizza.make_pizza(12, 'mushrooms','green peppers','extra cheese')

Python读取这个文件时,代码行import pizza让Python打开文件pizza.py,并将其中的所有函数都复制到这个程序中。我们看不到复制的代码,因为这个程序运行时,Python在幕后复制这些代码。我们只需知道,在making_pizzas.py中,可以使用pizza.py中定义的所有函数。
这些代码的输出与没有导入模块的原始程序相同:


Making a 16-inch pizza with the following toppings:
- pepperoni

Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

这就是一种导入方法:只需编写一条import语句并在其中指定模块名,就可在程序中使用该模块中的所有函数。如果我们使用这种import语句导入了名为module_name.py的整个模块,就可使用下面的语法来使用其中任何一个函数:

module_name.function_name()
2.13.6.2 导入特定的函数

我们还可以导入模块中的特定函数,这种导入方法的语法如下:

from moduel_name import function_name

通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:

from module_name import function_0, function_1, function_2

对于前面的making_pizzas.py示例,如果只想导入要使用的函数,代码将类似于下面这样:

from pizza import make_pizza
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms','green peppers','extra cheese')

若使用这种语法,调用函数时就无需使用句点。由于我们在import语句中显式地导入了函数make_pizza(),因此调用它时只需指定其名称。

2.13.6.3 使用as给函数指定别名

如果要导入的函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名——函数的另一个名称,类似于外号。要给函数指定这种特殊外号,需要在导入它时这样做。下面给函数make_pizza()指定了别名mp()。这是在import语句中使用make_pizzaas mp实现的,关键字as将函数重命名为我们提供的别名:

from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms','green peppers','extra cheese')

上面的import语句将函数make_pizza()重命名为mp();在这个程序中,每当需要调用make_pizza()时,都可简写成mp(),而Python将运行make_pizza()中的代码,这可避免与这个程序可能包含的函数make_pizza()混淆。
指定别名的通用语法如下:

from module_name import function_name as fn
2.13.6.4 使用as给模块指定别名

我们还可以给模块指定别名。通过给模块指定简短的别名(如给模块pizza指定别名p),让我们能够更轻松地调用模块中的函数。相比于pizza.make_pizza(),p.make_pizza()更为简洁:

import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms','green peppers','extra cheese')

上述import语句给模块pizza指定了别名p,但该模块中所有函数的名称都没变。调用函数make_pizza()时,可编写代码p.make_pizza()而不是pizza.make_pizza(),这样不仅能使代码更简洁,还可以让我们不再关注模块名,而专注于描述性的函数名。这些函数名明确地指出了函数的功能,对理解代码而言,它们比模块名更重要。给模块指定别名的通用语法如下:

import module_name as mn
2.13.6.5 导入模块中的所有函数

使用星号(*)运算符可让Python导入模块中的所有函数:

from pizza import *
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms','green peppers','extra cheese')

import语句中的星号让Python将模块pizza中的每个函数都复制到这个程序文件中。由于导入了每个函数,可通过名称来调用每个函数,而无需使用句点表示法。然而,使用并非自己编写的大型模块时,最好不要采用这种导入方法:如果模块中有函数的名称与我们的项目中使用的名称相同,可能导致意想不到的结果:Python可能遇到多个名称相同的函数或变量,进而覆盖函数,而不是分别导入所有的函数。
最佳的做法是,要么只导入我们需要使用的函数,要么导入整个模块并使用句点表示法。这能让代码更清晰,更容易阅读和理解。这里之所以介绍这种导入方法,只是想让我们在阅读别人编写的代码时,如果遇到类似于下面的import语句,能够理解它们:

from module_name import *

2.13.7 函数编写指南

编写函数时,需要牢记几个细节。应给函数指定描述性名称,且只在其中使用小写字母和下划线。描述性名称可帮助我们和别人明白代码想要做什么。给模块命名时也应遵循上述约定。
每个函数都应包含简要地阐述其功能的注释,该注释应紧跟在函数定义后面,并采用文档字符串格式。文档良好的函数让其他程序员只需阅读文档字符串中的描述就能够使用它:他们完全可以相信代码如描述的那样运行;只要知道函数的名称、需要的实参以及返回值的类型,就能在自己的程序中使用它。
给形参指定默认值时,等号两边不要有空格:

def function_name(parameter_0, paramete_1='default value')

PEP 8建议代码行的长度不要超过79字符,这样只要编辑器窗口适中,就能看到整行代码。如果形参很多,导致函数定义的长度超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来。
大多数编辑器都会自动对齐后续参数列表行,使其缩进程度与我们给第一个参数列表行指定的缩进程度相同:

def function_name(
parameter_0,paramete_1,paramete_2,
paramete_3,paramete_4,paramete_5):
function body...

如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开,这样将更容易知道前一个函数在什么地方结束,下一个函数从什么地方开始。
所有的import语句都应放在文件开头,唯一例外的情形是,在文件开头使用了注释来描述整个程序。

2.14 类

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,我们编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,我们定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了令我们惊讶的地步。
根据类来创建对象被称为实例化,这让我们能够使用类的实例。在本章中,我们将编写一些类并创建其实例。我们将指定可在实例中存储什么信息,定义可对这些实例执行哪些操作。我们还将编写一些类来扩展既有类的功能,让相似的类能够高效地共享代码。我们将把自己编写的类存储在模块中,并在自己的程序文件中导入其他程序员编写的类。
理解面向对象编程有助于我们像程序员那样看世界,还可以帮助我们真正明白自己编写的代码:不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维,让我们能够通过编写程序来解决遇到的几乎任何问题。
随着面临的挑战日益严峻,类还能让我们以及与我们合作的其他程序员的生活更轻松。如果我们与其他程序员基于同样的逻辑来编写代码,我们们就能明白对方所做的工作;我们编写的程序将能被众多合作者所理解,每个人都能事半功倍。

2.14.1 创建和使用类

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类Dog——它表示的不是特定的小狗,而是任何小狗。对于大多数宠物狗,我们都知道些什么呢?它们都有名字和年龄;我们还知道,大多数小狗还会蹲下和打滚。由于大多数小狗都具备上述两项信息(名字和年龄)和两种行为(蹲下和打滚),我们的Dog类将包含它们。这个类让Python知道如何创建表示小狗的对象。编写这个类后,我们将使用它来创建表示特定小狗的实例。

2.14.1.1 创建Dog类

根据Dog类创建的每个实例都将存储名字和年龄。我们赋予了每条小狗蹲下(sit())和打滚(roll_over())的能力:
dog.py

class Dog():"""一次模拟小狗的简单尝试"""def __init__(self, name, age):"""初始化属性name和age"""
        self.name = name ❹
        self.age = age
    def sit(self):"""模拟小狗被命令时蹲下"""
        print(self.name.title()+" is now sitting.")
    def roll_over(self):
        """模拟小狗被命令时打滚"""
        print(self.name.title()+" rolled over!")

在❶处,我们定义了一个名为Dog的类。根据约定,在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。在❷处,我们编写了一个文档字符串,对这个类的功能作了描述。

  • 方法__init__()

类中的函数称为方法;你前面学到的有关函数的一切都适用于方法,就目前而言,唯一重要的差别是调用方法的方式。❸处的方法__init__()是一个特殊的方法,每当你根据Dog类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
我们将方法__init__()定义成了包含三个形参:self、name和age。在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面。为何必须在方法定义中包含形参self呢?因为Python调用这个__init__()方法来创建Dog实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们创建Dog实例时,Python将调用Dog类的方法__init__()。我们将通过实参向Dog()传递名字和年龄;self会自动传递,因此我们不需要传递它。每当我们根据Dog类创建实例时,都只需给最后两个形参(name和age)提供值。
❹处定义的两个变量都有前缀self。以self为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name获取存储在形参name中的值,并将其存储到变量name中,然后该变量被关联到当前创建的实例。self.age = age的作用与此类似。像这样可通过实例访问的变量称为属性。
Dog类还定义了另外两个方法:sit()和roll_over()(见❺)。由于这些方法不需要额外的信息,如名字或年龄,因此它们只有一个形参self。我们后面将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。当前,sit()和roll_over()所做的有限,它们只是打印一条消息,指出小狗正蹲下或打滚。但可以扩展这些方法以模拟实际情况:如果这个类包含在一个计算机游戏中,这些方法将包含创建小狗蹲下和打滚动画效果的代码。如果这个类是用于控制机器狗的,这些方法将引导机器狗做出蹲下和打滚的动作。

2.14.2 使用类和实例

你可以使用类来模拟现实世界中的很多情景。类编写好后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。

2.14.2.1 Car类

下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:
car.py

class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self, make, model, year):"""初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
    def get_descriptive_name(self):"""返回整洁的描述性信息"""
        long_name = str(self.year)+' '+self.make+' '+self.model
        return long_name.title()
my_new_car = Car('audi', 'a4', 2016)print(my_new_car.get_descriptive_name())

在❶处,我们定义了方法__init__()。与前面的Dog类中一样,这个方法的第一个形参为self;我们还在这个方法中包含了另外三个形参:make、model和year。方法__init__()接受这些形参的值,并将它们存储在根据这个类创建的实例的属性中。创建新的Car实例时,我们需要指定其制造商、型号和生产年份。 在❷处,我们定义了一个名为get_descriptive_name()的方法,它使用属性year、make和model创建一个对汽车进行描述的字符串,让我们无需分别打印每个属性的值。为在这个方法中访问属性的值,我们使用了self.make、self.model和self.year。在❸处,我们根据Car类创建了一个实例,并将其存储到变量my_new_car中。接下来,我们调用方法get_descriptive_name(),指出我们拥有的是一辆什么样的汽车:

2016 Audi A4

为让这个类更有趣,下面给它添加一个随时间变化的属性,它存储汽车的总里程。

后续更新:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值