内置方法、反射

内置方法

Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类,这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发。

内置方法,我们也称魔术方法,简称魔法!!!

如何学习魔术方法,只需要记住各个魔术方法的触发条件即可!!!

1. __init__方法

初始化方法,调用类的时候自动触发,里面有一个self参数,用来接收对象的

class Student():
    def __init__(self, name, age):
        print('123')
        self.name = name
        self.age = age
Student('kevin', 20)
# > 输出结果是:123


stu = Student('kevin', 20)
print(stu)  # <__main__.Student object at 0x00000207E7CD0198>
print(stu.name)  # kevin
print(stu.age)  # 20

2. __str__方法,__repr__方法

__str__方法会在对象被打印时自动触发,print功能打印的就是它的返回值,我们通常基于方法来定制对象的打印信息,该方法必须返回字符串类型。__repr__方法相同。当__str__和__repr__方法同时存在的时候,__str__的优先级高于__repr__方法

2.1 __str__方法

class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        print('打印或者输出对象的时候,会自动触发的方法')
        '''返回值必须是字符串类型,如果不指定return返回值也会报错(不指定返回None,不是字符串)'''
        # return 123  返回值是整型会报错
        return 'name:%s' % self.name

stu = Student('kevin', 20)
print(stu)
# > 输出结果是:
# 打印或者输出对象的时候,会自动触发的方法
# name:kevin

2.2 __repr__方法

class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        print('打印或者输出对象的时候,会自动触发的方法')
        return '返回值必须是字符串类型'
        # return 123 会报错
stu = Student('kevin', 20)
print(stu)

当__str__和__repr__方法同时存在的时候,__str__的优先级高于__repr__方法

'''
	相同点:
		1.都是打印或者输出对象的时候,会自动触发的方法
		2.返回值必须是字符串类型
	不同点:	
		当__str__和__repr__方法同时存在的时候,__str__的优先级高于__repr__方法
'''
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    '''打印或者输出对象的时候,自动触发的'''
    # 返回值必须是字符串
    def __repr__(self):
        return 'repr执行了'

    '''打印或者输出对象的时候,会自动触发的方法,是要掌握的'''
    # 返回值必须是字符串类型
    def __str__(self):
        # return 'name:%s' % self.name
        print('我执行了!!!')
        return 'aaaa'
'''不同点:当__str__和__repr__方法同时存在的时候,__str__的优先级高于__repr__方法'''
stu = Student('kevin', 20)
print(stu)
# > 输出结果是:
# 我执行了!!!
# aaaa

拓展知识

f = open('a.txt', 'w')
# 打印出组装的返回类型,内部重写了str方法,然后组装成了如下形式返回
print(f)  # <_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>

案例

class School:
    def __init__(self, name, addr, type):
        self.name = name
        self.addr = addr
        self.type = type
    def __repr__(self):
        return 'School(%s,%s)' % (self.name, self.addr)
    def __str__(self):
        return '(%s,%s)' % (self.name, self.addr)

s1 = School('oldboy1', '北京', '私立')
# 可以指定使用方法
print('from repr: ', repr(s1))  # from repr:  School(oldboy1,北京)
print('from str: ', str(s1))  # from str:  (oldboy1,北京)
# str优先级更高
print(s1)  # (oldboy1,北京)

3. __del__方法

__del__会在对象被删除时自动触发。当程序全部执行完毕,也会自动调用触发。

# 1.对象被删除时自动触发
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.f = open('a.txt', 'r')
    # 1. 删除对象的时候自动触发的方法
    def __del__(self):
        print('触发了')
        self.f.close()  # 系统资源

stu = Student('kevin', 20)
del stu
print('123')
# > 输出结果是:
# 触发了
# 123

# 2.当程序全部执行完毕,也会自动调用触发
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age
    # 1. 删除对象的时候自动触发的方法
    # 2. 当程序全部执行完毕,也会自动调用触发
    def __del__(self):
        print('触发了')

stu = Student('kevin', 20)
print('123')
# > 输出结果是:
# 123
# 触发了

