• 【python之路】【之前没搞明白】1面向对象(语法)


    看了那么多视频,还是Alex的好懂,搬自https://book.apeland.cn/details/155/

    面向过程 VS 面向对象

    1、编程范式

    编程是 程序 员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 , 一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结出来的编程方式类别,即为编程范式。 不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。 两种最重要的编程范式分别是面向过程编程和面向对象编程。

    1.1 面向过程编程(Procedural Programming)

    Procedural programming uses a list of instructions to tell the computer what to do step-by-step.

    面向过程编程依赖 - 你猜到了- procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。

    举个典型的面向过程的例子, 有个需求是对网站日志进行分析,生成邮件报告,整个流程分以下几步:

    1. 到各台服务器上收集日志,因为有多台网站服务器,共同对外提供服务

    2. 对日志进行各种维度分析,比如pv,uv, 来源地区、访问的设备等

    3. 生成报告,发送邮件

    代码如下

    # 1 整合日志
    def collect_logs():
      print("log on server A ,get access.log")
      print("log on server B ,get access.log")
      print("log on server C ,get access.log")
      print("combine logs in to one file")
    
    # 2 日志分析 def log_analyze(log_file):   print("pv、uv分析....")   print("用户来源分析....")   print("访问的设备来源分析....")   print("页面停留时间分析....")   print("入口页面分析....")

    # 3 生成报告并发送 def send_report(report_data):   print("connect email server...")   print("send email....")

    def main():   collect_logs()   log_analyze('my_db')   send_report()

    if __name__ == '__main__':   main()

    这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改, 举个例子,如果程序开头你设置了一个变量值 为1 , 但如果其它子过程依赖这个值 为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程 , 那就会发生一连串的影响,随着程序越来越大, 这种编程方式的维护难度会越来越高。

    所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。

    1.2 面向对象编程(Object-Oriented Programming)

    OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

    面向对象的几个核心特性如下

      Class 类

    一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法

       Object 对象

    一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同

      Encapsulation 封装

    在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法

      Inheritance 继承

    一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

      Polymorphism 多态

     多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。


    编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

    对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。

    多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定

    1.3 面向对象vs面向过程总结

    面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。

    优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

    缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。

    面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。

    面向对象的程序设计的

    优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

    缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

    应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

    2. 类的定义

    class Dog: # 类名首字母要大写,驼峰体
      d_type = "京巴" # 公共属性,又称类变量
      def say_hi(self): # 类的方法,必须带一个self参数,代表实例本身,具体先不解释
        print("hello , I am a dog,my type is ",self.d_type) # 想调用类里的属性,都要加上self., 原因先不表
    
    d = Dog() # 生成一个狗的实例 d2 = Dog() # 生成一个狗的实例 d.say_hi() # 调用狗这个类的方法 d2.say_hi() print(d.d_type) # 调用Dog类的公共属性

    以上代码就是定义好了Dog这个类,相当于先生成了一个模板,接下来生成了2个实例d, d2,相当于2条有血有肉的狗被创造出来了。

    d_type是类变量,是Dog类下所有实例共有的属性,它存在Dog类本身的内存里。你可以查看d1.d_type,d2.d_type的内存地址,指向的是同一处

    除了共有属性,有没有私有的呢? 比如每条狗的名字、年龄、主人都不一样。可以的,如下操作就行:

    class Dog: # 类名首字母要大写,驼峰体
      d_type = "京巴" # 公共属性,又称类变量
      def __init__(self,name,age,master): # 初始化函数,只要一实例化,就会自动执行
        print('初始化这个实例....',name)
        self.name = name # self.name 就是实例自己的变量
        self.age = age
        self.master = master
      
      def say_hi(self): # 类的方法,必须带一个self参数,代表实例本身,具体先不解释     print("hello , I am a dog,my type is ",self.d_type) # 想调用类里的属性,都要加上self., 原因先不表
    d = Dog("毛毛",2,"Alex") # 生成一个狗的实例 d2 = Dog("二蛋",3,"Jack") # 生成一个狗的实例 d.say_hi() # 调用狗这个类的方法 d2.say_hi() print(d2.name, d2.age, d2.master) # 调用实例的变量

    执行输出

    初始化这个实例.... 毛毛
    初始化这个实例.... 二蛋
    hello , I am a dog,my type is 京巴
    hello , I am a dog,my type is 京巴
    二蛋 3 Jack

    我们并没有调用init(self,….),但它会自动执行,因为它叫初始化函数,就是在实例化的时候,用来初始化一些数据的,比如初始化你实例的名字、年龄等属性啦。

    这些写在init(self,xxxx)里 name,age,master变量,跟d_type有什么区别呢?

    区别就是, d_type是存在Dog类自己的内存里, self.name,self.age,self.master是存在每个实例自己的内存里

    3.self到底是个什么鬼?

    想明白self什么意思,我们先搞明白 ,实例化的过程,看下图;

    1. step 1, d = Dog(“毛毛”,2,”Alex”) 会申请一会内存空间,指向变量名d

    2. step 2, init(xxxx)这个初始化方法需要把接收到参数存下来, 存到这个d的内存空间里

    3. step 3, 传给初始化方法里的name,age,master想绑定到d的空间里,怎么存呢? 就得把d的内存空间传到这个方法里, 所以self就是用来接收d的地址的。d = Dog(“毛毛”,2,”Alex”) 相当于Dog(d,”毛毛”,2,”Alex”), 那self.name = name 也就相当于d.name = name 。我们在实例时没有手动传递d到Dog类里, 只写了d = Dog(“毛毛”,2,”Alex”), 是Python解释器帮你自动干了这个事。

    到此,我们终于明白 ,原来self就是代表实例本身。你实例化时python会自动把这个实例本身通过self参数传进去。

    你说好吧,假装懂了, 但下面这段代码你又不明白了, 为何say_hi(self),要写个self呢? 

    def say_hi(self): # 类的方法,必须带一个self参数,代表实例本身
      print("hello , I am a dog,my type is ",self.d_type,self.name) # 想调用类里的属性,都要加上self.

    那是因为,你自己也看到了, 这个类的方法其实就是一堆函数对吧。函数被一个实例调用时,它怎么知道是谁在调用它呢? 函数内部要用到一些实例的属性的时候去哪里取呢? 比如在say_hi函数里怎么取到d.name,d.age?只能你先传递给它。 所以这就是为何类下的每个方法第一个参数都要是self,因为是为了接收实例这个对象本身。

    *注意:self在实例化时自动将对象/实例本身传给init的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做,因为你瞎改别人就不认识。

    4.属性引用

      4.1类的公共属性引用(类名.属性)

    class Dog: # 类名首字母要大写,驼峰体
      d_type = "京巴" # 公共属性,又称类变量
      def say_hi(self):
        print("hello , I am a dog,my type is ",self.d_type)
        print(Dog.d_type) # 查看Dog的d_type属性
        print(Dog.say_hi) # 引用Dog的say_hi方法,注意只是引用,不是调用

      4.2实例属性引用(实例名.属性)

    d2 = Dog("二蛋",3,"Jack") # 生成一个狗的实例
    d2.say_hi() # 调用狗这个类的方法
    print(d2.name, d2.age, d2.master) # 调用实例的属性
    print(d2.d_type) # 注意通过实例也可以调用类的公共属性

    5.官方文档中的一个操作

    xf = x.f  # x类中的f方法,x.f是一个方法对象,重新赋给了xf,那么xf就成了一个方法函数
    
    xf() #后面加括号可以直接调用

    练习一

    设计一个类Person,生成若干实例,在终端输出如下信息

    小明,10岁,男,上山去砍柴

    小明,10岁,男,开车去东北

    小明,10岁,男,最爱大保健

    老李,90岁,男,上山去砍柴

    老李,90岁,男,开车去东北

    老李,90岁,男,最爱大保健

    老张…

  • 相关阅读:
    HTML5服务器发送事件(Server-Send Events)
    无人问津的排序(一)----希尔排序
    NB二人组(二)----归并排序
    40、常用字符串格式化有哪几种?
    39、请用代码简答实现stack
    38、一行代码实现删除列表中重复的值 ?
    37、如何在函数中设置一个全局变量 ?
    NB二人组(一)----堆排序
    快排
    LOW逼三人组(三)----插入排序
  • 原文地址:https://www.cnblogs.com/watalo/p/12305699.html
Copyright © 2020-2023  润新知