python13-面向对象进阶

Posted liangjiongyao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python13-面向对象进阶相关的知识,希望对你有一定的参考价值。

一、多态

定义

一切皆对象,不同的对象可以调用相同的方法,实现的过程不一样。但是该方法必须有意义,符合子类实例化后的对象的实际情况,也就是一定会被子类或者子类的实例调用

 

预热:
l1=[1,2,3]
str1="ljytest"
len(l1)的结果等于l1.__len__()的结果
len(str1)的结果等于str1.__len__()的结果
str类和list类都由type产生,而str1和l1是由不同的类产生的,但是都能调用__len__()方法,这就是一种多态。
其实len(l1)本质是调用__len__()方法实现的

 

下面再来举一个生活中的例子:

技术分享图片
class H2O: #水、冰、水蒸气都是由氢元素和氧元素,模仿list和str的元类type
    def __init__(self,name,temperature):
        self.name=name
        self.temperature=temperature
    def translation(self):
        if self.temperature < 0:
            print([%s]温度太低结冰了 %self.name)
        elif self.temperature > 0 and self.temperature < 100:
            print([%s]液化成水 %self.name)
        elif self.temperature > 100:
            print([%s]温度太高变成了水蒸气 %self.name)


class Water(H2O):
    pass
class Ice(H2O):
    pass
class Steam(H2O):
    pass

#实例化对象
w1=Water(,25)
i1=Ice(,-20)
s1=Steam(蒸汽,3000)

#Part1
#三个不同的对象执行相同的方法,得到不同的结果,调用方法的形式和效果就跟l.__len__()、str1.__len__()一样
w1.translation()
i1.translation()
s1.translation()
View Code
技术分享图片
class H2O: #水、冰、水蒸气都是由氢元素和氧元素,模仿list和str的元类type
    def __init__(self,name,temperature):
        self.name=name
        self.temperature=temperature
    def translation(self):
        if self.temperature < 0:
            print([%s]温度太低结冰了 %self.name)
        elif self.temperature > 0 and self.temperature < 100:
            print([%s]液化成水 %self.name)
        elif self.temperature > 100:
            print([%s]温度太高变成了水蒸气 %self.name)


class Water(H2O):
    pass
class Ice(H2O):
    pass
class Steam(H2O):
    pass

#实例化对象
w1=Water(,25)
i1=Ice(,-20)
s1=Steam(蒸汽,3000)

#Part2
#如何去实现len(l)、len(str)这种效果?在外部定义一个函数
def func(obj):
    obj.translation()

func(w1)  #本质在运行w1.translation()
func(i1)  #本质在运行i1.translation()
View Code

 

多态是由继承的产生的,它是继承的一种体现方式。而类的继承有两层意义,分别是"改变"和"扩展",多态就是类的这两层意义的一个具体的实现机制,即不同的对象可以调用相同

的方法,实现的过程不一样,python中的标准类型就是多态的概念的一个很好的示范。

继承给子类的方法,子类必须能用起来,子类怎么用这些方法,就是通过多态。如果你的父类实现了一个毫无意义的方法(如在HH2O类里面实现一个aaa方法,然后print("aaa")),

子类根本都不会去调用的,又谈何实现过程不一样?所以父类定义的方法,必须要考虑到子类是否会去调用。

记住:不能为了继承而继承

 

二、封装

什么是封装?"装"很容易理解,就是一个能装东西的容器,在类中就是属性字典,而"封"就是隐藏的意思,你把麻袋封口了,别人就不知道里面装的是啥

 

封装的三个层次

 第一个层次:作为一个使用者,我只管调用,不关心该方法如何实现

test.py文件中调用fengzhuang.py文件的People类

fengzhuang.py

技术分享图片
class People:
    star=earth
    def __init__(self,id,name,age,salary):
        self.id=id
        self.name=name
        self.age=age
        self.salary=salary

    def get_id(self):
        print(我是私有方法啊,我找到的id是[%s] %self.id)
View Code

test.py

技术分享图片
#test.py文件
from fengzhuang import People

p1=People(123123123123,alex,18,100000000)
p1.get_id() #能调用
print(p1.star) #能调用
View Code

 

第二个层次:下划线开头的属性

单下划线

技术分享图片
#单下划线
class People:
    _star=earth
    def __init__(self,id,name,age,salary):
        self.id=id
        self.name=name
        self.age=age
        self.salary=salary

    def get_id(self):
        print(我是私有方法啊,我找到的id是[%s] %self.id)


p1=People(442235,alex,18,100000000)
print(p1._star) 
#能访问,我擦又说是私有属性外部访问不了?注意,这个只是python约定,并没有真正限制。只是作为使用者,你不应该调用这种带单下划线的属性。
View Code

双下划线

