Python面向对象(基础篇)

引言

提到面向对象,总是离不开几个重要的术语:多态(Polymorphism),继承(Inheritance)和封装(Encapsulation)。Python也是一种支持OOP的动态语言,本文将简单阐述Python对面向对象的支持。

创建类和对象

面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。

  类就是一个模板,模板里可以包含多个函数,函数里实现一些功能

  对象则是根据模板创建的实例,通过实例对象可以执行类中的函数

class是关键字,表示类

创建对象,类名称后加括号即可

class Foo:     

    def Bar(self):

        print('Bar')

    def Hello(self, name):

        print('i am %s' %name)

# 根据类Foo创建对象obj

obj = Foo()

obj.Bar()            #执行Bar方法

obj.Hello('yanghao'') #执行Hello方法 
创建类

封装

封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。

所以,在使用面向对象的封装特性时,需要:

  • 将内容封装到某处
  • 从某处调用被封装的内容
class foo:

    def __init__(self,name,age):

        self.name = name

        self.age = age

obj1 = foo('yanghao',22)

obj2 = foo('xiaohaozi',22)
封装

self 是一个形式参数,当执行 obj1 = Foo('yanghao', 22 ) 时,self 等于 obj1

          当执行 obj2 = Foo('xiaohaozi', 22 ) 时,self 等于 obj2

所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性

第二步:从某处调用被封装的内容

调用被封装的内容时,有两种情况:

  • 通过对象直接调用
  • 通过self间接调用

1、通过对象直接调用被封装的内容

class Foo:
 
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
obj1 = Foo('yanghao', 22)
print(obj1.name)    # 直接调用obj1对象的name属性
print(obj1.age)     # 直接调用obj1对象的age属性
 
obj2 = Foo('xiaohaozi', 18)
print(obj2.name)    # 直接调用obj2对象的name属性
print(obj2.age)    # 直接调用obj2对象的age属性
调用

2、通过self间接调用被封装的内容

执行类中的方法时,需要通过self间接调用被封装的内容

class 类名:
    def __init__(self,参数1,参数2):
        self.对象的属性1 = 参数1
        self.对象的属性2 = 参数2

    def 方法名(self):pass

    def 方法名2(self):pass

对象名 = 类名(1,2)  #对象就是实例,代表一个具体的东西
                  #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
                  #括号里传参数,参数不需要传self,其他与init中的形参一一对应
                  #结果返回一个对象
对象名.对象的属性1   #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名()     #调用类中的方法,直接用 对象名.方法名() 即可
对象的相关知识
class Foo:
  
    def __init__(self, name, age):
        self.name = name
        self.age = age
  
    def detail(self):
        print self.name
        print self.age
  
obj1 = Foo('yanghao', 22)
obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 yanghao;self.age 是 22
  
obj2 = Foo('xiaohaozi', 18)
obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 xiaohaozi ; self.age 是 18
间接调用

综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。

类属性的补充

一:我们定义的类的属性到底存到哪里了?有两种方式查看

dir(类名):查出的是一个名字列表

类名.__dict__:查出的是一个字典,key为属性名,value为属性值

二:特殊的类属性

类名.__name__# 类的名字(字符串)

类名.__doc__# 类的文档字符串

类名.__base__# 类的第一个父类

类名.__bases__# 类所有父类构成的元组

类名.__dict__# 类的字典属性

类名.__module__# 类定义所在的模块

类名.__class__# 实例对应的类(仅新式类中)

练习

小明,10岁,男,上山去砍柴

小明,10岁,男,开车去东北

小明,10岁,男,最爱大保健

老李,90岁,男,上山去砍柴

老李,90岁,男,开车去东北

老李,90岁,男,最爱大保健

def A(name, age, gender):
    print ("%s,%s岁,%s,上山去砍柴" %(name, age, gender))

def B(name, age, gender):
    print ("%s,%s岁,%s,开车去东北" %(name, age, gender))

def C(name, age, gender):
    print ("%s,%s岁,%s,最爱大保健" %(name, age, gender))

A('小明', 10, '')
B('小明', 10, '')
C('小明', 10, '')

A('老李', 90, '')
B('老李', 90, '')
C('老李', 90, '')
用函数做
class Foo:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def A(self):
        print("%s,%s岁,%s,上山去砍柴" % (self.name, self.age, self.gender))

    def B(self):
        print("%s,%s岁,%s,开车去东北" % (self.name, self.age, self.gender))

    def C(self):
        print("%s,%s岁,%s,最爱大保健" % (self.name, self.age, self.gender))

xiaoming = Foo('小明', 10, '')
xiaoming.A()
xiaoming.B()
xiaoming.C()

laoli = Foo('老李', 90, '')
laoli.A()
laoli.B()
laoli.C()
面向对象

上述对比可以看出,如果使用函数式编程,需要在每次执行函数时传入相同的参数,如果参数多的话,又需要粘贴复制了...  ;而对于面向对象只需要在创建对象时,将所有需要的参数封装到当前对象中,之后再次使用时,通过self间接去当前对象中取值即可。

继承

继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。

例如:

  猫可以:喵喵叫、吃、喝、拉、撒

  狗可以:汪汪叫、吃、喝、拉、撒

如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,如下所示:

class 猫:

    def 喵喵叫(self):
        print('爬树')

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

class 狗:

    def 汪汪叫(self):
        print('看门')

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something
伪代码

上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:

  动物:吃、喝、拉、撒

     猫:喵喵叫(猫继承动物的功能)

     狗:汪汪叫(狗继承动物的功能)

class 动物:

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):

    def爬树(self):
        print('爬树')

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):

     def 看门(self):
        print('看门')
