【啃书系列】Python编程从入门到实践 下

阅读更多文章,请看学习笔记汇总

【啃书系列】Python编程从入门到实践 上
【啃书系列】Python编程从入门到实践 项目篇
官方权威Python教程zh

查官方文档,清楚得不得了或者用help(input)
1. RUNOOB.com_python3-tutorial函数功能查询

2. Python常用函数说明文档

第八章.函数

通过使用函数,程序的编写、阅读、测试和修复都将更容易

Python常用函数说明文档

8.1 定义函数def

使用关键词def告诉Python你要定义一个函数

函数定义:def 函数名(参数):

函数定义代码放在调用代码前面

# 定义函数
def greet_user(usename):
    """显示简单的问候语"""
    print("Hello, " + usename.title() + "!")

# 调用函数
greet_user('jesse') 

文档字符串(docstring),描述函数是做什么的,文档字符串用三引号括起来,如"""显示简单的问候语"""

在greet_user(‘jesse’)中,实参’jesse’传递给了函数greet_user(),这个值被存储在形参username中

8.3 返回值 直接return

函数可以返回任何类型的值,包括列表和字典等较复杂的数据结构

8.3.1 返回简单值

在函数中,可使用return语句将值返回到调用函数的代码行

def my_abs(x):
    if (x >= 0):
        return x
    else:
        return -x

res = my_abs(-3)
print(res) # 输出3

8.3.2 让实参变成可选的

给形参middle_name指定一个默认值——空字符串,并将它放在形参列表的末尾

这样调用时可以传两个参数,也可以传3个参数

传参数有两种方式

get_formatted_name('jimi', 'hendrix')
get_formatted_name(first_name='jimi', last_name='hendrix')
# 实参可选
def get_formatted_name(first_name, last_name, middle_name=''):
    if middle_name:
        full_name = first_name + ' ' + middle_name + ' ' + last_name
    else:
        full_name = first_name + ' ' + last_name

    return full_name.title()


print(get_formatted_name('jimi', 'hendrix'))  # 输出 Jimi Hendrix
print(get_formatted_name('john', 'hooker', 'lee'))  # 输出 John Lee Hooker

8.3.3 返回字典

def build_person(first_name, last_name, age=''):
    """返回一个字典,其中包含有关一个人的信息"""
    person = {'first': first_name, 'last': last_name}
    if age:
        person['age'] = age
    return person


musician = build_person('jimi', 'hendrix', 27)
# 或 musician = build_person(first_name='jimi', last_name='hendrix', age=27) 
# 这是其实参数的顺序可以随意

print(musician)  # 输出 {'first': 'jimi', 'last': 'hendrix', 'age': 27}

注意:这里虽然默认age=''是字符串,但是你可以输入一个int数值,相当于age = 27

8.3.4 pass空语句 用来占位

pass语句在函数中的作用:当你在编写一个程序时,执行语句部分思路还没有完成,这时你可以用pass语句来占位,保证格式完整,也可以当做是一个标记,标记这是我之后要来完成的函数

def isDigit():
    pass  # do nothing

定义一个函数isDigit,但函数体部分暂时还没有完成,又不能空着不写内容,因此可以用pass来替代占个位置

Python中的pass 类似C++中;

if(a == 2) {
; // do nothing
} else {
cout << a << endl; 
}

8.4 传递列表

8.4.1 在函数中修改列表

需要打印的设计存储在一个列表中,打印后移到另一个列表中

编写两个函数,实现上面的需求

# 定义函数-传递列表
def print_models(unprinted_designs, completed_models):
    """
    模拟打印每个设计,直到打印没有未打印的设计为止
    打印每个设计后,都将其移到列表completed_models中
    """
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        print("Printing model: " + current_design)
        completed_models.append(current_design)


def show_completed_models(completed_models):
    """显示打印好的所有模型"""
    print("\nThe following models have been printed:")
    for completed_model in completed_models:
        print(completed_model)


unprinted_designs = ['iphone', 'robot pendant', 'dodecahedron']
completed_models = []

# 调用上面定义的两个函数
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

描述性的函数名让别人阅读这些代码时更清晰

用了函数的好处:如果我们需要对打印代码进行修改,只需修改这些代码一次,就能影响所有调用该函数的地方,效率很高

理念:每个函数都应只负责一项具体工作,这由于使用一个函数来完成两项工作

8.4.2 禁止函数修改原列表

