Python面向对象初识

一 编程方式简介

1.1 面向过程

面向过程的程序设计的核心是过程(流水线式思维),根据过程(业务逻辑)从上到下写代码

优点:复杂度的问题流程化,只需要顺着执行的过程,堆叠代码即可

缺点:一套程序只能解决单一问题,修改会造成牵一发而动全身

应用场景:一旦完成基本很少改变的场景,如:Linux內核,git,Apache HTTP Server等

1.2 函数式

将完成某一功能的代码封装到函数中,以后如需使用,调用函数即可,不用重复编写

优缺点:介于面向过程与面向对象之间

应用场景:各个函数之间是独立且无共用的数据时可采用函数式编程

1.3 面向对象

对函数进行分类和封装,让开发更快更好更强,此方式的实施需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用

优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中

缺点:可控性差,无法像面向过程一样可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题

应用场景:多函数需使用共同的值;需要创建多个事物,每个事物属性个数相同,值不同而已;需求需求经常变化的软件,如:互联网应用,企业内部软件,游戏等

二 创建类与对象

类:它就是一个模板(具有相同特征的一类事物(比如:猫,狗,老虎等,它们都是动物类)),里面包含多个函数,函数里实现一些功能

对象:根据模板创建的实例对象(具体的某一事物),通过实例对象可以执行类中的函数

实例化:类到对象的过程(实例=类名(参数1,参数2))

原理展示

image

注:类中的函数叫方法

实例展示

# 创建类
class Foo:

    def func1(self):
        print('func1')

    def func2(self):
        print('func2')

# 根据类Foo创建对象obj
obj = Foo()
obj.func1()     # 执行func1方法
obj.func2()     # 执行func1方法

三 面向对象三大特性

面向对象的三大特性是指:封装、继承和多态

3.1 封装

封装,顾名思义就是将内容封装到某处,以后再去调用被封装在某处的内容。个人理解面向对象的封装体现在以下两点:

1. 封装方法和属性,将一类操作封装到一个类中

class Foo:
    
    xxx = 'xxx'
    
    def __init__(self, name, age):  # __init__称为构造方法,根据类创建对象时自动执行
        self.name = name
        self.age = age

    def show(self):
        print(self.name, self.age)

2. 数据的封装(将数据封装到对象中)

obj = Foo('joe1991',18)     # 将joe1991和18分别封装到obj中的name和age属性中

补充说明:封装分为两个层面

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

第一个层面的封装:创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.(类内部通过self.)的方式去访问里面的名字,这本身就是一种封装。

class Foo:

    xxx = 'xxx'

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

    def show(self):
        print(self.name, self.age)

print(Foo.xxx)          # xxx,通过类名.的方式访问
print(Foo.show)         # <function Foo.show at 0x000001FB870B0268>,通过类名.的方式访问

obj = Foo('joe1991',18)
print(obj.name)     # 直接调用obj对象的name属性
print(obj.age)      # 直接调用obj对象的age属性
obj.show()   # Python默认会将obj传给self参数,即:obj.show(obj),所以,此时方法内部的self=obj,即self.name是joe1991;self.age是18

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

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

在python中用双下划线的方式实现隐藏属性(设置成私有的)。类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class Foo:

    xxx = 'xxx'

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

    def show(self):
        print(self.__name, self.__age)

print(Foo.xxx)          # xxx,通过类名.的方式访问
print(Foo.show)         # <function Foo.show at 0x000001FB870B0268>,通过类名.的方式访问

obj = Foo('joe1991',18)
print(obj.__name)     # 报错
print(obj.__age)      # 报错
obj.show()   # Python默认会将obj传给self参数,即:obj.show(obj),所以,此时方法内部的self=obj,即self.__name是joe1991;self.__age是18

这种自动变形的特点:

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

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

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

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

这种变形需要注意的问题是:

1. 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,

2. 变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形

3.2 继承

3.2.1 什么是继承?

继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类子类,通过继承可以大大增加代码的重用性

