1.过程式编程
x = 2
y = 4
z = 8
xyz = x + y + z
xyz
上述代码每个过程都改变了程序的状态。
过程式编程,数据存储在全局变量中,并通过函数处理。示例如下:
rock = []
country = []
def collect_songs():
song = "Enter a song."
ask = "Type r or c. q to quit"
while True:
genre = input(ask)
if genre == "q":
break
if genre == "r":
rk = input(song)
rock.append(rk)
elif genre == ("c"):
cy = input(song)
country.append(cy)
else:
print("Invalid.")
print(rock)
print(country)
collect_songs()
分析:由于我们将程序的状态都存在全局变量中,如果程序慢慢变大就好碰到问题。程序规模扩大,可能会在多个函数中使用全局变量,我们很难记录都有哪些地方对一个全局变量进行了修改。例如:某个函数可能改变了一个全局变量的值,在后面的程序中又有一个函数改变了相同的变量,因为写第二个函数时程序忘记了已经在第一个函数中做了修改。会严重破坏程序的数据准确性。
弊端:程序功能变多,全局变量变多,最后随着程序规模变大需要修改变量,很快程序将无法维护。这种编程方式也会有副作用(side effects),其中之一就是会改变全局变量的状态。使用过程式编程时,经常会碰到意料之外的副作用,比如意外递增某个变量两次。
2.函数式编程
函数式编程通过消除全局状态,解决了过程式编程中出现的问题。函数式程序员依靠的是不使用或不改变全局状态的函数,他们唯一使用的状态就是传给函数的参数。一个函数的结果通常被继续传给另一个函数。因此,这些程序员通过函数之间传递状态,避免了全局状态的问题,也因此消除了由此带来的副作用和其他问题。
函数式代码有一个特征:没有副作用。它不依赖当前函数之外的数据,也不改变当前函数之外的数据。
一个带副作用的函数。示例如下:
a = 0
def increment():
global a
a += 1
print(a)
#测试代码:
increment() #返回1
print(a) #返回1
一个不带副作用的函数,示例如下:
def increment(a):
return a + 1
#测试代码:
print(increment(2))
第一个函数有副作用,因为它依赖函数之外的数据,并改变了当前函数之外的数据—递增了全局变量的值。第二个函数没有副作用,因为它没有依赖或修改自身之外的数据。
函数式编程的一个优点,在于它消除了所有由全局状态引发的错误(函数式编程中不存在全局状态)。但是也有缺点,即部分问题更容易通过状态进行概念化。例如,设计一个包含全局状态的人机界面,比设计没有全局状态的人机界面要更简单。如果你要写一个程序,通过按钮改变用户看到画面的可见状态,用全局状态来编写该按钮会更简单。你可以创建一个全局变量,值为True 时画面可见,值为False 时则不可见。如果不使用全局状态,设计起来就比较困难。
3.面向对象编程
面向对象(object-oriented)编程范式也是通过消除全局状态来解决过程式编程引发的问题,但并不是用函数,而是用对象来保存状态。在面向对象编程中,类(class)定义了一系列相互之间可进行交互的对象。类是程序员对类似对象进行分类分组的一种手段。假设有一袋橘子,每个橘子是一个对象。所有的橘子都有类似的属性,如颜色和重量,但是这些属性的具体值则各不相同。这里就可以使用类对橘子进行建模,创建属性值不同的橘子对象。例如,可定义一个橘子类,既支持创建深色、重10 盎司(约283克)的橘子对象,也支持创建浅色、重12 盎司(约340 克)的橘子对象。每个对象都是类的示例。如果定义了一个叫Orange 的类,然后创建两个Orange 对象,那么每个对象都是Orange 类的实例;它们的数据类型相同,都是Orange。对象和实例这两个术语可以替换使用。在定义类时,该类的所有实例是类似的:都拥有类中定义的属性,如颜色或种类,但是每个实例的具体属性值是不一样的。
在Python 中,类是一个复合语句,包含代码头和主体。可使用语法class [类名]:[代码主体]来定义类,其中[类名]是类的名称,[代码主体]是定义的类的具体代码。根据惯例,Python 中的类名都是以大写字母开头,且采用驼峰命名法,即如果类名由多个单词组成,每个单词的第一个字母都应该大写,如LikeThis,而不是用下划线分隔(函数的命令惯例)。类中的代码主体可以是一个单一语句,或一个叫方法(method)的复合语句。方法类似于函数,但因为是在类中定义的,只能在类创建的对象上调用方法(如本书第一部分中在字符串上调用.upper())。方法的命名则遵循函数命名规则,都是小写,并用下划线分隔。
方法的定义方式与函数定义方式相同,但有两处区别:一是必须在类的内部定义方法,二是必须接受至少一个参数(特殊情况除外)。按照惯例,方法的第一个参数总是被命名为self。创建方法时,至少要定义一个参数,因为在对象上调用方法时,Python会自动将调用方法的对象作为参数传入。示例如下:
class Orange():
def __init__(self,w,c):
self.weght = w
self.color = c
print("Created!")
可使用self 定义实例变量(instance variable):属于对象的变量。如果创建多个对象,各自都有不同的实例变量值。通过语法self.[变量名] = [变量值]定义实例变量。通常是在特殊方法__init__(代表初始化)中定义实例变量,创建对象时Python 会调
用该方法。
创建Orange 对象时(上例中没有创建),就会执行__init__中的代码,创建两个实例变量:weight 和color。可以在类的方法中,和使用普通变量一样使用这些实例变量。创建Orange 对象时,__init__中的代码还会打印Created!。双下划线包围的方法(如__init__),被称为魔法方法(magic method),即Python 用于创建对象等特殊用途的方法。
创建新Orange 对象的语法,与调用函数的语法类似:[类名]([参数]),将[类名]替换为想要用来创建对象的类的名称,[参数]替换为__init__接受的参数即可。这里不用传入self 参数,Python 会自动传入。创建新对象的过程,也被称为创建类的例。 Python告诉我们它是一个Orange 对象,并打印其在内存中的地址(你在计算机上运行时得到的结果,将不同于本例中的结果)。
class Orange():
def __init__(self,w,c):
self.weght = w
self.color = c
print("Created!")
orl = Orange(10,"dark")
print(orl)
#返回:
#Created!
#<__main__.Orange object at 0x02B30490>
print(orl.weght)
print(orl.color)
#也可使用语法[对象名].[变量名] = [新的值]改变实例变量的值:
orl.weght = 100
orl..color = yellow
print(orl.weght)
print(orl.color)
#可以使用Orange 类创建多个橘子对象
orl2 = Orange(23,"white")
orl3 = Orange(12,"red")
#还有其他的属性。它可能还会腐烂,这些都可以通过方法来模拟。下面的代码为Orange 对象增加了腐烂的属性:
class Orange():
def __init__(self,w,c):
self.weght = w
self.color = c
self.mold = 0
print("Created!")
def rot(self,days,temp):
self.mold = days * temp
orl = Orange(7,"dark")
print(orl.mold)
orl.rot(10,98)
print(orl.mold)
#rot 方法接受两个参数:橘子被摘下的天数,以及这些天的平均温度。调用该方法时,
#其使用公式改变了变mold 的值,这是因为可以在任意方法中改变实例变量的值。现在,橘子就可以腐烂了
面向对象编程有多个优点:鼓励代码复用,从而减少了开发和维护的时间;还鼓励拆解问题,使代码更容易维护。但有一个缺点便是编写程序时要多下些功夫,因为要做很多的事前规划和设计。
习题:
1.定义一个叫Apple 的类,创建4 个实例变量,表示苹果的4 种属性。
2.定义一个叫Circle 的类,创建area 方法计算其面积。然后创建一个Circle对象,调用其area 方法,打印结果。可使用Python 内置的math 模块中的pi 函数。
3.定义一个叫Triangle 的类,创建area 方法计算并返回面积。然后创建一个Triangle 对象,调用其area 方法,打印结果。
4.定义一个叫Hexagon 的类,创建cacculate_perimeter 方法,计算并返回其周长。然后创建一个Hexagon 对象,调用其calculate_perimeter 方法,并打印结果。