面向对象(一)

面向过程跟面向对象:

  一直写的代码都是属于面向过程的代码,写完以后不会轻易改变的。

    优点:极大降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

    缺点:一套流水线或流程就是用来解决一个问题,代码牵一发而动全身。

  面向对象的代码可以是经常变化的,类似游戏,数据经常会发生改变。

    优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

    缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一量开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。

类和对象:

  类:具有相同特征的一类事物(人、狗、老虎)

  对象/实例:具体的某一个事物(隔壁老王)

  实例化:类---->对象的过程

类:

  类的语法:

class Person:        #定义一个类,类一般首字母大写,用class定义
    role = 'person'     #人的角色属性都是人
    def walk(self):     # 人都可以走路,也就是有一个走路方法,也叫动态属性 
        print('person is walking...')

  类的两种作用:

    属性引用:(类名.属性)   

class Person:   #定义一个人类
    role = 'person'  #人的角色属性都是人
    def walk(self):  #人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  #查看人的role属性
print(Person.walk)  #引用人的走路方法,注意,这里不是在调用

    实例化:(__init__函数) 实例化就是类--->对象的过程

class Person:   #定义一个人类
    role = 'person'  #人的角色属性都是人
    def __init__(self,name):
        self.name = name  # 每一个角色都有自己的昵称;
        
    def walk(self):  #人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  #查看人的role属性
print(Person.walk)  #引用人的走路方法,注意,这里不是在调用

    查看属性和调用方法

print(egg.name)     #查看属性直接 对象名.属性名
print(egg.walk())   #调用方法,对象名.方法名()

    类属性的补充: 

一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值

二:特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
特殊类

    人狗大战小游戏:

class Person:
    def __init__(self,name,hp,aggr):
        self.name = name
        self.hp   = hp
        self.aggr = aggr
    def attack(self,dog):
        dog.hp -= self.aggr
        if dog.hp > 0:
            print('%s发起攻击,%s受到%s伤害'%(self.name,dog.name,self.aggr))
        else:
            print('%s发起攻击,%s受到%s伤害,%s已经死亡' % (self.name, dog.name, self.aggr, dog.name))

class Dog:
    def __init__(self,name,hp,aggr):
        self.name = name
        self.hp   = hp
        self.aggr = aggr
    def bite(self,person):
        person.hp -= self.aggr
        if person.hp >0:
            print('%s发起攻击,%s受到%s伤害'%(self.name,person.name,self.aggr))
        else:
            print('%s发起攻击,%s受到%s伤害,%s已经死亡'%(self.name,person.name,self.aggr,person.name))

dd = Dog('二哈',100,5)
men = Person('Saber',100,1000)
men.attack(dd)

  类命名空间与对象、实例的命名空间 

    创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性

    而类有两种属性:静态属性和动态属性

      静态属性就是直接在类中定义的变量

      动态属性就是定义在类中的方法

    其中类的数据属性是共享给所有对象的

>>>id(egg.role)
4341594072
>>>id(Person.role)
4341594072

    类的动态属性是绑定到所有对象的

>>>egg.attack
<bound method Person.attack of <__main__.Person object at 0x101285860>>
>>>Person.attack
<function Person.attack at 0x10127abf8> 

 创建一个对象实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性

 地obj.name会先从obj自己的名称空间里找name,找不到则云类中找,类也找不到就找父类...最后都找不到就抛出异常。

面向对象的组合

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

  练习:计算圆环的周长和圆环的面积

from math import pi
class Circle:
    def __init__(self,radius):
        self.radius = radius
    def area(self):
        return pi * self.radius * self.radius
    def perimeter(self):
        return 2 * pi * self.radius

circle = Circle(10)
areal = circle.area()
per1 = circle.perimeter()
print(areal,per1)

class Ring:
    def __init__(self,outside,inside):
        self.outside = Circle(outside)
        self.inside = Circle(inside)
    def area(self):
        return self.outside.area() - self.inside.area()
    def permieter(self):
        return self.outside.area() + self.inside.area()
ring = Ring(10,5)
print(ring.permieter())
print(ring.area())

面向对象的三大特性:

一、继承:

  继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。

  python中类的继承分为:单继承和多继承

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

查看继承

>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。

继承与抽象(先抽象再继承):

  抽象即抽取类似或者比较像的部分。

  继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

继承与重用性:

  在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,这就用到了类的继承的概念。

  通过继承的方式新建类B,让B继承A,B会遗传A的所有属性(数据属性和函数属性),实现代码重用

class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的昵称;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    pass

class Person(Animal):
    pass

egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
egg.eat()
ha2.eat()

注:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大减少了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就缩短了软件开发周期,对大型软件开发来说,意义重大。

派生:

  子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一量重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

  在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是调用普通函数的方式,即类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值。

  在python3中,子类执行父类的方法可以直接用super方法查看。

class A:
    def hahaha(self):
        print('A')

class B(A):
    def hahaha(self):
        super().hahaha()
        #super(B,self).hahaha()
        #A.hahaha(self)
        print('B')

