面向对象进阶

一、类的继承

1、1 什么是继承

  • 继承是一种新建类的方式,新建的类称为子类,被继承的类称为父类

  • 继承的特性是:子类会遗传父类的属性

  • 继承是类与类之间的关系

1、2 为什么用继承

  • 使用继承可以减少代码的冗余

  • 属性的查找顺序:先找对象——>类中找——>父类中找(多继承)——>报错

1、3对象的继承

  • Python中支持一个类同时继承多个父类

  •  

  • 使用__bases__方法可以获得对象继承的类

  •  

  • 在Python3中如果一个类没有继承任何类,则默认继承object类

  • 在Python2中如果一个类没有继承任何类,不会继承object类

1、4 类的分类

1、4、1 新式类/子类

  • 继承了object的类以及该类的子类,都是新式类

  • Python3中所有的类都是新式类

1、4、2 经典类/派生类

  • 没有继承object的类以及该类的子类,都是经典类

  • 只有Python2中才有经典类

1、5 重用父类方法

1、5、1 指名道姓法(跟继承无关)

class Person(object):
   def __init__(self,name,age):
       self.name=name
       self.age=age


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

class Student:
   school = 'yyyy'
   def __init__(self,name,age,course):
       #指名道姓的使用Person的__init__方法
       # Person.__init__(self,name,age)
       # init_1(self,name,age)
       self.course=course

stu=Student('nick',19,'python')
print(stu.name)

 

1、5、2通过super关键字(跟继承有关)

class Person(object):
   def __init__(self,name,age):
       self.name=name
       self.age=age

class Student(Person):
   school = 'yyyy'
   def __init__(self,name,age,course):
       #super()相当于得到了一个特殊对象,第一个参数不需要传,调用绑定方法,会把自己传过去
       ##########   self不需要传,不需要传
       # super().__init__(name,age)
       #看到别人这么写:super(类名,对象) 在py3中为了兼容py2
       #在py3中这么写和省略写法完全一样
       #在py2中必须super(Student,self)写
       super(Student,self).__init__(name,age)
       self.course=course

stu=Student('nick',19,'python')
print(stu.name)
print(stu.age)
print(stu.course)

 

1、6 super的使用方法

  • 如果继承了多个父类,super是按照mro列表找,现在想指名道姓的用某个父类的某个方法,就需要指名道姓的使用

二、组合

2、1什么是组合

  • 对象的属性是另一个类的对象

2、2为什么用组合

  • 通过组合减少代码冗余

2、3组合的使用方法

  • 需求:假如我们需要给学生添加课程属性,但是又不是所有学生一进学校就有了课程属性,课程属性是学生来到学校后选出来的,也就是说课程需要后期学生们添加进去的

  • 实现思路:如果我们直接在学生中添加课程属性,那么学生刚被定义就需要添加课程属性,这就不符合我们的要求,因此我们可以使用组合能让学生未来以添加课程属性

    class Person:
       school = 'oldboy'

    class Teacher(Person):
       def __init__(self, name, age, level, course):
           self.name = name
           self.age = age
           self.level =level
           #course是课程对象,表示老师教授的课程
           self.course = course

    class Student(Person):
       def __init__(self, name, age):
           self.name = name
           self.age = age
           # course是课程对象,表示学生选的课程
           self.course_list = []
       def choose_course(self, course):
           #把课程对象追加到学生选课的列表中
           self.course_list.append(course)
       def tell_all_course(self):
           #循环打印学生选课列表,每次打印一个
           for course in self.course_list:
               print(course.name)
    class Course:
       def __init__(self, course_name, course_price, course_period):
           self.name = course_name
           self.price = course_price
           self.period = course_period

    course = Course('python', 20199, 7)
    stu1 = Student('hanyi', 21)
    stu1.choose_course(course)
    stu2 =Student('cheng', 20)
    stu2.choose_course(course)
    stu2.choose_course(Course('linux', 19999, 5))
    1. 查看学生选的所有课程名称方式一(通过普通函数)

      def tell_all_course(student):
         for course in student.course_list:
             print(course.name)
      tell_all_course(stu1)
      tell_all_course(stu2)

       

    2. 查看学生选的所有课程名称方式二(通过对象的判定方法)

      stu1.tell_all_course()
      stu2.tell_all_course()

       

