Python教程:从零到大师

首先, 什么是Python? 用python作者Guido van Rossum自己的话来说,Python是这样的一门语言:

"它是一门高级编程语言, 它的核心设计理念是让所有代码变得更易阅读,并给开发者们提供一种“仅仅几行代码就能编写编程逻辑”的语法。

那么,对我来说,让我学习Python的第一个理由,就是它漂亮而优雅,能够顺畅自然地实现我的想法。

另一个理由,就是Python支持多种编程领域,如:

数据科学

web开发

机器学习

比如,Quora、Pinterest、Spotify,这些项目,都是使用python开发他们的后端。

那么,接下来,就开始学习Python吧!

基础

1. 变量

可以把变量简单理解为一个存储值的单词。

讲道理,变量是什么就不用特地解释了…大家都懂。

在Python里面,定义变量、给变量赋值都非常简单。比如你想把数字1存储到一个变量里面,而这个变量名叫one,那么,你只需要这样:

one =1

非常简单吧? 举一反三,完全可以自由发挥,就像下面,把2赋值给two,把10000赋值给some_number:

two =2

some_number =10000

当然,除了整型以外,我们也可以设置布尔类型、字符串、单精度,以及一些其他数据类型。如下:

# booleans

true_boolean =True

false_boolean =False

# string

my_name ="Leandro Tk"

# float

book_price =15.80

2.流程控制: 分支语句

if,这个语句用来判断是否符合条件,它的后面紧跟着逻辑表达式,表达式最后的值为True或False,如果是true,则执行if里面的语句。如下:

ifTrue:

print("Hello Python If")

if2>1:

print("2 is greater than 1")

因为2大于1,条件成立,所以print语句就会被执行

当然,如果不满足条件,那么else就派上用场了!

如果,if后面跟着的逻辑表达式最终值是false,则会运行else里面的程序,如下:

if1>2:

print("1 is greater than 2")

else:

print("1 is not greater than 2")

你也可以使用elif,是else if的缩写,但千万别写错~

if1>2:

print("1 is greater than 2")

elif2>1:

print("1 is not greater than 2")

else:

print("1 is equal to 2")

3. 循环 / 迭代器

在Python中,我们有多种迭代的方式,我在这里说两种:

While 循环: 当逻辑表达式为true的时候,while下缩进的代码块就会被循环执行. 所以下面的代码片段,将会从1打印到10。

num =1

whilenum <=10:

print(num)

num +=1

上面这种循环方式,需要一个循环条件,如果循环条件是true,就会继续进行迭代,在上面的例子中,当num变成11的时候,循环条件就会等于False"

再看看下面的基础代码块,以便于理解:

loop_condition =True

whileloop_condition:

print("Loop Condition keeps: %s"%(loop_condition))

loop_condition =False

只要循环条件为True,就会被一直循环执行,直到循环条件变成False

For循环: 与其他语言一样,这用于计次循环,它循环的次数,取决于后面那个range方法。

range,代表从在循环里,它用于表示从x到n,如下,就是从1到11,第三个参数可空,意思是每次递进的加数,默认每循环一次给i加1,填2的话,就给i加2

foriinrange(1,11):

print(i)

列表: 集合 | 数组 | 数据结构

想象一下,你想把整数1存储在一个变量中。 但也许现在你想要存储 2 和 3,4,5 。。。

我是否有另一种方法来存储我想要的所有整数,但不是以百万计的变量? 你猜对了 —— 确实有另一种方法来存储它们。

List是一个可以用来存储一列值的集合(比如你想要的这些整数)。 那么让我们使用它:

my_integers = [1,2,3,4,5]

这真的很简单,我们创建了一个数组并将其存储到my_integer里。

但是也许你在问: 『 我怎样才能从这个列表中获得值? 』

很好的问题。List有一个叫做索引的概念。 第一个元素获取索引 0 (零)。 第二个取 1 ,依此类推。 明白了吧。

为了使其更清楚,我们可以用它的索引来表示数组和每个元素。 我可以画出来:

使用 Python 语法,它也很容易理解:

my_integers = [5,7,1,3,4]

print(my_integers[0])# 5

print(my_integers[1])# 7

print(my_integers[4])# 4

想象一下现在你不想存储整数了。你只是想存储字符串,就像你亲戚名字的列表一样。 看起来像这样:

