面向对象

2022/4/11 6:14:20

本文主要是介绍面向对象,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

引入(轮流互锤小游习)

简单形式

      person = {'name': 'oliver', 'attack_val': 200, 'life_val': 1000}
      dog = {'name': 'black', 'attack_val': 100, 'life_val': 300}
      # 让人打狗一下
      print('人的血量是%s' % person.get('life_val'))
      dog['life_val'] = dog.get('life_val') - person.get('attack_val')
      print('人%s打了狗%s一下,掉血%s,剩余血量%s' % (person['name'], dog['name'],     person['attack_val'], dog['life_val']))

如果角色非常多的时候,创建角色过于麻烦

函数封装

      def get_person(name, attack_val, life_val):
          person_dict = {'name': name, 'attack_val': attack_val, 'life_val': life_val}
          return person_dict


      def get_dog(name, attack_val, life_val):
          dog_dict = {'name': name, 'attack_val': attack_val, 'life_val': life_val}
          return dog_dict


      def dog_attack_func(person, dog):
          print('狗的生命值是%s' % dog.get('life_val'))
          dog['life_val'] = dog.get('life_val') - person.get('attack_val')
          print('人%s打了狗%s一下,狗掉血%s,剩余血量%s' % (person['name'], dog['name'], person['attack_val'], dog['life_val']))


      def person_attack_func(dog, person):
          print('人的生命值是%s' % person.get('life_val'))
          person['life_val'] = person.get('life_val') - dog.get('attack_val')
          print('狗%s咬了人%s一下,人掉血%s,剩余血量%s' % (dog['name'], person['name'], dog['attack_val'], person['life_val']))


      person1 = get_person('oliver', 200, 1000)  # 产生角色就比较简单了
      dog1 = get_dog('black', 30, 400)
      # print(person1['name'])
      print(dog1)

      dog_attack_func(person1, dog1)
      # dog_attack_func(person1, dog1)
      person_attack_func(dog1, person1)
      person_attack_func(person1, dog1)  # 调用函数的角色发生错误

功能内的角色顺序错误时,会发生无厘头的事情

将角色与功能进行绑定(将功能函数写进创建觉得函数里)

      def get_person(name, attack_val, life_val):
          def person_attack(person, dog):
              print('狗%s当前的生命值%s' % (dog['name'], dog['life_val']))
              dog['life_val'] -= person.get('attack_val')
              print('人%s锤了狗%s一下,掉血%s,剩余血量%s' % (person.get('name'), dog.get('name'), person.get('attack_val'), dog.get('life_val')))
          person_dict = {'name': name, 'attack_val': attack_val, 'life_val': life_val, 'attack': person_attack}
          return person_dict


      def get_dog(name, attack_val, life_val):
          def dog_attack(dog, person):
              print('人%s当前的生命值%s' % (person['name'], person['life_val']))
              person['life_val'] -= dog.get('attack_val')
              print('狗%s咬了人%s一下,掉血%s,剩余血量%s' % (dog.get('name'), person.get('name'), dog.get('attack_val'), person.get('life_val')))
          dog_dict = {'name': name, 'attack_val': attack_val, 'life_val': life_val, 'attack': dog_attack}
          return dog_dict


      person1 = get_person('oliver', 200, 1000)
      dog1 = get_dog('black', 30, 400)
      # print(person1)
      # print(dog1)

      person1['attack'](person1, dog1)
      dog1['attack'](dog1, person1)

上述操作的目的就是将功能与数据进行绑定,某些数据的特定功能只有该数据才能调用,这里的将功能与数据绑定的操作其实就是面向对象编程的思想

面向对象编程理论

编程的两种思想

思想一: 面向过程编程

 之前我们学习的大多数编程方式都是面向过程编程,面向过程编程思想就是指按照事物的正常进行步骤进行编程,面型过程编程就像工程里的流水线,每一步的功能都是特定的,逐渐向我们得目的结果靠近。

