zoukankan      html  css  js  c++  java
  • 第九章 类的定义属性和方法

    # 第九章 类的定义属性和方法
    ## 一、类的定义
    ### 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、举一个别人举过的例子:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 对不,然后你女朋友回家以后还真给你发了条信息。小伙子,你有戏了。其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。
     
    执行该代码,结果为:
     
    ---
    我是回调函数:回到家发个信息哦
     
    我是主函数:我回到家啦
     
    ---
     
     
     
    如有问题请留言,谢谢!
  • 相关阅读:
    编程题#2: 魔兽世界之二:装备
    程序设计实习MOOC / 继承和派生——编程作业 第五周程序填空题1
    【转】C++动态创建二维数组,二维数组指针
    HDU-2571命运
    HDU-1203 I NEED A OFFER!
    HDU-1003 Max Sum
    HDU2196-Computer
    HDU-1520 Anniversary party
    ChineseHelper(获取汉字字符串的首拼)
    车牌号正则表达式(新能源车牌)
  • 原文地址:https://www.cnblogs.com/yunsi/p/13203080.html
Copyright © 2011-2022 走看看