有时需要禁止函数修改原列表,可以像函数传递列表的副本而不是原件,这样函数所做的任何修改都只影响副本,而丝毫不影响原件

切片表示法[:]创建列表副本
如可以把上面例子中的调用修改为

print_models(unprinted_designs[:], completed_models)

这样原列表unprinted_designs不会被修改

但是一般不建议创建副本,除非特别需要。因为让函数使用现成的列表可避免花时间和内存创建副本,从而提高效率,在处理大型列表时尤其如此

8.5 传任意多的实参*toppings元组

有时你预先不知道函数需要接受多少个实参,好在Python允许函数从调动语句中收集任意数量的实参

以制作披萨为例
我们无法预先知道顾客要多少种配料

形参*toppings中的星号让Python创建一个名为toppings的空元组,并将接收的所有值都封装到这个元组中

def make_pizza(*toppings):
    """打印顾客点的所有配料"""
    print(toppings)


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

输出

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

8.6 传递任意多的实参**user字典

有时你想收集有关用户的信息,但不确定会是什么样的信息。

在下面的例子中,函数build_profile()接受名first_name和姓last_name,同时还接受任意数量的关键字实参:

形参**user_info中的两个星号让Python创建一个名为user_info的空字典,并将收到的所有键-值对,如field='physics',都封装在这个字典里

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'}

8.7 lambda表达式

python 使用 lambda 来创建匿名函数

使用lambda比普通的def定义函数要简单,不需要写def和return

lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能

lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值

带参数

f = lambda x, y: 2 * x + y
print(f(4, 3))  # 输出11

不带参数

R = lambda : print("Hi")
R() # 输出 Hi

例:

# 输入样例:
# 4 3
# 0 0 2 1
# 2 0 1 1

R = lambda: map(int, input().split())
# R()读入一行数,R() = map(int, input().split()) # 迭代器
n, m = R() # n = 4, m = 3
list_a, list_b = list(R()), list(R())  # 读入两行数
# list_a = [0, 0, 2, 1]

说明:函数名可以赋给变量,简化有些赋值的函数名,当然这会降低可读性

s = sorted
print(s([2,3,1])) # 输出 [1, 2, 3]

8.8 将函数存储在模块中

导入模块相对于把模块中的代码隐形地复制到当前程序中,所以如果模块开头有一句print(“Hello”),则当运行import语句时就会输出Hello

强力推荐的写法
用多少func.py中函数,就导入多少

from func import function_1 as f1, function_2 as f2, function_3 as f3

将函数存储在被称为模块的独立文件中,再将模块导入主程序中,import语句运行在当前运行的程序文件中使用模块中的代码

将函数存储在独立的文件中后,可与其他程序员共享这些文件而不是整个程序,还可以让你知道如何使用其他程序员编写的函数库

一个标准库文件中的import语句示例:

import _collections_abc
from operator import itemgetter as _itemgetter, eq as _eq
from keyword import iskeyword as _iskeyword
import sys as _sys
import heapq as _heapq
from _weakref import proxy as _proxy
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
from reprlib import recursive_repr as _recursive_repr

sys.path.append()
当我们需要添加自己的搜索目录时,可以通过列表的append()方法,特别是模块和自己写的程序不在同一个目录的情况

import sys
sys.path.append('/Users/macos/Documents/Wilson79/Python/Program')

8.8.1 导入整个模块import(同一目录)

模块是扩展名为.py的文件

下面例子中,Python读取文件时,代码行import models让Python打开同一目录下的models.py,并将其中的所有函数都复制到这个程序中。你看不到复制的代码,因为这是幕后复制的,你只需要知道你可以使用models.py中定义的所有函数

调用方法:模块名.函数名(参数)

main.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author    :   Wilson79
@Filename  :   main.py.py  
@Date      :   2020/1/22 下午06:45 
"""
import models  # 让Python打开models.py(必须在同一目录下),并将其中的所有函数复制到当前程序中

unprinted_designs = ['iphone', 'robot pendant', 'dodecahedron']
completed_models = []

# 调用方法:模块名.函数名(参数)
models.print_models(unprinted_designs[:], completed_models)  # 创建列表副本(一般不建议)
models.show_completed_models(completed_models)

models.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author    :   Wilson79
@Filename  :   models.py  
@Date      :   2020/1/22 下午06:44 
"""


def print_models(unprinted_designs, completed_models):
    """
    模拟打印每个设计,直到打印没有未打印的设计为止
    打印每个设计后,都将其移到列表completed_models中
    """
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        print("Printing model: " + current_design)
        completed_models.append(current_design)