思想二: 面向对象编程

  面型对象编程思想的核心是'对象',刚刚我们了解了对象其实就是将数据与功能进行绑定的方式进行整合的结合体,满足这个要求的都是对象

  • 注意一: python中可以认为万物皆对象,我们所接触到的数据类型(整型,字符串……都有内置方法,也就是说数据也是对象)
  • 注意二: 面向过程编程与面向对象编程并没有优劣之分,适用条件不同, 并且大多数情况是混用的

类与对象

定义

  • 对象: 指数据与功能进行整合之后的结合体
  • 类: 是指多个对象共有特征(数据、功能)的集合

注意:类只描述公共特征,个性特征应该由对象自己描述

类的创建(class)

'''
      定义类是用的class关键字
          结构:
              class 类名:
                  类体代码
                  
          解释:
              类名: 类似于函数名,推荐首字母大写,用于辨别
              类体代码: 存放公共特征的位置

      编程过程中要先定义类,再产生对象
          类体代码不需要调用,会直接执行,产生类的名称空间
'''
# 示例
      class People:
          kind = 'people'
          shape = 'two arms and two legs'
          def run(self):
              print('都会跑')

查看类名称空间的方法: dict

      print(People.__dict__)  # 返回值是字典
      print(People.__dict__.get('kind'))  # 获取类中的公共特征
      print(People.__dict__['shape'])  # 获取类中的公共特征
      print(People.__dict__['run'])  # <function People.run at 0x0000024F82C979D8>
# 产生对象的方法
      obj1 = People()  # 类名加括号产生对象
      print(obj1)
      print(obj1.__dict__)

      People.__dict__['run'](obj1)
      print(People.kind)  # people
      print(People.shape)
      print(obj1.kind)  # people
      print(obj1.shape)
      People.run(obj1)
      obj1.run()  # 调用功能
      People.kind = 'person'  # 更改类的特征数据 People.__dict__['kind'] = 'person'
      print(People.kind)  # person
      print(obj1.kind)  # person

对象实例化

      class People:
          kind = 'people'
          shape = 'two arms and two legs'
          def run(self):
              print('都会跑')

      obj = People()
      print(obj)
      print(obj.__dict__)  # 对象obj没有个性数据
      obj.__dict__['name'] = 'oliver'  # obj.name = 'oliver'
      obj.__dict__['age'] = 25  # obj.age = 25
      obj.__dict__['gender'] = 'male'  # obj.gender = 'male'  逐步添加姓名、年龄、性别
      print(obj)
      print(obj.__dict__)  # 获取到个性数据
      print(obj.name)  # oliver

      将添加数据的步骤封装成函数
      def set_info(obj, name, age, gender):
          obj.name = name
          obj.age = age
          obj.gender = gender

      obj = People()
      set_info(obj, 'oliver', 25, 'male')  # 给obj添加键值对
      print(obj)
      print(obj.kind)
      print(obj.name)

      将添加个性数据的函数写到类体代码里
      class People:
          def set_info(obj, name, age, gender):
              obj.name = name
              obj.age = age
              obj.gender = gender
          kind = 'people'
          shape = 'two arms and two legs'
          def run(self):
              print('都会跑')

      obj = People()
      print(obj.__dict__)
      People.set_info(obj, 'oliver', 25, 'male')  # 类调用该方法时需要传一个对象给第一个参数
      print(obj.__dict__)
      obj.set_info('oliver', 25, 'male')  # 对象调用该方法时不需要添加第一个参数,因为会将该对象本身传给第一个参数


      class People:
          def __init__(self, name, age, gender):
              """
              给对象添加个性数据
              :param name:  接收名字
              :param age:   接收年龄
              :param gender:   接收性别

              在类体代码内直接定义__init__可以直接添加个性数据
              """
              self.name = name
              self.age = age
              self.gender = gender

          kind = 'people'
          shape = 'two arms and two legs'
          def run(self):
              print('都会跑')


      obj = People('oliver', 25, 'male')
      print(obj.kind)
      print(obj.__dict__)
      print(obj.name)

