Loading

Python学习笔记(八)之面向对象编程(上)

1. 面向过程和面向对象的区别

1.1 面向过程

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

1.2 面向对象

面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

  • 面向对象的设计思想是从自然界中来的,在自然界中类(class)实例(instance)的概念是很自然的。

    • 来个实例就明白了:

      # !user/bin/env python
      # coding=utf-8
      
      
      __author__ = "zjw"
      
      
      class Student(object):
      
          def __init__(self, name, score):
              self.name = name
              self.score = score
      
          def print_information(self):
              print self.name, self.score
      
      
      
      if __name__ == '__main__':
      
          stu = Student("Kangkang", 12)
          stu.print_information()
      
    • 输出:

      Kangkang 12

    • Class是一种抽象概念,比如我们自定义一个学生(Student)类,包含学生姓名(name)和成绩(score)的类。

    • Instance则是一个个具体的Student,比如stu就是一个具体的Student对象。

2. 类(Class) 和实例(Instance)

  • 例2.1:

    # !user/bin/python
    # coding=utf-8
    
    
    __author__ = "zjw"
    
    
    # 定义的一个Person类
    class Person(object):
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        def print_information(self):
            print self.name, self.age, self.gender
    
    
    if __name__ == '__main__':
        # 实例化一个新的Person
        person = Person("Michael", 20, "男")
        person.print_information()
    
  • 输出:

    Michael 20 男

  • 分析:

    • class关键字后面跟的是一个类名,即Person,类名通常规定大写字母开头。
    • (object)表示该类继承自object类。通常没有合适的继承类,就直接继承object类,这是所有类最终都会继承的类。
    • __init__是一个特殊方法,前后都有两个下划线。__init__方法的第一个参数永远是self,表示创建的实例本身,在__init__方法内部,我们就可以将各种属性绑定到self
      • 注:有了__init__方法,创建实例时就不能传入空的参数了,必须传入与该方法匹配的参数,self不需要传入。
      • 如果想要传入的参数不受数量限制可以用def __init__(self, **pro)来进行传入的传递,或则直接不写__init__方法。
    • 数据封装
      • 面向对象的一个重要特点就是数据封装
      • 在上面Person类中定义一个print_information方法,用于实例后的具体对象访问自身的属性内容。当然我们可以通过在外面写一个函数来实现,但是Person实例本身就有这些数据,难道我们的个人信息需要别人来帮我们介绍吗,况且有些信息我们还想保密起来的,所以说访问类内部的属性就没必要从外面的函数去访问,可以直接在Person类内部定义一个方法实现,即我们上面定义的一个print_information方法,这样,我们就把数据给封装起来了。
      • 之所以在这里不叫print_information函数,是因为该封装数据的函数是和Person类本身是关联起来的,所以称之为类的方法
      • 这样以来,我们在main函数中,创建实例时需要传入name,agegender三个参数,而打印这个实例的各个属性,都是Person内部数据封装的事情了,这些数据和逻辑被“封装”起来了,调用起来容易,且外部调用者不需要考虑内部实现的细节。

3. 访问限制

3.1 思考

  • 在java中,我们一般将Class内部的属性设置为private型,不让外部直接改变,而是通过public的setter和getter方法来改变和调用,那么我们在Python中该如何实现呢?
  • 当我们想通过某种规则改变实例中的某个属性时,我们不必取得该属性,而是应该在该类中存在某种对应方法来改变,而如果我们是通过外部操作来改变,就不是实例该做的事情了,说明实例的行为受到了外部的干扰,强制改变了,这还有人性吗?所以一般情况下,我们会将实例中的属性设置为private型,来限制外部的随意改变,且设置一些方法来对实例中的属性进行相应的操作。