relatives_names = [

"Toshiaki",

"Juliana",

"Yuji",

"Bruno",

"Kaio"

]

print(relatives_names[4])# Kaio

它的工作方式与整数相同,漂亮。

我们刚刚了解到Lists索引是如何工作的。 但是我仍然需要告诉你如何将一个元素添加到List数据结构(一个项目到列表)。

添加一个值到List最常见的方法是append。让我们看看他是如何工作的:

bookshelf = []

bookshelf.append("The Effective Engineer")

bookshelf.append("The 4 Hour Work Week")

print(bookshelf[0])# The Effective Engineer

print(bookshelf[1])# The 4 Hour Work Week

append非常的简单。您只需要将元素(例如『 The Effective Engineer 』)作为『 append 』参数应用即可。

那么,关于Lists到这里就结束了,让我们来谈谈另一个数据结构。

字典: 键-值 数据结构

现在我们知道Lists使用整数来索引. 但是如果我们不想使用整数来索引呢? 一些其他的数据结构可以使用数字,字符串或者其他的类型来做索引.

让我们来学习Dictionary数据结构.Dictionary是一个键值对集合. 它长下面这样:

dictionary_example = {

"key1":"value1",

"key2":"value2",

"key3":"value3"

}

键用来索引到值. 那么我们如何访问Dictionary的值呢? 你猜对啦 --- 使用键. 试一下吧:

dictionary_tk = {

"name":"Leandro",

"nickname":"Tk",

"nationality":"Brazilian"

}

print("My name is %s"%(dictionary_tk["name"]))# My name is Leandro

print("But you can call me %s"%(dictionary_tk["nickname"]))# But you can call me Tk

print("And by the way I'm %s"%(dictionary_tk["nationality"]))# And by the way I'm Brazilian

我创建了一个关于我的Dictionary. 我的名字, 昵称和国籍. 这些属性是Dictionary的键.

我们知道访问List使用下标, 我们在这也使用下标 (Dictionary中的键的内容) 来访问存在Dictionary中的值.

在例子中, 我打印出了存在Dictionary中的所有关于我的短语. 非常简单滴~?

另一件关于Dictionary非常帅气的事情就是我们可以使用任何东西来做为字典的值.在我创建的Dictionary中, 我想添加键为 "age" 且值为我的整数年龄进去:

dictionary_tk = {

"name":"Leandro",

"nickname":"Tk",

"nationality":"Brazilian",

"age":24

}

print("My name is %s"%(dictionary_tk["name"]))# My name is Leandro

print("But you can call me %s"%(dictionary_tk["nickname"]))# But you can call me Tk

print("And by the way I'm %i and %s"%(dictionary_tk["age"], dictionary_tk["nationality"]))# And by the way I'm Brazilian

这里我们有一个键 (age) 值 (24) 对 使用字符串来作为键,整数来作为值.

像我们学习Lists一样,让我们来学习如何在Dictionary中添加元素.在Dictionary中, 一个键指向一个值是很重要的. 这就是为什么我们在添加元素的时候讨论它:

dictionary_tk = {

"name":"Leandro",

"nickname":"Tk",

"nationality":"Brazilian"

}

dictionary_tk['age'] =24

print(dictionary_tk)# {'nationality': 'Brazilian', 'age': 24, 'nickname': 'Tk', 'name': 'Leandro'}

我们只需要指定一个值到Dictionary的键上. 一点也不复杂,484啊?

迭代:循环Python中的数据结构

当我们在学习 Python基础时, 会发现列表的迭代是一件十分简单的事情 ,通常我们Python开发者会使用For来循环迭代它. 现在让我们尝试一下:

bookshelf = [

"The Effective Engineer",

"The 4 hours work week",

"Zero to One",

"Lean Startup",

"Hooked"

]

forbookinbookshelf:

print(book)

如你所见我们已经对书架中的书进行了for操作,我们输出打印了其中的书(当然你可以在循环中对它们做任何事情)。简单而又直观,这就是Python。

同样对于哈希类型的数据结构,比如像是Python中的字典,我们同样也可以对其使用for循环进行迭代操作,但是此时我们则需要用到key:

dictionary = {"some_key":"some_value"}

forkeyindictionary:

print("%s --> %s"%(key, dictionary[key]))

# some_key --> some_value

这是一个循环字典类型变量的小例子,对于dictionary变量我们使用for循环操作其中的key,接着我们打印输出他的key以及其相对应匹配的value值。

当然我们还有另外一种方法去实现它,就是去使用iteritems:

dictionary = {"some_key":"some_value"}

forkey, valueindictionary.items():

print("%s --> %s"%(key, value))

# some_key --> some_value

你看我们已经命名了两个参数key,value,但这并不是必须的,你甚至可以给它们起任何一个名字^.^,让我们来看一下:

dictionary_tk = {

"name":"Leandro",

"nickname":"Tk",

"nationality":"Brazilian",

"age":24

}

forattribute, valueindictionary_tk.items():

print("My %s is %s"%(attribute, value))

# My name is Leandro

# My nickname is Tk

# My nationality is Brazilian

# My age is 24

哈哈,可以看到我们已经使用了attribute作为了Dictionary的key参数,代码运行十分正确。赞!

类型与对象

一点基础理论:

对象代表现实世界中像轿车、狗、自行车这些事物。对象具有数据和行为两个主要特征。

在面向对象编程中,我们把数据当作属性,把行为当作方法。即:

数据 → 属性 和 行为 → 方法

类型是创造单个对象实例的蓝本。在现实世界中,我们经常发现很多对象实例拥有相同的类型,比如轿车。他们都具有相同的构造和模型(具有发动机,轮子,门等等)。每辆车都是根据同一张设计图制作的,并且具有相同的组成部分。

Python 的面向对象编程模式:ON

Python,作为一门面向对象编程的语言,具有类和对象的概念。

类是蓝图,对象是模型。

同样,一个类,它只是一个模型,或者一种定义属性和行为的方法(正如我们在理论部分所讨论的)。例如,车辆类有自己的属性,定义什么是车辆。车轮的数量、能源的类型、座位容量和最大速度都是车辆的属性。

考虑到这一点,让我们看看类的Python语法:

classVehicle:

pass

我们用一个类声明来定义类 ,仅此而已。很简单,不是吗?

对象是一个类的实例,我们用命名类来创建一个实例。

car = Vehicle()

print(car)# <__main__.Vehicle instance at 0x7fb1de6c2638>

这里 ‘car’ 是 ‘Vehicle’ 类的一个对象(或者说实例)。

记住,我们的 ‘Vehicle’ 类有四个属性:轮子数量,能源类型,座位容量,和最大速度。我们创建一个 ‘Vehicle’ 对象时设置所有这些属性 。所以在这里,我们定义我们的类初始化时要接收数据时:

classVehicle:

def__init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):

self.number_of_wheels = number_of_wheels

self.type_of_tank = type_of_tank

self.seating_capacity = seating_capacity

self.maximum_velocity = maximum_velocity

我们使用了 ‘init’方法。我们称它为构造方法。所以创建 ‘vehicle’ 对象时可以定义这些属性。假设我们喜欢Tesla Model S,我们要创建这种对象。它有4个轮子,使用电能,有5个座位,最大时速250km/h (155mph)

tesla_model_s = Vehicle(4,'electric',5,250)

4个“轮子”+电能“能源”+5个“座位”+250km/h“最大速度”。

所有属性都设置完成了。但是我们如何获取这些属性值?我们发送一个消息到对象来问他们。 我们称之为方法. 方法是对象的行为. 让我们来实现它:

classVehicle:

def__init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):

self.number_of_wheels = number_of_wheels

self.type_of_tank = type_of_tank

self.seating_capacity = seating_capacity

self.maximum_velocity = maximum_velocity

defnumber_of_wheels(self):

returnself.number_of_wheels

defset_number_of_wheels(self, number):

self.number_of_wheels = number

这里创建了两个方法: number_of_wheels 和 set_number_of_wheels. 我们称它为获取&设置. 因为第一个获取了属性值,然后第二个设置了一个新的属性值。

Python 中,我们可以用 “@property” (“decorator”) 去定义 "getters" 和 “setters”。请看以下代码:

classVehicle:

def__init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):

self.number_of_wheels = number_of_wheels

self.type_of_tank = type_of_tank

self.seating_capacity = seating_capacity

self.maximum_velocity = maximum_velocity

@property

