python面向对象

面向对象三大特性

1、封装 根据职责属性方法封装到一个抽象的类内
2、继承 主要是为了实现代码的重用,相同的代码不需要重复的编写
3、多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
python中一切皆对象,包括变量,数据,函数等等内容

面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了
查看一个对象的方法可以使用以下两种方法:

  • 在ipython中,在标识符/数据后输入一个. ,然后按下tab键会提示可用方法,此方法有局限性
  • 使用内置函数dir传入标识符/数据,即可显示出所有属性和方法,包括python内置的方法

定义类

使用class关键字定义类,注意类名命名规则要符合大驼峰命名法,非必须,但是默认如此

引用在面向对象中的应用

  • 在python中使用类创建对象后,对象名中记录的是创建出来对象的内存地址,也就是使用了一个变量名引用了新建的对象
  • 使用print函数,传入对象名称,可以得到这个变量引用的对象是由哪个类创建的对象,以及他在内存中的地址(是以十六进制来表示的)
  • %d 可以输出十进制的数字
  • %x 可以输出十六进制的数字

self

python中给对象设置属性,虽然可以通过对象.属性名 = 值 的方式直接设置,但是并不推荐使用

  • self的使用
    • 定义类的方法时,需要设置self参数
    • 由哪一个对象调用了此方法,那么self就是哪一个对象的引用,self就等同于创建的对象名字
    • 调用方法时,无需传递此参数,会自动将对象的引用传入
    • 方法内部,可以通过self. 访问对象的属性,也可以通过self. 调用其他的对象方法

初始化方法

  • 在日常开发中,不应使用在类外部给对象增加属性,因为一旦找不到程序会报错
  • 对象包含的属性,应当在类中通过__init__ 内置方法来设置
  • 当创建对象时,会自动为对象在内存中分配空间,并未对象的属性设置初始值,就输init方法中定义的属性
  • init方法是专门用来定一个类具有哪些属性的方法

两个内置方法和属性

del方法

  • 对象的生命周期
    • 一个对象使用类名创建时,生命周期开始
    • 一个对象的del方法一旦被调用,生命周期结束
    • 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
  • 一个对象被从内存中销毁前,会自动调用del方法,创建多个对象,被销毁前,均会调用此方法,并且输出顺序是计算机

str方法

  • 在python中,使用print输出对象变量,默认情况下,会输出这个变量引用的对象是由哪个类创建的对象,以及在内存中的地址(十六进制)
  • 使用str方法,可以是打印出的内容是是此方法的返回值,另外str方法必须返回一个字符串

封装

  • 将属性和方法封装到一个抽象的类中,外界通过类创建对象,然后让对象来调用类中的方法,对象方法的细节都被封装在类的内部

  • 在对象的方法内,是可以直接使用对象的属性的

  • 同一个类,创建的多个对象,都占有独立的内存空间,属性方法之间互不干扰
  • 在开发过程中,对抽象出来的类,优先开发需要被使用的类,以方便定义其他类的时候可以直接调用这些类
  • 一个对象的属性,可以是另一个类创建的对象,创建此类属性时,可以先将其值设置为None,然后将另一个类创建的对象赋值给此属性,然后在定义的方法中将此对象传入,并调用他的方法,实现在一个类中调用另一个类的方法
  • 在定义属性时,如果不知道设置什么初始值,可以设置为None,表示什么都没有,一个空对象,没有方法和属性,时一个特殊的常量,可以将企赋值给任何一个变量
  • %s格式化字符串,可以接收任意类型的数据(比如列表,数字等),然后将其转换为字符串

身份运算符

身份运算符用于比较两个对象的内存地址是否一致,即是否是对同一个对象的引用
is is是判断两个标识符是否引用了同一个对象
is not 判断两个标识符是否引用不同对象
在python中针对None比较时,建议使用is判断,而非==

is 与 == 的区别

  • is 是判断两个变量引用对象是否为同一个
  • == 用于判断引用变量的值是否相等

继承

单继承

  • 子类拥有父类的所有属性方法(包括init,类属性,类方法,静态方法等,除了私有属性,私有方法无法继承)
  • 子类应根据职责,封装子类特有的属性和方法

方法的重写

  • 当父类的方法无法满足子类的需求时,可以对继承的方法进行重写(override),和父类方法的名称一致
  • 重写有两种情况:
    • 直接覆盖父类方法
      • 在子类中重新编写父类的方法实现,实现方式,就相当于在子类中定一个了一个和父类同名的方法并实现
    • 扩展父类的方法
      • 首先再子类中重写父类的方法
      • 而后使用super().父类方法 来调用父类的方法执行
      • 代码其他位置针对子类的需求,编写子类特有的代码实现
    • super是一个特殊的类,super()是创造出来的对象,用在重写父类方法时,调用父类的方法,实现对父类方法的扩展
  • 调用父类的方法,在python2版本中,使用父类名.方法(self) 实现,python3中依旧支持此方式,但是不要使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改,super()不存在父类名,因此可以避免这种情况的发生
  • 以上两种方式不要混用,另外,如果不小心使用了当前子类名来调用方法,会形成递归调用,出现死循环

父类的私有属性和私有方法的继承问题

  • 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
  • 子类对象可以通过父类的公共方法间接的访问到私有属性和私有方法