类中的__init__方法会在类产生对象时自动执行

类产生对象的步骤:

  1. 先创建一个没有个性数据的空对象 {}
  2. 将空对象和类的括号里传入的数据一起交给__init__方法
      __init__方法的第一个参数就是本身
  3. 将创建好的对象自动返回
     针对__init__方法的第一个self形参只是一个普通的变量名而已,只不过编程中默认使用self来接收对象

绑定方法

 类中定义的函数默认都是给对象使用的,即对象调用该方法时,对象被默认的传给第一个参数

      class People:
          def __init__(self, name, age, gender):
              """
              给对象添加个性数据
              :param name:  接收名字
              :param age:   接收年龄
              :param gender:   接收性别

              在类体代码内直接定义__init__可以直接添加个性数据
              """
              self.name = name
              self.age = age
              self.gender = gender

          kind = 'people'
          shape = 'two arms and two legs'
          def run(self):
              print(self.name)
          def walk(self):
              print(self.age)

      obj1 = People('oliver', 25, 'male')
      obj2 = People('kevin', 13, 'male')
      obj1.run()  # oliver
      obj2.walk()  # 13

动静态方法

动态方法

绑定给对象的方法

      class People:
          def __init__(self, name, age, gender):
              """
              给对象添加个性数据
              :param name:  接收名字
              :param age:   接收年龄
              :param gender:   接收性别

              在类体代码内直接定义__init__可以直接添加个性数据
              """
              self.name = name
              self.age = age
              self.gender = gender

          kind = 'people'
          shape = 'two arms and two legs'
          def run(self):
              print(self)
          def walk(self):
              print(self)


      People.run(1234)  # 1234

      obj1 = People('oliver', 25, 'male')
      obj2 = People('kevin', 13, 'male')
      obj1.run()  # <__main__.People object at 0x000001D22E9A23C8>
      obj2.walk()  # <__main__.People object at 0x000001D22E9A2320>

类调用绑定给对象的方法:有几个参数就需要传几个参数;对象调用绑定给对象的方法:会自动将对象当做第一个参数传入

绑定给类的方法

      class People:
          def __init__(self, name, age, gender):
              """
              给对象添加个性数据
              :param name:  接收名字
              :param age:   接收年龄
              :param gender:   接收性别

              在类体代码内直接定义__init__可以直接添加个性数据
              """
              self.name = name
              self.age = age
              self.gender = gender

          kind = 'people'
          shape = 'two arms and two legs'
          @classmethod  # 动态方法,将方法绑定给类
          def run(self):
              print(self)
          def walk(self):
              print(self)


      obj1 = People('oliver', 25, 'male')
      obj2 = People('kevin', 13, 'male')
      People.run()  # <class '__main__.People'>
      People.walk(obj1)  # <__main__.People object at 0x00000219D68924E0>
      obj1.run()  # <class '__main__.People'>
      obj1.walk()  # <__main__.People object at 0x00000219D68924E0>

类调用绑定给类的方法:会自动将类当做第一个参数传入,对象调用绑定给类的方法:会将产生该对象的类自动当做第一个参数传入

静态方法

      class People:
          def __init__(self, name, age, gender):
              """
              给对象添加个性数据
              :param name:  接收名字
              :param age:   接收年龄
              :param gender:   接收性别

              在类体代码内直接定义__init__可以直接添加个性数据
              """
              self.name = name
              self.age = age
              self.gender = gender

          kind = 'people'
          shape = 'two arms and two legs'
          @staticmethod  # 改变为静态方法
          def run(self):
              print(self)
          def walk(self):
              print(self)


      obj1 = People('oliver', 25, 'male')
      obj2 = People('kevin', 13, 'male')
      People.run(1234)  # 1234
      People.walk(obj1)  # <__main__.People object at 0x00000219D68924E0>
      obj1.run(4321)  # 4321
      obj1.walk()  # <__main__.People object at 0x00000219D68924E0>