技术分享图片
#双下划线
class People:
    __star=earth
    def __init__(self,id,name,age,salary):
        self.id=id
        self.name=name
        self.age=age
        self.salary=salary

    def get_id(self):
        print(我是私有方法啊,我找到的id是[%s] %self.id)


p1=People(442235,alex,18,100000000)
print(p1.__star) #访问报错
print(People.__dict__)#字典里面该属性名字为"_People__star"
print(p1._People__star) #双下划__开头的属性会被python重命名,变成:_类名__变量名
View Code

 

第三个层次:明确区分内外

技术分享图片
class People:
    __star=earth111111111111
    def __init__(self,id,name,age,salary):
        print(----->,self.__star)
        self.id=id
        self.name=name
        self.age=age
        self.salary=salary

    def get_id(self):
        print(我是私有方法啊,我找到的id是[%s] %self.id)

    #访问函数
    def get_star(self):
        print(self.__star)



p1=People(123123123123,alex,18,100000000)
print(People.__dict__)
print(p1.__star) #如果你知道属性字典里面该属性叫什么名字,你当然可以这样调用,可如果你不知道呢?
p1.get_star() #调用类提供的访问函数
View Code

 

关于第三点我觉得有必要说明一下

为了让外部能够访问内部私有属性,我们在内部定义一个接口函数供外部调用去访问这些私有属性。
既然可以通过接口函数去访问私有属性,那么有些人可能就会把所有属性都会设置成私有属性。比如,一间房子的长宽高,这种本来就经常调用的属性,如果你一开始将它设置成私有属性,那别人调用长宽高去计算你房子的面积和体积时,你就必须给别人开一个接口函数。每开一个接口函数就相当于多暴露一层,到时候你的程序就会千仓百孔。当然工资这种事情,也不方便公开讨论的,所以你可以设置成私有属性,如果别人要调用,那就只能给他一个接口,这种是符合实际的。

所以,我感觉python没有严格限制外部访问私有属性是怕有些人滥用,不能为了封装而封装,你必须要符合实际情况。

 

三、面向对象相关概念

面向对象的好处:
1.通过封装明确内外。上帝造物,但是你并知道上帝是怎么造物的
2.通过继承+多态在语言层面支持了归一化设计


注意:不用面向对象也可以做封装、归一化设计

