HOME/Articles/

Python中的面向对象

Article Outline

Python中的面向对象

<!--more-->

# Python中的面向对象

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

  • 面向过程:根据业务逻辑从上到下写代码
  • 面向对象:将数据和函数绑定到一起,进行封装,这样能够更快速的进行开发程序,减少重复代码的书写

类和对象的关系

在面向对象的概念中,对象是核心,那么为了将一堆具有相同特征和行为的对象进行抽象定义,就提出了一个概念叫

举个例子:类就相当于小时候组装赛车的图纸,组装出来的赛车就是对象。

类的组成

类名,类的属性(数据),类的方法(对数据操作的行为)

举个例子:

class Person:
    def sayHi(self):
        print('hello')

    def introduce(self):
        print('this is %s speaking'%self.name)

DeeJay = Person()

DeeJay.name = 'DeeJay'

DeeJay.sayHi() # hello
DeeJay.introduce() # this is DeeJay speaking

上面定义了一个Person类,DeeJay是这个类的一个对象。

DeeJay = Person()这一句就是创建了一个Person类的对象,其中Person()就是在内存中创建了一个对象,同时这个语句的返回值是这个对象的引用,将其赋给了DeeJay这个变量。

DeeJay.name = 'DeeJay'这一句就是给DeeJay这个对象增添一个属性name。

sayHi()introduce()都是Person类定义时候的方法。

值得一提的是上面方法定义时的参数self,类比一下JS中构造函数内部的this,很好理解,当然可以不写成self,但是必须保证要有一个形参

另外写一个JS中构造函数创建示例的例子,来进行对比理解:

function Person(name) {
    this.name = name
}
Person.prototype.sayHi = function() {
    console.log('hello')
}
Person.prototype.introduce = function() {
    console.log(`this is ${this.name} speaking`)
}

let DeeJay = new Person('DeeJay')
DeeJay.introduce()

__init__(self)方法

##### 简介

class Person:

    def __init__(self):
        pass

    def introduce(self):
        print('this is %s speaking'%self.name)

DeeJay = Person()

介绍__init__()就得了解对象创建的流程,在上述例子DeeJay = Person()这一句创建对象的语句中,有如下流程:

  1. 在内存中创建了一个对象
  2. Python自动调用__init__(self)方法,此时传入的这个self就是创建的那个对象的引用
  3. 执行__init__()方法中的一些默认属性的设定
  4. 返回创建好的对象的引用,(前4步就是 Person()这个语句做的事)
  5. 将引用赋值给DeeJay
__init__()方法的具体使用举例:
class Person:

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

    def introduce(self):
        print('this is %s speaking'%self.name)

DeeJay = Person('DeeJay',21)

DeeJay.introduce() # this is DeeJay speaking

上述例子中,Person('DeeJay',21)这个语句,在执行到调用init()的时候,self指向的是创建的这个对象,init()方法还可以接受除了self以外的参数,这些参数可以给创建的这个对象进行属性的初始化。

这么一写,就不需要创建好对象之后再进行属性的添加了。

__str__()方法

上面的例子中,如果创建了DeeJay对象之后,使用print(DeeJay)来输出这个对象,得到的是:<__main__.Person object at 0x7f39a0ca5a58>

但是如果我们定义了__str__()方法之后,再进行print对象的时候,就会调用这个方法:

class Person:

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

    def __str__(self):
        return "该对象的name为:%s,age为:%d"%(self.name,self.age)

    def introduce(self):
        print('this is %s speaking'%self.name)

DeeJay = Person('DeeJay',21)

Yang = Person('Yang',22)

# print(DeeJay) # <__main__.Person object at 0x7f39a0ca5a58>
print(DeeJay) # 该对象的name为:DeeJay,age为:21
print(Yang) # 该对象的name为:Yang,age为:22

值得注意的是,__str__()中是输出return的值。

私有属性

如果有一个对象,需要对其属性进行修改时,一般有2种方法:

  • 对象.属性 = 数据 # 直接修改
  • 对象.方法() # 间接修改

一般为了保护对象的属性,即不能随意修改,不建议直接给对象属性直接赋值,一般我们可以采用定义一个方法,来进行给对象添加属性。

class Dog():
    def get_age(self):
        return self.age

