Fork me on GitHub

第九章 类的定义属性和方法

# 第九章 类的定义属性和方法
## 一、类的定义
### 1、类的概念
  Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。
  
  编写类时,你定义一大类对象都有通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了惊讶的地步。
  
  在面向对象的世界里,类是用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。你的代码通常被称为类的方法,而数据通常称为类的属性,实例化的数据对象通常称为实例。  
### 2、类的语法
 

 

 

  Python使用class创建类。每个定义的类都有一个特殊的方法,名为__init__(),可以通过这个方法控制如何初始化对象。
## 二、类的属性和方法
  一个对象的特征称为"属性",一个对象的行为称为"方法"。属性在代码层面上来看就是变量,方法实际就是函数,通过调用这些函数来完成某些工作。
### 1、创建类和使用类
  使用类几乎可以模拟任何东西。属性在代码层面上来看就是变量。下面来编写一个表示小狗的简单类Dog--它表示的不是特定的狗,而是任何小狗。对于大多数狗,我们都知道什么呢?大多数狗会蹲下和摇尾巴。由于大多数小狗都具备上述两项信息(名字和年龄)和两种行为。我们的Dog类将包含它们。这个类让Python知道如何创建表示小狗的对象。编写这个类后,我们将使用它表示特定小狗的实例。
  
1)、创建Dog类
 
  根据Dog类创建的每一个实例都将存储名字和年龄,我们赋予了每条小狗蹲下和摇尾巴的能力:
  
  dog.py
 

 

 

 
  在①出,定义了一个名为Dog的类。根据约定,Python中,首字母大写的名称指的是类。在②处,编写了一个文档字符串,对这个类的功能作了描述。
  
  在④处的方法__init__()是一个特殊的方法,每当你根据Dog类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和结尾各有两个下划线,这是一种约定,旨在避免Python默认方法和普通方法名称冲突。在__init__()方法定义了三个形参:self,name和age。这个默认方法必不可少的是形参self,还必须位于其他形参的前面。所以,Python调用这个_init__()方法来创建实例时,将自动传入这个实参self。让实例能够自动访问类中的属性和方法。
  
  在⑥、⑦处定义的两个变量都有前缀self。以self前缀的变量都可提供类中所有方法使用。我们可以通过类的任何实例访问这些变量。self.name=name获取存储在形参name中的值,并将其存储在变量name中。像这样通过实例访问的变量称为属性。
  Dog类还定义了另外两种方法:sit()和Wangging(),由于这些方法不需要额外的信息,如名字和年龄,因此他们只有一个形参self。后面创建实例也能够访问这些方法。其中(.title())这个方法是表示将属性name的值的首字母自动变为大写。
### 2、根据类创建实例
  有了类之后,创建对象实例很容易。只需将对类名的调用赋至各个变量,根据需要创建多个对象实例。根据类来创建对象被称为实例化。
  下面来创建一个表示特定小狗的实例:

 

 

 
在①处,创建了一个名字为‘while’、6岁的小狗。通过传入这两个实参调用了类中的方法__init__()。方法__init__并没有含有return语句,但Python自动返回一个表示小狗的实例。我们将这个实例存储在变量my_dog中。命名约定:通常可以认为首字母大写的名称(如Dog)指的就是类,而小写的名称(如my_dog)指的根据类创建的实例。
1)、访问属性
要访问实例的属性,可使用句点表示法。在21行,我们编写了my_dog.name来访问my_dog的属性name的值。在第二条'print'语句中,str(my_dog.age)将my_dog的属性age的值6转换为字符串。
执行该代码,结果为:
---
My dog's name is Willie.
 
My dog's is 6 years old.
 
---
2)、调用方法
根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。下面来让小狗蹲下和摇尾巴:
 

 

 

  要调用方法,可指定实例的名称(这里是my_log)和调用的方法,并用句点分割它们。遇到代码my_dog.sit()时,Python在类Dog中查找sit方法并运行代码。Python以同样的方式解读Wagging()。
  执行该代码,结果为:
  
---
Willie 会蹲下。
 
Willie 摇尾巴
 
---
3)、创建多个实例
  可按需求根据类创建任意数量的实例。下面再创建一个名为your_dog实例:
  

 

 

在这个实例中,我们创建了两条小狗。他们分别名为willie和lucy,每条狗都是一个独立的实例,有自己的属性,能够获得相同的方法。
执行该代码,结果为:

 

 

 
### 3、使用类和实例
  你可以使用类来模拟现实世界中很多情景。类编写好,你的大部分时间将花在使用根据类创建的实例上。
  
  首先,类的每个属性都必须有初始值,哪怕是0或者空字符串。如设置默认值时,在方法__init__)内指定初始值可以,这样,实例化就不用为它提供初始值的形参了。如果我们狗狗一般每天喝水量为200ml,那么,我们在方法__init__()里增加一个属性名为water默认值为'200'的属性。
  
   还是继续使用Dog.py这个案例,稍微修改成我们需要的案例:
  
 
可以看出,在方法__init__()中,新增了第⑧行一个属性名为water值为'200'的新属性,这个就是给属性指定默认值。在⑩行中,我们定义了一个名为message()的方法,那么,实例化对象通过调用message()就能清晰获悉这个母狗的具体信息。
 
执行该代码,结果为:
 
---
Willie eats 200 of water every day.
 
Willie is 6 years old.
 
---  
  然后,接下来我们的任务是修改实例的属性,有哪些方式呢?
  