概念总结:
抽象:指对现实世界问题和实体的本质表现,行为和特征建模
现实:对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装:对数据/信息进行隐藏,在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。
接口:本质是函数,提供接口和访问函数以便外部访问内部的数据属性
组合:类与类之间没有太多共性,甚至没有共性,但是这些类合起来能构成一个完整的系统,彼此是“有一个”的关系
派生:描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义
继承:述了子类属性从祖先类继承这样一种方式
泛化(只继承:表示所有子类与其父类及祖先类有一样的特点
特化(子类定义与父类重名的属性):描述所有子类自定义的、让它与其祖先类不同的属性
多态:指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性:指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类冰、水蒸气、都继承于水,它们都有一个同名的方法就是。变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样

 

四、反射

实现反射的四个函数:hasattr、getattr、setattr、delattr,均适用于实例和类

hasattr:判断object中有没有一个name字符串对应的方法或属性
hasattr(obj,str)

setattr:设置object的字符串key的值为value。相当于obj.key=value,用于修改或者新增属性,可设置方法。
setattr(obj,key,value)

getattr:获取object中一个叫name的字符串对应的方法或属性,返回对象的某个属性,函数则返回函数地址,没有则报错,相当于对象.属性
getattr(obj,str,[default])

delattr:删除object中一个叫name的字符串对应的方法或属性,相当于 del obj.属性名
delattr(obj.str)

技术分享图片
class FatBoss:
    feture="sly" #sly是狡猾的意思
    def __init__(self,name,weight):
        self.name=name
        self.weight=weight

    def hire_people(self):
        print("[%s]的团队正在招人,傻逼才去" %self.name)

    def drink_wine(self):
        print("[%s]叫你去喝酒,傻逼才去" %self.name)

print(hasattr(FatBoss,feture)) #打印FatBoss是否有"feture"属性

b1=FatBoss("little_tiger","200kg")
print(b1.name) #相当于print(b1.__dict__[‘name‘])
print(b1.__dict__) #{‘name‘: ‘Dhz‘, ‘weight‘: ‘200kg‘}

#hasattr
print(hasattr(b1,"name")) #返回True
print(hasattr(b1,"hire_people")) #返回True
print(hasattr(FatBoss,"hire_people")) #返回True
print(hasattr(b1,selasdfasdfsadfasdfasdfasdfasdl_hourse)) #返回False

#getattr
print(getattr(b1,"name")) #little_tiger
print(getattr(b1,"drink_wine")) #返回<bound method FatBoss.drink_wine of <__main__.FatBoss instance at 0x025B0C88>>
func=getattr(b1,"drink_wine")
func() #[little_tiger]叫你去喝酒,傻逼才去

#setattr
setattr(b1,"BigSB",True) #设置实例的属性
setattr(b1,"name","sb") #修改实例的属性
setattr(FatBoss,"feture","fat") #修改类的属性
print(b1.__dict__)#{‘BigSB‘: True, ‘name‘: ‘sb‘, ‘weight‘: ‘200kg‘}
print(FatBoss.__dict__)#‘feture‘: ‘fat‘

setattr(b1,"func",lambda x:x+1)
setattr(b1,func1,lambda self:self.name+sb)
print(b1.func) #返回lambda函数内存地址
print(b1.func(10)) #返回11
print(b1.func1(b1))#完成字符串拼接,返回sbsb

#delattr
delattr(b1,"name")
print(b1.__dict__) #没有了name
View Code

 

反射好处一:事先定义好接口,接口只有在被完成后才会真正执行,实现即插即用(一种‘后期绑定’)。也就是说你可以事先把主要的逻辑写好(只定义接口),到后期再去实现接口的功能

比如说,现在你和杨老细要一起开发一个FTP客户端,杨老细负责客户端的上传下载功能,你负责其他功能。但是杨老细没有写完代码就请假去嫖了,此时你又赶进度
你不可能也停下来等他回来写好逻辑你再写吧?这时就要用到反射了。

 

杨老细的代码

技术分享图片
class FtpClient:
    ftp客户端,但是还么有实现具体的功能
    def __init__(self,addr):
        print(正在连接服务器[%s] %addr)
        self.addr=addr
    def put(self):
        print(正在上传文件)
View Code

 

你的代码

技术分享图片
from ftp_client import FtpClient

f1=FtpClient(1.1.1.1)
# f1.put() 如果FtpClient里面已经写好该方法,那么调用的话就不会报错,可如果还没写好,难道你这边的代码也要因此而耽搁吗?
# 所以,如果你事先知道put方法没写好,那么你可以在你的程序里面用反射进行判断,如果还没写好,那就跳过这段代码,先实现其他逻辑
# 等到put方法写好了,反射判断为True,就会执行,只要你事先写好了hasattr的逻辑

if hasattr(f1,put):
    func_get=getattr(f1,put)
    func_get()
else:
    print(其他的逻辑)
View Code

 

反射的好处二:动态导入模块

如果你导入的模块是字符串咋办?如下例子:m1是包名,t是m1下的模块,t模块里面有test1函数,_test2函数是私有属性

技术分享图片

技术分享图片
module_t=__import__(m1.t)
print(module_t)#你想导入的是模块t,但是导入的结果是最顶端的模块m
module_t.t.test1() #只导入了m1包,所以,还是要module_t.t.test()这样调用

from m1.t import *
test1()#能运行
_test2() #不能运行

from m1.t import test1,_test2
test1()#能运行
_test2()#能运行

import  importlib
m=importlib.import_module(m1.t)#该方法能导入你只想导入的模块,python官方推荐
print(m)#导入的是t
m.test1() #能运行
m._test2() #能运行
View Code

 

 五、__getattr__、__setattr__、__delattr__

类内置的方法,你不定义,系统默认就有,你定义了,就用你的

 

__getattr__:调用对象不存在的属性时才触发执行,比较常用
__delattr__:删除一个对象属性就会触发,可用于控制属性删除,不常用
__setattr__:设置属性时触发,可用于控制使用者设置的属性类型,不常用。比如,要求使用者设置的属性必须是字符串类型,不能是其他类型

技术分享图片
class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print(执行__getattr__)

f1=Foo(10)
print(f1.y)
print(getattr(f1,y)) #你以为这会跟len(str)--->str.__len__()的效果一样,但getattr(f1,‘y‘)的效果并不等同于f1.__getattr__(‘y‘)
f1.sssssssssssssssssssssssssssssssssssss #触发了__getattr__

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __setattr__(self, key, value):
        print(__setattr__执行)
        # self.key=value 实例化调用self.y=y就会触发__setattr__,而__setattr__下面又是self.key=value,又再次触发__setattr__,如此递归下去...
        self.__dict__[key]=value #正确的姿势

f1=Foo(10)
print(f1.__dict__)#设置进去了
f1.z=2
print(f1.__dict__) #设置进去了

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __delattr__(self, item):
        print(删除操作__delattr__)
          del self.item 这是无限递归,因为del self.item会触发__delattr__,如此循环往复,直到溢出

f1=Foo(10)
del f1.y #触发了__delattr__
del f1.x #触发了__delattr__
View Code

 

__getattr__、__setattr__、__delattr__的简单应用:

技术分享图片
class Foo:
    def __init__(self,name):
        self.name=name

    def __getattr__(self, item):
        print("__getsttr__")

    def __setattr__(self,k,v):
        print("执行__setattr__",k,v) #k是name,v是ljy
        if type(v) is str:
            print("开始设置")
            self.__dict__[k]=v.upper()
        else:
            print("必须是字符串类型")
    def __delattr__(self, item):
        if (type(self.__dict__[item])) is str:
            self.__dict__.pop(item)
        else:
            print(不允许删除属性%s %item)

f1=Foo("ljy") #触发__setattr__
f1.age=18 #触发__setattr__,但是数字不能设置
print(f1.__dict__)

del f1.name #触发__delattr__
print(f1.__dict__) #删除了name
View Code

 

二次加工标准类型数据

技术分享图片
class List(list):
    def append(self,p_object):
        if type(p_object) is str:
            # self.append(p_object)你自己没有append方法
            super().append(p_object) #继承父类的append方法
        else:
            print("只能添加字符串类型")

    def show_middle(self):
        mid_index=int(len(self)/2)
        return self[mid_index]

l1=list("hello")
print(l1)

l2=List("hello")
print(l2)

print(l2.show_middle())#返回l
l2.append(111111) #追加不成功
l2.append("SB") #追加成功
print(l2)
View Code

 

六、个人定制

二次加工包装标准类型,通过继承和派生的的概念,定制自己的数据类型。

技术分享图片
class List(list):
    def append(self, p_object): #p_object是追加的字符串
        if type(p_object) is str:
            # self.append(p_object)用自己的会无限递归
            super().append(p_object) #list.append(self,p_object),调自己的会出现无限递归,那我就用方法
        else:
            print(只能添加字符串类型)

    def show_midlle(self):
        mid_index=int(len(self)/2)#取得中间元素的下标
        return self[mid_index]


# l2=list(‘hell oworld‘)
# print(l2,type(l2))

l1=List(helloworld)
# print(l1,type(l1))
# print(l1.show_midlle())
l1.append(1111111111111111111111)
l1.append(SB)
print(l1)
View Code

 

授权

授权的关键点就是覆盖原有的__getattr__方法,授权不是通过继承来做的

技术分享图片
import time
class FileHandle:
    def __init__(self,filename,mode=r,encoding=utf-8):
        # self.filename=filename
        self.file=open(filename,mode,encoding=encoding) #这里用真正的open打开一个文件,赋值给一个self.file,相当于平时的f=open(...)
        self.mode=mode 
        self.encoding=encoding

    def __getattr__(self, item):
        # print(item,type(item)) item是传进来的属性名(字符串类型),在这里是read
        # self.file.read,self.file具有文件操作的一切方法
        return getattr(self.file,item)

f1=FileHandle(a.txt,w+)
print(f1.file) #open的一个文件句柄
print(f1.__dict__)#file属性对应的是一个文件句柄
print(==>,f1.read) #触发__getattr__,为什么会触发__getattr__?因为f1的字典里面没有read方法,FileHandle类里面也没有,那就肯定触发__getattr__
# 我们的目的就是想要触发__getattr__,利用__getattr__完成授权
print(f1.write) #写方法也如此,最后肯定触发__getattr__,找到里面的self.file
print(f1.write("11111\\n"))
f1.seek(0) #道理同上
print(f1.read())

#self.file=open(filename,mode,encoding=encoding)就相当于sys_f=open("b.txt","w+"),print(getattr(sys_f,"read")),sys_f这个文件句柄里面有read方法


自定制一个write方法,每一行的开头都加时间
class FileHandle:
    def __init__(self,filename,mode=r,encoding=utf-8):
        # self.filename=filename
        self.file=open(filename,mode,encoding=encoding) #这里用真正的open打开一个文件,赋值给一个self.file
        self.mode=mode 
        self.encoding=encoding
    def write(self,line):
        print(------------>,line)
        t=time.strftime(%Y-%m-%d %X)
        self.file.write(%s %s %(t,line))

    def __getattr__(self, item):
        # print(item,type(item))
        # self.file.read
        return getattr(self.file,item)

f1=FileHandle(a.txt,w+)
f1.write(1111111111111111\\n) #不触发__getattr__,因为类里面有了
f1.write(cpu负载过高\\n)
f1.write(内存剩余不足\\n)
f1.write(硬盘剩余不足\\n)
f1.seek(0)
print(--->,f1.read())
View Code

 

















































以上是关于python13-面向对象进阶的主要内容,如果未能解决你的问题,请参考以下文章

python13-面向对象进阶

python-前方高能-面向对象-进阶3

python-面向对象进阶

python-面向对象进阶

Python基础-week06 面向对象编程进阶

python学习笔记-面向对象进阶&异常处理