• 面向对象编程(2)


    一 面向对象特性之继承

    1.1 基本继承语法

    面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。

    通过继承创建的新类称为子类派生类,被继承的类称为基类父类超类

    继承语法

    class 派生类名(基类名)
        ...
    # 无继承方式
    
    # class Dog:
    #     def run(self):
    #         print("running...")
    #
    #     def sleep(self):
    #         print("sleep...")
    #
    #     def toshetou(self):
    #         print("toshetou...")
    #
    # class Cat:
    #     def run(self):
    #         print("running...")
    #
    #     def sleep(self):
    #         print("sleep...")
    #
    #     def climb_tree(self):
    #         print("climb_tree...")
    
    
    # 继承方式
    
    class Animal:
    
        def run(self):
            print("running...")
    
        def sleep(self):
            print("sleep...")
    
    
    class Dog(Animal):
    
        def toshetou(self):
            print("toshetou...")
    
    class Cat(Animal):
    
        def climb_tree(self):
            print("climb_tree...")
    
    
    alex=Dog()
    alex.run()

    这里一定一定要注意查找变量的顺序!

    面试题:

    class Base:
        def __init__(self):
            self.func()
        def func(self):
            print('in base')
    
    class Son(Base):
        def func(self):
            print('in son')
    
    s = Son()

    1.2 多重继承

    如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。

    语法:

    派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

    class SubClassName (ParentClass1[, ParentClass2, ...]):
        ...

    多继承有什么意义呢?还拿上面的例子来说,蝙蝠和鹰都可以飞,飞的功能就重复定义了。

    class Animal:
    
        def run(self):
            print("running...")
    
        def sleep(self):
            print("sleep...")
    
    
    class Dog(Animal):
    
        def toshetou(self):
            print("toshetou...")
    
    class Cat(Animal):
    
        def climb_tree(self):
            print("climb_tree...")
    
    ##########################################

    class Eagle(Animal): def fly(self): print("fly...") class Bat(Animal): def fly(self): print("fly...")

    有同学肯定想那就放到父类Animal中,可是那样的话其他不会飞的动物还怎么继承Animal呢?

    所以,这时候多重继承就发挥功能了:

    class Fly:
        def fly(self):
            print("fly...")
    
    class Eagle(Animal,Fly):
        pass
    
    class Bat(Animal,Fly):
        pass

    是不是很棒呢

    补充

    说到多重继承,就不得不提一下c3算法了。mro即 method resolution order (方法解释顺序),主要用于在多继承时判断属性的路径(来自于哪个类)。

    在python2.2版本中,算法基本思想是根据每个祖先类的继承结构,编译出一张列表,包括搜索到的类,按策略删除重复的。但是,在维护单调性方面失败过(顺序保存),所以从2.3版本,采用了新算法C3。

    为什么采用C3算法

    C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。

    • 本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
    • 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。

    c3算法详细玩法

    二 面向对象特性之封装

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

    好处:将变化隔离、便于使用、提高重用性、提高安全性

    封装原则:将不需要对外提供的内容都隐藏起来、把属性都隐藏,提供公共方法对其访问。

    使用封装有三大好处:

     

    1、良好的封装能够减少耦合。

    2、类内部的结构可以自由修改。 

    3、可以对成员进行更精确的控制。

    4、隐藏信息,实现细节。

    2.1 私有化变量

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

    但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

    class Student(object):
    
        def __init__(self, name, score):
            self.name = name
            self.age = score
    
    alex=Student("alex",12)
    yuan=Student("yuan",34)
    
    
    alex.age=1000
    print(alex.age)

    如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

    class Student(object):
    
        def __init__(self, name, score):
            self.__name = name
            self.__age = score
    
    alex=Student("alex",12)
    yuan=Student("yuan",34)
    
    print(alex.__age)

    改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了。

    这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

    但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

    class Student(object):
    
        def __init__(self, name, score):
            self.__name = name
            self.__age = score
    
        def get_name(self):
            return self.__name
    
        def get_age(self):
            return self.__age
    
    alex=Student("alex",12)
    yuan=Student("yuan",34)
    
    print(alex.get_name())
    print(alex.get_age())

    如果又要允许外部代码修改age怎么办?可以再给Student类增加set_age方法:

    class Student(object):
    
        def __init__(self, name, score):
            self.__name = name
            self.__age = score
    
        def get_name(self):
            return self.__name
    
        def get_age(self):
            return self.__age
    
        def set_age(self,age):
            self.__age=age
    
    alex=Student("alex",12)
    print(alex.get_age())
    alex.set_age(1000)
    print(alex.get_age())

    你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

    class Student(object):
        ...
        def set_age(self,age):
            if isinstance(age,int) and 0 <= age <= 100:
                self.__age = age
            else:
                raise ValueError('bad age!')

    需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

    注意:

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

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

    3.单下划线、双下划线、头尾双下划线说明:

    • __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。
    • _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问。(约定成俗,不限语法)
    • __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

    2.2 私有化方法

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

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

    2.3 property属性

    什么是特性property

    property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

    例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
    
    成人的BMI数值:
    过轻:低于18.5
    正常:18.5-23.9
    过重:24-27
    肥胖:28-32
    非常肥胖, 高于32
      体质指数(BMI)=体重(kg)÷身高^2(m)
      EX:70kg÷(1.75×1.75)=22.86
    #####################################
    class People:
        def __init__(self,name,weight,height):
            self.name=name
            self.weight=weight
            self.height=height
        @property
        def bmi(self):
            return self.weight / (self.height**2)
    
    p1=People('egon',75,1.85)
    print(p1.bmi)
    import math
    class Circle:
        def __init__(self,radius): #圆的半径radius
            self.radius=radius
    
        @property
        def area(self):
            return math.pi * self.radius**2 #计算面积
    
        @property
        def perimeter(self):
            return 2*math.pi*self.radius #计算周长
    
    c=Circle(10)
    print(c.radius)
    print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
    print(c.perimeter) #同上
    '''
    输出结果:
    314.1592653589793
    62.83185307179586
    '''
    #################################################
    #注意:此时的特性area和perimeter不能被赋值
    c.area=3 #为特性area赋值
    '''
    抛出异常:
    AttributeError: can't set attribute
    '''

    为什么要用property

    将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现。

    class Foo:
        def __init__(self,val):
            self.__NAME=val #将所有的数据属性都隐藏起来
    
        @property
        def name(self):
            return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
    
        @name.setter
        def name(self,value):
            if not isinstance(value,str):  #在设定值之前进行类型检查
                raise TypeError('%s must be str' %value)
            self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
    
        @name.deleter
        def name(self):
            raise TypeError('Can not delete')
    
    f=Foo('egon')
    print(f.name)
    # f.name=10 #抛出异常'TypeError: 10 must be str'
    del f.name #抛出异常'TypeError: Can not delete'

    一个静态属性property本质就是实现了get,set,delete三种方法

    class Foo:
        @property
        def AAA(self):
            print('get的时候运行我啊')
    
        @AAA.setter
        def AAA(self,value):
            print('set的时候运行我啊')
    
        @AAA.deleter
        def AAA(self):
            print('delete的时候运行我啊')
    
    #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
    f1=Foo()
    f1.AAA
    f1.AAA='aaa'
    del f1.AAA
    #################################################

    class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA

    怎么用?

    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()
    print(obj.price)         # 获取商品价格
    obj.price = 200   # 修改商品原价
    print(obj.price)
    del obj.price     # 删除商品原价

    2.4  classmethod方法与staticmethod方法

    classmethod

    class Classmethod_Demo():
        role = 'dog'
    
        @classmethod
        def func(cls):
            print(cls.role)
    
    Classmethod_Demo.func()

    staticmethod

    class Staticmethod_Demo():
        role = 'dog'
    
        @staticmethod
        def func():
            print("当普通方法用")
    
    Staticmethod_Demo.func()

    练习:

    class Foo:
        def func(self):
            print('in father')
    
    
    class Son(Foo):
        def func(self):
            print('in son')
    
    s = Son()
    s.func()
    # 请说出上面一段代码的输出并解释原因?
    class A:
        __role = 'CHINA'
        @classmethod
        def show_role(cls):
            print(cls.__role)
    
        @staticmethod
        def get_role():
            return A.__role
    
        @property
        def role(self):
            return self.__role
    
    a = A()
    print(a.role)
    print(a.get_role())
    a.show_role()
    # __role在类中有哪些身份?
    # 以上代码分别输出哪些内容?
    # 这三个装饰器分别起了什么作用?有哪些区别?

    三 面向对象特性之多态

    3.1 归一化设计

    预备知识

    l=[123,456,789]
    info={"name":"alex","age":1000}
    s="hello"
    print(len(l))
    print(len(info))
    print(len(s))

    支付接口归一化

    ##################### 归一化设计 #####################
    
    # 支付宝 微信 银行卡 nfc支付
    
    class AliPay(object):
        def __init__(self,name,money):
            self.money=money
            self.name=name
        def pay(self):
            # 支付宝提供了一个网络上的联系渠道
            print('%s通过支付宝消费了%s元'%(self.name,self.money))
    
    class WeChatPay(object):
        def __init__(self,name,money):
            self.money=money
            self.name=name
        def pay(self):
            # 微信提供了一个网络上的联系渠道
            print('%s通过微信消费了%s元'%(self.name,self.money))
    
    def pay_func(pay_obj):
        pay_obj.pay()
    
    alipay=AliPay("alex",100)
    wechatpay=WeChatPay("yuan",200)
    
    pay_func(alipay)
    pay_func(wechatpay)

    3.2 规范化方法

    '''
    
    规范化方法
    支付宝 微信 银行卡 nfc支付
    同事协作之间的代码规范问题
    规定:Payment 就是一个规范类,这个类存在的意义不在于实现实际的功能,而是为了约束所有的子类必须实现pay的方法
    Payment : 抽象类
        pay = Payment() # 抽象类: 不能实例化
        抽象类主要就是作为基类/父类,来约束子类中必须实现的某些方法
        抽象类的特点:
            必须在类定义的时候指定metaclass = ABCMeta
            必须在要约束的方法上方加上@abstractmethod方法
    '''
    
    
    
    from abc import ABCMeta,abstractmethod #(抽象方法)
    
    class Payment(metaclass=ABCMeta):   # metaclass 元类  metaclass = ABCMeta表示Payment类是一个规范类
        def __init__(self,name,money):
            self.money=money
            self.name=name
    
        @abstractmethod      # @abstractmethod表示下面一行中的pay方法是一个必须在子类中实现的方法
        def pay(self,*args,**kwargs):
            pass
    
        @abstractmethod
        def back(self):
            pass
    
    class AliPay(Payment):
    
        def pay(self):
            # 支付宝提供了一个网络上的联系渠道
            print('%s通过支付宝消费了%s元'%(self.name,self.money))
    
    class WeChatPay(Payment):
    
        def pay(self):
            # 微信提供了一个网络上的联系渠道
            print('%s通过微信消费了%s元'%(self.name,self.money))
    
    def pay_func(pay_obj):
        pay_obj.pay()
    
    alipay=AliPay("alex",100)
    wechatpay=WeChatPay("yuan",200)
    
    pay_func(alipay)
    pay_func(wechatpay)

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

    3.3 多态的概念

    要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

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

    判断一个变量是否是某个类型可以用isinstance()判断:

    >>> isinstance(a, list)
    True
    >>> isinstance(b, Animal)
    True
    >>> isinstance(c, Dog)
    True 

    看来abc确实对应着listAnimalDog这3种类型。

    但是等等,试试:

    >>> isinstance(c, Animal)
    True

    看来c不仅仅是Dogc还是Animal

    不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!

    所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

    >>> b = Animal()
    >>> isinstance(b, Dog)
    False
    

    Dog可以看成Animal,但Animal不可以看成Dog

    所以,上面的支付的例子,如果我们再定义一个ApplePay类型,也从Payment类派生:

    class ApplePay(Payment):
        def pay(self):
           print('%s通过苹果支付消费了%s元'%(self.name,self.money))
    
    applepay=ApplePay("egon",800)
    '''
    你会发现,新增一个Payment的子类,不必对pay()做任何修改,实际上,任何依赖Payment作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
    
    多态的好处就是,当我们需要传入AliPay、WeChatPay、ApplePay……时,我们只需要接收Payment类型就可以了,因为AliPay、WeChatPay、ApplePay……都
    是Payment类型,然后,按照Payment类型进行操作即可。由于Payment类型有pay()方法,因此,传入的任意类型,只要是Payment类或者子类,就会自动调用实
    际类型的pay()方法,这就是多态的意思: 对于一个变量,我们只需要知道它是Payment类型,无需确切地知道它的子类型,就可以放心地调用pay()方法,而具体调用的pay()方法是作用在AliPay、WeChatPay、
    ApplePay哪个类对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Payment的子类时,只要确保pay()方
    法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则: 对扩展开放:允许新增Payment子类; 对修改封闭:不需要修改依赖Payment类型的pay()等函数。
    '''

    3.4 鸭子类型

    class CardPay(object):
        def __init__(self,name,money):
            self.money=money
            self.name=name
        def pay(self):
           print('%s通过银联卡支付消费了%s元'%(self.name,self.money))
    
    def pay_func(pay_obj):
        pay_obj.pay()
    
    cp=CardPay("alvin",1000)
    pay_func(cp)

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

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

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

    四 反射

    反射函数

    getattr(obj, name[, default]) : 访问对象的属性。
    hasattr(obj,name) : 检查是否存在一个属性。
    setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
    delattr(obj, name) : 删除属性。  

     当操作属性是一个字符串时,不能在通过句点符进行操作,这时候,反射就可以发挥功能了!

    class Animal(object):
        role="person"
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def run(self):
            print("%s is running"%self.name)
    
    # alex=Animal("alex",34)
    # print(alex.name)
    # print(alex.age)
    # alex.run()
    
    ########################################
    alex=Animal("alex",34)
    while 1:
        attr=input("查询alex的什么属性>>>")
        if hasattr(alex,attr):
            print(getattr(alex,attr))
        else:
            print("alex没有该属性")
    
    # 当然类对象也可以反射
    print(getattr(Animal,"role"))
    
    # 模块也可以反射
    from sys import modules
    print(modules[__name__])
    print(getattr(modules[__name__],"Animal"))

    反射应用

    class FTP(object):
    
        def __init__(self):
            self.run()
    
        def run(self):
           print('''
               提示:
                   上传:   put 路径/文件名称
                   下载:   get 路径/文件名称
    
           '''
           )
           while 1:
               input_str=input(">>>")
               action,params=input_str.split(" ")
               if hasattr(self,action):
                   getattr(self,action)()
               else:
                   print("不存在该方法")
    
        def put(self):
            print("上传...")
        def get(self):
            print("下载...")
    
    
    ftp=FTP()

    五 类的魔法方法

    # 1 初始化方法:__init__
    class A(object):
        def __init__(self):
            print("初始化执行方法")
    A()
    
    # 2 构造方法:__new__
    class B(object):
    
        def __new__(cls,*args,**kwargs):
            print("我是用来开辟一块空间的")
            obj=super().__new__(cls)
            return obj
    
        def __init__(self):
            print("self就是__new__开辟的空间地址")
    
    B()
    
    # 应用:比如单例模式
    
    # 3 __str__
    
    class C(object):
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __str__(self):  # 必须返回字符串类型
            return self.name
    
    c1=C("c1",20)
    c2=C("c2",23)
    print(c1)
    print(c2)
    
    # 4 __call__
    class D(object):
    
        def __call__(self, *args, **kwargs):
            print("call 被调用...")
    
    print(callable(D))
    d1=D()
    print(callable(d1))
    d1()
    
    # 5 析构方法 __del__
    
    class F(object):
        def __del__(self):
            print("删除对象时被调用!")
    
    f=F()
    # del f # 思考:注释掉为什么也会调用__del__
    # import time
    # time.sleep(100)
    
    # 应用
    
    class Filehandler(object):
        file="a.text"
        def __init__(self):
            self.f=open(self.file)
    
        def __del__(self):
            self.f.close()
    
    
    
    # 6 __getitem__
    
    
    class G(object):
        def __init__(self):
            pass
    
        def __getitem__(self,item):
            print("__getitem__被调用")
    
        def __setitem__(self, key, value):
            print("__setitem__被调用")
    
        def __delitem__(self, key):
            print("__delitem__被调用")
    
    g=G()
    g["name"]="alex"
    print(g["name"])
    del g["name"]
    
    # 7 __getattr__
    
    class H(object):
        def __init__(self):
            pass
    
        def __getattr__(self, item):
            print("__getattr__被调用")
    
        def __setattr__(self, key, value):
            print("__setattr__被调用")
    
        def __delattr__(self, item):
            print("__delattr__被调用")
    
    h=H()
    h.name="alex"
    print(h.name)
    del h.name
    
    
    # 8 __eq__
    
    class I(object):
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __eq__(self, other):
    
            if self.name==other.name and self.age==other.age:
                return True
            else:
                return False
    
    i1=I("alex",30)
    i2=I("alex",30)
    print(i1==i2)
    
    
    # __len__
    
    class G(object):
        def __len__(self):
            return 100
    
    g=G()
    print(len(g))

    六 面向对象作业

    学生管理系统

  • 相关阅读:
    51nod 1081 子段求和
    51nod 1085 背包问题
    51nod 1012 最小公倍数LCM
    51nod 1046 A^B Mod C
    51nod 1057 N的阶乘
    死锁 必然
    two-sum
    一些基本定义
    常用命令
    python_99_面向对象多态
  • 原文地址:https://www.cnblogs.com/pyedu/p/10411808.html
Copyright © 2020-2023  润新知