由于Python自带的垃圾回收机制会自动清理Python程序的资源,所以当一个对象只占用应用程序级资源时,完全没必要为对象定制__del__方法,但在产生一个对象的同时涉及到申请系统资源(比如系统打开的文件、网络连接等)的情况下,关于系统资源的回收,Python的垃圾回收机制便派不上用场了,需要我们为对象定制该方法,用来在对象被删除时自动触发回收系统资源的操作

# 可以用于回收系统资源
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.f = open('a.txt', 'r')

    def __del__(self):
        print('触发了')  # 可以用于关闭文件,
        self.f.close()  # 回收系统资源

stu = Student('kevin', 20)
print(stu.f)
print('123')  # 当程序全部执行完毕,也会自动调用触发__del__方法
# > 输出结果是:
# <_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>
# 123
# 触发了

4.isinstance(obj,cls)和issubclass(sub,super)

之前学习的isinstance():是判断某个数据值是不是某个数据类型

if type('abc') is dict:
    print('正确')
    
print(isinstance('abc', str))

1.isinstance(obj,cls)检查是否obj是否是类 cls 的对象

class Foo(object):
    pass

class Bar(object):
    pass

obj = Foo()
stu = Bar()
print(isinstance(obj, Foo))  # True
print(isinstance(stu, Foo))  # False

# 例子2:
class Student():
    pass
class Foo(Student):
    pass
class Bar(Foo):
    pass
obj = Bar()
print(isinstance(obj, Bar))  # True
print(isinstance(obj, Foo))  # True
print(isinstance(obj, Student))  # True

2.issubclass(sub, super)检查sub类是否是 super 类的派生类(子类)

class Foo(object):
    pass


class Bar(Foo):
    pass


print(issubclass(Bar, Foo))  # True
print(issubclass(Bar, object))  # True

5.__doc__方法

提取注释中的信息

class Foo:
    """
    这是注释!!!
    author:ly
    date:2020-03-20
    email:123456@qq.com
    """
    # 这是#注释  但是不能识别警号的注释,返回None
    pass
print(Foo.__doc__)

该属性无法继承给子类

class Foo:
    """
    这是注释!!!
    """
    pass


class Bar(Foo):
    pass


print(Foo.__doc__)  # 这是注释!!!
print(Bar.__doc__)  # None
'''该属性无法继承给子类'''

6. __enter__方法,__exit__方法

# 我们知道在操作文件对象的时候可以这么写

with open('a.txt') as f:
  '代码块'

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

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

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
        # return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
    

with Open('a.txt') as f:
    print('=====>执行代码块')
    # print(f)  # <__main__.Open object at 0x000001D8BFB6F208>
    # print(f.name)  # a.txt

__exit__中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

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

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)  # 异常类型
        print(exc_val)  # 异常值/异常信息
        print(exc_tb)  # 追溯信息:traceback


with Open('a.txt') as f:
    print('=====>执行代码块')
    raise TypeError('***着火啦,救火啊***')  # 它是主动抛出异常
print('0' * 100)  # ------------------------------->不会执行

# > 输出结果是:
'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class 'TypeError'>
***着火啦,救火啊***
<traceback object at 0x0000022C7C11B948>
Traceback (most recent call last):
  File "E:\python project\day28\01 魔术方法.py", line 215, in <module>
    raise TypeError('***着火啦,救火啊***')  # 它是主动抛出异常
TypeError: ***着火啦,救火啊***
'''

如果__exit__()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

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

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True

with Open('a.txt') as f:
    print('=====>执行代码块')
    raise TypeError('***着火啦,救火啊***')  # 它是主动抛出异常
print('0' * 100)  # ------------------------------->会执行

# > 输出结果是:
'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class 'TypeError'>
***着火啦,救火啊***
<traceback object at 0x000002608EDDA948>
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
'''

7. setattrdelattrgetattr()方法

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

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


    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value #这就无限递归了,你好好想想
        # self.__dict__[key]=value #应该使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)

# __setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)

# __delattr__删除属性的时候会触发
f1.__dict__['a']=3  # 我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)

# __getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxx

7.1 __getattr__方法

