面向对象

一.什么是类与对象.

类从概念层次上理解的来说就是一些有着共同特性或特征的集合,而对象就是在现实生活中某个类下面某个存在的个体,也就是类似于集合里面的包含与被包含的关系。例如:人,猪,老虎,是一个个的对象,也是一个个的类,人对于某个人而言也是一个类,但人,猪,老虎也是被动物这个大集合所包含的,所有说,类是抽象的,对象往往都是具体的,在现实生活中,往往是先有的对象,再有的类,而在python语言中,都是先有类,在根据类去产生一个个的对象。

二,从python语言的构造去理解为什么要有类与对象。

python是一门语言,是认为设计的,既然是设计的,就是存在就是被需求,从前面的知识我们知道,数据类型的产生,为什么会有整型,浮点型,列表,字典等数据类型,目的就是为了能够更好的描述变量的某些特征或者状态,比如年龄:整型,同理,类的产生也是为了更好更高效的解决问题或者去更加准确的描述事物。比如,函数是产生是为了减少代码的重复,为了更加高效,类也是如此,函数是某个功能的体现,而模块是多个函数功能集合体,而类就是人为的设定一个工厂,让从这个工厂出去的产品都具体某些相同的属性和方法,但又具有某个独特的特征,比如我们知道的华为手机,有那么多版本,但通信,发短信,上网都是共有的,但某些特殊的比如高像素的摄像,指纹识别等又是某个型号所特有的,这里手机就是一个类,而一个个不同版本的手机就是这个类的对象。

三.类的定义