def show_completed_models(completed_models):
    """显示打印好的所有模型"""
    print("\nThe following models have been printed:")
    for completed_model in completed_models:
        print(completed_model)

搜索路径
当你导入一个模块,Python 解析器对模块位置的搜索顺序是:

1、当前目录
2、如果不在当前目录,Python 则搜索在 shell 变量 PYTHONPATH 下的每个目录。
3、如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/。
模块搜索路径存储在 system 模块的 sys.path 变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。

8.8.2 导入特定函数from module import fun()

有时不需要导入全部的函数,可以只导入特定的函数

语法:
from module_name import function_1, function2, function3
注意:只需写出函数名,不用加()

调用方法
直接使用函数,不需使用句号,因为import语句显式地导入了要用的函数

示例:
new.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author  :   Wilson79
@File    :   new.py  
@Time    :   2020/1/19 下午03:45 
"""

from func import function_1, function_2

function_1()  # import显式导入了函数,故可直接使用
function_2()
# function_3() # error,因为没有导入这个函数

func.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author    :   Wilson79
@Filename  :   func.py
@Date      :   2020/1/22 下午07:00 
"""


def function_1():
    print("星期一")


def function_2():
    print("星期二")


def function_3():
    print("星期三")

8.8.3 用as给模块或函数指定别名

给模块指定别名
import module_name as p
说明:方便你更轻松地调用模块中的函数 p.function(),让你更专注于描述性的函数名,而不是模块名

给函数指定别名
from module_name import function_name as fn
说明:防止导入函数与当前程序冲突,或函数的名字太长

8.8.4 导入模块中的所有函数from module_name import *

使用*运算符可以让Python导入模块中的所有函数
from module_name import *

由于导入了所有函数,不需要使用句点表示法,而是直接使用函数