class Foo:
    x = 1
    def __init__(self, y):
        self.y = y
    '''有对象.属性(属性是不存在的)这个句式,就会被触发'''
    # 查找的属性不存在的时候会被执行
    def __getattr__(self, item):
        print('item:%s' % item)
        print('----> from getattr:你找的属性不存在')
obj = Foo(2)
print(obj.y)  # 2
print(obj.x)  # 1
print(obj.z)
# > 输出结果是:
'''
item:z
----> from getattr:你找的属性不存在
None
'''

7.2 __setattr__方法

class Foo:
    x = 1

    def __init__(self, y):
        self.y = y

    '''只要存在,对象.属性名 = 值,(添加/修改属性)就会被触发'''

    def __setattr__(self, key, value):
        print('----> from setattr')
        print(key, value)
        # self.key=value #这就无限递归了,你好好想想
        # self.__dict__[key] = value  # 应该使用它


obj = Foo(2)
# 调用类名()就会触发__init__()方法,其中有self.y = y句式,由会触发__setattr__()方法
print(obj.__dict__)  # {} # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
obj.z = 1
print(obj.__dict__)

7.3 __delattr__方法

class Foo:
    x = 1

    def __init__(self, y):
        self.y = y

    '''__delattr__删除属性的时候会触发'''
    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)

obj = Foo(2)
print(obj.__dict__)  # {'y': 2}
del obj.y  # ----> from delattr
print(obj.__dict__)  # {}

8.setitemgetitem,__delitem__方法

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

    def __getitem__(self, item):
        print('__getitem__执行了', item)
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        print(key, value)
        print('__setitem__执行了')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)


obj = Foo('kevin')
# print(obj.name)  # kevin
# __getitem__对象通过中括号取值的时候会触发它的执行,键存在,则调用。键不存在,则报错
obj['name']
# '''
# __getitem__执行了 name
# kevin
# '''
# __setitem__对象中括号添加/修改属性的时候,会触发它的执行
obj['name'] = 'tony'  # 键存在,值改变
obj['name1'] = 'jack'  # 键不存在,值增加
print(obj.__dict__)
'''
name tony
__setitem__执行了
name1 jack
__setitem__执行了
{'name': 'tony', 'name1': 'jack'}
'''
# __delitem__对象中括号删除属性的时候,会触发它的执行。键存在,则删除。键不存在,则报错。
del obj['name1']
print(obj.__dict__)
del obj.name
print(obj.__dict__)
'''
del obj[key]时,我执行
{'name': 'tony'}
del obj.key时,我执行
{}
'''

9.__call__方法

对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call()方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        pass

    # 对象加括号的时候会自动触发执行
    def __call__(self, *args, **kwargs):
        print('__call__执行了')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__
'''后续课程flask框架源码的时候,__call__()方法就是入口'''

反射

在Python中,反射指的是'通过字符串来操作对象的属性'

涉及到四个内置函数的使用:(Python中一切皆对象,类和对象都可以用下述四个方法)
1. getattr()
2. setattr()
3. delattr()
4. hasattr()

class Student():
    school = 'SH'

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

    def func(self):
        print('from Student.func')


stu = Student('kevin')
# print(stu.school)  # SH
print(stu.name)  # kevin
print(stu.__dict__)  # {'name': 'kevin'}

1.四个内置函数

  1. getattr()

获取对象属性,当获取的属性不存在的时候,会报错

res = getattr(stu, 'name')
print(res)  # kevin
res1 = getattr(stu, 'name1')
# print(res1)  # 会报错

如果给了第三个参数,当属性不存在的时候,第三个参数就是默认值,但不会改变对象字典

res = getattr(stu, 'name1', 666)
print(res)  # 666
print(stu.__dict__)  # {'name': 'kevin'}

如果是方法名,得到的结果就是函数的内存地址,要想执行,就要结果加括号

res = getattr(stu, 'func')
print(res)  # <bound method Student.func of <__main__.Student object at 0x000002A83817D4A8>>
res()  # from Student.func
# getattr(stu, 'func')()  # 执行方法可以直接在后面加括号

  1. setattr()
