这次主要从实用性的角度总结几种常用的做法。
扩展内置数据结构
通过继承和重写构造方法的方式可以对 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 = age
class Student2(object):
__slots__ = ["sid", "name", "age"]
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
s1 = 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")
退出作用域时不需要使用 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 self
def __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
NetworkManager
Bye.
另外通过 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.14
def get_value(self, key):
# getattr表示从self中取名为 "get_" + key 的方法,返回该方法的对象
if hasattr(self, "get_"+key):
return getattr(self, "get_" + key)()
def get_radius(self):
return self.radius
def get_perimeter(self):
return self.pi * self.radius * 2
def get_area(self):
return self.pi * self.radius ** 2
c1 = 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()