在python中类的继承分为:单继承或多继承(Java和C#中则只能继承一个类)

class ParentClass1: # 定义父类
    pass
 
class ParentClass2: # 定义父类
    pass
 
class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass1
    pass
 
class SubClass2(ParentClass1,ParentClass2): # python支持多继承,用逗号分隔开多个继承的类
    pass

3.2.2 多继承查找方式

如果继承的多个类每个类中都有相同的方法,那么那一个会被使用呢?这里有两种方式:分别是:深度优先广度优先

image

那什么时候采用深度优先?什么时候采用广度优先?

  • 当类是经典类时,按照深度优先方式查找
  • 当类是新式类时,按照广度优先方式查找

何为经典类?何为新式类?

# python2中分为:经典类、新式类
# 经典类:
class A:
    pass
# 新式类:
class B(object):
    pass

# python3中均为新式类,默认继承object类
class A:
    pass
class D:

    def func(self):
        print 'D.func'


class C(D):

    def func(self):
        print 'C.func'


class B(D):

    def func(self):
        print 'B.func'


class A(B, C):

    def func(self):
        print 'A.func'

a = A()
a.func()
# 执行func方法时:查找顺序:A --> B --> D --> C
经典类的多继承
class D(object):

    def func(self):
        print 'D.func'


class C(D):

    def func(self):
        print 'C.func'


class B(D):

    def func(self):
        print 'B.func'


class A(B, C):

    def func(self):
        print 'A.func'

a = A()
a.func()
# 执行func方法时:查找顺序:A --> B --> C –> D
新式类的多继承

注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了;若一直未找到,则报错

3.2.3 继承原理

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:

在上面的例子中,我们可以通过mro()方法查询执行顺序,等同于__mro__,注意只有新式才有这个属性

print(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>]

3.2.4 子类中调用父类的方法

方式一:父类名.父类方法()

class Animal:           # 定义动物类

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

    def eat(self):
        print('吃饭了...')


class Cat(Animal):      # 猫
    def __init__(self, name, age, species):
        Animal.__init__(self, name, age )
        self.species = species

    def eat(self):
        print('%s品种的猫' %self.species, end='')
        Animal.eat(self)


garfield = Cat('傻猫', '3', '加菲')
garfield.eat()
View Code

方式二:super()

class Animal:           # 定义动物类

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

    def eat(self):
        print('吃饭了...')


class Cat(Animal):      # 猫
    def __init__(self, name, age, species):
        # super(Cat, self).__init__(name, age)   # 在python3中super()等同于super(Cat,self)
        super().__init__(name, age)
        self.species = species

    def eat(self):
        print('%s品种的猫' %self.species, end='')
        # super(Cat, self).eat()
        super().eat()


garfield = Cat('傻猫', '3', '加菲')
garfield.eat()
View Code

补充:在子类外使用父类方法:super(子类名,对象名))方法名()

当我们使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次

注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表

3.2.5 其他说明

1. 父类里没有的方法,在子类中有,这样的方法就叫做派生方法

2. 父类里有,子类也有的方法,就叫做方法的重写(就是把父类里的方法重写了)

3. 查看所有继承的父类

  • 类名.__base __:查看从左到右继承的第一个子类
  • 类名.__bases__:查看所有继承的父类

如果没有指定父类,python会默认继承object类,object是所有python的父类。

3.2.5 isinstance 和 issubclass

isinstance(obj,cls):判断obj是不是cls的对象(传两个参数,一个是对象,一个是类)

issubclass(sub,super):判断sub是不是super的子类(传两个参数,一个是子类,一个是父类)

class Parent:
    pass

class Child(Parent):
    pass

p = Parent()
c = Child()

print(isinstance(p,Parent))             # True
print(isinstance(c,Child))              # True
print(isinstance(c,Parent))             # True,子类产生的对象也为Ture
print(issubclass(Child,Parent))         # True

# 我们也可以使用type判断
print(type(c) is Parent)        # False
print(type(c) is Child)         # True

3.3 多态

在说明多态之前我们先看一个例子:

class Animal:
    def walk(self):
        print('Animal is walking.')


class Dog(Animal):
    def walk(self):
        print('Dog is walking.')


class Pig(Animal):
    def walk(self):
        print('Pig is walking.')


class Cat(Animal):
    def walk(self):
        print('Cat is walking.')


dog.walk()       # Dog is walking.
pig.walk()       # Pig is walking.
cat.walk()       # Cat is walking.

从上面的例子可以看出,当子类和父类都存在相同的walk()方法时,子类的walk()覆盖了父类的walk(),在代码运行的时候,总是会调用子类的walk()。这样,我们就获得了继承的另一个好处:多态。

现在可以说明:多态就是同一类事物的多种形态

接下来我们将上面的例子改写一下:定义一个函数,它可以接受一个Animal类型的变量

class Animal:
    def walk(self):
        print('Animal is walking.')


class Dog(Animal):
    def walk(self):
        print('Dog is walking.')


class Pig(Animal):
    def walk(self):
        print('Pig is walking.')


class Cat(Animal):
    def walk(self):
        print('Cat is walking.')


# 定义一个函数,它可以接受一个Animal类型的变量
def walking(animal):
    animal.walk()

animal = Animal()
dog = Dog()
pig = Pig()
cat = Cat()

walking(animal)     # Animal is walking.
walking(dog)        # Dog is walking.
walking(pig)        # Dog is walking.
walking(cat)        # Cat is walking.

当我们传入Animal的实例(包括子类实例)时,函数就会执行相应类型的walk()方法。另外,我们可以新增一个Animal的子类,不必对walking()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入Dog、Cat等时,我们只需要接收Animal类型就可以了,因为Dog、Cat等都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有walk()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的walk()方法,这就是多态的意思

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用walk()方法,而具体调用的walk()方法是作用在Animal、Dog、Cat还是其他Animal的子类对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保walk()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则

