了解python之面向对象

面向对象概念:

面向对象编程(Object Oriented Programming,简称OOP)是一种程序涉及思想。OOP把对象作为程序的基本单元,一个对象包含数据和操作数据的函数。面向对象程序设计把计算机程序视为一组对象的集合,每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间的传递。

在python中,所有数据类型都被视为对象,也可以自定义对象。自定义对象数据类型就是面向对象中的类(Class)的概念。

 

面向对象术语介绍:

  • 类:用来描述具有相同属性和方法的对象的集合。类定义了集合中每个对象共有的属性和方法。对象是类的实例。
  • 类变量(属性):类变量在整个实例化的对象中是公用的,类变量定义在类中,且在方法之外,类变量通常不作为实例变量使用。类变量也称为属性。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,就可以对其进行改写,这个过程称为方法的覆盖,也称为方法的重写
  • 实例变量:定义在方法中的变量只作用于当前实例的类。
  • 多态:对不同类的对象使用同样的操作。
  • 封装:对外部世界隐藏对象的工作细节。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承允许把一个派生类的对象作为一个基类对象对待,以普通类为基础建立专门的类对象。
  • 实例化:创建一个类的实例,类的具体对象。
  • 方法:类中定义的函数。
  • 对象:通过类定义的数据结构实例,对象包括两个数据成员(类变量和实例变量)和方法。

python中的类提供了面向对象编程的所有基本功能;类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法、方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。

 

类的定义与使用

1)类的定义:

class MyCLass(object):
  i=123
  def f(self):
     return 'hello world'

从面可以看到类的语法如下:

class ClassName(object):
  <statement-1>
  ...
  <statement-N>

 python中定义类使用class关键词,class后面接类的名字,如上面例子中的MyClass,类的名字通常是大写开头的单词;紧接着是(object),表示MyClass这个类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。类包含属性(相当于函数中的语句)和方法(类中的函数)。

 

2)类的使用

class MyClass(object):
    i=123
    def f(self):
       return 'hello world'
use_class=MyClass()   #类的实例化。即创建一个类的实例,use_class变量称为类的具体对象
print('调用类的属性:',use_class.i)   #调用类的属性,就是前面我们说的类变量
print('调用类的方法:',use_class.f())  #调用类的方法

##输出结果:
调用类的属性: 123
调用类的方法: hello world

上面定义的f()函数可以不用self参数吗?

在类定义方法的要求:在类中定义方法时,第一个参数必须是self。除了第一个参数外,类的方法和普通函数没有区别,可以使用默认参数,可变参数,关键字参数等。

类中调用方法的要求:要调用方法,在实例变量上直接调用即可。除了self不用传递参数,其他参数正常传入。

类对象支持两种操作,即属性引用和实例化。

属性引用的标准语法:

obj.name  # obj代表类对象,name代表属性

深入类

1)类的构造方法

class MyClass(object):
    i=123
    def __init__(self,name):
        self.name=name
    def f(self):
       return 'hello world'

use_class=MyClass('lucky')           #类的实例化。即创建一个类的实例,use_class变量称为类的具体对象
print('调用类的属性:',use_class.i)     #调用类的属性,就是前面我们说的类变量
print('调用类的方法:',use_class.f())  #调用类的方法

##输出结果:
调用类的属性: 123
调用类的方法: hello world

在python中,__init__()方法是一个特殊的方法,在对象实例化时会被调用。__init__()意思是初始化,是initialization的简写。这个方法的书写方式:先输入两个下划线,后面接init,再接着两个下划线。这个方法叫做构造函数。在定义类的时候,若不显示的定义一个__init__()方法,则程序默认会调用一个无参的__init__()方法。

 

比方说下面的代码:

class DefaultInit(object):
    def __init__(self):
        print('在类实例化时执行我,我是__init__方法')
    def show(self):
        print('我是类中定义的方法,需要通过实例化对象调用')

test=DefaultInit()
print('类实例化结束')
test.show()

##输出结果:
在类实例化时执行我,我是__init__方法
类实例化结束
我是类中定义的方法,需要通过实例化对象调用


/

class DefaultInit(object):
    def show(self):
        print('我是类中定义的方法,需要通过实例化对象调用')

test=DefaultInit()
print('类实例化结束')
test.show()

##输出结果:
类实例化结束
我是类中定义的方法,需要通过实例化对象调用