三、多态和多态性

3、1 什么是多态

  • 一类实物的多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承)

  • 多态的用处:

    1. 增加了程序的灵活性

    2. 增加了程序的可扩展性

  • 多态基础

    class Animal:
       def speak(self):
           pass
    class Pig(Animal):
       def speak(self):
           print('哼哼')

    class Dog(Animal):
       def speak(self):
           print('汪汪')

    class People(Animal):
       def speak(self):
           print('say hello')

    pig = Pig()
    dog = Dog()
    people = People()
    pig.speak()
    dog.speak()
    people.speak()

     

3、2 什么是多态性(多态性和多态是两个概念)

  • 多态性指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名来调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现。即执行不同的函数。

  • 多态性的使用

    1. 第一种方式:用abc实现接口统一化,约束代码(用的比较少)

      import abc
      #第一在括号内写metaclass=abc.ABCMeta
      class Animal(metaclass=abc.ABCMeta):
         #第二在要约束的方法上,写@abc.abstractmethod
         @abc.abstractmethod
         def speak(self):
             pass
      class Pig(Animal):
         def speak(self):
             print('哼哼')

      class Dog(Animal):
         def speak(self):
             print('汪汪')

      class People(Animal):
         def speak(self):
             print('say hello')

      pig = Pig()
      dog = Dog()
      people = People()
      pig.speak()
      dog.speak()
      people.speak()

      注意:这种方法约束了子类必须要写def speak(self):,不然会报错。

    2. 第二种方式:用异常处理来实现(常用)

      class Animal:
         def speak(self):
             #主动抛出异常
             raise Exception('子类必须重写这个方法')
             pass
      class Pig(Animal):
         def speak(self):
             print('哼哼')

      class Dog(Animal):
         def speak(self):
             print('汪汪')

      class People(Animal):
        #错误示范,这个下面必须写speak方法
         def zz(self):
             print('say hello')

       

    3. 鸭子类型:只要走路像鸭子(对象中某个绑定方法),那你就是鸭子

      class Pig:
         def speak(self):
             print('哼哼')

      class Dog:
         def speak(self):
             print('汪汪')

      class People:
         def zz(self):
             print('say hello')


      pig = Pig()
      dog = Dog()
      people = People()
      def animal_speak(obj):
         obj.speak()
      animal_speak(pig)
      animal_speak(dog)

       

  • 文件的多态性

    1. 传统写法

      class File:
         def read(self):
             pass
         def write(self):
             pass
      #内存类
      class Memory(File):
         def read(self):
             print('Memory....read')
         def write(self):
             print('Memory....write')
      class NetWork(File):
         def read(self):
             print('NetWork....read')
         def write(self):
             print('NetWork....write')

       

    2. 鸭子类型写法

      class Memory():
         def read(self):
             print('Memory....read')
         def write(self):
             print('Memory....write')
      class NetWork():
         def read(self):
             print('NetWork....read')
         def write(self):
             print('NetWork....write')
      def read(obj):
         obj.read()
      m = Memory()
      n = NetWork()
      read(m)
      read(n)

       

四、封装

4、1 什么是封装

  • 封装就好像是拿来一个麻袋,把小猫、小狗、小王八,一起装进去,然后把麻袋封上口

4、2 为什么要封装

  • 封装数据的主要的原因是:保护隐私

  • 封装方法的主要原因是:隔离复杂度

