首页 > 技术文章 > 面向对象编程(2)

pyedu 2019-02-21 13:08 原文

一 面向对象特性之继承

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))

六 面向对象作业

学生管理系统

 

推荐阅读