类中定义的函数分为两大类:绑定方法和非绑定方法。
其中绑定方法又分为绑定到对象的对象方法和绑定到类的类方法。
在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。
我在之前的章节中已经介绍过对象方法了,本节我主要介绍类方法。类方法通常用来在__init__的基础上提供额外的初始化实例的方式:
# 类方法的应用 HOST = '127.0.0.1' PORT = 3306 class MySQL: def __init__(self, host, port): self.host = host self.port = port @classmethod def from_conf(cls): # 读取配置进行初始化 return cls(HOST, PORT) print(MySQL.from_conf) # 绑定到类的方法 # <bound method MySQL.from_conf of <class '__main__.MySQL'>> conn = MySQL.from_conf() """调用类方法,自动将类MySQL当作第一个参数传给cls"""
绑定到类的方法就是专门给类用的,但其实对象也可以调用,只不过自动传入的第一个参数仍然是类,也就是说这种调用是没有意义的,并且容易引起混淆,这也是Python的对象系统与其他面向对象语言对象系统的区别之一,比如Smalltalk和Ruby中,绑定到类的方法与绑定到对象的方法是严格区分开的。
为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,因而没有自动传值那么一说:
import uuid class MySQL: def __init__(self, host, port): self.id = self.create_id() self.host = host self.port = port @staticmethod def create_id(): return uuid.uuid1() conn = MySQL('127.0.0.1', 3306) print(conn.id) # 7e402dbe-5756-11ec-8cdc-b0359fd6cd6a """类或对象来调用create_id发现都是普通函数,而非绑定到谁的方法""" print(MySQL.create_id) # <function MySQL.create_id at 0x000001E6B11741F8> print(conn.create_id) # function MySQL.create_id at 0x000001E6B11741F8>
总结绑定方法与非绑定方法的使用:
若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法、需要引用类则将其定义成类方法、无需引用类或对象则将其定义成静态方法。
在Python中,反射指的是通过字符串来操作对象的属性,涉及到四个内置函数的使用(Python中一切皆对象,类和对象都可以用下述四个方法)
class Teacher: def __init__(self, full_name): self.full_name = full_name t = Teacher('Jason Lin') # hasattr(object,'name') hasattr(t, 'full_name') # 按字符串'full_name'判断有无属性t.full_name # getattr(object, 'name', default=None) getattr(t, 'full_name', None) # 等同于t.full_name,不存在该属性则返回默认值None # setattr(x, 'y', v) setattr(t, 'age', 18) # 等同于t.age = 18 # delattr(x, 'y') delattr(t, 'age') # 等同于del t.age
基于反射可以十分灵活地操作对象的属性,比如将用户交互的结果反射到具体的功能执行:
class FtpServer: def serve_forever(self): while True: inp = input('input your cmd>>: ').strip() cmd, file = inp.split() if hasattr(self, cmd): # 根据用户输入的cmd,判断对象self有无对应的方法属性 func = getattr(self, cmd) # 根据字符串cmd,获取对象self对应的方法属性 func(file) def get(self, file): print('Downloading %s...' % file) def put(self, file): print('Uploading %s...' % file) server = FtpServer() server.serve_forever()
执行结果为:
input your cmd>>: get a.txt Downloading a.txt... input your cmd>>: put b.txt Uploading b.txt...
Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类,这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发,我们以常用的__str__和__del__及其他了解的几个方法为例来简单介绍它们的使用。
__str__方法会在对象被打印时自动触发,print功能打印的就是它的返回值,我们通常基于方法来定制对象的打印信息,该方法必须返回字符串类型:
class People: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return '<Name:%s Age:%s>' % (self.name, self.age) # 返回类型必须是字符串 p = People('lili', 18) print(p) # 触发p.__str__(),拿到返回值后进行打印 # <Name:lili Age:18>
__del__会在对象被删除时自动触发。由于Python自带的垃圾回收机制会自动清理Python程序的资源,所以当一个对象只占用应用程序级资源时,完全没必要为对象定制__del__方法,但在产生一个对象的同时涉及到申请系统资源(比如系统打开的文件、网络连接等)的情况下,关于系统资源的回收,Python的垃圾回收机制便派不上用场了,需要我们为对象定制该方法,用来在对象被删除时自动触发回收系统资源的操作:
class MySQL: def __init__(self, ip, port): self.conn = connect(ip, port) # 这里是伪代码,表示发起网络连接,需要占用系统资源 def __del__(self): self.conn.close() # 关闭网络连接,回收系统资源 obj = MySQL('127.0.0.1', 3306) # 在对象obj被删除时,自动触发obj.__del__()
__call__方法的触发条件是对象后面加括号,即会触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于__call__方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): print('__init__') def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 打印 __init__ obj() # 打印 __call__
我们知道在操作文件对象的时候可以这么写:
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,f.name)
打印结果为:
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊
__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) with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0' * 100) # ------------------------------->不会执行
如果__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 AttributeError('***着火啦,救火啊***') print('0' * 100) # ------------------------------->会执行
模拟open案例:
class Open: def __init__(self, filepath, mode='r', encoding='utf-8'): self.filepath = filepath self.mode = mode self.encoding = encoding def __enter__(self): # print('enter') self.f = open(self.filepath, mode=self.mode, encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): # print('exit') self.f.close() return True def __getattr__(self, item): return getattr(self.f, item) with Open('a.txt', 'w') as f: print(f) f.write('aaaaaa') f.wasdf # 抛出异常,交给__exit__处理
用途或者说好处:
1、使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预;
2、在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你便无须再去关注这个问题,这将大有用处;