第8天,面向对象进阶

第8天,面向对象进阶

@(python)[笔记]

目录

一、isinstance()和issubclass()
    1. isinstance()
    2. issubclass()

二、反射
    1. hasattr()
    2. getattr()
    3. setattr()
    4. delattr()
    5. 扩展用法

三、__setattr__、__delattr__、__getattr__
四、二次加工标准类型(包装)
    授权
五、__next__和__iter__实现迭代器协议
六、__doc__
七、__module__和__class__
八、__str__
九、__del__析构方法
十、__setitem__,__getitem__,__delitem__
十一、__enter__和__exit__
十二、__call__
十三、元类metaclass
    先来看看exec
    未完

一、isinstance()和issubclass()

1. isinstance()

语法:isinstance(obj,cls)
功能:检查对象obj是否是类cls的实例

class foo:
    pass
obj = foo()
print(isinstance(obj,foo))  #True

2. issubclass()

语法:issubclass(sub, super)
功能:检查sub类是否是super类的派生类(子类)

class foo:
    pass
obj = foo()
print(isinstance(obj,foo))  #True

class bar(foo):
    pass
print(issubclass(bar,foo))  #True

二、反射

反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,也可以称为自省

python面向对象中的反射是指通过字符串的形式操作对象相关的属性。python中的一切事物都是对象,都可以使用反射。

python中可以实现自省的四个函数:

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

1. hasattr()

语法:hasattr(object,"name")
功能:判断object中有没有一个name字符串对应的方法或属性。
用法:

class foo:
    name = "egon"
    pass

obj = foo()
print(hasattr(obj,"name"))  #True
print(hasattr(obj,"func"))  #True
print(hasattr(foo,"name"))  #True
print(hasattr(foo,"func"))  #True

2. getattr()

语法:getattr(object, "name"[, default=None])
功能:从对象object获取一个name字符串对应的方法,如果没有name字符串对应的方法,并且没有设置default,则报错;如果没有name字符串对应的方法,设置了default,则将default的值返回。getattr(object,"name")等价于object.name

class foo:
    name = "egon"
    def func(self):
        print("Hello world")

obj = foo()
print(getattr(obj,"func"))
    #<bound method foo.func of <__main__.foo object at 0x000000000112ACC0>>
    #可以看出,输出结果是一个绑定方法

f1 = getattr(obj,"func")
f2 = getattr(obj,"bar","不存在")
f1()   #Hello world
print(f2)  #不存在

3. setattr()

语法:setattr(x, y, v)
功能:设置属性,setattr(x,'y',v)等价于“x.y = v
用法:

class foo:
    name = "egon"
    def func(self):
        print("Hello world")

obj = foo()
setattr(obj,"age",20)
setattr(obj,"show_name",lambda self:self.name+"_NB")
print(obj.__dict__)   #查看属性字典
print(obj.show_name(obj))

'''
输出:
{'age': 20, 'show_name': <function <lambda> at 0x000000000115E2F0>}
egon_NB
'''

4. delattr()

语法:delattr(x, y)
功能:删除属性,delattr(x,'y')相当于```del x.y`''
用法:

delattr(obj,"show_name")
# delattr(obj,"sex")   #不存在,则报错
print(obj.__dict__) #{'age': 20}

5. 扩展用法

反射当前模块

import sys
def s1():
    print("s1")

def s2():
    print("s2")

this_module = sys.modules[__name__]
print(hasattr(this_module,"s1"))   #True
print(getattr(this_module,"s2"))
    #<function s2 at 0x00000000006EE378>,加括号可执行

导入其他模块,利用反射查找该模块是否存在某个方法
程序目录:

  • module_test.py
  • current.py
# module_test.py
def test():
    print("from the module_test.test")
#current.py

import module_test as mt

print(hasattr(mt,"test"))   #True
f = getattr(mt,"test","不存在")
f()   #from the module_test.test

三、__setattr____delattr____getattr__

