Python|Python 经验 - 类与对象2

这次主要从实用性的角度总结几种常用的做法。
扩展内置数据结构 通过继承和重写构造方法的方式可以对 Python 内置的数据结构进行功能扩展,如下面正整数元组的例子:

class IntTuple(tuple): def __new__(cls, tup): new_tup = (x for x in tup if isinstance(x, int) and x > 0) # 把新的元组传入父类的构造方法并实例化 return super(IntTuple, cls).__new__(cls, new_tup)tup = IntTuple([-1, 1, "asgag", "1", 99, -5.1]) print(tup)

执行结果:
(1, 99)

使用 __slots__ 节省内存 需要创建大量实例时,定义 __slots__ 可以限制类实例的属性(定义后不支持动态绑定属性)。
class Student1(object): def __init__(self, sid, name, age): self.sid = sid self.name = name self.age = ageclass Student2(object): __slots__ = ["sid", "name", "age"]def __init__(self, sid, name, age): self.sid = sid self.name = name self.age = ages1 = Student1("1", "ywh", "22") s2 = Student2("2", "hwy", "12")s1.sex = "male" print(s1.sex) s2.sex = "female" print(s2.sex)

执行结果:
male Traceback (most recent call last): File "E:/Projects/PythonLearning/Notes/data_model.py", line 37, in s2.sex = "female" AttributeError: 'Student2' object has no attribute 'sex'

除此之外对比 dir(s1)dir(s2) 也可以发现可见 s2 少了 __dict__(实现动态绑定、解除属性)和 __weakref__(弱引用,引用时不增加引用计数),因此能节省大量内存。
实现上下文管理对象 在我们进行文件读写操作时常会用到 with 语句,作用是进入上下文作用域,作用域内可使用 with 语句返回的对象:
with open("/tmp/file.txt", "r+b") as f: f.write("xxx")

【Python|Python 经验 - 类与对象2】退出作用域时不需要使用 f.close() 手动关闭文件对象,这在 open 类型已经内部定义好:当退出作用域时自动关闭文件对象。
实现 __enter____exit__ 方法后,可用 with 语句划分作用域,使任意类型都支持这种上下文管理:
try: pass except: pass else:# 没有抛出异常时执行 pass finally:# 不管是否抛出异常都会执行,资源释放操作(关闭文件、关闭数据库连接等) pass# 如果 finally 中包括 return 语句,则会以 finally 中的 return 返回(从上到下入栈、出栈执行)# 上下文管理器协议 class Sample:def __enter__(self): print ("enter") # 获取资源 return selfdef __exit__(self, exc_type, exc_val, exc_tb): # 释放资源 print ("exit")def do_something(self): print ("doing something")with Sample() as sample: sample.do_something()

又如下面建立 SSH 连接远程执行命令的例子:
import paramiko# paramiko 是基于 SSH2 协议的第三方模块,可建立 SSH 连接class SSH(object): def __init__(self, hostname, port, username, password): self.hostname = hostname self.port = port self.username = username self.password = password self.connection = None# 远程执行命令 def execute(self, cmd): stdin, stdout, stderr = self.connection.exec_command(cmd) print(stdout.read())# 进入作用域时自动执行 def __enter__(self):# 返回的对象即为“with a as b”的 b self.connection = paramiko.SSHClient()# 创建一个连接对象 self.connection.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.connection.connect(# 使用用户名-密码的方式登录,也可以使用密钥登录 hostname=self.hostname, port=self.port, username=self.username, password=self.password ) print("\nConnecting to {}:{}...\n".format(self.hostname, self.port)) return self# 退出作用域时自动执行 def __exit__(self, exc_type, exc_val, exc_tb):# 三个参数分别用于处理异常、执行清理工作和执行回调函数 self.connection.close() print("Bye.")config = { "hostname": "localhost", "port": 22, "username": "root", "password": "123456" }with SSH(**config) as ssh: ssh.execute("ls /etc | head -5")