通过上面两段代码可以得出:
在代码中定义了init()方法时,实例化类时会调用该方法;若没有定义init()方法,实例化类时也不会报错,此时调用默认的__init__()方法。在python中定义类时,若没有定义构造方法(__init__()),则在类实例化时系统调用默认的构造方法。另外,__init__()方法可以有参数,参数通过__init__()传递到类的实例化操作上。

 

2)可以在一个类中定义多个构造方法吗?

class DefaultInit(object):
    def __init__(self):
        print('我是不带参数的init方法')

test=DefaultInit()
print('类实例化结束')

##输出结果:
我是不带参数的init方法
类实例化结束


//

class DefaultInit(object):
    def __init__(self):
        print('我是不带参数的init方法')
    def __init__(self,name):
        print('我是带参数的init方法,参数值是:',name)

test=DefaultInit('hello')
print('类实例化结束')

##输出结果:
我是带参数的init方法,参数值是: hello
类实例化结束


/
class DefaultInit(object):
    def __init__(self, name):
        print('我是带参数的init方法,参数值是:', name)
    def __init__(self):
        print('我是不带参数的init方法')

test=DefaultInit()
print('类实例化结束')

##输出结果:
我是不带参数的init方法
类实例化结束

在一个类中可以定义多个构造方法,但是实例化类时只实例化最后的构造方法,即后面的构造方法会覆盖前面的构造方法,并需要根据最后一个构造方法的形式进行实例化。建议一个类中只定义一个构造方法。

类的访问权限

在类的内部有属性和方法,外部代码可以直接通过调用实例变量的方法操作数据,这样就隐藏了内部的复杂逻辑。

例如:

class Student(object):
    def __init__(self,name,score):
        self.name=name
        self.score=score
    def info(self):
        print('学生: %s; 分数: %s' % (self.name,self.score))

stu=Student('lucky',96)
print('修改前的分数:',stu.score)
stu.info()
stu.score=0
print('修改后的分数:',stu.score)
stu.info()


##输出结果:
修改前的分数: 96
学生: lucky; 分数: 96
修改后的分数: 0
学生: lucky; 分数: 0

类中定义的非构造方法可以调用类中的构造方法实例变量的属性,调用的方式为self.实例变量属性名,如代码中self.name和self.score。可以在类的外部修改类的内部属性。

 

如果要让类的内部属性不被外部访问,怎么办?

要让内部的属性不被外部访问,可以在属性名称前加两个下划线__。在python,如果实例的变量名以__开头,就会变成私有变量,只有内部可以访问,外部不能访问。对上面的代码修改:

class Student(object):
    def __init__(self,name,score):
        self.__name=name
        self.__score=score
    def info(self):
        print('学生: %s; 分数: %s' % (self.__name,self.__score))

stu=Student('lucky',96)
print('修改前的分数:',stu.__score)
stu.info()
stu.__score=0
print('修改后的分数:',stu.__score)
stu.info()

##输出结果:
AttributeError: 'Student' object has no attribute '__score'

把类的中的变量变成私有变量有什么用呢?
可以确保外部的代码不能随意修改对象内部的状态,通过访问限制的保护,代码更加安全。比如上面分数是一个比较重要的对象,是不允许在程序外随意的修改的。

如果外部的代码想要获取类中的name和score怎么办?
在python中可以增加get_attrs()方法,获取类中私有变量,可以在上面的例子条件一个get_score方法,如下:

class Student(object):
    def __init__(self,name,score):
        self.__name=name
        self.__score=score
    
   def info(self):
        print('学生: %s; 分数: %s' % (self.__name,self.__score))

    def get_score(self):        #在类外部可以得到类内部的私有变量
        return self.__score

    def set_score(self,score):
        self.__score=score

stu=Student('lucky',96)
print('修改前的分数:',stu.get_score())
stu.info()
stu.set_score(0)                #在类的外部修改私有变量
print('修改后的分数:',stu.get_score())
stu.info()

##输出结果:
修改前的分数: 96
学生: lucky; 分数: 96
修改后的分数: 0
学生: lucky; 分数: 0

类的私有方法

私有方法调用的方式:
self.__private_methods

class PrivatePublicMethod(object):
    def __init__(self):
        pass

    def __foo(self):  #定义私有方法
        print('这是私有方法')

    def foo(self):     #公共方法
        print('这是公共方法')
        print('公共方法中调用私有方法')
        self.__foo()
        print('公共方法调用私有方法结束')