__setattr__ :添加/修改属性会触发它的执行;
__delattr__ :删除属性的时候会触发;
__getattr__ :只有在使用点调用属性且属性不存在的时候才会触发。

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

    def __getattr__(self, item):
        print("---> 你找的属性不存在")

    def __setattr__(self, key, value):
        print("---> from __setattr__")
        # self.key = value  #这样会陷入无限递归,只能通过__dict__字典进行赋值
        self.__dict__[key] = value   #这样才可以正确赋值

    def __delattr__(self, item):
        print("---> from __delattr__")

f1 = Foo(10)  #相当于设值,触发__setattr__执行,---> from __setattr__
print(f1.__dict__)
    #{},直接打印为空,是因为你自己重写了__setattr__方法,
    # 而你在__setattr__方法中没有真正赋值

f1.z          #触发__getattr__执行,---> 你找的属性不存在

del f1.x      #触发__delattr__执行,---> from __delattr__

四、二次加工标准类型(包装)

包装:python为用户提供了标准数据类型,以及丰富的内置方法,其在很多场景下,我们需要基于标准数据类型来定制我们自己的数据类型,新增 / 改写方法,这就用到继承和派生的知识,其他标准类型均可以通过下面的方式进行二次加工。

示例1:对list进行二次加工,限制append只能增加int整型数据;并且增加mid方法,得到列表的中间值;其余方法都继承list的。

class List(list):
    def append(self,p_object):
        #派生出自己的append方法,会覆盖父类list中的append方法
        if not isinstance(p_object,int):
            raise TypeError("%s must be int"%p_object)
        super(List, self).append(p_object)

    @property
        #中间值听起来更像一个属性,而非方法,所以使用property
    def mid(self):
        mid_num = len(self) // 2
        return self[mid_num]


l = List([1,2,3,4])
print(l)             #[1, 2, 3, 4]
l.append(5)
print(l)             #[1, 2, 3, 4, 5]
print(l.mid)         #中间值3

示例二:为listclear方法增加权限

class List(list):
    def __init__(self,item,perm=False):
        super(List, self).__init__(item)
        self.perm = perm
            #先设定一个默认的权限

    def clear(self):
        if not self.perm:
            raise PermissionError("权限拒绝")
        super(List, self).clear()

l = List([1,2,3])
print(l)          #[1, 2, 3]
# l.clear()       #抛出“权限拒绝”的异常
l = List([1,2,3],True)  #给一个授权参数为True
l.clear()
print(l)          #[],可以正常清空列表

授权

授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建、修改或删除原有产品的功能,其它的则保持原样。授权的过程就是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__方法

示例三:利用open()函数重新定制一个文件处理器,增加写内容添加时间的功能;

import time

class FileHandler:
    def __init__(self,filename,mode='r',encoding="utf-8"):
        self.file = open(filename,mode,encoding=encoding)
        #self.file获取到一个文件句柄

    def write(self,line):
        t = time.strftime("%Y-%m-%d %X")
        self.file.write("%s %s"%(t,line))

    def __getattr__(self, item):
        return getattr(self.file,item)
        #当对象调用FileHandler类不存在的方法时,会返回open()函数的item字符串对应的方法;
        

f1 = FileHandler("a.txt","r+")
f1.write("你好吗\n")
f1.seek(0)
print(f1.tell())   #0

示例四:

#我们来加上b模式支持
import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        if 'b' in mode:
            self.file=open(filename,mode)
        else:
            self.file=open(filename,mode,encoding=encoding)
        self.filename=filename
        self.mode=mode
        self.encoding=encoding

    def write(self,line):
        if 'b' in self.mode:
            if not isinstance(line,bytes):
                raise TypeError('must be bytes')
        self.file.write(line)

    def __getattr__(self, item):
        return getattr(self.file,item)

    def __str__(self):
        if 'b' in self.mode:
            res="<_io.BufferedReader name='%s'>" %self.filename
        else:
            res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding)
        return res
f1=FileHandle('b.txt','wb')
# f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write('你好啊'.encode('utf-8'))
print(f1)
f1.close()

示例五:利用授权的方式重新定制listappend方法,只能往列表中添加int整型数据;与示例一作对比,看一下两者的区别。


#授权,定制list

class List:
    def __init__(self,seq):
        self.seq = list(seq)

    def append(self,p_object):
        if not isinstance(p_object,int):
            raise TypeError("'%s' must be int"%p_object)
        self.seq.append(p_object)

    def __getattr__(self, item):
        return getattr(self.seq,item)

    def __str__(self):
        return str(self.seq)