多继承

  • 多继承就是子类可以拥有多个父类,并且具有所有父类的方法和属性

多继承的两种方式

  • 第一种,使用(类名.方法)继承,方法中第一个参数需要传入self指向当前的对象,在子类中使用此方法,传入的参数是实参,父类方法中的参数才是形参,注意区别
print("******多继承使用类名.__init__ 发生的状态******")
class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        Parent.__init__(self, name)
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init开始被调用')
        self.gender = gender
        Parent.__init__(self, name)
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        Son1.__init__(self, name, age)  # 单独调用父类的初始化方法
        Son2.__init__(self, name, gender)
        print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
以上代码Parent类中的内容会被执行两次,因为Son1和Son2各自执行了一次

这种方法和在全局使用(类名.方法)调用函数应当是一个性质,假设以上代码的Parent类中有方法为run

gs = Grandson('grandson', 12, '男')
Parent.run(gs)
# 此种方式也是可以的

类对象中保存的都属于类的内存中所有,包括类属性,类方法,静态方法,实例属性,property方法等等都在类对象的内存空间中。
使用类创建出来的对象有单独的空间,初始化时制造的实例属性在各个对象的空间中存储,但是方法是在调用的时候去类对象中执行得到结果,并不会单独存储一份,实例对象中通过__class__指向了创造此对象的类。

  • 第二种,使用super().__init__()继承
    此方法无需传入self参数,注意参数的传递
class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        # super(Grandson, self).__init__(name, age, gender)
        super().__init__(name, age, gender)
        print('Grandson的init结束被调用')

print(Grandson.__mro__)  # 得到继承时的调用顺序
  • super().__init__相对于类名.__init__,在单继承上用法基本无差
  • 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次
  • 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
  • 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
  • 多继承时,相对于使用类名.init方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因

多继承使用注意事项

  • 开发时,应尽量避免在继承的不同的父类中存在同名的方法,容易产生混淆,如果父类之间存在同名的属性或方法,应该尽量避免使用多继承
  • python中针对同名的情况提供了一个内置属性mro 可以查看方法搜索顺序
  • mro指的是method resolution order,用于在多继承时判断方法,属性的调用路径,这个属性是针对子类使用的,子类可以来调用此方法,使用print将结果输出查看
  • 如果在当前类中找到方法,则不再搜索,直接执行,如果找到最后一个还没有找到,就直接报错,程序崩溃

新式类和经典类

object是pthon为所有对象提供的基类,提供了一些内置的方法和属性,可以使用dir函数进行查看

  • 推荐使用新式类,以object为基类
  • 不推荐使用经典类,不以object为基类
  • python3版本中,不写object,会默认使用此类作为基类,python3中全部都是新式类,没有旧式
  • python2版本中,不指定object,就不会以object作为基类
    新式类和经典类在多继承时,会影响到方法的搜索顺序,新式类是广度优先,经典类是深度优先
  • 在定义类时,统一写入object

多态

多态指不同的子类对象调用相同的父类方法,产生不同的执行结果

  • 多态可以增加代码灵活性
  • 多态以继承重写父类方法为前提
  • 是调用方法的技巧,不会影响到类内部的设计

多态更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化

单例

单例设计模式

  • 设计模式
    • 设计模式是前人工作的总结和提炼,广泛使用的设计模式都是针对某一特定问题的成熟的解决方案
    • 使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码的可靠性
  • 单例设计模式
    • 目的是让类创建的对象,在系统中只有唯一一个实例
    • 每一次执行类名() 返回的对象,内存地址是相同的

__new__方法

  • 使用类名创建对象时,会首先调用new方法为对象分配空间
  • __new__ 是一个由object基类提供的内置静态方法,作用有二:
    • 在内存中为对象分配空间
    • 返回对象的引用
  • python解释器获得对象的引用后,将引用作为第一个参数传递给init方法(self)
  • 使用类创建对象时,引用的关系:__new__ 方法的返回值 ——>self——>实例对象的名称

    重写__new__方法的代码非常固定

  • 重写new方法一定要return super().__new__(cls)

  • 否则python的解释器得不到分配的空间引用,就不会调用对象的初始化方法
  • 此方法是一个静态方法,在调用时需要主动传递cls参数

python中的单例

创建步骤:

  • 定义一个类属性,初始值为None,用来存储单例对象的引用
  • 重写__new__ 方法
  • 如果类属性是None,调用父类方法分配空间并在类属性中记录结果
  • 返回类属性中记录的对象引用

只执行一次初始化工作

  • 每次使用类创建对象时,解释器都会自动调用两个方法:
    • __new__方法分配空间,返回对象引用
    • __init__ 对象初始化
  • 让初始化动作只被执行一次的方法
    • 定义一个类属性标记是否执行过初始化动作,初始值为False
    • 在init方法中,判断标记,如果是False,就执行初始化动作,并将标记的值设置为True
    • 再次自动调用init方法时,初始化动作就不会再次被执行了

静态方法的意义

  • 如果有多个类要调用一个函数名相同,但内容却不同的函数(尤其在团队开发中可能存在此种冲突),此时在代码中后写的函数会覆盖之前的,那么这些类中就只能使用一个函数了,此时,放在类中,各用各的,避免了以上的情况发生,这也体现了面向对象的封装性。
展开阅读全文

没有更多推荐了,返回首页