pri_pub=PrivatePublicMethod()
print('开始调用公共方法:')
pri_pub.foo()
print('开始调用私有方法:')
pri_pub.__foo()

##输出结果:
AttributeError: 'PrivatePublicMethod' object has no attribute '__foo'
开始调用公共方法:
这是公共方法
公共方法中调用私有方法
这是私有方法
公共方法调用私有方法结束
开始调用私有方法:

上面代码在调用私有方法时候, 报错。 注意:私有方法和私有变量类似,不能在类外部被调用。

 

类的继承

面向对象编程带来的好吃之一就是代码的重用,实现重用的方法之一是通过继承机制。继承可以理解成类之间类型和子类型的关系。

在面向对象程序设计中,当我们定义一个class时,可以从某个现有的class继承,定义的新的class称为子类(Subclass),而被继承的class称为基类、父类或者超类(Base Class、Super Class)

 

继承的定义如下:

class DerivedClassName(BaseClassName):
  <statement-1>
  ...
  <statement-2>

需要注意的是,继承语法 class 子类名(基类名) 时,  #基类名写在括号里,基本类是在定义类的时候,在元组中指明的。

在python中,继承有以下特点:
1)在继承中,基类的构造方法(__init__)不会被自动调用,需要在子类的构造方法中专门调用。
2)在调用基类的方法时,需要加上基类的类名前缀,并带上self参数变量。区别在于类中调用普通函数时不需要带self参数。
3)在python中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    pass

class Cat(Animal):
    pass

dog=Dog()
dog.run()

cat=Cat()
cat.run()

##输出结果:
Animal is running...
Animal is running...

继承有什么好处?
继承最大的好处是子类获得了父类全部非私有的功能。由于在Animal中定义了run()非私有的方法,作为Animal的子类,Dog和Cat什么方法都没有定义,自动拥有Animal这个基类中的run()方法。

 

子类中可以拥有自己的方法:

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def eat(self):
        print('Eating...')

class Cat(Animal):
    pass

dog=Dog()
dog.run()
dog.eat()

##输出结果:
Animal is running...
Eating...

从结果可以看到,既执行了父类的方法,又执行了自己定义的方法。

 

子类不能继承父类的私有方法,也不能调用父类的私有方法,父类的定义如下:

class Animal(object):
    def run(self):
        print('Animal is running...')
    def __run(self):
        print('I am a private method..')

class Dog(Animal):
    def eat(self):
        print('Eating...')

dog=Dog()
dog.run()
dog.eat()
dog.__run()

##输出结果:
Animal is running...
Eating...
AttributeError: 'Dog' object has no attribute '__run'

从结果可以看到,子类不能调用父类的私有方法,子类虽然继承了父类,但是调用父类的私有方法相当于从外部调用类中的方法,因此调用不成功。

对于父类中扩展的非私有方法,子类可以拿来即用,如在父类Animal增加一个jump方法:

class Animal(object):
    def run(self):
        print('Animal is running...')
    def jump(self):
        print('Animal is jumping..')

class Dog(Animal):
    def eat(self):
        print('Eating...')

dog=Dog()
dog.run()
dog.jump()
dog.eat()

##输出结果:
Animal is running...
Animal is jumping..
Eating...

继承可以一级一级继承下来,所有类最终都可以追朔到根类object。

 

类的多态

当子类和基类存在相同的run方法的时候,子类的run方法会覆盖基类的run方法。在代码运行时总会调用子类的run()方法,称之为多态。

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def eat(self):
        print('Eating...')

class Cat(Animal):
    pass

dog=Dog()
dog.run()

cat=Cat()
cat.run()

##输出结果:
Animal is running...
Animal is running...
class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def run(self):
        print('Dog is running...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')

dog=Dog()
dog.run()

cat=Cat()
cat.run()

##输出内容:
Dog is running...
Cat is running...

 

当我们定义一个类的时候,实际上就定义了一种数据类型,自定义的数据类型和python自带的数据类型(字符串、列表、元组、字典等)没什么区别。

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def run(self):
        print('Dog is running...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')

dog=Dog()
dog.run()

cat=Cat()
cat.run()

a=list()   #变量a是list类型
b=Animal()  #变量b是Animal
c=Dog()     #c是Dog类型

