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 = 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 的判断逻辑就会线性递增,既不方便也不美观。
使用 hasattrgetattr 的方法(也有人称之为“反射”)可以通过字符串在当前的类或模块中获取函数对象

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()

你可能感兴趣的:(Python 经验 - 类与对象2)