对扩展开放:允许新增Animal子类

对修改封闭:不需要修改依赖Animal类型的walking()等函数。

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用walk()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个walk()方法就可以了:

class X(object):
    def walk(self):
         print('...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子

以上多态部分参考:廖雪峰老师官方网站

四 组合

在上面我们提到继承可以完成代码的重用性,除此之外还有另外一种方式可以完成代码的重用性,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

方式一:在__init__方法里面组合

# 创建一个性别类
class Gender:
    def __init__(self, gender):
        self.gender = gender


# 创建一个学生类
class Studnet:

    def __init__(self,name):
        self.name = name
        self.gender = Gender('male')      # 用Gender实例化一个对象,赋值给实例的gender属性


s = Studnet('Joe991')
print(s.gender.gender)   # male

方式二:在外面组合

class Gender:
    def __init__(self, gender):
        self.gender = gender


class Studnet:

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


s = Studnet('Joe991')
s.gender = Gender('male')        # 用Gender实例化一个对象,赋值给实例的gender属性
print(s.gender.gender)           # male

组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,

1. 继承的方式

通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

2. 组合的方式

用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如:老师有课程,学生有成绩,学生有课程,学校有学生等

class People:
    """
    人类
    """
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.sex = gender

class Course:
    """
    课程类
    """
    def __init__(self, name, period, price):
        self.name = name
        self.period = period
        self.price = price

    def course_info(self):
        print('<课程名称:{0}--课程周期:{1}--价格:{2}>' .format(self.name, self.period, self.price))

class Teacher(People):
    """
    老师类
    """
    def __init__(self, name, age, gender, position):
        # People().__init__(self, name, age, gender)
        super().__init__(name, age, gender)
        self.position = position
        self.course = []
        self.students = []


class Student(People):
    """
    学生类
    """
    def __init__(self, name, age, gender):
        # People().__init__(self, name, age, gender)
        super().__init__(name, age, gender)
        self.course = []


t = Teacher('xxx',18,'male','讲师')
s = Student('joe',18,'male')

python = Course('Python', '6months', 3000)
linux = Course('Linux', '6months', 3000)

# 为老师和学生添加课程
t.course.append(python)
t.course.append(linux)
s.course.append(python)

# 为老师添加学生
t.students.append(s)

# 使用
for obj in t.course:
    obj.course_info()

for obj in s.course:
    obj.course_info()
实例--继承与组合

提醒:当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好

五 其他

5.1 id、type和value

在python中一切皆对象,每产生一个对象会对应三个属性:id、类型type和数值value
id可以理解为指向对象在内存中的位置,通过id()可以获取对象的地址
id相同,数值肯定相同;id不相同,数值可能相同。我们可以通过is判断id是否相同,利用==判断数值是否相同

>>> x=10
>>> y=10
>>> id(x)
1644307440
>>> id(y)
1644307440
>>> x is y
True
>>> x == y
True
>>> x=1000
>>> y=1000
>>> id(x)
1923155316688
>>> id(y)
1923156892720
>>> x is y
False
>>> x ==y
True

注意:python中数字[-5,256]id是固定的。

5.2 __init_的作用

构造对象的同时被对象自动调用,完成对事物的初始化,一个类只要生成一个类对象,它一定会调用__init__构造函数。有以下特点:
1. 一个类中只能有一个初始化构造函数
2. 不能有返回值
3. 可以用它来为每个实例定制自己的特征

5.3 self关键字的用法

为了区分正在处理哪个对象,self指针变量指向当前时刻正在处理的对象,即构造出来的对象
在构造方法中self代表的是:self指针变量指向当前时刻正在创建的对象
构造函数中self.name = name 的含义:将局部变量name的数值发送给当前正在创建的对象中的name成员

class Student():
    def __init__(self):
        print("当前对象的地址是:%s" %self)

if __name__ == '__main__':
    s1 = Student()
    print(s1)
    s2 = Student()
    print(s2)

# 运行结果
当前对象的地址是:<__main__.Student object at 0x0000020623A7D8D0>
<__main__.Student object at 0x0000020623A7D8D0>
当前对象的地址是:<__main__.Student object at 0x0000020623A83E10>
<__main__.Student object at 0x0000020623A83E10>

在上面的程序中,s1、s2、self实际上都是指针变量,存放的是地址,指定当前时刻正在调用的那个对象

5.4 类和对象在内存中的保存

类以及类中的方法在内存中只有一份,而根据类创建的每一个对象都在内存中需要存一份,大致如下图:

image

如上图所示,根据类创建对象时,对象中除了封装 name 和 age 的值之外,还会保存一个类对象指针,该值指向当前对象的类

当通过 obj1 执行func1() 时,过程如下:

  • 根据当前对象中的类对象指针找到类中的方法
  • 将对象 obj1 当作参数传给方法的第一个参数 self
posted @ 2018-07-11 17:10  Joe1991  阅读(107)  评论(0)    收藏  举报