wangwang = Dog()
wangwang.age = 10 # 一般不这么写  而是通过定义一个方法来进行添加属性

print(wangwang.get_age())

所以可以写成:

class Dog():
    def __init__(self):
        self.__age = 0 # 定义了一个私有属性 __age

    def set_age(self,new_age):
        if(new_age > 0):
            self.__age = new_age
        else:
            self.__age = 0 # 可以在添加属性的时候,同时处理一些异常值的情况

    def get_age(self):
        return self.__age

wangwang = Dog()
wangwang.set_age(10)

print(wangwang.get_age()) # 10

wangwang.set_age(-10)
print(wangwang.get_age()) # 0

这样就通过方法来设置这个私有属性。注意私有属性前要加__,比如说上述例子中的__age,这样通过wangwang.__age就访问不到这个

私有方法

先来看一个例子:

class Dog:
    def print1(self):
        print('---1----')

    def print2(self):
        print('----2---')


dog = Dog()

dog.print1() # ---1----
dog.print2() # ----2---

Dog这个类创建出来的dog对象,可以使用定义的print1()print2()这两个方法。

现在稍作修改,将print1()改为__print1()

class Dog:
    def __print1(self):
        print('---1----')

    def print2(self):
        print('----2---')


dog = Dog()

dog.print2() # ----2---
dog.__print1() # 报错了 AttributeError: 'Dog' object has no attribute '__print1'

那么__print1()就是一个私有方法,不允许直接让对象调用,只能在其他方法内部进行调用。

举个实际的例子来说明:

class Msg():
    def __init__(self,money):
        self.money = money

    def __sendMsg(self):
        print("发送短信")
        self.money -= 10

    def checkMoneyAndSendMsg(self):
        if(self.money > 10):
            self.__sendMsg() # 调用私有方法 
        else:
            print('余额不够')

message1 = Msg(100)
message1.checkMoneyAndSendMsg() # 发送短信

message2 = Msg(0)
message2.checkMoneyAndSendMsg() # 余额不够

这个Msg类定义了一个私有方法__sendMsg()用来发送短信,但是得先判断money属性是否足够,所有不允许直接使用对象调用,而是给个公有方法,判断之后在该方法里进行self.__sendMsg()来进行调用

### __del__()

创建对象后,Python默认调用__init__()

删除对象时,则会调用__del__()

注意这个删除对象,值得是当前指向这个对象的所有引用都消失的时候,程序会自动调用__del__()

来看例子:

class Test():
    def __del__(self):
        print('I\'m dead!')

t1 = Test()
del t1 # I'm dead!

注意del t1仅仅是将t1对创建的那个对象的引用删除了,如果t1指向的对象同时还在其他地方被引用,那这个对象就没被删除,__del__()不会触发。

可以使用sys模块的sys.getrefcount()方法来判断当前目标有几个引用。

class T:
    pass

t1 = T()

import sys

print(sys.getrefcount(t1)) # 2

# 注意这边输出的是2  因为调用sys.getrefcount(t1) 的时候 将t1也传入了一次
# 所以输出的引用数为 2 

t2 = t1 # 现在将t1的引用复制一份给t2

print(sys.getrefcount(t1)) # 3

del t2

print(sys.getrefcount(t1)) # 2

del t1

print(sys.getrefcount(t1)) # 报错了  NameError: name 't1' is not defined

# 所以这个方法返回值最小就为2

继承

先来看一下最简单的一个继承:

class Animal():
    def eat(self):
        print('eating...')


class Dog(Animal): # Dog类继承Animal类
    def bark(self):
        print('wang wang')



wangcai = Dog()
wangcai.eat() # eating...

class Dog(Animal): 就实现了继承

子类可以继承父类(以及父类的父类等)的属性和方法

重写

子类可以在内部重新定义继承来的方法 称为重写

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

class Husky(Dog):
    def bark(self):
        print('哈士奇在汪汪汪')


h = Husky()
h.bark() # 哈士奇在汪汪汪

这样就实现了重写,但是会有这样一个需求,子类在重写的方法中还想调用父类中被重写的方法,这时候有2种方法:

第一种:

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

class Husky(Dog):
    def bark(self):
        print('哈士奇在汪汪汪')
        # 第一种方式
        Dog.bark(self)