1)、直接修改属性的值
要修改属性的值,最简单的方式就是通过实例直接访问它。如果我们将狗狗的性别由母狗改为公狗:
类还是上面的Dog类,实例化对象和调用方法就是以下代码:
 
 
  my_dog.water=300直接使用句点法直接访问狗的水量,让Python在实例my_dog中找到属性water,并将该属性的值设置为300。
  
执行该代码,结果为:
---
Willie eats 300ml of water every day.
 
Willie is 6 years old.
 
---  
2)、通过方法修改属性的值
如果有替你更新属性的方法,将大有裨益。这样,你就无需直接访问属性,而可将值传递给一个方法,由它在内部更新。
 
 
  仔细观察,可以发现,对Dog类所作的唯一修改就是在⑩行新增了方法water_yield(),这个方法接受一个狗的性别参数,并将其存储在 self.water中。在十九行处,调用了water_yield(),并提供了实参300,然后在message()打印处狗的信息。
  
  执行代码,结果和直接修改属性的指输出的结果一样。
  
  可对方法water_yield()进行扩展,使其在修改狗的喝水量时能做额外的工作,逻辑上一条狗每天喝水量最好不应该超过默认值200即200ml的水量,如果超过这个水量要给与警告:
  
 

 

 

  如上所示,将方法water_yield()加上条件判断后,如果调用此方法输入实参250,那么执行改代码,结果为:
  
---
Water exceed the standard!
 
Willie eats 200ml of water every day.
 
Willie is 6 years old.
 
---   
传入水量值为250,判断条件中,只有小于200才能覆盖原来的默认值,大于200就没有改变默认值,并返回一条警告信息。
3)、通过方法对属性的值进行递增
  那么,如果需要将属性值递增特定的量,而不是设置为全新的值。假如某条小狗昨天喝了200ml的水量,今天增加了5ml的水量,下面直接看代码:
  

 

 

  在十一行处,使用+=water进行追加传入的水量值,灵活去更新默认值。那么,执行该代码,结果为:
  
 
## 三、销毁方法
  与__init__() 方法对应的是__del__()方法__init__()方法用于构造当前类的实例化对象,而__del__() 则用于销毁实例化对象,即在任何实例化对象将要被系统回收之时,系统都会自动调用该对象的__del__()方法。
  
  当程序不再需要一个Python对象时,系统必须把该对象所占用的内存空间释放出来,这个过程被称为垃圾回收,Python会自动回收所有对象所占用的内存空间,因此开发者无须关心对象垃圾回收的过程。
  
  大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制(下面会讲),能自动将不需要使用的实例对象进行销毁。
  
  无论是手动销毁,还是 Python 自动帮我们销毁,都会调用__del__()方法。举个例子:
  

 

 

执行该代码,结果为:
 
---
调用__init__()方法构造对象
 
调用__del__()销毁对象,释放其空间
 
---  
但是,千万不要误认为,只要为该实例对象调用__del__()方法,该对象所占用的内存空间就会被释放。举个例子:
 

 

 

执行该代码,结果为:
 
---
 
调用__init__()方法构造对象
 
***********
 
调用__del__() 销毁对象,释放其空间
 
---  
  注意,最后一行输出信息,是程序执行即将结束时调用__del__()方法输出的。可以看到,当程序中有其它变量(比如这里的 cl)引用该实例对象时,即便手动调用__del__()方法,该方法也不会立即执行。这和Python 的垃圾回收机制的实现有关。
 
  Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用__del__() 方法将其回收。
  
  如果在上面程序结尾,添加如下语句:

 

 

  执行该代码,结果为:
 
---
 
调用__init__()方法构造对象
 
***********
 
调用__del__() 销毁对象,释放其空间
 
+++++
 
---  
  可以看到,当执行 del cl 语句时,其应用的对象实例对象 C 的计数器继续 -1(变为 0),对于计数器为 0 的实例对象,Python 会自动将其视为垃圾进行回收。
 
  
 
## 四、课堂练习
#### 1、
 

 

 

### 2、定义一个类,实例化的时候打印'正在实例化',最后结束的时候,输出'正在销毁'。
## 五、上一节课堂练习答案
#### 1、命名一个a列表,然后定义一个函数,函数里有a列表。用案例说明列表是可以在局部被修改;说明列表不能重新赋值;如果需要重新赋值,需要在函数内部使用global定义全局变量。
1)全局列表a可以在局部被修改

 

 

执行该代码,结果为:
 
---
['global', 'python', 'nonlocal']
 
['global', 'python', 'nonlocal']
 
---
发现上面的a并没有使用galobal但是值却改变了, 说明列表是可以在局部被修改的。
 
2)局部变量赋值不能改变全局变量的值
 

 

 

执行该代码,结果为:
 
---
nonlocal
 
['global', 'python']
 
---
3)使用了global关键字后, 变量被重新赋值
 

 

 

执行该代码,结果为:
 
---
nonlocal
 
nonlocal
 
---
#### 2、计算1到100之间相加之和;通过循环和递归两种方式实现。
1)循环方式
 

 

 

执行该代码,结果为:
 
---
 

 

 

 
 
---
每一次循环都会输出结果,缺点是程序繁琐消耗内存。
2)递归方式
 

 

 

执行该代码,结果为:
 
---
5050
 
---
  递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
#### 3、举一个别人举过的例子:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 对不,然后你女朋友回家以后还真给你发了条信息。小伙子,你有戏了。其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。

 

 

 
执行该代码,结果为:
 
---
我是回调函数:回到家发个信息哦
 
我是主函数:我回到家啦
 
---
 
 
 
posted @ 2020-06-28 15:37  python终极者  阅读(1507)  评论(0编辑  收藏  举报
AmazingCounters.com
页脚Html代码