new.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author  :   Wilson79
@File    :   new.py  
@Time    :   2020/1/19 下午03:45 
"""

from func import *  # 导入func所有函数,且可以直接使用

function_1()  # import显式导入了所有函数,故可直接使用
function_2()
function_3()

特别注意:如果不是自己编写的函数库,不建议使用这种导入方式
因为模块中有函数名称与你的项目中的函数使用相同的名称时,会出现意想不到的结果:Python遇到相同名称的函数会覆盖函数,而不是导入所有的函数

8.8.5 Python会覆盖原先定义的函数

from func import *  # 导入func所有函数,且可以直接使用

# import显式导入了所有函数,故可直接使用
function_1()  # 输出 星期一


# Python会覆盖原先定义的函数
def function_1():
    print("你好")  # 输出 你好


function_1()


def function_1():
    print("函数")


function_1()  # 输出 函数

第九章.类

9.1 面向过程与面向对象编程的区别

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为

例:一次选举大会有很多的步骤要进行,甲可能需要先为自己发言;然后乙为自己发言;接着甲为别人投票;乙选择弃票;乙提出质疑…
上面一共列举了5个步骤,我们不关心具体某个步骤,而是关心每个对象(甲、乙)在这次选举大会上的整体行为

9.2 创建和使用Dog类

根据类来创建对象被称为实例化

相对于把一系列的函数和变量封装在一起,用来表示一种事物

属性 = 以self为前缀与这个实例关联起来的变量


  • 属性必须在__init__方法内定义
  • 通过self将变量t和age与整个实例关联起来,成为属性
  • 以self为前缀的变量(属性)都可供类中所有的方法使用
  • 变量tmp不是在__init__方法内以self定义的,所以不是属性,不能被其他方法使用

  1. 类内访问属性要使用self.属性名,如self.age
  2. 类外访问实例中的属性(t, age)要使用实例名.属性名,但不需self,如my_dog.age
  3. 类外调用方法实例名.方法名(参数)调用类方法时,self会自动传递
    my_dog.roll_over('up')

示例:使用Dog类和实例
根据Dog类创建的每个实例都将存储名字和年龄,我们赋予了每条小狗蹲下和打滚的能力

class Dog():
    """一次模拟小狗的简单尝试"""

    def __init__(self, name, age):  # 这里name和age是形参
        """初始化属性t和age"""
        # 属性必须在__init__方法内定义
        self.t = name  # 通过self将变量t和age与整个实例关联起来,成为属性
        self.age = age  # 以self为前缀的变量(属性)都可供类中所有的方法使用

    def sit(self):
        """模拟小狗被命令时蹲下"""
        tmp = " probably"  # 这个变量tmp不是在__init__方法内以self定义的,所以不是属性,不能被其他方法使用
        print(self.t.title() + tmp + " is now sitting")  # 类内访问属性要使用self.t

    def roll_over(self, command):
        """模拟小狗被命令时打滚"""
        print("The command is " + command)
        print(self.t.title() + " rolled over!")


# 每个小狗都是一个独立的实例,有自己的一组属性
my_dog = Dog('willie', 7)  # 根据Dog类来创建新实例
your_dog = Dog('lucy', 3)  # self会自动传递

# 类外访问实例中的属性t, age要使用句点表达式,但不需加self
print("My dog, " + my_dog.t.title() + ", is " + str(my_dog.age) + " years old.")
my_dog.sit()
my_dog.roll_over('up')

print("\nYour dog, " + your_dog.t.title() + ", is " + str(your_dog.age) + " years old.")
your_dog.sit()
your_dog.roll_over('down')

输出

My dog, Willie, is 7 years old.
Willie probably is now sitting
The command is up
Willie rolled over!

Your dog, Lucy, is 3 years old.
Lucy probably is now sitting
The command is down
Lucy rolled over!

方法__init__()
类中的函数称为方法,__init()是一个特殊的方法,每次你根据Dog类创建新实例时,Python都会自动运行它,而且虽然没有显式的return,但是Python会自动返回一个表示这条小狗的实例,一般我们把它存到一个变量中,如my_dog = Dog('willie', 7)

为什么类方法定义中包含self ?
因为Python在调用类方法时自动传入实参self,它是一个指向实例本身的引用(类似C++ this指针),让实例能够访问类中的属性和方法

self.t = name获取存储在形参name中的值,并将其存储在变量t中,然后该变量被关联到当前创建的实例。这样的变量t被称为属性

约定:首字母大写的名称(如:Dog)指的是类,小写的名称(如my_dog)指的是根据类创建的实例


在Python2.7中创建类有细微区别,需在括号内包含单词object

class Classname(object)	
	--snip--

9.3 修改属性的值Car类

说明:类中的每个属性(只能在__init__方法中定义)都必须有初始值,哪个这个值是0或空字符串

有三种不同的方法修改属性的值

  1. 直接通过实例进行修改
    my_car.odometer_reading = 23
  2. 直接通过实例进行修改
    my_car.update_odometer(23)
  3. 通过方法进行递增(增加特定的值)
    my_car.increment_odometer(100)

Car.py

# 2020年1月23日 星期四 14:10:40
class Car():
    def __init__(self, make, model, year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0  # 每个属性必须初始化

    def read_odometer(self):
        """打印汽车的里程信息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self, odometer_reading):
        """更新里程信息"""
        self.odometer_reading = odometer_reading

    def increment_odometer(self, increment):
        """
        增加特定里程
        禁止里程表往回调
        """

        if increment < 0:
            print("You can't roll back an odometer!")
        else:
            self.odometer_reading += increment


my_car = Car('Audi', 'A4', 2016)
my_car.read_odometer()

my_car.odometer_reading = 1200  # 法一:直接通过实例进行修改
my_car.read_odometer()

my_car.update_odometer(1400)  # 法二:直接通过实例进行修改
my_car.read_odometer()

my_car.increment_odometer(100)  # 法三:通过方法增加特定值
my_car.read_odometer()

输出

This car has 0 miles on it.
This car has 1200 miles on it.
This car has 1400 miles on it.
This car has 1500 miles on it.

9.4 继承

9.4.1 子类与父类

如果你要编写的类是另一个现成类的特殊版本,可使用继承

一个类继承另一个类后,它将自动获得另一个类的所有属性和方法;原有的类称为父类(superclass),而新类称为子类

子类继承了其父类所有的属性和方法,同时还可以定义自己的属性和方法

创建子类的实例时,首先需要给其父类的所有属性赋值,为此子类的方法__init__()需要父类施以援手

下面创建一个简单的ElectricCar类版本,它具有Car类的所有功能
核心代码super().__init__(make, model, year)
让子类包含父类Car中的所有属性和方法

super()是一个特殊函数,帮助Python把子类和父类关联起来