h = Husky()
# 想在h.bark()中调用Dog类中被重写的bark()
h.bark() # 哈士奇在汪汪汪 汪汪汪

通过在子类的bark()中写上Dog.bark(self)即可调用,注意self不可省略

第二种,使用super()

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

class Husky(Dog):
    def bark(self):
        print('哈士奇在汪汪汪')
        # 第二种方式
        super().bark()


h = Husky()
# 想在h.bark()中调用Dog类中被重写的bark()
h.bark() # 哈士奇在汪汪汪 汪汪汪

直接写super().bark()即可

私有属性和私有变量的继承机制

来介绍一下私有属性和方法的继承

class A:
    def __init__(self):
        self.__privateProperty = 'private'
        self.publicProperty = 'public'

    def __privateFn(self):
        print('privateFn')

    def publicFn(self):
        print('publicFn')

class B(A):
    pass

b = B()

b.publicFn() # publicFn
print(b.publicProperty) # public

b.__privateFn() # 报错 AttributeError: 'B' object has no attribute '__privateFn'
print(b.__privateProperty) # 报错 AttributeError: 'B' object has no attribute '__privateProperty'

通过例子我们可以看出,私有属性和私有方法,是不可以直接被继承的

但是也有例外,我们如果定义一个共有方法,在其中使用私有方法和属性的话,这个是允许的。

class A:
    def __init__(self):
        self.__privateProperty = 'private'
        self.publicProperty = 'public'

    def __privateFn(self):
        print('privateFn')

    def publicFn(self):
        print('publicFn')

    def anotherPublicFn(self): #定义一个共有方法来使用私有变量
        print(self.__privateProperty)
        self.__privateFn()

class B(A):
    pass

b = B()
b.anotherPublicFn() # private privateFn

如上述例子所示。

多继承

多继承指的是一个类同时继承多个类,举个例子,骡子继承驴和马

class Horse:
    def horseSay(self):
        print('我是马')

class Donkey:
    def DonkeySay(self):
        print('我是驴')

class Mule(Horse,Donkey): # 实现了多继承
    pass

mule = Mule()
mule.horseSay() # 我是马
mule.DonkeySay() # 我是驴

上述例子,Mule类就实现了对Horse类和Donkey类的多继承。

多继承中的注意点 __mro__

class Base(object):
    def test(self):
        print('Base')

class A(Base):
    def test(self):
        print('A')

class B(Base):
    def test(self):
        print('B')

class C(A,B):
    def test(self):
        print('C')

c = C()
c.test()

现在有如上例子,C类是对A和B多继承,A和B都继承自Base类,其中他们都有一个test方法,现在如果调用c.test(),到底输出什么?

我们可以输出C.__mro__,结果为:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)

输出了一个元祖,同时表明了去寻找方法的路径,即由C->A->B->Base->object依次查找,如果找不到就报错说没有该方法。

所以我们知道上面例子会输出啥,先查找到C中的test,如果C中没定义test(),就去A里面找,以此类推。

__mro__就是调用一个方法的搜索顺序

多态

多态:定义时的类型和运行时的类型不一样,此时就成为多态

class Dog(object):
    def print_self(self):
        print('汪汪汪')

class Husky(Dog):
    def print_self(self):
        print('我是哈士奇')

def introduce(obj): #定义一个函数
    obj.print_self() # 调用传入的obj的print_self()

dog = Dog()
husky = Husky()

introduce(dog) # 汪汪汪
introduce(husky) # 我是哈士奇

上述例子就是一个多态,introduce()方法定义时只知道要调用一个对象的方法,但是至于这个方法是调用父类还是子类,要等到开始运行的时候才能确定。

类属性 实例属性

先来搞清楚什么叫类对象,什么叫实例对象 我们在Python中定义了一个类之后,这个类也是一个对象,被称为类对象, 而通过类创建的对象,为了和类对象区分,称为示例对象。

类属性就是类对象所拥有的属性,它被类对象实例对象所共有,在内存中只存在一个副本

下面的例子就是 类属性

class People(object):
    name = 'DeeJay' # 共有的 类属性
    __age = 21 # 私有的 类属性

p = People()