伪代码
class Animal:

    def eat(self):
        print("%s 吃 " % self.name)

    def drink(self):
        print("%s 喝 " % self.name)

    def shit(self):
        print("%s 拉 " % self.name)

    def pee(self):
        print("%s 撒 " % self.name)

class Cat(Animal):

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

    def cry(self):
        print("爬树")

class Dog(Animal):

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

    def cry(self):
        print("看门")

c1 = Cat("小白")
c1.eat()
c1.cry()

c2 = Cat("小黄")
c2.drink()
c2.cry()

d1 = Dog("小黑")
c1.shit()
d1.cry()
代码实例

所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。

注:除了子类和父类的称谓,你可能看到过 派生类 和 基类 ,他们与子类和父类只是叫法不同而已。

那么问题又来了,多继承呢?

  • 是否可以继承多个类
  • 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?

1、Python的类可以继承多个类,Java和C#中则只能继承一个类

2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先广度优先

 

  • 当类是经典类时,多继承情况下,会按照深度优先方式查找
  • 当类是新式类时,多继承情况下,会按照广度优先方式查找

经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。

class D:

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

class C(D):

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

class B(D):

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

class A(B, C):

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

a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> D --> C
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
经典类多继承
class D(object):

    def bar(self):

        print('D.bar')

class C(D):

    def bar(self):

        print("C.bar")

class B(D):

    def bar(self):

        print('B.bar')

class A(B,C):

    def bar(self):

        print('A.bar')

a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> C --> D
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
新式类多继承

经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错

新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错

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

补充:那么问题来了A类和B类都有独立的__init__那么如何使用共同的属性并且使用自己独立的属性

方法一:类名.__init__

class A:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age


class B(A):
    def __init__(self, name, sex, age, stature):
        self.stature = stature
        #方法一:
        A.__init__(self,name,sex,age)

    def func(self):
        return '姓名:%s 性别:%s 年龄:%s 身高:%s' % (self.name, self.sex, self.age, self.stature)


obj = B('小白', '', 23, 1.70)
print(obj.func())
View Code

方法二:super

class A:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age


class B(A):
    def __init__(self, name, sex, age, stature):
        self.stature = stature
        #方法二:通过super
        super().__init__(name, sex, age)

    def func(self):
        return '姓名:%s 性别:%s 年龄:%s 身高:%s' % (self.name, self.sex, self.age, self.stature)


obj = B('小白', '', 23, 1.70)
print(obj.func())
View Code

多态

Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚“鸭子类型”。

class F1:
    pass

class S1(F1):

    def show(self):
        print 'S1.show'

class S2(F1):

    def show(self):
        print 'S2.show'

# 由于在Java或C#中定义函数参数时,必须指定参数的类型
# 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,定义了一个S1和S2类的父类
# 而实际传入的参数是:S1对象和S2对象

def Func(F1 obj):
    """Func函数需要接收一个F1类型或者F1子类的类型"""
    
    print obj.show()
    
s1_obj = S1()
Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show

s2_obj = S2()
Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show
Python伪代码实现JAVA或者C#的多态
class F1:

    pass

class S1(F1):

    def show(self):

        print('S1.show')

class S2(F1):

    def show(self):

        print('S2.show')

def Func(obj):

    print(obj.show())

s1_obj = S1()

Func(s1_obj)

s2_obj = S2()

Func(s2_obj)
Python“鸭子类型”

 

posted @ 2018-06-14 15:27  杨灏  阅读(390)  评论(0编辑  收藏  举报