class Car():
    def __init__(self, make, model, year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0  # 每个属性必须初始化

    def read_odometer(self):
        """打印汽车的里程信息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self, odometer_reading):
        """更新里程信息"""
        self.odometer_reading = odometer_reading

    def increment_odometer(self, increment):
        """
        增加特定里程
        禁止里程表往回调
        """
        if increment < 0:
            print("You can't roll back an odometer!")
        else:
            self.odometer_reading += increment


# 继承
class ElectricCar(Car):  # 父类名写在括号内
    """电动汽车的独特之处"""

    def __init__(self, make, model, year, color):
        """初始化父类(superclass)Car中的属性"""
        super().__init__(make, model, year)  # 继承核心代码:让子类包含父类Car中的所有属性和方法
        self.license_number = 'A2873L'       # 子类可以定义自己的属性和方法
        self.color = color


my_tesla = ElectricCar('tesla', 'model s', 2016, 'red')
my_tesla.odometer_reading = 22  # 可以使用父类的属性或方法
my_tesla.read_odometer()        # 输出 This car has 22 miles on it.
print("The license number is " + my_tesla.license_number + ", and the color is " + my_tesla.color)
# 输出 The license number is A2873L, and the color is red

继承的一点说明:如果一个属性或方法是任何汽车都有的,而不是电动汽车特有的,就应将其加入到Car类而不是ElectricCar类中。这样,使用Car类的人可以获得相应的功能,而使用ElectricCar类只包含处理电动汽车特有的属性和行为代码

所以上面的代码其实不够好,因为color和license_number所有汽车都有,如果是battery则可以加到子类中


Python2.7中的继承

class Car(object):
	def __init__(self, make, model, year):
		--snip--

class ElectricCar(Car):
	def __init__(self, make, model, year):
		super(ElectricCar, self).__init__(make, model, year)
		--snip--

函数super()需要两个实参:子类名和对象self
另外在Python2.7使用继承时务必在定义父类时在括号内指定object

9.4.2 重写父类方法—去其槽粕

对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。只需在子类中定义一个同名的方法,Python只会关心子类中定义的方法,不会考虑同名的父类方法

假如Car类有一个名为fill_gas_tank()的方法,它对电动汽车毫无意义的,因此子类需要重写

class ElectricCar(Car)
	--snip--
	def fill_gas_tank(self):
		"""电动汽车没有油箱"""
		print("This car doesn't need a gas tank!")

使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕

9.4.3 将实例用作属性

9.5 def greeting(name: str) -> str:含义

函数接受并返回一个字符串,注释像下面这样

def greeting(name: str) -> str:
    return 'Hello ' + name

在函数greeting 中,参数name 预期是 str 类型,并且返回 str 类型

官方文档

9.6 导入模块中的类import

Python允许将类存储在模块中,也是以.py为后缀的文件

导入模块(类或函数)是一种有效的编程方式,它能够让你专注于主程序的高级逻辑

导入类的方式其实和导入函数完全一样,也有三种
car.py存了Car类
则可在my_car.py中

  1. 使用from car import Car导入,这样可以直接使用Car类,就好像它是在这个文件中定义的一样(因为已经显式导入了类名)
  2. 使用import car导入,这种方式会导入car中所有的类,调用时要用car.类名
  3. 使用from car import *导入,这种方式不建议使用

当然在一个模块中也可以导入另一个模块

from car import Car  # 导入car.py模块中的Car类

class Battery():
	--snip--
class ElectricCar(Car):  # 定义Car的子类
	--snip--

示例

主程序是my_car.py,导入的模块是car.py和electric_car.py

主程序my_car.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author    :   Wilson79
@Filename  :   my_car.py
@Date      :   2020/1/23 下午06:56 
"""

# 主程序my_car.py 导入模块car.py和electric_car.py

from car import Car
from electric_car import ElectricCar, Battery

my_beetle = Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

输出

2016 volkswagen beetle
2016 tesla roadster
This car has a 70-KWh battery.

类模块 car.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author    :   Wilson79
@Filename  :   car.py  
@Date      :   2020/1/23 下午06:53 
"""

"""一个可用于表示汽车的类"""


class Car():
    def __init__(self, make, model, year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0  # 每个属性必须初始化

    def get_descriptive_name(self):
        """返回整洁的描述名称"""
        long_name = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name

    def read_odometer(self):
        """打印汽车的里程信息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self, odometer_reading):
        """更新里程信息"""
        self.odometer_reading = odometer_reading

    def increment_odometer(self, increment):
        """
        增加特定里程
        禁止里程表往回调
        """
        if increment < 0:
            print("You can't roll back an odometer!")
        else:
            self.odometer_reading += increment