print(p.name) # DeeJay
print(People.name) # DeeJay

print(p.__age) # AttributeError: 'People' object has no attribute '__age'
print(People.__age) # AttributeError: 'People' object has no attribute '__age'

上述例子中,在定义类的时候,直接给类定义了属性,那么这些属性就是类属性

实例对象中的属性就叫实例属性,来看例子:

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

p = Person('DeeJay')
print(p.name) # DeeJay

上述例子中,并没有在Person类中直接定义属性,而是在创建实例对象的时候,给实例对象内部创建了属性,所以被称为实例属性。

  • 实例对象类对象的区别

那么对于实例属性来讲,它只和具体的实例有关系,并且一个实例对象和另一个实例对象之间是不共享的;

相应的对于类属性来说,类属性所属于类对象,并且多个实例对象之间,共享同一个类属性,另外前面已经提到过,类属性只会定义一次。

对于类属性来讲,一个类创建的所有对象中,都共享这个类中的类属性

class Person(object):
    name = 'DeeJay'


p1 = Person()
p2 = Person()


print(p1.name) # DeeJay
Person.name = 'Yang' # 修改了类属性之后  由于多个实例之间共享 所以所有的实例都改变了
print(p1.name) # Yang
print(p2.name) # Yang

类方法 实例方法 静态方法

先来回顾一下类属性和实例属性以及实例方法:

class Game(object):
    num = 0 # Game类的类属性

    def __init__(self,name): # 这个方法  称为实例方法
        self.name = name # name为创建的实例的属性

rainbowSix = Game('rainbowSix Siege')

上述例子中,num为Game的类属性,__init__(self)是rainbowSix的实例方法,而name是rainbowSix的实例属性

那么 如果想给Game类增加一个类方法,可以这么写:

class Game(object):
    num = 0 # Game类的类属性

    def __init__(self,name): # 这个方法  称为实例方法
        self.name = name # name为创建的实例的属性

    def printName(self): # printName 也是实例方法
        print(self.name)

    @classmethod # 类方法定义使用装饰器
    def get_Game_num(cls): # 形参不写self写cls表示传入的是class的引用
        return cls.num

rainbowSix = Game('rainBowSix Siege')
rainbowSix.printName() # rainBowSix Siege  实例对象调用实例方法
print(Game.get_Game_num()) # 0  类对象调用类方法 

# 值得注意的是,想调用类方法,实例对象也是可以的

print(rainbowSix.get_Game_num()) # 0

通过一个装饰器@classmethod来定义一个类方法

上述例子展示了怎么定义即调用一个类方法,再强调一下,调用类方法有2种方法:

  • 使用类对象直接调用
  • 通过该类创建的实例对象也可以调用类方法

其中不管怎么调用,cls形参指向的还是这个Game类。

接下来再来看静态方法,同样定义也是需要一个@staticmethod装饰器来定义一个静态方法。

不同的是,实例方法接收一个self表示实例对象,类方法接收一个cls参数表示接收一个类对象,而静态方法可以不接收参数(当然也可以接收自定义的一些参数)。

静态方法一般只完成一些基本的功能,和类以及实例都没什么太大的关系

class Game(object):
    num = 0 # Game类的类属性

    def __init__(self,name): # 这个方法  称为实例方法
        self.name = name # name为创建的实例的属性

    def printName(self): # printName 也是实例方法
        print(self.name)

    @classmethod # 定义类方法
    def get_Game_num(cls): # 形参不写self写cls表示传入的是class的引用
        return cls.num

    @staticmethod # 定义静态方法
    def introClass(): # 可以不接受任何参数
        print('这是一个关于游戏的class')


rainbowSix = Game('rainBowSix Siege')
# 关于静态方法的调用  同样也是2种方式 通过类或者是实例对象
rainbowSix.introClass() # 实例对象调用
Game.introClass() # 类对象调用

简单工厂模式

# 使用class中的方法来解耦
class factory:
    def method(self):
        pass

工厂方法模式

# 在基类中定义一些方法 但是不实现 留到子类中重写方法来实现功能
class Store(object:
    def select_car(self):
        pass

    def order(self):
        self.select_car()

class carStore(Store):
    def select_car(self): # 在子类中重写方法来实现功能