defnumber_of_wheels(self):

returnself.number_of_wheels

@number_of_wheels.setter

defnumber_of_wheels(self, number):

self.number_of_wheels = number

同时,我们可以使用这些方法作为属性:

tesla_model_s = Vehicle(4,'electric',5,250)

print(tesla_model_s.number_of_wheels)# 4

tesla_model_s.number_of_wheels =2# setting number of wheels to 2

print(tesla_model_s.number_of_wheels)# 2

这个与定义方法有些许不同。这些方法的工作机制与属性不同。例如,当我们设置轮子数量时,我们需要把2赋值给一个变量,只需要设置 “number_of_wheels” 的值为2。这是一种写 “pythonic”、 ”getter“、“setter” 代码的方法。

而且同时我们也可以使用其他方法,比如 “make_noise” 方法。请看下面的例子。

classVehicle:

def__init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):

self.number_of_wheels = number_of_wheels

self.type_of_tank = type_of_tank

self.seating_capacity = seating_capacity

self.maximum_velocity = maximum_velocity

defmake_noise(self):

print('VRUUUUUUUM')

当我们调用这个方法时,它返回字符串 ”VRRRRUUUUM“。

tesla_model_s = Vehicle(4,'electric',5,250)

tesla_model_s.make_noise()# VRUUUUUUUM

封装:信息隐藏

封装是一种限制直接访问对象数据和方法的机制。但是它加快了对象方法中数据的访问。

"封装可以在定义中隐藏数据和函数成员,意味着从外部隐藏了对象定义中的内部描述“--- Wikipedia

对象从外部隐藏了其内部描述。只有对象可以与它的内部数据进行交互。

首先,我们需要了解 “public” 和 “non-public” 变量实例的工作机制。

Public 变量实例

对于一个 Python 类型,我们可以使用构造方法初始化一个公共变量实例。我们看这个:

通过构造方法:

classPerson:

def__init__(self, first_name):

self.first_name = first_name

这里我们使用 “first_name” 的值作为一个参数传递给公共变量实例。

tk = Person('TK')

print(tk.first_name)# => TK

在类中:

classPerson:

first_name ='TK'

这里,我们不需要使用 “first_name” 作为一个参数,所有的对象实例都有一个用 “TK” 初始化的类属性。

tk = Person()

print(tk.first_name)# => TK

漂亮。我们已经学习到可以使用公共变量实例和类型属性。另一件关于 “public” 部分有趣的事情是我们可以管理它的变量的值。我的意思是什么呢?我们的对象可以管理它的变量值:获取和设置变量值。

记住 “Person” 类,我们想要设置另一个值给它的 “first_name” 变量:

tk = Person('TK')

tk.first_name ='Kaio'

print(tk.first_name)# => Kaio

好了,我们刚刚设置了另一个值("kaio")给对象变量 “first_name”,并且它更新了它的值。就是这么简单,因为这个 “public” 变量,我们可以这样做。

Non-public 变量实例

“在这里,我们不用‘私有‘来形容 ,因为在Python中没有真正“私有”的属性(避免了一般情况下不必要的工作)。”--- PEP 8

和公共变量实例一样,我们可以在构造函数或类内部定义非公共变量实例。语法上的差异是: 对于非公共变量实例,我们在变量名前加一道下划线(_)。

“在Python中,无法从内部访问‘私有’变量实例的对象是不存在的。但是,大多数Python代码遵循一个惯例:一个名字前有一道下划线的对象应该被认为是API中非公共的部分,例如_spam,无论它是一个函数、方法或是数据成员。” --- Python Software Foundation

这是一个例子:

classPerson:

def__init__(self, first_name, email):

self.first_name = first_name

self._email = email

看到email变量了吗?这就是定义一个非公共变量的方法。

tk = Person('TK','tk@mail.com')

print(tk._email)# tk@mail.com

所谓非公共变量只是一个惯例,没有机制禁止我们从外部访问并更新它。但按照惯例,我们应该把它作为API中非公共的部分来对待。

在类内部,我们通常使用方法来操作“非公共变量”,让我们实现两个方法(email和update_email)来理解。

classPerson:

def__init__(self, first_name, email):

self.first_name = first_name

self._email = email

defupdate_email(self, new_email):

self._email = new_email

defemail(self):

