美文网首页
Python 经验 - 类与对象2

Python 经验 - 类与对象2

作者: 千反田爱瑠爱好者 | 来源:发表于2018-09-13 14:38 被阅读18次

这次主要从实用性的角度总结几种常用的做法。

扩展内置数据结构

通过继承-重写构造方法的方式可以对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 <module>
    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 <module>
    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 <type 'int'>

通过名称调用方法

有些时候我们获取到方法的名称(例如客户端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()

相关文章

  • Python 经验 - 类与对象2

    这次主要从实用性的角度总结几种常用的做法。 扩展内置数据结构 通过继承-重写构造方法的方式可以对Python内置的...

  • Python 经验 - 类与对象

    Python中的一切皆对象,包括class、function等,具体表现在: 可赋值给变量 可添加到集合 可作为函...

  • python中类与对象

    python中类与对象是2个非常重要的面相对象编程:类是对象的模子对象是类的具体实例 class car():de...

  • python面向对象和元类的理解

    1 python类对象与实例对象 python中一切皆对象(广义上的对象),类也不例外,我们可以称类为类对象。py...

  • 23.Python之面向对象(类与对象)

    Python之类与对象 类类:用来描述具有相同的属性和方法的对象的集合(在Python中类与类型是一个概念)。特点...

  • python自定义功能之面向对象

    本章主要内容 类与对象 定义和使用类 类的属性和方法 类的继承 类的方法重载 总结: python中的类与对象与J...

  • 【第24天】python全栈从入门到放弃

    1 python2和python3区别 2 类方法: @classmethod 3 通过类方法调用(可以不用对象调...

  • Python - 类与对象

    使用类的三步骤 Step 1. 定义类 类是对象的蓝图和模板 有了类就可以创建对象定义类需要做的两件事情:数据抽象...

  • python类与对象

    类是一种数据类型,而对象是具有这种数据类型的变量。类是抽象的,不占有内存空间。而对象是具体的,占有储层空间。 py...

  • Python 类与对象

    Python 中面向对象编程的2个非常重要的概念:类和对象 对象是面向对象编程的核心,在使用对象的过程中,为了将具...

网友评论

      本文标题:Python 经验 - 类与对象2

      本文链接:https://www.haomeiwen.com/subject/lemwgftx.html