第8天,面向对象进阶

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 第8天,面向对象进阶@(python)[笔记]目录一、isinstance()和issubclass() 1. isinstance() 2.

第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

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6天前
|
算法 Java 程序员
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
17 0
|
2月前
|
人工智能 运维 Java
面向对象进阶
面向对象进阶
37 0
|
8月前
|
设计模式 算法 搜索推荐
C++的编程思想和进阶编程
● 一个模式描述了一个不断发生的问题以及这个问题的解决方案;模式是前人的设计经验上总结出来的对于一些普遍存在的问题提供的通用的解决方案;比如单例模式、观察者模式等 ● 软件中有很多模式 面向对象常见的23中设计模式; 反模式;企业应用架构模式等
34 0
|
10月前
|
存储 开发框架 Java
|
11月前
|
程序员 测试技术 C语言
c++面向对象程序设计入门
c++面向对象程序设计入门
138 0
|
Java 程序员 编译器
【Java编程进阶】面向对象思想初识
之前我们学习了 C 语言,在使用 C 语言解决实际的问题时,我们会把解决问题的步骤封装成不同的函数,然后在需要使用时调用函数执行,这就是面向过程编程的思想。使用面向过程使程序的流程十分清楚,例如完成早上去上学这件事情,我们要粗略做以下的步骤,只要以函数的形式详细的完成每个步骤,就成功的解决了这个问题: - 起床 - 穿衣服 - 刷牙洗脸 - 上学 而面向对象中,我们可以抽象出一个学生类,该类中包含了上面的四个方法,此时我们并不需要按上面的步骤来完成。
77 1
【Java编程进阶】面向对象思想初识
|
存储 PHP 开发者
面向对象基础总结|学习笔记
快速学习面向对象基础总结
108 0
|
数据安全/隐私保护 C++
C++面向对象程序设计简介
C++面向对象程序设计简介
171 1
|
存储
笔记08-面向对象
笔记08-面向对象
笔记08-面向对象
|
存储 安全 数据库
面向对象基础进阶01
面向对象基础进阶01
96 0
面向对象基础进阶01