class People:类名为首字母大写
    country = 'China' #静态属性,为名词
    def __init__(self,name,age):
    self.name = name
    self.age = age
    def talk(self):#动态方法,一般为动词
        print('这是我们共有的方法")
p1 = People('yuan',18)
p2 = People('rong',18)
p1.country
p2.country  #共有属性
p1.name #特有属性
p2.name
p1.talk()#绑定到对象上的方法
对于类产生的对象而言,country就是其共有的属性,而__init__就是对象特有属性的初始化,
即可以通过外部参数的传值而人为设定
talk()函数对于类People而言是属于类的函数属性,对于对象p1,p2而言是绑定到对象上的方法。
注意事项:
People.talk():类调用自己的函数属性时,需要往里面传想对应的参数,而对象而言p1.talk()会自动
将自身传进talk()里面,也就是self,这里的self是指p1的内存地址,与__init__进行初始化时一样会将
自身传入。

在操作系统中,一切皆文件,在python中一切皆对象,所以对于类而言,也是一个对象,例如list

也是一个对象,也是一个类,list.append()就是调用该类下的append()方法去执行添加元素的方法。

四.类的三大特性:

1.继承与派生:继承是一种创建新类的方式,新建的类可以继承一个或者多个父类,父类又称为基类,父类产生的新类可以称为派生类或者子类。

2.继承分为单继承和多继承:

单继承是指:一个子类只继承一个父类,子类拥有父类的属性和方法,当调用某个属性或方法时,会优先从自己的名称空间中去寻找对应的属性或者方法,如果没有就会去父类中寻找。

class A:
    country = 'China'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print('这是父类中的talk')
class A1(A):
    def talk(self):
        print('这是子类的中talk')

A1.talk(112)
a1 = A1('yuan',18)
a1.talk()
print(a1.country)

A1.talk(112) #一优先寻找A1下面的talk方法,打印这是子类中的talk方法。
如果A1下没有talk方法,会寻找A下面的talk方法,会打印这是父类中的talk方法。
对于a1是A1产生的一个对象,当调用其country属性是时,父类A1没有,就会去A1的父类中去寻找,打印China,如果A中也没有就会报错
单继承

 多继承是指:一个子类继承多个父类,父类也去其他类的子类。

多继承在python2和python3中有区别:

对于Python2中的类有经典类和新式类之分:

class A:  经典类  

class B(object):新式类

python3中只有经典类:

class A: 默认继承object类,相当于 class A(object):

对于多继承而言,对于一个对象的属性寻找路径,在python2和python3中有着明显的区别:

class A:
    print('1111')
    country = 'A中的China'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print('这是A中的talk')
class B(A):
    # country = 'B中的country'
    def talk(self):
        print('这是B的中talk')
class C(A):
    def talk(self):
        print('这是C的中talk')

class D(C):
    country = '这是D中的country'
    def talk(self):
        print('这是D的中talk')
class E(B):

    def talk(self):
        print('这是E的中talk')
class F(E,D):
    def talk(self):
        print('这是F的中talk')
f = F('yuan',18)
print(f.country)
print(F.mro())# 经典类没有mro()方法

>>>>>>>:
1111
这是D中的country 

新式类的继承关系:
[<class '__main__.F'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>]
经典类的继承关系:
[<class '__main__.F'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.D'>, <class '__main__.C'>, <class'__main__.A'>]
多继承的继承顺序

总结:对于多继承而言,经典类遵守深度优先,对于新式类而言,多继承遵循广度优先。

方法一:
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        Vehicle.run(self)

line13=Subway('中国地铁','180m/s','1000人/箱','',13)
line13.run()


方法二:super()内置函数

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
    pass

line13=Subway('中国地铁','180m/s','1000人/箱','',13)
line13.run()
子类调用父类的方法
方法一:
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        A.__init__(self)


class C(A):
    def __init__(self):
        print('C的构造方法')
        A.__init__(self)


class D(B,C):
    def __init__(self):
        print('D的构造方法')
        B.__init__(self)
        C.__init__(self)

    pass
'''
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
'''

方法二:super()
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        super(B,self).__init__()


class C(A):
    def __init__(self):
        print('C的构造方法')
        super(C,self).__init__()


class D(B,C):
    def __init__(self):
        print('D的构造方法')
        super(D,self).__init__()

f1=D() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
两种调用方式的区别

类的封装:

#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
#类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

#A._A__N是可以访问到的,
#这种,在外部是无法通过__x这个名字访问到
#正常情况
>>> class A:
...     def fa(self):
...         print('from A')
...     def test(self):
...         self.fa()
... 
>>> class B(A):
...     def fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from B
 

#把fa定义成私有的,即__fa
>>> class A:
...     def __fa(self): #在定义时就变形为_A__fa
...         print('from A')
...     def test(self):
...         self.__fa() #只会与自己所在的类为准,即调用_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from A
在继承中父类去隐藏自己的属性和方法

封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???

1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

class Teacher:
    def __init__(self,name,age):
        # self.__name=name
        # self.__age=age
        self.set_info(name,age)

    def tell_info(self):
        print('姓名:%s,年龄:%s' %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age,int):
            raise TypeError('年龄必须是整型')
        self.__name=name
        self.__age=age


t=Teacher('egon',18)
t.tell_info()

t.set_info('egon',19)
t.tell_info()
View Code

2:封装方法:目的是隔离复杂度

封装方法举例: 

1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。

2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!

3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性

class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

隔离复杂度的例子
View Code

3: 了解

python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的

其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点

python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶

3>>.类的多态:

一 什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)

多态性是指在不考虑实例类型的情况下使用实例

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同

多态性分为静态多态性和动态多态性

  静态多态性:如任何类型都可以用运算符+进行运算

  动态多态性:如下

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()

二 为什么要用多态性(多态性的好处)

其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?

1.增加了程序的灵活性

  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性

  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

>>> class Cat(Animal): #属于动物的另外一种形态:猫
...     def talk(self):
...         print('say miao')
... 
>>> def func(animal): #对于使用者来说,自己的代码根本无需改动
...     animal.talk()
... 
>>> cat1=Cat() #实例出一只猫
>>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao

'''
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
'''

三  鸭子类型

逗比时刻:

  Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

例2:其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()

len(s)
len(l)
len(t)

 

posted @ 2019-04-21 14:15  心动丶男孩  阅读(128)  评论(0编辑  收藏  举报