a = A()
b = B()
b.hahaha()
super(B,b).hahaha()
super

抽象类与接口类:

  接口类:

    继承有两种用途:

      一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)

      二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

class Alipay:
    '''
    支付宝支付
    '''
    def pay(self,money):
        print('支付宝支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)


def pay(payment,money):
    '''
    支付函数,总体负责支付
    对应支付的对象和要支付的金额
    '''
    payment.pay(money)


p = Alipay()
pay(p,200)

  抽象类:

    什么是抽象类:

    与java一样,python也有抽象类的概念但是同样需要借用模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。

    为什么要有抽象类:

    如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来,内容包括数据属性和函数属性。

#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
抽象类

抽象类与接口类:

  抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。

  抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。

  在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,也只可以用。

1.多继承问题:

  在继承抽象类的过程中,尽量避免多继承:

  而在继承接口的时候,我们反而鼓励多继承接

  接口隔离原则:使用多个专门的接口,而不使用单一的总接口。即客户端不依赖那些不需要的接口。

2.方法的实现

  在抽象类中,我们可以对一些抽象方法做出基础实现,

  而在接口类中,任何方法只是一种规范,具体的功能需要子类实现。

钻石继承

  继承顺序

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类
继承顺序

继承原理

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

>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

 

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类

 继承小结 :

继承的作用:

  减少代码的重用,提高代码可读性,规范编程模式

3个名词:

  抽象:抽象即抽取类似或者说比较像的部分。是一个从具体到抽象的过程

  继承:子类继承了父类的方法和属性

  派生:子类在父类方法和属性的基础上产生了新的方法和属性

 抽象类与接口类  

python中没有接口类,有抽象类,抽象类:abc模块中的metaclass = ABCMeta,@abstractmethod

本质是做代码规范用的,希望子类中实现和父类方法名字完全一样的方法

在java的角度上看是有区别的:

  java本来就支持单继承,所以就有了抽象类。

  java没有多继承,所以为了接口隔离原则,设计了接口这个概念,支持多继承。

python既支持单继承也支持多继承,所以对于接口类和抽象类的区别就不那么明显了

甚至在python中没有内置接口类 

  1.多继承问题

    在继承抽象类的过程中,我们应该尽量避免多继承,而在继承接口的时候,我们反而多继承接口。

  2.方法的实现

    在抽象类中,我们可以对一些抽象方法做出基础实现,而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现。

  

钻石继承

  新式类:广度优先,经典类:深度优先

二、多态

多态指的是一类事物有多种形态,python天生支持多态。

from abc import abstractmethod,ABCMeta
class Animal(metaclass=ABCMeta):
    @abstractmethod
    def talk(self):
        pass
class Dog(Animal):
    def talk(self):
        print('say wangwang')
class Pig(Animal):
    def talk(self):
        print('say pipi')

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

在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!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崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

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

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

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

例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

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

    def write(self):
        pass

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

例子

 三、封装

封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处:

  1.将变化隔离

  2.便于使用

  3.提高复用性

  4.提高安全性

封装原则:

  1.将不需要对外提供的内容都隐藏起来

  2.把属性都隐藏,提供公共方法对其访问

私有变量和私有方法

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

私有变量:

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__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是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

封装的特点:

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

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

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

封装的问题:

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

2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形

3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

#正常情况
>>> 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

 

property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

from math import pi
class Circle:
    def __init__(self,r):
        self.r = r
    @property
    def perimeter(self):
        return 2*pi*self.r
    def area(self):
        return self.r**2*pi
c1 = Circle(5)
print(c1.area())
print(c1.perimeter)

区别:加上@property以后在外面调用时不用再加()了,就不会认为perimeter是一个函数。

面对对象的封装有三种方式:

  1.public:这种就是不封装,是对外公开的

  2.protected:这种封装方式对外不公开,但对朋友或子类公开

  3.private:这种封装对谁都不公开

property:修改、删除。(setter、deleter)

class Goods:

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price


obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
print(obj.price)
del obj.price     # 删除商品原价

 

method方法

  staticmathod  静态的方法

  classmethod  类的方法

 

classmethod:

把一个方法变成一个类中的方法,这个方法就直接可以被类调用 ,不需要依托任何对象(cls)。

class Goods:
    __discount = 0.8
    def __init__(self,price):
        self.__price = price
    @property
    def price(self):
        return self.__price * Goods.__discount
    @classmethod
    def change(cls,new_price):
        cls.__discount = new_price
apple = Goods(5)
print(apple.price)
Goods.change(0.1)
print(apple.price)

staticmethod

在完全面向对象的程序中,如果一个函数既和对象没有关系也和类没有关系,可以用这个将函数变成一个静态方法。

class Login:
    def __init__(self,name,password):
        self.name = name
        self.password = password
    def login(self):
        pass
    @staticmethod
    def get_usr_pwd():
        usr = input('用户名:')
        pwd = input('密码:')
        Login(usr,pwd)
Login.get_usr_pwd()

 

posted @ 2018-01-15 15:53  2009_boy  阅读(145)  评论(0编辑  收藏  举报