3.2 实例解析

  • 在Class内部,有属性和方法,而外部的代码可以直接通过变量直接访问实例的方法来操作数据,这样我们可以影藏内部的复杂逻辑

  • 我们来看上面的Person类,因为没有多加限制,所以我们访问属性时,可以直接通过实例化后的变量名.属性名就可以直接访问和修改内部的属性值,按上面的思考这是很不合理的。如果想让内部的属性不被外部访问,这就涉及到了作用域,前面我们已经学到作用域的相关知识,是在变量前面加上两个下划线,即__

  • 因此根据作用域的相关知识,我们运用到类内部就是在原来定义的属性名前面加上两个下划线__,即属性就变成一个类内部的私有属性,即java中的private型。

  • 说了这么多,我们何不改变一下上面的Person类来试一下咯。

    • 实例3.2.1:

      # !user/bin/python
      # coding=utf-8
      
      
      __author__ = "zjw"
      
      
      # 定义的一个Person类                                             
      class Person(object):
      
          def __init__(self, name, age, gender):
              self.__name = name
              self.__age = age
              self.__gender = gender
      
          def print_information(self):
              print self.__name, self.__age, self.__gender
      
      
      if __name__ == '__main__':
          # 实例化一个新的Person
          person = Person("Michael", 20, "男")
          person.print_information()
      
    • 输出:

      Michael 20 男

    • 分析:可以看到,我将__init__方法内部的属性都改成了私有属性,当然我们在外部实例的person对象调用print_information方法是没有问题,但是如果我们通过person.__name,那么很遗憾系统会报错。不妨来试一下吧。

      Traceback (most recent call last):
      File "G:/PyCharm-workspace/Python_Learning/person.py", line 23, in
      person.__name
      AttributeError: 'Person' object has no attribute '__name'

      这些是用到person.__name时的报错信息。

    • 改进思想:

      • 刚刚我将各个属性改成了private型,那么我们该如何访问和修改实例的属性值呢?这就和java中的setter和getter方法一个道理了。我们可以写相应的方法来访问和修改相应的属性。

      • 经过修改后的Person类是下面这样子的:

        # !user/bin/python
        # coding=utf-8
        
        
        __author__ = "zjw"
        
        
        # 定义的一个Person类
        class Person(object):
        
            def __init__(self, name, age, gender):
                self.__name = name
                self.__age = age
                self.__gender = gender
        
            def print_information(self):
                print self.__name, self.__age, self.__gender
        
            def get_name(self):
                return self.__name
        
            def get_age(self):
                return self.__age
        
            def get_gender(self):
                return self.__gender
        
            def set_name(self, name):
                self.__name = name
        
            def set_age(self, age):
                self.__age = age
        
            def set_gender(self, gender):
                self.__gender = gender
        
        
        if __name__ == '__main__':
            # 实例化一个新的Person
            person = Person("Michael", 20, "男")
            # 我们先打印一下此时的person实例
            person.print_information()
            # 然后我们在通过相应的setter方法修改一下person实例中的相应属性值
            person.set_name("Jane")
            person.set_age(12)
            person.set_gender("女")
            # 最后我们在通过相应的getter方法来获得此时person类中的相应属性值
            # 我们用print来打印getter方法获得的属性值
            print person.get_name(), person.get_age(), person.get_gender()
        
      • 输出:

        Michael 20 男
        Jane 12 女

      • 分析:我们可以看到,上面的Person类中,为三个属性name,age和gender都添加了相应的settergetter方法,那么在main函数中我们就可以通过设置好的setter,getter方法自如的访问和修改person类中的相应的属性值。

3.3 注意事项

  • 当我们将属性值设置成private型时,我们是否就无法通过直接获得属性值了,其实并不是,我们可以通过下面的方法获得。依旧以上面的例子延续下来,我们可以通过_Person__name来获得此时的person类的__name属性值。来试一下吧(修改一下上面的main函数):

    • 例3.3.1:

      if __name__ == '__main__':
          # 实例化一个新的Person
          person = Person("Michael", 20, "男")
          print person._Person__name
      
    • 输出:

      Michael

    • 可以看到我们通过_Person__name变量来获取到了__name的属性值,冒充了get_name()方法,真是罪不可恕,浑水摸鱼的做法啊,所以这种方法是非常不推荐,应该说是禁止使用的。且在编译器中,我们会看到一些提示信息也建议我们修改掉这种用法。Python解释器本身不会阻止我们干这种坏事,一切全靠我们自觉咯。

  • 接下来再说另一个错误示范(同样是修改了上面的main方法):

    • 例3.3.2:

      if __name__ == '__main__':
          # 实例化一个新的Person
          person = Person("Michael", 20, "男")
          # 我们先打印一下此时person的__name
          print person.get_name()
          # 我们通过一种错误的示范来修改__name值
          person.__name = "NewName"
          # 再次打印__name值
          print person.get_name()
          # 通过person.__name来打印__name值试一下
          print person.__name
      
    • 输出:

      Michael
      Michael
      NewName

    • 分析:可以看到,上面的实例中我们通过person.__name = "NewName"该操作来修改__name属性值,但是两次通过get_name方法获得的值确实相同的,而最后我们通过person.__name来取__name属性值时,获得的却是改变后的值,这是为什么呢?

    • 其实表面上,我们是成功的修改了__name变量,但实际上__name变量和此时的class内部的__name属性并不是同一变量!原来内部的变量被解释器解改成了_Person__name,而外面我们通过该方法修改变量则是我们新设置的新的变量,所以修改的并非同一个变量,所以两次通过getter方法获得的__name属性值是相同的。

    • 所以得出结论,我们还是强烈不建议使用该方法获得和修改类中的相应的属性值

4. 总结一下

  • 这次学习Python的面向对象主要还是通过类比java的面向对象来学习的,通过联想java中的类,属性,方法以及实例,类比学习了Python中的面向对象,学习下来其实都是大同小异,只是在语法上存在着一些不同。
  • 在设置属性为private上,通过__前缀实现的Python显得更加的简洁,也使我们编写的时候显得更加的明然,直接通过变量名即可判断属性和方法的公开性。
  • 再则是构造函数上,java中的构造函数可以写很多个,且如果写了构造函数后,只要再写一个无参构造函数,那么外面实例化的时候就可以不传入参数也可以创建;但是Python中的构造函数__init__(这里暂且叫他构造函数吧),只要设置了相应的传入参数,那么在外部实例化时,则必须传入相对应的参数,也不能无参实例化,这可能是一个小小的弊端,或则说是学到这里,我并没有找到更好的方法来解决这个问题,只有上面提到的在__init__函数的参数里用一个**pro参数,即关键字参数。我的一个猜想是可能Python的缔造者为了尽量简化Python代码,限制了传入的参数后,下面我们不必再一个个修改,反而增加了代码的行数。
  • 接下来就是我们提到的,在Python中即使是设置成private的属性,也可以通过某种奇妙的方式来获得属性值,但是用该方式获得属性值其实是非法的,就像你的个人资料随意的让人修改一样,这是很不合理的,所以要避免使用这种操作。
posted @ 2020-01-28 20:39  August_丶  阅读(155)  评论(0编辑  收藏  举报