类模块 electric_car.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Author    :   Wilson79
@Filename  :   electric_car.py  
@Date      :   2020/1/23 下午06:54 
"""

"""一组可用于表示电动汽车的类"""

from car import Car  # ElectricCar类要访问其父类Car,所以要导入Car类


class Battery():
    """模拟电动汽车电瓶的简单尝试"""

    def __init__(self, battery_size=70):
        """初始化电瓶的属性"""
        self.battery_size = battery_size

    def describe_battery(self):
        """打印电瓶容量信息"""
        print("This car has a " + str(self.battery_size) + "-KWh battery.")


# ElectricCar继承Car
class ElectricCar(Car):
    """电动汽车的独特之处"""

    def __init__(self, make, model, year):
        """初始化父类中的属性"""
        super().__init__(make, model, year)  # 继承核心代码:让子类包含父类Car中的所有属性和方法
        # 每个ElectricCar实例都会包含一个自动创建的Battery实例
        self.battery = Battery()  # 使用实例作为属性

9.7 Python标准库和类编码风格

Python标准库是一组模块,安装的Python都包含它

我们可以使用标准库中的任何函数和类
只需在程序开头包含一条简单地import语句
在这里插入图片描述
以使用模块collections中的一个类——OrderedDict为例

OrderedDict实例会记录你添加时的键-值对的顺序

from collections import OrderedDict

favorite_languages = OrderedDict()  # 创建一个空的有序字典
favorite_languages['kevin'] = 'python'
favorite_languages['jeny'] = 'java'
favorite_languages['sarah'] = 'c'
favorite_languages['wilson'] = 'c++'

for name, language in favorite_languages.items():
    print(name.title() + "'s favorite language is "
          + language.title() + ".")

类编码风格

  • 类名应采用驼峰命名法,如ElectricCar,不能使用下划线

  • 每个类定义后面包含一个文档字符串,简要地描述类的功能

  • 同时导入标准库中的模块和自己编写的模块时,先编写导入标准库模块的import语句,再用一个空行隔开,再写导入自己的模块的import语句,这样让人更容易明白程序使用了各个模块来自何方

from collections import OrderedDict  # 标准库中模块

from car import Car  # 自己编写的模块
from electric_car import ElectricCar, Battery

第十章.文件和异常

这一章很有用,以后做爬虫,做数据分析都要用到这章的知识

我们将学习处理文件,让程序能够快速地分析大量的数据

10.1 从文件中读取数据

读取文本文件时,Python将其中的所有文本都解读为字符串

10.1.1 读取整个文件read()

要以任何方式使用文件——哪怕仅仅是打印其内容,都得先打开文件,这样才能访问它

read(n) 方法用于从文件读取n个字节,如果未给定n或n为负则读取所有

如果文件最后有多个空行,也会以\n的形式读取进来,直到读取完所有字符

#  open('filename')返回一个文件对象(同目录下),并存到变量file_object中
with open('in.txt') as file_object:  
	# 读入全部内容,形成一个很长的字符串"1\n2\n3\n4"
    contents = file_object.read()    
    print(contents)

in.txt

1
2
3
4

关键词with在不再需要访问文件后会自动将文件关闭

推荐用with的原因:一般可以调用open()和close()来打开和关闭文件,但如果程序存在bug,导致close语句没有执行,那文件就不会被关闭,而未妥善关闭文件可能导致数据丢失或受损。通过with结构可以让Python自己决定,你只管打开文件,并在需要的时候使用它,Python会自动在合适的时候自动将其关闭


不用with的结构(不推荐)
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:

>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

你可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险

with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')

10.1.2 相对路径和绝对路径

如果你要打开的文件不在程序文件所属的目录中,则可以使用相对路径或绝对路径来打开文件

相对路径

即相对于当前运行的程序所在目录

# 相对路径:从当前目录Program开始,继续匹配路径
with open('text_files/in.txt') as file_object:  
    contents = file_object.read()
    print(contents)

在这里插入图片描述
绝对路径

  • 当你精准的告诉Python文件的具体位置,那你就不用担心当前的运行程序存储在什么地方了。这称为绝对文件路径

  • Linux或OS X路径用斜杠/
    Windows路径用反斜杠\

  • 通过使用绝对路径,可读取系统任何地方的文件

  • 绝对路径通常比相对路径更长,建议先把它存储在一个变量中,再用open(变量)会有所帮助

# 绝对路径
file_path = '/Users/macos/Documents/Wilson79/LocalStore/综合文件/How to solve a problem.txt'
with open(file_path) as file_object:
    contents = file_object.read()
    print(contents)
  • PyCharm获取绝对路径快速方法,右键Copy Path,或设置快捷键shift + cmd + p
    在这里插入图片描述

10.1.3 创建包含文件各行的列表readlines()

使用关键词with时,open()返回的文件对象只在with代码块内可用,如果要在外面访问,可以把各行存在一个列表中

# 相对路径
file_path = 'text_files/in.txt'
with open(file_path) as file_object: 
    lines = file_object.readlines()  # 返回一个列表

print(lines)
# 输出 ['1\n', '2\n', '3\n', '\n', '4\n', 'This is last line!']
for line in lines:
    print(line.rstrip()) # 输出和in.txt一模一样
   

in.txt

1
2
3

4
This is last line!

对于你处理的数据量,Python没有任何限制,只要系统的内存足够多,你想处理多少数据都可以

比如一个包含一百万位的大型文件,也可以处理。当然打印时为了避免终端为显示全部1000000位而不断地翻滚

逐行读取还可以直接用for循环

with open(filename) as file_object:
	for line in file_object:
		print(line.rstrip())

注:

if 'ab' in 'helloabhere':  # 判断字符串s1是否在字符串s2中
    print("Yes")

10.1.4 读取一行readline()

with open('text_files/in.txt') as file_object:
    line = file_object.readline()
    print(line)  # 第一行会有个\n,print也会有个\n

输出

1


10.2 写入文件write()

filename = 'text_files/in.txt'

# w写入模式 会覆盖原有文件内容
with open(filename, 'w') as file_object: 
    file_object.write("\tI love programming.\n")
    file_object.write("\tI love creating new games.\n")

打开文件时,open()可以输入两个实参,第一个是文件名,第二个是以什么模式打开文件

open(filename, ‘a’)

  • 只读模式 r (缺省时默认是只读模式)
  • 写入模式 w (覆盖原文件)
  • 附加模式 a (给文件加内容)
  • 读写模式 r+ (读写文件,w不会覆盖原文件)

如果要写入的文件不存在,函数open()会自动创建它

可以使用空格、制表符和空行来设置写入内容的格式

python只能将字符串写入文件

注意:写入模式w会清空原文件,要特别小心使用

10.3 异常 try-except

目前(至少)有两种可区分的错误:语法错误异常
语法错误又称解析错误,可能是你在学习Python 时最容易遇到的错误

即使语句或表达式在语法上是正确的,但在尝试执行时,它仍可能会引发错误。 在执行时检测到的错误被称为异常


Python使用被称为异常的特殊对象来管理程序执行期间发生的错误

在执行时,Python会自动返回一个异常对象

如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告

异常是使用try-except代码块处理的。使用try-except代码块Python会执行指定的操作,同时告诉Python发生异常时该怎么办。通常会显示你自己编写的友好的错误消息,而不是令用户迷惑的traceback


程序崩溃时让用户看到traceback不太好,因为如果用户怀着恶意,他会通过traceback获悉一些你不想让ta知道的信息,如被获取程序文件的名称,还将看到不能正确运行的代码,有时训练有素的攻击者就会根据这个信息对你的代码发起相应的攻击

File "/Users/macos/Documents/Wilson79/Python/Program/division.py", line 17
	second_number = input(a"Second number: ")
                                           ^
SyntaxError: invalid syntax

10.3.1 处理ZeroDivisionError异常

print(5/0)

显然这样会出错,返回的错误ZeroDivisionError是一个异常对象

Python无法按照你要求的去做时就会创建一个异常对象

Traceback (most recent call last):
  File "<input>", line 1, in <module>
ZeroDivisionError: division by zero

下次我们就可以告诉Python发生指定错误时该怎么办了

try:
    print(5 / 0)
except ZeroDivisionError:  # 告诉Python发生指定的异常该怎么做
    print("Attention, you can't divide by zero!")

如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;如果try中的代码出现错误,且except后指定的错误和引发的错误相同,则运行except中的代码

10.3.2 try-except-else代码块

有一些仅在try代码块成功执行时才需要运行的代码,这些代码应放在else代码块中

这样即使发生错误,用户也根本看不到traceback,程序还是会继续运行下去

# 2020年1月25日 星期六 14:58:27
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
    first_number = input("\nFirst number: ")
    if first_number == 'q':
        break

    second_number = input("Second number: ")
    if second_number == 'q':
        break

    try:
        answer = int(first_number) / int(second_number)  # 5/2 = 2.5
    except ZeroDivisionError:
        print("You can't divide by 0!")
    else:  # 如果try正常执行,则执行else内容
        print(answer)

输出

Give me two numbers, and I'll divide them.
Enter 'q' to quit.

First number: 5
Second number: 0
You can't divide by 0!

First number: 5
Second number: 2
2.5

First number: 3
Second number: q

这种结构让我们的程序即使面对无效数据或缺失数据,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击

10.3.3 try-except的三种格式

语句中的else是可有可无的
except语句中的as e也可以不加

  1. 一个 try 语句可能包含多个 except 子句
try:
    print(7 / 0)
except ZeroDivisionError as e:
    print('除数为0')
except ValueError:
    pass  # 什么也不做;还可以充当占位符,提醒你什么地方还没有做
else:
	...
  1. 也可以合并except子句
try:
    print(7 / 0)
except (ZeroDivisionError, ValueError):
    print('程序异常')
  1. 无论遇到的是哪一种异常, 均做统一处理
try:
    print(7 / 0)
except :
    print('程序异常')

10.3.4 示例-分析文本的单词数

分析多个文本,统计每个文本的单词数(默认以空白字符分隔)

def count_word(filename):
    try:
        with open(filename) as f_obj:
            content = ""
            for line in f_obj:
                content += line
    except FileNotFoundError:
        print("Sorry, the file " + filename + " does not exist.")
    else:
        wlist = content.split()  # 返回一个单词列表
        print("This file " + filename[10:] + " has " + str(len(wlist)) + " words.")

filenames = ['text_files/斗破苍穹.txt', 'text_files/in.txt', '牧神记.txt']

for filename in filenames:
    count_word(filename)  # 中文统计不准确,因为默认以空白字符分隔单词

决定报告哪些错误

编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络链接,就可能发生异常。凭借经验可判断该在什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息

10.4 使用json格式保存和读取用户数据

很多程序要求用户输入某种信息,而当用户下次启动时他们之前输入的信息还是存在的

模块json能够让你把简单的python数据结构存储到文件中,并在程序再次运行时加载该文件中的数据

JSON(JavaScript Object Notation)格式最初是JavaScript开发的,后来被各大语言广泛采用

两个常用的函数api
json.dump(data, file_object)
data = json.load(file_object)

import json

numbers = [1, 2, 3]

# 使用json.dump()存储数据
filename = 'numbers.json'
with open(filename, 'w') as file_object:
    json.dump(numbers, file_object)

# 使用json.load()读取数据
with open(filename) as file_object:
    numbers_ = json.load(file_object)

print(numbers_)

对于用户生成的数据,使用json保存它们大有裨益,因为如果不以某种方式进行存储,等程序停止运行时用户的信息将丢失

例:用户首次运行程序时被提示输入自己的名字,这样再次运行程序时就记住他了

import json

# 如果以前存储了用户名就加载它
# 否则,就提示用户输入用户名并存储它
filename = 'username.json'
try:
    with open(filename) as file_object:
        username = json.load(file_object)
except FileNotFoundError:  # 此时说明用户首次运行程序
    username = input("What is your name? ")
    with open(filename, 'w') as file_object:
        json.dump(username, file_object)
        print("We'll remember you when you can back, " + username + "!")
else:
    print("Welcome back, " + username)

10.5 重构—划分成具体函数

代码能够正常运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这个过程被称为重构

重构让代码更清晰、更易于理解、更容易扩展

上面一小节的代码,一个函数里包含了多个工作,下面我们来划分代码,让一个工作由一个函数来完成,这样易于维护和扩展

  • get_stored_username()
  • get_new_username()
  • greet_user()
import json


def get_stored_username():
    """如果存储了用户名,就获取它"""
    filename = 'username.json'
    try:
        with open(filename) as file_object:
            username = json.load(file_object)
    except FileNotFoundError:
        return None
    else:
        return username


def get_new_username():
    """提示用户输入用户名,并保存到json"""
    username = input("What is your name? ")
    filename = 'username.json'
    with open(filename, 'w') as f_obj:
        json.dump(username, f_obj)
    return username


def greet_user():
    """问候用户"""
    username = get_stored_username()
    if username:
        print("Welcome back, " + username + "!")
    else:
        username = get_new_username()
        print("We'll remember you when you come back, " + username + "!")


greet_user()
发布了182 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_43827595/article/details/104081604