l = List([1,2,3])
l.append("4")   #TypeError: '4' must be int
print(l)

总结:授权这种方式用在定制源不是类的情况下。例如示例三中,定制open()函数

五、__next____iter__实现迭代器协议

迭代器必须要有__next____iter__方法;
现在就来自己实现一个迭代器吧;
简单实现:

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

    def __iter__(self):
        return self

    def __next__(self):
        n=self.x
        self.x+=1
        return self.x

f=Foo(3)
for i in f:
    print(i)

模拟实现range()函数:

class foo:
    def __init__(self,start,stop=None,step=1):
        self.start = start
        self.stop = stop
        self.step = step
        if isinstance(self.start,str) \
                or isinstance(self.stop,str) \
                or isinstance(self.step,str):
            raise InterruptedError("Must be Numeric")

    def __next__(self):
        if self.stop:
            res = self.compute()
        else:
            self.stop = self.start
            # 如果只传了一个数字,则将其设为迭代停止数字
            self.start = 0
            # 如果只传了一个数字,则默认从0开始迭代
            res = self.compute()
        return res

    def __iter__(self):
        return self
        #迭代器执行__iter__方法,返回的是它本身

    def compute(self):
        if self.start >= self.stop:
            #判断是否超出迭代停止数字
            raise StopIteration
            # 这是超出迭代器范围后迭代器协议规定的抛出异常
        iter_val = self.start     #迭代后的值
        self.start += self.step
        return iter_val

for i in foo('a',20,3.5):
    print(i)

for i in foo(5,15,3):
    print(i)

六、__doc__

查看对象的描述信息

def func():
    '''我是函数的描述信息'''
    pass
print(func.__doc__)

class Foo:
    '''我是类的描述信息'''
    pass

class bar(Foo):
    pass

obj = Foo()
print(obj.__doc__)
print(Foo.__doc__)
print(bar.__doc__)   #该属性不会继承给子类

'''
输出:
我是函数的描述信息
我是类的描述信息
我是类的描述信息
None
'''

七、__module____class__

__module__ 表示当前操作的对象在那个模块

__class__ 表示当前操作的对象的类是什么

以下两个文件在同一级目录下:

#current.py

class C:
    def __init__(self):
        self.name = "alex"
#test.py

import current

def test():
    print("from the test.test")

obj = current.C()
print(obj.name)
print(obj.__class__)  #输出是哪个类实例化得到的对象
print(obj.__module__) #输出属性哪个模块
'''
输出:
alex
<class 'current.C'>
current
'''

八、__str__

l = list([1,2,3])
print(l)   #打印的是[1, 2, 3]
class mysql:
    def __init__(self,host,port):
        self.host = host
        self.port = port

conn = mysql("127.0.0.1","3306")
print(conn) #打印的是<__main__.mysql object at 0x0000000000701898>

从以上两段代码可以看到,我们自己定义的类,生成的对象直接被打印时,打印的是对象的内存地址,而这并不是我们想要的,我们实际想要的也是像第一段代码那样返回一个有用的信息,这时就要用到__str__这个内置方法了,它定义在类的内部,只要类被实例化,就会自动触发__str__的执行,并返回一个值给实例化后的对象。如下示例:

class mysql:
    def __init__(self,host,port):
        self.host = host
        self.port = port

    def __str__(self):
        return "Host:%s,Port:%s"%(self.host,self.port)

conn = mysql("127.0.0.1","3306")
#会将`__str__`方法的返回值赋值给对象conn
print(conn) 

'''
输出:
Host:127.0.0.1,Port:3306
'''

九、__del__析构方法

析构方法,当对象在内存中被释放时,会自动触发__del__执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

简单示例:

class Foo:
    def __del__(self):
        print("执行__del__")

f1 = Foo()
del f1   #释放f1,会触发__del__执行
print("------->")

'''
输出:
执行__del__
------->
'''

再看看下面这种情况:

class Foo:
    def __del__(self):
        print("执行__del__")

f1 = Foo()
# del f1    #注释掉
print("------->")