4、3 两个层面的封装

  • 封装其实分为两个层面,但无论那种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需切不能够直接访问到内部隐藏的死结,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制是使用者的访问)

    4、3、1 第一个层面

    • 第一个层面的封装(什么都不做):创建类和对象会分别创建二者的名称空间,我们只能用l类名或者obj的方式去访问里面的名字,这也是封装。

      注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口

    4、3、2 第二个层面

    • 第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)共外部访问。

      • 在python中用双下划线的方式实现隐藏属性(设置成私有)

      • 类中所有双下划线开头的名称,例如__x都会自动变形成:_类名__x的形式:

        隐藏属性:通过 __变量名来隐藏;隐藏方法:通过 __方法名来隐藏

        class Person:
           def __init__(self,name,age):
               self.__name=name
               self.__age=age
           def __speak(self):
               print('6666')

        这种自动变形的特点:

        1. 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

        2. 这种变形其实正是针对内部的变形,在外部是无法通过__这个名字访问到的。

        3. 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了_夫类名__x,即双下划线开头的属性在继承给子类时,子类时无法覆盖的。

          注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它的内部访问被隐藏的属性,然后我们在外部也就可以使用了

          我们需要注意的是这种机制并没有真正意义上限制我们从外部直接访问属性,知道类名和属性名我们也能够拼出名字:_类名__属性,这样我们也能够访问了。

    • property装饰器:把方法包装成数据属性

      class Person:
         def __init__(self,name,height,weight):
             self.name=name
             self.height=height
             self.weight=weight
         @property
         def bmi(self):
             return self.weight/(self.height**2)
             # return self.weight/(self.height*self.height)

       

    • property之setter和deleter

      class Person:
         def __init__(self,name,height,weight):
             self.__name=name
             self.__height=height
             self.__weight=weight
         @property
         def name(self):
             return '[我的名字是:%s]'%self.__name
         #用property装饰的方法名.setter
         @name.setter
         def name(self,new_name):
             # if not isinstance(new_name,str):
             if type(new_name) is not str:
                 raise Exception('改不了')
             if new_name.startswith('sb'):
                 raise Exception('不能以sb开头')
             self.__name=new_name

         # 用property装饰的方法名.deleter
         @name.deleter
         def name(self):
             # raise Exception('不能删')
             print('删除成功')
             # del self.__name

五、类的property特性

5、1 什么是 property特性

  • property装饰器用于将被装饰的方法伪装成一个数据属性,在使用时可以不用加括号而直接使用

# ############### 定义 ###############
class Foo:
   def func(self):
       pass

   # 定义property属性
   @property
   def prop(self):
       pass


# ############### 调用 ###############
foo_obj = Foo()
foo_obj.func()  # 调用实例方法
foo_obj.prop  # 调用property属性

如下的例子用于说明如何定一个简单的property属性:

class Goods(object):
   @property
   def size(self):
       return 100


g = Goods()
print(g.size)
100

property属性的定义和调用要注意一下几点:

1. 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数

2. 调用时,无需括号

5、1 简单示例

对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:

  1. 根据用户请求的当前页和总数据条数计算出 m 和 n

  2. 根据m 和 n 去数据库中请求数据

# ############### 定义 ###############
class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val


# ############### 调用 ###############
p = Pager(1)
print(p.start)  # 就是起始值,即:m
0
print(p.end)  # 就是结束值,即:n
10

从上述可见Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。

5、3 property属性的两种方式

  1. 装饰器 即:在方法上应用装饰器(推荐使用)

  2. 类属性 即:在类中定义值为property对象的类属性(Python2历史遗留)

5.3.1 装饰器

在类的实例方法上应用 @property 装饰器

Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类 )

经典类,具有一种 @property 装饰器:

# ############### 定义 ###############
class Goods:
  @property
  def price(self):
      return "laowang"


# ############### 调用 ###############
obj = Goods()
result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
print(result)
laowang

新式类,具有三种 @property 装饰器:

#coding=utf-8
# ############### 定义 ###############
class Goods:
   """python3中默认继承object类
      以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter @xxx.deleter
  """

   @property
   def price(self):
       print('@property')

   @price.setter
   def price(self, value):
       print('@price.setter')

   @price.deleter
   def price(self):
       print('@price.deleter')


# ############### 调用 ###############
obj = Goods()
obj.price  # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
@property
obj.price = 123  # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
@price.setter
del obj.price  # 自动执行 @price.deleter 修饰的 price 方法
@price.deleter

注意:

  • 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法

  • 新式类中的属性有三种访问方式,并分别对应了三个被 @property、@方法名.setter、@方法名.deleter 修饰的方法

由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Goods(object):
   def __init__(self):
       # 原价
       self.original_price = 100
       # 折扣
       self.discount = 0.8

   @property
   def price(self):
       # 实际价格 = 原价 * 折扣
       new_price = self.original_price * self.discount
       return new_price

   @price.setter
   def price(self, value):
       self.original_price = value

   @price.deleter
   def price(self):
       print('del')
       del self.original_price


obj = Goods()
print(obj.price)  # 获取商品价格
80.0
obj.price = 200  # 修改商品原价
print(obj.price)
160.0
del obj.price  # 删除商品原价
del