执行结果:
Connecting to 123.207.226.173:22...DIR_COLORS DIR_COLORS.256color DIR_COLORS.lightbgcolor GeoIP.conf NetworkManagerBye.

另外通过 contextlib 模块可实现上下文管理器的简化
import contextlib# contextlib.contextmanager 装饰器的函数自带上下文管理器功能@contextlib.contextmanager def file_open(file_name): print ("file open") yield {} print ("file end")with file_open("bobby.txt") as f_opened: print ("file processing")

管理对象属性 在类中实现内置的 property 方法可以动态地获取、设置、删除类/对象属性:
class Circle(object): def __init__(self, radius): self.radius = radius self.pi = 3.14# 获取圆半径 def get_radius(self): return self.radius# 设置圆半径 def set_radius(self, value): if not isinstance(value, (int, long, float)): raise ValueError("Wrong type.") self.radius = float(value)# 获取圆面积 @ property def area(self): return self.radius ** 2 * self.pi# 四个参数分别为get、set、del、doc,实现后可以获取、设置、删除属性 R = property(get_radius, set_radius)# 也可以作为装饰器使用c = Circle(3.0) print(c.R) c.R = 4.0 print(c.R)

执行结果:
3.0 4.0

其中 property 方法也可以通过装饰器的方式实现,加入 property 装饰器的方法将转变为特性,可以使该方法支持以字段的方式访问),但默认只可读:
print c.area# 50.24

要使属性可写,只需再实现 setter 装饰器:
class Circle(object): ... @area.setter def area(self, value): pass

使用描述符做类型检查 使用描述符(定义 getter、setter 方法)可以限制类属性可接收的类型,判断失败抛出异常:
class Attr(object): def __init__(self, val, attr): self.val = val self.attr = attr# 获取属性 def __get__(self, instance, cls): return instance.__dict__[self.val]# 修改属性:使用描述符作类型检查 def __set__(self, instance, value): if not isinstance(value, self.attr): raise TypeError("Expected an {}".format(self.attr)) instance.__dict__[self.val] = value# 析构:自动清除对象 def __delete__(self, instance): del instance.__dict__[self.val]class Student(object): sid = Attr("sid", str) name = Attr("name", str) age = Attr("age", int)s1 = Student() s1.age = 5 print(s1.age) s1.age = "6" print(s1.age)

执行结果:
5 Traceback (most recent call last): File "E:/Projects/PythonLearning/Notes/data_model.py", line 196, in s1.age = "6" File "E:/Projects/PythonLearning/Notes/data_model.py", line 181, in __set__ raise TypeError("Expected an {}".format(self.attr)) TypeError: Expected an

通过名称调用方法 有些时候我们获取到方法的名称(例如客户端 get 或 post 传来的字符串数据),要根据这个名称在后端调用相应的方法,一般的做法是使用 if...else 判断。但当程序规模变大,使用 if...elif...else 的判断逻辑就会线性递增,既不方便也不美观。
使用 hasattr、getattr 的方法(也有人称之为“反射”)可以通过字符串在当前的类或模块中获取函数对象:
class Circle(object): def __init__(self, radius): self.radius = radius self.pi = 3.14def get_value(self, key): # getattr表示从self中取名为 "get_" + key 的方法,返回该方法的对象 if hasattr(self, "get_"+key): return getattr(self, "get_" + key)()def get_radius(self): return self.radiusdef get_perimeter(self): return self.pi * self.radius * 2def get_area(self): return self.pi * self.radius ** 2c1 = Circle(3) print(c1.get_value("radius"))# 3 print(c1.get_value("area"))# 28.26 print(c1.get_value("perimeter"))# 18.84

其中 hasattr 是判断 self(即当前对象)中是否有名为 "get_" + key 的方法,getattr 则获取该方法的对象来执行。
当要从 当前模块中 获取函数对象,可以这样写:
if hasattr(sys.modules[__name__], func_name): func = getattr(sys.modules[__name__], func_name) func()

    推荐阅读