无论是类还是对象调用静态方法,都必须传固定个的参数

面向对象的三大特征:封装(重要)

继承的含义

 类似于现实里的继承家产,继承了父辈的财产后,父辈的财产就属于你。面向对象继承的是子类(派生类)与父类(基类)的关系,继承的是父类的数据与方法

继承的目的

 现实生活中的继承(可以继承多个人的)可以获得财产,从而达到快速积累财富的目的。面向对象的继承的目的是能够快速使用父类(可以是多个父类)数据和方法的目的,简化编程代码的重复,节省开发时间

继承的使用

      class A:
          name_a = 'from A'

      class B():
          name_b = 'from B'

      class C(B, A):
          name_c = 'from C'

      obj = C()
      print(obj.name_c)  # from C
      print(obj.name_b)  # from B
      print(obj.name_a)  # from A

类C生成的对象可以访问到类B和类A里的数据

继承的本质

  • 抽象: 从子类抽取相同特征成为父类

  • 继承: 从父类向子类进行,子类可以白嫖父类的数据和方法

  • 类和父类的目的: 减少代码冗余

  • 对象: 数据与功能的整合体

  • 类: 多个对象的共同特征的结合体

  • 父类: 多个类共同特征的结合体

      class People:
          def __init__(self, name, age, gender):
              self.name = name
              self.age = age
              self.gender = gender
          kind = 'people'
          shape = 'two arms'
          def run(self):
              print(f'{self.name} is running')

          def walk(self):
              print(f'{self.name} is walking')

      class Yellow(People):
          def yell(self):
              print(f'{self.name} is yellow')

      class Black(People):
          def bla(self):
              print(f'{self.name} is black')

      obj1 = Yellow('oliver', 25, 'male')
      obj2 = Black('kevin', 18, 'male')
      print(obj1)  # <__main__.Yellow object at 0x0000018562775FD0>
      print(obj2)  # <__main__.Black object at 0x000001856277F0F0>
      print(obj1.__dict__)  # {'name': 'oliver', 'age': 25, 'gender': 'male'}
      print(obj2.__dict__)  # {'name': 'kevin', 'age': 18, 'gender': 'male'}
      obj1.run()  # oliver is running
      obj1.yell()  # oliver is yellow
      obj2.walk()  # kevin is walking
      obj2.bla()  # kevin is black

名字的查找顺序

不继承情况

 名字查找顺序: 先从对象自己的名称空间中查找,找不到再去类的名称空间内查找

单继承情况

 名字查找顺序: 先从对象自己的名称空间查找,找不到的时候去产生对象的类的名称空间查找,还找不到的话,去继承父类的名称空间中查找.

多继承情况

  • 在python2中有新式类和经典类之分
  • 在python3中只有新式类(所有的类中都有一个默认的object类)
'''
      经典类: 不继承任何类
      新式类: 直接或者间接继承了object类或其子类的类
    
      class Myclass(object):  # 习惯性的继承object类
          pass
      为了兼容Python2和Python3
'''
非菱形继承

 父类中的名字的查找顺序是按照继承时从左向右依次查找如果多个父类有分类,遵循'深度优先'

菱形继承

 父类中名字的查找顺序就是按照继承时从左往右依次查找,如果多个父类有分类 遵循"广度优先"
注意:名字的查找顺序永远都是 先从当前对象自身开始查找

派生类

      class People:
          def __init__(self, name, age, gender):
              self.name = name
              self.age = age
              self.gender = gender

      class Skin(People):
          def __init__(self, name, age, gender, color):
              super().__init__(name, age, gender)
              self.color = color

      class Address(People):
          def __init__(self, name, age, gender, address):
              super().__init__(name, age, gender)
              self.address = address


      person_skin1 = Skin('oliver', 25, 'male', 'yellow')
      person_address1 = Address('kevin', 15, 'female', 'asia')
      print(person_address1)
      print(person_skin1)
      print(person_address1.__dict__)
      print(person_skin1.__dict__)
      print(person_address1.address)
      print(person_skin1.color)