5.3.2 类属性方式

创建值为property对象的类属性

注意:当使用类属性的方式创建property属性时,经典类和新式类无区别

class Foo:
  def get_bar(self):
      return 'laowang'

  BAR = property(get_bar)


obj = Foo()
reuslt = obj.BAR # 自动调用get_bar方法,并获取方法的返回值
print(reuslt)
laowang

property方法中有个四个参数

  1. 第一个参数是方法名,调用 对象.属性 时自动触发执行方法

  2. 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法

  3. 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法

  4. 第四个参数是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

#coding=utf-8
class Foo(object):
   def get_bar(self):
       print("getter...")
       return 'laowang'

   def set_bar(self, value):
       """必须两个参数"""
       print("setter...")
       return 'set value' + value

   def del_bar(self):
       print("deleter...")
       return 'laowang'

   BAR = property(get_bar, set_bar, del_bar, "description...")


obj = Foo()

obj.BAR  # 自动调用第一个参数中定义的方法:get_bar
getter...





'laowang'
obj.BAR = "alex"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
setter...
desc = Foo.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
print(desc)
description...
del obj.BAR  # 自动调用第三个参数中定义的方法:del_bar方法
deleter...

由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Goods(object):
   def __init__(self):
       # 原价
       self.original_price = 100
       # 折扣
       self.discount = 0.8

   def get_price(self):
       # 实际价格 = 原价 * 折扣
       new_price = self.original_price * self.discount
       return new_price

   def set_price(self, value):
       self.original_price = value

   def del_price(self):
       del self.original_price

   PRICE = property(get_price, set_price, del_price, '价格属性描述...')


obj = Goods()
obj.PRICE  # 获取商品价格
80.0
obj.PRICE = 200  # 修改商品原价
print(obj.PRICE)
160.0
del obj.PRICE  # 删除商品原价

综上所述:

  • 定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方式针对经典类和新式类又有所不同。

  • 通过使用property属性,能够简化调用者在获取数据的流程

5、4 property+类的封装

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

   @property  # 查看obj.name
   def name(self):
       return '<名字是:%s>' % self.__name


peo1 = People('nick')
print(peo1.name)
<名字是:nick>
try:
   peo1.name = 'EGON'
except Exception as e:
   print(e)
can't set attribute

5、5 应用

5.5.1 私有属性添加getter和setter方法

class Money(object):
  def __init__(self):
      self.__money = 0

  def getMoney(self):
      return self.__money

  def setMoney(self, value):
      if isinstance(value, int):
          self.__money = value
      else:
          print("error:不是整型数字")

5.5.2 使用property升级getter和setter方法

class Money(object):
  def __init__(self):
      self.__money = 0

  def getMoney(self):
      return self.__money

  def setMoney(self, value):
      if isinstance(value, int):
          self.__money = value
      else:
          print("error:不是整型数字")

  # 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
  money = property(getMoney, setMoney)


a = Money()
a.money = 100 # 调用setMoney方法
print(a.money) # 调用getMoney方法
100

5.5.3 使用property取代getter和setter方法

重新实现一个属性的设置和读取方法,可做边界判定

class Money(object):
  def __init__(self):
      self.__money = 0

  # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
  @property
  def money(self):
      return self.__money

  # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
  @money.setter
  def money(self, value):
      if isinstance(value, int):
          self.__money = value
      else:
          print("error:不是整型数字")


a = Money()
a.money = 100
print(a.money)
100

5、6 练习

计算圆的周长和面积

import math


class Circle:
   def __init__(self, radius):  # 圆的半径radius
       self.radius = radius

   @property
   def area(self):
       return math.pi * self.radius**2  # 计算面积

   @property
   def perimeter(self):
       return 2 * math.pi * self.radius  # 计算周长


c = Circle(10)
print(c.radius)
10
print(c.area)  # 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
314.1592653589793
print(c.perimeter)  # 同上
62.83185307179586

六、类和对象的绑定方法及非绑定方法

类中定义的方法大致可以分为两类:绑定方法和非绑定方法。其中绑定方法又可以分为绑定到对象的方法和绑定到类的方法。

6、1 绑定方法

6.1.1 对象的绑定方法

在类中没有被任何装饰器修饰的方法就是 绑定到对象的方法,这类方法专门为对象定制。