'''
输出:
------->
执行__del__
'''
#可以看出__del__仍然会在程序执行完毕后,被触发执行

十、__setitem__,__getitem__,__delitem__

类中有这三个方法,那就意味着类中的属性可以像字典一样进行操作;

class Foo:
    def __init__(self,name):
        self.name = name

    def __getitem__(self, item):
        print("from __getitem__")

    def __setitem__(self, key, value):
        print("from __setitem__")
        self.__dict__[key] = value  #加入此代码,才会真正设值

    def __delitem__(self, key):
        print("from __delitem__")
        self.__dict__.pop(key)

f1 = Foo("egon")
# f1.age = 18     #不会触发__setitem__的执行
f1["age_2"] = 20  #会触发__setitem__的执行,并不会真正设值;
print(f1.__dict__)
del f1["age_2"]
print(f1.__dict__)
print(f1["name"])   #会触发__getitem__执行,如果__getitem__没有设定返回值,则会返回一个None

'''
输出结果:
from __setitem__
{'name': 'egon', 'age_2': 20}
from __delitem__
{'name': 'egon'}
from __getitem__
None
'''
#从结果可以看出,age_2 = 20并没有真正设值成功

十一、__enter____exit__

通过__enter____exit__这两个方法可以实现上下文件管理协议,即with语句。为了让一个对象兼容with语句,必须在这个对象的类中声明__enter____exit__方法。

class Open:
    def __init__(self,name):
        self.name = name

    def __enter__(self):
        #出现with语句,对象的__enter__方法就会被触发,有返回值则赋值给as声明的变量
        print("from __enter__")
        return 3   #会返回给as语句后面的变量

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("from __exit__, with代码块执行完毕时触发")

with Open("egon") as f:
    print("from with 代码块")
    print("f:",f)

'''
输出:
from __enter__
from with 代码块
f: 3
from __exit__, with代码块执行完毕时触发
'''

__exit__(self, exc_type, exc_val, exc_tb)中的三个参数的含义:

  • exc_type 代表异常类型
  • exc_val 代表异常值
  • exc_tb 代表异常的追溯信息
class Open:
    def __init__(self,name):
        self.name = name

    def __enter__(self):
        #出现with语句,对象的__enter__方法就会被触发,有返回值则赋值给as声明的变量
        # print("from __enter__")
        return self   #会返回给as语句后面的变量

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("from __exit__, with代码块执行完毕时触发")
        print("exc_type:",exc_type)
        print("exc_val:",exc_val)
        print("exc_tb:",exc_tb)
        return True  #返回True,则表示不抛出异常,with代码之后的代码可正常执行,但with子代码raise后的代码不会执行。

with Open("egon") as f:
    print("from with 代码块")
    raise AttributeError("属性错误")
    # print("=====> 在异常之后")    #不会执行
print("=====> 在异常之后,with之外")   #__exit__返回True才会执行

'''
输出:
from with 代码块
from __exit__, with代码块执行完毕时触发
exc_type: <class 'AttributeError'>
exc_val: 属性错误
exc_tb: <traceback object at 0x014B58A0>
=====> 在异常之后
'''

示例:模拟open(),并实现上下文管理

#模拟open()功能

class Open:
    def __init__(self,filename,mode='r',encoding="utf-8"):
        self.filename = filename
        self.mode = mode
        self.encoding = encoding

    def __enter__(self):
        self.file = open(self.filename,self.mode,encoding =self.encoding)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return True  #遇到异常不抛出,with之外的代码继续执行

with Open("a.txt","w+") as f:
    f.write("11111\n")
    f.write("22222\n")
    f.seek(0)
    print(f.tell())
    f.abc    #抛出异常,交给__exit__处理

十二、__call__

对象后面加括号,就会触发__call__方法的执行;反之类中有了__call__方法,通过这个类生成的对象,才能加括号执行。

class foo:
    def __init__(self):
        print("from __init__")

    def __call__(self, *args, **kwargs):
        print("from __call__")

obj = foo()    #执行__init__
obj()          #执行__call__

'''
输出:
from __init__
from __call__
'''

十三、元类metaclass

先来看看exec

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容