派生功能前瞻

      class Myclass(list):
          def append(self,args):
              if args == 123:
                  print('数字123不能添加')
                  return
              super(Myclass, self).append(args)

      obj1 = Myclass()
      obj1.append(1234)
      print(obj1)
      obj1.append(12345)
      print(obj1)
      obj1.append(123456)
      print(obj1)
      obj1.append(123)
      print(obj1)

派生的实际应用

      import datetime
      import json


      class MyJsonEncoder(json.JSONEncoder):
          def default(self, o):  # 将不能序列化的数据类型转换为可以被数列化的类型
              if isinstance(o, datetime.datetime):
                  return o.strftime('%Y-%m-%d %X')
              elif isinstance(o, datetime.date):
                  return o.strftime('%Y-%m-%d')
              return super().default(o)  # 调用父类的default方法


      d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
      res = json.dumps(d1, cls=MyJsonEncoder)
      print(res)
'''
TypeError: Object of type 'datetime' is not JSON serializable
json不能序列化python所有的数据类型 只能是一些基本数据类型
    json.JSONEncoder
    
1.手动将不能序列化的类型先转字符串
    {'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}
2.研究json源码并重写序列化方法
    研究源码发现报错的方法叫default
        raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)
3.我们可以写一个类继承JSONEncoder然后重写default方法
'''

封装

封装的含义

 将类中的某些名字隐藏起来,外界不可以直接调用,隐藏的目的是为了提供专门的通道(接口)去访问,可以在通道内添加额外的功能

      class Student(object):
          school = 'HAUT'
          __grade = 'six'  # '_Student__grade': 'six'

          def __init__(self, name, age):
              self.name = name
              self.age = age

          def check_grade(self):
              print('%s的年级是%s' % (self.name, self.__grade))


      stu1 = Student('oliver', 25)
      # print(stu1.__grade)  # 'Student' object has no attribute '__grade'
      stu1.check_grade()  # oliver的年级是six
  • 将数据隐藏起来就限制了类外部对数据的直接操作
      然后类内应该提供相应的接口来允许类外部间接地操作数据
      接口之上可以附加额外的逻辑来对数据的操作进行严格地控制

  • 目的的是为了隔离复杂度

  • 例如ATM程序的取款功能,该功能有很多其他功能组成

  • 比如插卡、身份认证、输入金额、打印小票、取钱等,对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来

property 伪装

  • 目的: 将方法伪装成数据
      我们已经学习了对象是数据与方法绑定的结合体。有时候很多数据需要经过计算才可以获得, 此时我们就可以直接将该方法封装在对象内部。但是这些数据给我们的感觉应该属于数据而不是功能

  • 示例:BMI指数>>>:应该属于人的数据而不是人的功能

class Person(object):
    def __init__(self, name, height, weight):
        self.__name = name
        self.height = height
        self.weight = weight
    # @property  # 装饰器语法糖结构,将方法伪装成数据
    def BMI(self):
        # print('%s的BMI指数是:%s' % (self.name, self.weight / (self.height ** 2)))
        return '%s的BMI指数是:%s' % (self.__name, self.weight / (self.height ** 2))


person1 = Person('olive', 1.80, 80)
print(person1.BMI())  # olive的BMI指数是:24.691358024691358
# print(person1.BMI)  # olive的BMI指数是:24.691358024691358

多态

多态性

  # 不完全多态
      class Animal(object):
          def bark(self):
              print('动物的叫')

      class Cat(Animal):
          def miao(self):
              print('猫叫')

      class Dog(Animal):
          def wang(self):
              print('狗叫')

      dog1 = Dog()
      cat1 = Cat()
      dog1.wang()
      dog1.bark()
      cat1.miao()
      cat1.bark()

  # 完全多态
      class Animal(object):
          def bark(self):
              print('动物的叫')

      class Cat(Animal):
          def bark(self):
              print('猫叫')

      class Dog(Animal):
          def bark(self):
              print('狗叫')

      dog1 = Dog()
      cat1 = Cat()
      Dog.bark(dog1)  # 狗叫
      Cat.bark(cat1)  # 猫叫
      dog1.bark()  # 狗叫
      dog1.bark()  # 狗叫
      cat1.bark()  # 猫叫
      cat1.bark()  # 猫叫
  • 类似于len关键字在数据类型里的作用
  • 多态性的好处在于增强了程序的灵活性和可扩展性
      比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.speak()