setattr(stu, 'x', 123)  # 给对象增加属性, 属性名不存在的时候,增加属性
setattr(stu, 'name', '123')  # 给对象增加属性,属性名存在的时候,修改属性
print(stu.__dict__)
# {'name': '123', 'x': '123'}
  1. delattr()
delattr(stu, 'name')
print(stu.__dict__)  # {}
  1. hasattr()
res1 = hasattr(stu, 'name')
res2 = hasattr(stu, 'name1')
print(res1)  # True
print(res2)  # False

2.案例:

跟用户交互,用户传入的都是字符串 >>> 使用反射,之后就可以跟用户进行交互

什么时候应该考虑使用反射 只要需求中出现了关键字
对象....字符串....

案例1:将用户交互的结果反射到具体的功能执行

class FtpServer:
    def serve_forever(self):
        while True:
            # 输入方法名 文件名(以输入get a.txt为例)
            inp = input('input your cmd>>: ').strip()  # get a.txt
            cmd, file = inp.split()  # 用空格来切分,get a.txt
            if hasattr(self, cmd):  # 根据用户输入的cmd,判断对象self有无对应的方法属性
                # 等同于self.full_name => server.get
                func = getattr(self, cmd)  # 根据字符串cmd,获取对象self对应的方法属性名字
                func(file)  # => get()  # 调用方法

    def get(self, file):  # 下载文件的函数
        print('Downloading %s...' % file)  # 但没有写具体的功能

    def put(self, file):  # 卸载文件函数,没有写具体的功能
        print('Uploading %s...' % file)


server = FtpServer()  # 调用类产生对象
server.serve_forever()  # 对象点方法名是对象调方法

案例2:模拟cmd终端

class WinCmd:
    def tasklist(self):
        print(""" 
        1.学习编程
        2.学习python
        3.学习英语
            """)

    def ipconfig(self):
        print("""
        地址:127.0.0.1
        地址:上海浦东新区
        """)

    def get(self, target_file):
        print('获取指定文件', target_file)

    def put(self, target_file):
        print('上传指定文件', target_file)

    def server_run(self):
        print('欢迎进入简易版本cmd终端')
        while True:
            target_cmd = input('请输入您的指令>>>:')
            res = target_cmd.split(' ')  # 因为有的函数是要输入两个参数。用空格切分
            if len(res) == 1:  # 切分后是列表形式
                if hasattr(self, res[0]):  # 判断类中有该方法
                    getattr(self, res[0])()  # 执行该方法
                else:
                    print(f'{res[0]}不是内部或者外部命令')
            elif len(res) == 2:
                if hasattr(self, res[0]):
                    getattr(self, res[0])(res[1])  # 把第二个命令当成参数传进去
                else:
                    print(f'{res[0]}不是内部或者外部命令')

obj = WinCmd()
obj.server_run()

案例3:一切皆对象

# 利用反射保留某个py文件中所有的大写变量名及对应的数据值,忽略小写变量名
import settings  # settings文件
print(dir(settings))  # dir列举对象可以使用的名字,双下划暂时不用考虑

useful_dict = {}  # 用来存储有用的变量名
for name in dir(settings):
    if name.isupper():  # 判断是否全大写isupper()
        useful_dict[name] = getattr(settings, name)  # 字典[名字]=对应值
print(useful_dict)

# 判断某个文件中是否有输入的名字
while True:
    target_name = input('请输入某个名字')
    if hasattr(settings, target_name):
        print(getattr(settings, target_name))
    else:
        print('该模块文件中没有该名字')

3.补充

反射也可以用来操作模块

import time
# time.sleep(3)

time = getattr(time, 'sleep')  # 通过字符串来操作模块的方法
print(time)  # <built-in function sleep>  #内置方法sleep
time(3)
# 以上可以简写为:
getattr(time, 'sleep')(3)  # <==> time.sleep(3)

导入模块还可以直接使用:双下划import

# import time
# 导入模块还可以直接使用:双下划import
time = __import__('time')  # <==> import time
time.sleep(3)

# import random
random = __import__('random')
res = random.randint(1, 9)
print(res)  # 7

posted @ 2023-03-26 16:34  星空看海  阅读(14)  评论(0编辑  收藏  举报