内置方法、反射
内置方法
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. setattr,delattr,getattr()方法
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.setitem,getitem,__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.四个内置函数
- 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')() # 执行方法可以直接在后面加括号
- setattr()
setattr(stu, 'x', 123) # 给对象增加属性, 属性名不存在的时候,增加属性
setattr(stu, 'name', '123') # 给对象增加属性,属性名存在的时候,修改属性
print(stu.__dict__)
# {'name': '123', 'x': '123'}
- delattr()
delattr(stu, 'name')
print(stu.__dict__) # {}
- 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