returnself._email

现在,我们可以通过这些方法来访问、更新非公共变量。

tk = Person('TK','tk@mail.com')

print(tk.email())# => tk@mail.com

tk._email ='new_tk@mail.com'

print(tk.email())# => tk@mail.com

tk.update_email('new_tk@mail.com')

print(tk.email())# => new_tk@mail.com

我们以first_nameTK 和emailtk@mail.com 初始化一个Person对象。

通过方法访问非公共变量email,并打印出来。

从类外部直接设置一个新的email。

我们应该把非公共变量作为API中非公共的部分来对待。

通过实例方法更新非公共变量email。

成功!我们可以通过预设的方法来更新它。

公共方法

通过公共方法, 我们也可以在我们类的外部使用这些方法了:

classPerson:

def__init__(self, first_name, age):

self.first_name = first_name

self._age = age

defshow_age(self):

returnself._age

让我们来试下:

tk = Person('TK',25)

print(tk.show_age())# => 25

赞——用起来没有任何问题。

非公共方法

但是通过非公共方法我们却无法做到这一点。 我们先来实现一个同样的Person类,不过这回我们加个下划线(_)来定义一个show_age的非公共方法。

classPerson:

def__init__(self, first_name, age):

self.first_name = first_name

self._age = age

def_show_age(self):

returnself._age

那么现在,我们来试着通过我们的对象调用这个非公共方法:

tk = Person('TK',25)

print(tk._show_age())# => 25

我们可以访问并且更新它。非公共方法只是一类约定俗成的规定,并且应当被看做接口中的非公共部分。

关于我们该怎么使用它,这有个例子:

classPerson:

def__init__(self, first_name, age):

self.first_name = first_name

self._age = age

defshow_age(self):

returnself._get_age()

def_get_age(self):

returnself._age

tk = Person('TK',25)

print(tk.show_age())# => 25

这里我们有一个_get_age非公共方法和一个show_age公共方法。show_age可以由我们的对象调用(在类的外部)而_get_age只能在我们类定义的内部使用(内部show_age方法)。但是再次强调下,这只是个约定俗成的规定。

封装总结

通过封装我们可以从外部隐藏对象的内部表示。

继承:行为和特征

某些对象具有共同点:如行为和特征。

例如,我从我父亲那里继承了一些特征和行为。我继承了他的眼睛和头发作为特征,继承了他的急躁和内向作为行为。

在面向对象编程中,类能够从其他类中继承特征(数据)和行为(方法)。

让我们看另外一个例子。

假定一辆车。轮子的数量、载客量和最高时速是车的所有属性。那么我们可以认为ElectricCar类从这个Car类中继承了这些属性。

classCar:

def__init__(self, number_of_wheels, seating_capacity, maximum_velocity):

self.number_of_wheels = number_of_wheels

self.seating_capacity = seating_capacity

self.maximum_velocity = maximum_velocity

我们的Car类实现之后:

my_car = Car(4,5,250)

print(my_car.number_of_wheels)

print(my_car.seating_capacity)

print(my_car.maximum_velocity)

一旦初始化后,我们可以使用所有已创建的实例变量。很好。

在Python中我们可以将父类作为子类定义时的参数。一个ElectricCar类能从之前的Car类中继承。

classElectricCar(Car):

def__init__(self, number_of_wheels, seating_capacity, maximum_velocity):

Car.__init__(self, number_of_wheels, seating_capacity, maximum_velocity)

简单如上。我们不需要实现任何其他的方法,因为这个类已经有了(继承自Car类)。让我们确认一下:

my_electric_car = ElectricCar(4,5,250)

print(my_electric_car.number_of_wheels)# => 4

print(my_electric_car.seating_capacity)# => 5

print(my_electric_car.maximum_velocity)# => 250

漂亮。

就到这里!

关于Python基础,我们学会了很多:

变量

分支语句

循环语法

列表:集合 | 数组

字典:键值对的集合

如何迭代这些数据结构

对象和类

用属性作为对象的数据

用方法作为对象的行为

getters、setters 和 property 装饰器

封装:信息隐藏

继承:行为和特征

恭喜!你完成了Python的这段密集的内容。

坚持学习,坚持编程,祝你玩得开心!

觉得不错,请把这篇文章分享给你的朋友

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容