强制性的措施来实现多态性

      import abc


      # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
      class Animal(metaclass=abc.ABCMeta):
          @abc.abstractmethod  # 该装饰器限制子类必须定义有一个名为talk的方法
          def talk(self):  # 抽象方法中无需实现具体的功能
              pass


      class Person(Animal):  # 但凡继承Animal的子类都必须遵循Animal规定的标准
          def talk(self):
              pass


      p1 = Person()  # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化

鸭子类型

  • 由多态性衍生出一个鸭子类型理论
      只要你看着像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子!!!

  • 在linux系统中有一句话>>>:一切皆文件!!!
      内存可以存取数据
      硬盘可以存取数据...

      class Memory(object):
          def read(self):
              pass

          def write(self):
              pass


      class Disk(object):
          def read(self):
              pass

          def write(self):
              pass

得到内存或者硬盘对象之后 只要想读取数据就调用read 想写入数据就调用write 不需要考虑具体的对象是谁

反射

定义

 程序可以访问、检测和修改本身状态或者行为的一种能力。其实就是通过字符串操作对象的数据和功能

方法

    hasattr():判断对象是否含有字符串对应的数据或者功能
    getattr():根据字符串获取对应的变量名或者函数名
    setattr():根据字符串给对象设置键值对(名称空间中的名字)
    delattr():根据字符串删除对象对应的键值对(名称空间中的名字)

应用

      class Student(object):
          school = 'HAUT'
          def get(self):
              pass

      stu1 = Student()
      print(stu1.__dict__)
      print(Student.__dict__)  # {'__module__': '__main__', 'school': 'HAUT', 'get': <function Student.get at 0x000001E2F02F6820>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
      want_name = input('want_name>>>>: ').strip()  # 字符串 school
      print(hasattr(Student, want_name))  # True
      print(hasattr(Student, want_name))  # True
      print(getattr(Student, want_name))  # HAUT
      print(getattr(Student, want_name))  # HAUT


      want_name = input('want_name>>>>: ').strip()  # 字符串 get
      print(hasattr(Student, want_name))  # True
      print(hasattr(Student, want_name))  # True
      print(getattr(Student, want_name))  # <function Student.get at 0x000002A38FA96700>
      print(getattr(Student, want_name))  # <function Student.get at 0x000002A38FA96700>

      guess_name = input('请输入你想要查找的名字>>>:').strip()
      if hasattr(Student, guess_name):
          target_name = getattr(Student, guess_name)
          if callable(target_name):
              print('类中有一个功能名字是%s'%guess_name,target_name)
          else:
              print('类中有一个数据名字是%s'%guess_name,target_name)
      else:
          print('类中没有该名字')

对象 和 字符串(用户输入、自定义、指定) 那么肯定用反射

案例

      import settings
      dir(settings)  # 获取对象中所有可以使用的名字
      getattr(settings, 'NAME')


      class FtpServer:
           def serve_forever(self):
               while True:
                   inp=input('input your cmd>>: ').strip()
                   cmd,file=inp.split()
                   if hasattr(self,cmd): # 根据用户输入的cmd,判断对象self有无对应的方法属性
                       func=getattr(self,cmd) # 根据字符串cmd,获取对象self对应的方法属性
                       func(file)
           def get(self,file):
               print('Downloading %s...' %file)
           def put(self,file):
               print('Uploading %s...' %file)

      obj = FtpServer()
      obj.serve_forever()


这篇关于面向对象的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程