class Person:
   country = "China"

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

   def speak(self):
       print(self.name + ', ' + str(self.age))


p = Person('Kitty', 18)
print(p.__dict__)
{'name': 'Kitty', 'age': 18}
print(Person.__dict__['speak'])
<function Person.speak at 0x10f0dd268>

speak即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。

通过对象调用绑定到对象的方法,会有一个自动传值的过程,即自动将当前对象传递给方法的第一个参数(self,一般都叫self,也可以写成别的名称);若是使用类调用,则第一个参数需要手动传值。

p = Person('Kitty', 18)
p.speak()  # 通过对象调用
Kitty, 18
Person.speak(p)  # 通过类调用
Kitty, 18

6.1.2 类的绑定方法

类中使用 @classmethod 修饰的方法就是绑定到类的方法。这类方法专门为类定制。通过类名调用绑定到类的方法时,会将类本身当做参数传给类方法的第一个参数。

class Operate_database():
    host = '192.168.0.5'
    port = '3306'
    user = 'abc'
    password = '123456'

    @classmethod
    def connect(cls):  # 约定俗成第一个参数名为cls,也可以定义为其他参数名
        print(cls)
        print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)


Operate_database.connect()
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456

通过对象也可以调用,只是默认传递的第一个参数还是这个对象对应的类。

Operate_database().connect()  # 输出结果一致
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456

6、2 非绑定方法

在类内部使用 @staticmethod 修饰的方法即为非绑定方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。

import hashlib


class Operate_database():
  def __init__(self, host, port, user, password):
      self.host = host
      self.port = port
      self.user = user
      self.password = password

  @staticmethod
  def get_passwrod(salt, password):
      m = hashlib.md5(salt.encode('utf-8')) # 加盐处理
      m.update(password.encode('utf-8'))
      return m.hexdigest()


hash_password = Operate_database.get_passwrod('lala', '123456') # 通过类来调用
print(hash_password)
f7a1cc409ed6f51058c2b4a94a7e1956
p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
hash_password = p.get_passwrod(p.user, p.password) # 也可以通过对象调用
print(hash_password)
0659c7992e268962384eb17fafe88364

简而言之,非绑定方法就是将普通方法放到了类的内部。

6、3 练习

假设我们现在有一个需求,需要让Mysql实例化出的对象可以从文件settings.py中读取数据。

# settings.py
IP = '1.1.1.10'
PORT = 3306
NET = 27
# test.py
import uuid


class Mysql:
   def __init__(self, ip, port, net):
       self.uid = self.create_uid()
       self.ip = ip
       self.port = port
       self.net = net

   def tell_info(self):
       """查看ip地址和端口号"""
       print('%s:%s' % (self.ip, self.port))

   @classmethod
   def from_conf(cls):
       return cls(IP, NET, PORT)

   @staticmethod
   def func(x, y):
       print('不与任何人绑定')

   @staticmethod
   def create_uid():
       """随机生成一个字符串"""
       return uuid.uuid1()


# 默认的实例化方式:类名()
obj = Mysql('10.10.0.9', 3307, 27)
obj.tell_info()
10.10.0.9:3307

6.3.1 绑定方法小结

如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法

如果函数体代码需要用外部传入的对象,则应该将该函数定义成绑定给对象的方法

# 一种新的实例化方式:从配置文件中读取配置完成实例化
obj1 = Mysql.from_conf()
obj1.tell_info()
1.1.1.10:27
print(obj.tell_info)
<bound method Mysql.tell_info of <__main__.Mysql object at 0x10f469240>>
print(obj.from_conf)
<bound method Mysql.from_conf of <class '__main__.Mysql'>>

6.3.2 非绑定方法小结

如果函数体代码既不需要外部传入的类也不需要外部传入的对象,则应该将该函数定义成非绑定方法/普通函数

obj.func(1, 2)
不与任何人绑定
Mysql.func(3, 4)
不与任何人绑定
print(obj.func)
<function Mysql.func at 0x10f10e620>
print(Mysql.func)
<function Mysql.func at 0x10f10e620>
print(obj.uid)
a78489ec-92a3-11e9-b4d7-acde48001122

 

 

原文地址:https://www.cnblogs.com/hanyi12/p/11575532.html