print('a是否是list类型:',isinstance(a,list))     #isinstance()方法判断一个变量是否是某个类型
print('b是否是Animal类型:',isinstance(b,Animal))
print('c是否是Dog类型:',isinstance(c,Dog))
print('c是否是Dog类型:',isinstance(c,Dog))
print('c是否是Animal类型:',isinstance(c,Animal))
print('b是否是Dog数据类型:',isinstance(b,Dog))  #b是否是Dog数据类型: False

##输出内容:
Dog is running...
Cat is running...
a是否是list类型: True
b是否是Animal类型: True
c是否是Dog类型: True
c是否是Dog类型: True
c是否是Animal类型: True
b是否是Dog数据类型: False

可以看到c既是Dog类型又是Animal类型。该怎么理解?
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以看作是父类。但是反过来不行,如下:

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def run(self):
        print('Dog is running...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')

class Bird(Animal):
    def run(self):
        print('Bird is flying the sky...')

def run_two_times(animal):
    animal.run()
    animal.run()

run_two_times(Animal())
run_two_times(Dog())
run_two_times(Cat())
run_two_times(Bird())

##输出内容:
Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...
Bird is flying the sky...
Bird is flying the sky...

新增的Animal子类Bird不必对run_two_tomes()方法做任何修改。实际上,任何依赖Animal作为参数的函数或方法都可以不加修改的正常运行,原因就在于多态。

 

多态的好处:
当我们需要传入Dog、Cat、Bird等对象时,只需要接收Animal类型就可以了,因为Dog、Cat、Bird都是Animal类型,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此传入的类型只要是Animal类或者继承自Animal类,都会调用实际类型的run()方法。

多态对于一个变量,我们只需要知道它是Animal类型,无需确切知道它的子类型,就可以方法调用run()方法,具体调用的run方法作用于Animal、Cat、Dog、或Bird对象,由运行时该对象的确切类型决定。

 

封装:封装是全局作用域中其他地区隐藏多余信息的原则。听起来有些像多态,使用对象而不用知道其内部细节。它们都是抽象原则,都会帮忙处理程序组件而不用关心过多细节,就像函数一样。封装并不等同于多态,多态可以让用户对不知道类的对象进行方法调用,而封装可以不用关心对象是如何创建的,直接使用即可。
 

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

std=Student('lucky',96)

def info(std):
    print('学生: %s;分数: %s' % (std.name,std.score))

info(std)

##输出结果:
学生: lucky;分数: 96

///

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

    def info(self):
        print('学生: %s;分数: %s' % (self.name,self.score))

stu=Student('lucky',96)
stu.info()

##输出结果:
学生: lucky;分数: 96

这样一来,我们从外部看Student类,只需要知道创建实例需要给出的name和socre,如何输出是在Student类内部定义的,这些数据和逻辑被封装起来,调用很容易,但却不知道内部实现的细节。

 

多重继承

语法:

class DerivedClassName(Base1,Base2,Base3):
   <statement-1>
   ...
   <statement-2>

多重继承就是有多个基类。

需要注意圆括号中基类的顺序,若基类中有相同的方法名,在子类使用时未指定,python会从左到右搜索。若方法在子类中未找到,则从左到右查找基类中是否包含方法。

 

假设要实现4种动物:Dog(狗)、Bat(蝙蝠)、Parrot(鹦鹉)、Ostrich(鸵鸟)

如果按照哺乳动物和鸟类分类:

  • 哺乳类:能跑的哺乳类和能飞的哺乳类
  • 鸟类:能跑的鸟类和能飞的鸟类

哺乳类:Dog和Bat  

  •    能跑的哺乳类:Dog
  •    能飞的哺乳类:Bat   

鸟类:Parrot和Ostrich

  •    能跑的鸟类:Ostrich
  •    能飞的鸟类:Parrot
class Animal(object):
    pass

 #大的分类
class Mammal(Animal):
    pass

class Bird(Animal):
    pass

#定于能跑和能飞的类
class Runnable(object):
    def run(self):
        print('Running...')

class Flable(object):
    def fly(self):
        print('Flying...')

#各种动物分类
class Dog(Mammal,Runnable):
    pass

class Bat(Mammal,Flyable):
    pass

class Parrot(Bird,Flyable):
    pass

class Ostrich(Bird,Runnable):
    pass

通过上面的多重继承,一个子类就可以继承多个基类,同时获得多个基类所有非私有功能。