【python进阶 高级语法 笔记】OOP的属性/方法、property属性、魔法属性/方法、面向对象设计、with与上下文管理器 等

【python进阶 高级语法 笔记】

目录

1. OOP的属性/方法

1.1. 类属性、实例属性

1.2. 实例方法、类方法 和 静态方法

2. property属性(重点)

2.1. 什么是property属性

2.2.  装饰器/类属性 创建property属性

2.3. property属性应用

3. 魔法属性/方法

4.面向对象设计

扫描二维码关注公众号,回复: 10233172 查看本文章

5.with与 上下文管理器

5.1 with关键字

5.2. 上下文管理器(Context Manager)


1. OOP的属性/方法

1.1. 类属性、实例属性

通过实例理解:

class Province(object):
    # 类属性
    country = '中国'

    def __init__(self, name):
        # 实例属性
        self.name = name


# 创建一个实例对象
obj = Province('山东省')
# 访问实例属性,需要通过对象来访问
print(obj.name)
# 访问类属性,通过类访问
Province.country

直接定义在类中的属性称为类属性,一般公有的属性作为类属性。类的方法里的变量是实例属性,创建对象时才会创建内存空间。

  1. 语句 obj = Province('山东省') 会:(1)调用 __new__ 方法创建对象,即创建了一个内存空间;(2)调用 __init__方法,对申请的空间进行初始化;(3)解释器自动把对象的引用给self;
  2. 通俗的说Province是类对象(一个)类对象的变量即 类属性 ;"山东省" 是实例对象(n个)实例对象的变量即 实例属性
  3. country作为类属性,由实例对象共用,实例对象通过 __class__ 属性 记录创建该对象的类的地址。
  • 类属性在内存中保存一份
  • 实例属性在每个对象中都要保存一份
  • 通过类创建实例对象时,如果每个对象需要具有相同名字的属性,那么就使用类属性,用一份既可
  • 实例属性需要通过对象来访问类属性通过类访问,在使用上可见实例属性和类属性的归属不同

1.2. 实例方法、类方法 和 静态方法

方法包括:实例方法静态方法类方法,三种方法在内存中都归属于类,区别在于调用方式不同。

  • 实例方法:由对象调用;至少一个self参数 (传实例对象的引用);执行实例方法时,自动将调用该方法的对象赋值给self;
  • 类方法:由 @classmethod 修饰,由类调用; 至少一个cls参数 (传类对象的引用);执行类方法时,自动将调用该方法的类赋值给cls;
  • 静态方法:由 @staticmethod 修饰,由类调用;无默认参数。
  • 相同点:对于所有的方法而言,均属于类,所以 在内存中也只保存一份;
  • 不同点:方法调用者不同、调用方法时自动传入的参数(传递的引用)不同
  •  
  • 注. 简单理解静态方法当一个函数与class是平等的关系,函数逻辑上与类对象和实例对象无关,可单独拿出来作为普通的函数。但是内容上可能相关,为了方便使用以及不与其他函数混淆,这样的函数可以作为类的静态方法。
  • 实例方法访问、修改类属性:通过  对象.__class__.类属性
  • 类方法访问、修改类属性:传的参数是类对象的引用,可直接改。
  • 实例对象可以调用 实例方法、类方法、静态方法
  • 类对象只能调用 类方法、静态方法

实例:

class Foo(object):
    def __init__(self, name):
        self.name = name

    def ord_func(self):
        """ 定义实例方法,至少有一个self参数 """
        # print(self.name)
        print('实例方法')

    @classmethod
    def class_func(cls):
        """ 定义类方法,至少有一个cls参数 """
        print('类方法')

    @staticmethod
    def static_func():
        """ 定义静态方法 ,无默认参数"""
        print('静态方法')


f = Foo("中国")
# 调用实例方法
f.ord_func()

# 调用类方法
Foo.class_func()

# 调用静态方法
Foo.static_func()

2. property属性(重点)

2.1. 什么是property属性

property属性一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。使方法调用像是获取变量值一样。如下:

# ############### 定义 ###############
class Foo:
    def func(self):
        pass

    # 定义property属性
    @property
    def prop(self):
        pass

# ############### 调用 ###############
foo_obj = Foo()
foo_obj.func()  # 调用实例方法
foo_obj.prop  # 调用property属性
  • 定义:在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
  • 调用property属性无需括号 ,使用时不需要考虑传参问题:  对象.方法名 .

Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。

简单的实例:

网页中显示的数据列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从 第m条到第n条(一页的起始) 的所有数据 这个分页的功能包括:

  • 根据用户请求的当前页和总数据条数计算出一页的起始 m 和 n

  • 根据m 和 n 去数据库中请求数据。

# ############### 定义 ###############
class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10 

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val  # 返回起始值

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val  # 返回结束值

# ############### 调用 ###############
p = Pager(1)  # current_page 为 1
p.start  # 就是起始值,即:m
p.end  # 就是结束值,即:n

2.2.  装饰器/类属性 创建property属性

  • 装饰器 即:在方法上应用装饰器;
  • 类属性 即:在类中定义值为property对象的类属性。

2.2.1 装饰器创建property属性

  • 经典类,具有一种@property装饰器:@property ;

  • 新式类(继承object),具有种@property装饰器: @property(取值)、 @方法名.setter(赋值)、@方法名.deleter(删除值),可将三个方法定义为对同一个属性:获取修改删除;

  • 经典类中的属性只有种访问方式,其对应被 @property 修饰的方法;
  • 新式类中的属性有种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法;
#coding=utf-8
# ############### 定义 ###############
class Goods:
    """python3中默认继承object类
        以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter  @xxx.deleter
    """
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')

# ############### 调用 ###############
obj = Goods()
obj.price          # 自动执行 @property 修饰的 price 方法,并获取方法的返回值  (获取值
obj.price = 123    # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数 (赋值 
del obj.price      # 自动执行 @price.deleter 修饰的 price 方法 (删除值

实例Demo

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price

obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价

2.2.2. 类属性创建property属性

通俗的说,在类里面定义一个 类属性=property(方法名, ...) 。调用时,对象.类属性名 自动调用方法名对应的方法。

如下示例:

类属性BAR = property(get_bar),实例对象为obj,obj.BAR会自动调用给property传的get_bar方法,并获取方法的返回值。

class Foo:
    def get_bar(self):
        return 'laowang'

    BAR = property(get_bar)

obj = Foo()
reuslt = obj.BAR  # 自动调用get_bar方法,并获取方法的返回值
print(reuslt)

实际上,property方法中有四个参数 ( 用help(property)可查 )

  • 参数1是方法名,接收要获取值时对应函数的函数引用,调用 对象.属性 时自动触发执行方法;
  • 参数2是方法名,接收要设置值时对应函数的函数引用,调用 对象.属性 = XXX 时自动触发执行方法;
  • 参数3是方法名,接收要删除值时对应函数的函数引用,调用 del 对象.属性 时自动触发执行方法;
  • 参数4是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息

由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

实例Demo:

#coding=utf-8
class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'laowang'

    def set_bar(self, value): 
        """必须两个参数"""
        print("setter...")
        return 'set value' + value

    def del_bar(self):
        print("deleter...")
        return 'laowang'

    BAR = property(get_bar, set_bar, del_bar, "description...")

obj = Foo()

obj.BAR  # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
desc = Foo.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR  # 自动调用第三个参数中定义的方法:del_bar方法

综上:

  • 定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方式针对经典类和新式类又有所不同。
  • 通过使用property属性,能够简化调用者在获取数据的流程

:WEB框架 Django 的视图中 request.POST 就是使用的类属性的方式创建的property属性。

2.3. property属性应用

2.3.1. 私有属性添加getter和setter方法(python不推荐这样做)

私有属性外部无法访问,添加getter和setter方法用来获取和设置。

如下:

class Money(object):
    def __init__(self):
        self.__money = 0

    def getMoney(self):
        return self.__money

    def setMoney(self, value):
        if isinstance(value, int):  # 判断对象value是否是一个int类型
            self.__money = value
        else:
            print("error:不是整型数字")

2.3.2. 使用property升级getter和setter方法(类属性方式)

用 property属性(类属性)来达到getter和setter的目的。好处,调用时就像操作一个属性(变量)一样,无需关心方法的代码。

如下:

class Money(object):
    def __init__(self):
        self.__money = 0

    def getMoney(self):
        return self.__money

    def setMoney(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

    # 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
    money = property(getMoney, setMoney)  

a = Money()
a.money = 100  # 调用setMoney方法
print(a.money)  # 调用getMoney方法

# 输出 100

2.3.3.  使用property取代getter和setter方法(装饰器方式)

用 property属性(装饰器)来达到getter和setter的目的。如下:

class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
    @property
    def money(self):
        return self.__money

    # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
    @money.setter
    def money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

a = Money()
a.money = 100
print(a.money)

3. 魔法属性/方法

引入:其实python的 私有属性 并非不可访问,而是通过名字重整(重整原则: _类名__属性名,使原来的名字无法访问了。如上例中, 通过 a.__dict__ 可返回类或对象中的所有属性(字典方式),包括私有属性;通过 对象._类名__属性名 即可访问属性,如 a._Money__money 即可访问私有属性money。

对于一个类,python定义了许多可用的魔法属性,存在着一些具有特殊含义的属性,有些每个类都默认存在,有些需要用户手动定义。常用的魔法属性如下。

 

3.1. __doc__

  • 该属性表示类的描述信息,记录了类的说明文档,用 类 和 实例引用 指向的都是类的__doc__属性,如果没有默认为None。
class Foo:
    """ 描述类信息、说明文档 """
    def func(self):
        pass

print(Foo.__doc__)
#输出:类的描述信息

3.2. __module__ 和 __class__

  • __module__ 表示当前操作的对象在那个模块。该属性记录类定义的位置(哪个模块),如果定义的位置正好是主程序,那么该值为"_main_",否则是类属于的模块的名字;
  • __class__ 表示当前操作的对象(实例)的类是什么。该属性指向该对象(实例)的类,即实例指向类对象,类对象指向元类;

test.py

# -*- coding:utf-8 -*-

class Person(object):
    def __init__(self):
        self.name = 'laowang'

main.py

from test import Person

obj = Person()
print(obj.__module__)  # 输出 test 即:输出模块 (因为对象obj对应的类定义在模块test.py)
print(obj.__class__)  # 输出 test.Person 即:输出类 (因为obj由test模块的Person创建)

3.3. __new__

该方法是类创建实例调用的第一个方法,返回一个实例;这是一个实例从无到有必须调用的方法,在单例模式中常用,其他不常用。创建实例时会将参数传入new方法,但new方法中无法更改参数。

class Person(object):
    def __new__(cls, *args, **kwargs):
        print(args)
        return object.__new__(cls)

if __name__ == "__main__":
    person = Person('cai')

3.4. __init__

  • 初始化方法,通过类创建对象时,自动触发执行。python在调用new方法后会紧接着调用init方法,我们将实例的一些初始化操作放在该方法中,即对__dict__属性进行操作。(__new__方法和__init__方法加起来才类似其他语言的构造方法)
class Person:
    def __init__(self, name):
        self.name = name
        self.age = 18


obj = Person('laowang')  # 自动执行类中的 __init__ 方法

3.5. __del__

  • 当对象在内存中被释放时,自动触发执行。即在实例对象引用计数变为0或del关键字调用的时候触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,__del__的调用是由解释器在进行垃圾回收时自动触发执行的。

class Foo:
    def __del__(self):
        pass

 

3.6. __call__

  • 对象后面加括号,触发执行。

注:

  • __init__方法的执行是由创建对象触发的,即:对象 = 类名() ;
  • 而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()(注:类名()创建了对象,再加一个括号等价与对象())
class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):  # 保证有call方法
        print('__call__')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__ 

3.7. __dict__

返回类或对象中的所有属性

又分为 的__dict__属性 和 实例的__dict__属性。类的实例属性属于对象;类中的类属性和方法等属于类。

  • 类的__dict__属性: 存储了类定义的所有类属性、类方法等组成的键值对,但不包括继承而来的属性和方法;

  • 实例的__dict__属性: 存储了所有的实例属性的键值对,如果没有就为空;

  • __init__方法其实就是对__dict__属性的初始化赋值;


class Province(object):
    country = 'China'

    def __init__(self, name, count):
        self.name = name
        self.count = count

    def func(self, *args, **kwargs):
        print('func')

# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}

obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}

obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}

3.8. __str__

  • 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo:
    def __str__(self):
        return 'hello'

obj = Foo()
print(obj)
# 输出:hello

3.9. __getitem__、__setitem__、__delitem__

  • 用于索引操作,如字典。以上分别表示获取、设置、删除数据。

下例,obj['k1'] 把 obj 对象当成了字典对象。

# -*- coding:utf-8 -*-

class Foo(object):

    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)


obj = Foo()

result = obj['k1']      # 自动触发执行 __getitem__, 把k1 传给了__getitem__的参数 key
obj['k2'] = 'hello'     # 自动触发执行 __setitem__, k2、'hello' 分别传给 __setitem__ 的参数 key、value
del obj['k1']           # 自动触发执行 __delitem__

3.10. __getslice__、__setslice__、__delslice__

  • 该三个方法用于分片操作,如:列表
# -*- coding:utf-8 -*-

class Foo(object):

    def __getslice__(self, i, j):
        print('__getslice__', i, j)

    def __setslice__(self, i, j, sequence):
        print('__setslice__', i, j)

    def __delslice__(self, i, j):
        print('__delslice__', i, j)

obj = Foo()

obj[-1:1]                   # 自动触发执行 __getslice__(切片)
obj[0:1] = [11,22,33,44]    # 自动触发执行 __setslice__(注:给列表切片设置值)
del obj[0:2]                # 自动触发执行 __delslice__

4.面向对象设计

三大编程范式(编程范式即编程的方法论,一种编程风格

    1.面向过程编程

    2.函数式编程

    3.面向对象编程

面向对象:

  • 继承 - 是基于Python中的属性查找(如X.name)
  • 多态 - 在X.method方法中,method的意义取决于X的类型
  • 封装 - 方法和运算符实现行为,数据隐藏默认是一种惯例

以下实例主要用于理解面向对象的设计思想

参考实例1:腾讯即时通信模块的初级封装

  • 定义了类 Message ;
  • 定义了类 MsgDict 作为基类,其他消息类型继承自该类;
#! /usr/bin/env python
# coding: utf-8

import random
import time


class Message(object):

    def __init__(self, msgarr=[], toacc=''):
        self.msgbody = msgarr # 此处为MsgDict对象实例的列表或者空列表
        self.toacc = toacc # toacc为字符串(单发)或者列表(批量发)
        self.msgrandom = random.randint(1, 1000000000)
        self.msgrequest = {
            'To_Account': toacc, # 消息接收方账号
            'MsgRandom': self.msgrandom, # 消息随机数,由随机函数产生
            'MsgBody': [t.msg for t in msgarr]
        }

    def del_option(self, option):
        if option in (set(self.msgrequest)-set(['To_Account', 'MsgRandom', 'MsgBody'])):
            self.__dict__.pop(option)
            self.msgrequest.pop(option)

    def append_msg(self, msg):
        self.msgbody.append(msg)
        self.msgrequest['MsgBody'].append(msg.msg)

    def insert_msg(self, index, msg):
        self.msgbody.insert(index, msg)
        self.msgrequest['MsgBody'].insert(msg.msg)

    def del_msg(self, index):
        if index in range(len(self.msgbody)):
            del self.msgbody[index]
            del sel.msgrequest['MsgBody'][index]

    def set_from(self, fromacc):
        # 指定消息的发送方,默认为服务器发送
        self.fromacc = fromacc
        self.msgrequest['From_Account'] = fromacc

    def set_to(self, toacc):
        # 指定消息的接收方,可以为String(单发),可以为List(批量发送)
        self.toacc = toacc
        self.msgrequest['To_Account'] = toacc

    def refresh_random(self):
        self.msgrandom = random.randint(1, 1000000000)
        self.msgrequest['MsgRandom'] = self.msgrandom, # 消息随机数,由随机函数产生

    def set_sync(self, sync):
        # 同步选项:1, 把消息同步到From_Account在线终端和漫游上
        #           2, 消息不同步至From_Account
        #           若不填写,默认情况下会将消息同步
        #           仅在单发单聊消息中可调用
        self.sync = sync
        self.msgrequest['SyncOtherMachine'] = sync

    def set_timestamp(self):
        # 设置消息时间戳,unix时间, 仅在单发单聊消息中可以调用
        self.timestamp = int(time.time())
        self.msgrequest['MsgTimeStamp'] = self.timestamp

    def set_offlinepush(self, pushflag=0, desc='', ext='', sound=''):
        # 仅适用于APNa推送,不适用于安卓厂商推送
        self.msgrequest['OfflinePushInfo'] = {
            'PushFlag': pushflag,
            'Desc': desc,
            'Ext': ext,
            'Sound': sound
        }


class MsgDict(object):

    def __init__(self, msgtype='', msgcontent={}):
        self.msgtype = msgtype
        self.msgcontent = msgcontent

    @property
    def msg(self):
        return {
            'MsgType': self.msgtype,
            'MsgContent': self.msgcontent
        } 

    def set_content(self, content):
        self.msgcontent = content


class TextMsg(MsgDict):

    def __init__(self, text='', msgtype='TIMTextElem'): 
        self.text = text
        content = {'Text': text}
        super(TextMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_text(self, text):
        self.text = text
        self.msgcontent['Text'] = text


class LocationMsg(MsgDict):

    def __init__(self, desc='', latitude=0, longitude=0, msgtype='TIMLocationElem'): 
        self.desc = desc
        self.latitude = latitude
        self.longitude = longitude
        content = {
            'Desc': desc,  # 地理位置描述信息, String
            'Latitude': latitude, # 纬度, Number
            'Longitude': longitude # 经度, Number
        }
        super(LocationMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_desc(self, desc):
        self.desc = desc
        self.msgcontent['Desc'] = desc

    def set_location(self, latitude, longitude):
        self.latitude = latitude
        self.longitude = longitude
        self.msgcontent['Latitude'] = latitude
        self.msgcontent['Longitude'] = longitude

    def set_latitude(self, latitude):
        self.latitude = latitude
        self.msgcontent['Latitude'] = latitude

    def set_longitude(self, longitude):
        self.longitude = longitude
        self.msgcontent['Longitude'] = longitude


class FaceMsg(MsgDict):

    def __init__(self, index=1, data='', msgtype='TIMFaceElem'): 
        self.index = index 
        self.data = data
        content = {
            'Index': index, # 表情索引,用户自定义, Number
            'Data': data # 额外数据, String
        }
        super(TextMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_index(self, index):
        self.index = index
        self.msgcontent['Index'] = index

    def set_data(self, data):
        self.data = data
        self.msgcontent['Data'] = data


class CustomMsg(MsgDict):

    def __init__(self, data='', desc='', ext='', sound='', msgtype='TIMCustomElem'): 
        self.data = data
        self.desc = desc
        self.ext = ext
        self.sound = sound
        content = {
            'Data': data, # 自定义消息数据。不作为APNS的payload中字段下发,故从payload中无法获取Data字段, String
            'Desc': desc, # 自定义消息描述,当接收方为iphone后台在线时,做ios离线Push时文本展示
            'Ext': ext, # 扩展字段,当接收方为ios系统且应用处在后台时,此字段作为APNS请求包Payloads中的ext键值下发,Ext的协议格式由业务方确定,APNS只做透传
            'Sound': sound # 自定义APNS推送铃声
        }
        super(CustomMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_data(self, data):
        self.data = data
        self.msgcontent['Data'] = data

    def set_desc(self, desc):
        self.desc = desc
        self.msgcontent['Desc'] = desc

    def set_ext(self, ext):
        self.ext = ext
        self.msgcontent['Ext'] = ext

    def set_sound(self, sound):
        self.sound = sound
        self.msgcontent['Sound'] = sound


class SoundMsg(MsgDict):

    def __init__(self, uuid='', size=0, second=0, msgtype='TIMSoundElem'): 
        self.uuid = uuid 
        self.size = size 
        self.second = second
        content = {
            'UUID': uuid, # 语音序列号,后台用于索引语音的键值,String
            'Size': size, # 语音数据大小, Number 
            'Second': second # 语音时长,单位秒 Number 
        }
        super(SoundMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_uuid(self, uuid):
        self.uuid = uuid
        self.msgcontent['UUID'] = uuid

    def set_size(self, size):
        self.size = size
        self.msgcontent['Size'] = size

    def set_second(self, second):
        self.second = second
        self.msgcontent['Second'] = second


class ImageMsg(MsgDict):

    def __init__(self, uuid='', imgformat=0, imginfo=[], msgtype='TIMImageElem'): 
        self.uuid = uuid 
        self.imgformat = imgformat 
        self.imginfo = imginfo 
        content = {
            'UUID': uuid, # 图片序列号,后台用于索引语音的键值,String
            'ImageFormat': imgformat, # 图片格式, BMP=1, JPG=2, GIF=3, 其他=0, Number 
            'ImageInfoArray': [t.info for t in imginfo] # 原图,缩略图或者大图下载信息, Array
        }
        super(ImageMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_uuid(self, uuid):
        self.uuid = uuid
        self.msgcontent['UUID'] = uuid

    def set_format(self, imgformat):
        self.imgformat = imgformat
        self.msgcontent['ImageFormat'] = imgformat

    def append_info(self, info):
        # info 为ImageInfo对象实例
        self.imginfo.append(info)
        self.msgcontnet['ImageInfoArray'].append(info.info)

    def insert_info(self, index, info):
        self.imginfo.insert(index, info)
        self.msgcontent['ImageInfoArray'].insert(index, info.info)

    def del_info(self, index):
        del self.imginfo[index]
        del self.msgcontent['ImageInfoArray'][index]


class FileMsg(MsgDict):

    def __init__(self, uuid='', size=0, name='', msgtype='TIMFileElem'): 
        self.uuid = uuid 
        self.size = size 
        self.name = name
        content = {
            'UUID': uuid, # 文件序列号,后台用于索引语音的键值,String
            'FileSize': size, # 文件数据大小, Number 
            'FileName': name # 文件名称/路径, String
        }
        super(FileMsg, self).__init__(msgtype=msgtype, msgcontent=content)

    def set_uuid(self, uuid):
        self.uuid = uuid
        self.msgcontent['UUID'] = UUID

    def set_size(self, size):
        self.size = size
        self.msgcontent['FileSize'] = size

    def set_name(self, name):
        self.name = name
        self.msgcontent['FileName'] = name


class ImageInfo(object):

    def __init__(self, itype=1, size=0, width=0, height=0, url=''):
        #图片类型, 1-原图, 2-大图, 3-缩略图, 0-其他
        self.itype = itype 
        # 图片数据大小,Number
        self.size = size
        # 图片宽度,Number
        self.width = width
        # 图片高度, Number
        self.height = height
        # 图片下载地址,String
        self.url = url

    @property
    def info(self):
        return {
            'Type': self.itype, 
            'Size': self.size,  
            'Width': self.width,  
            'Height': self.height, 
            'URL': self.url 
        }

    def set_type(self, itype):
        self.itype = itype

    def set_size(self, size):
        self.size = size

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def set_url(self, url):
        self.url = url

5.with与 上下文管理器

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。

比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件。否则可能出现 "Too many open files"等错误。

对于数据库,如果连接数过多而没有及时关闭的话,就可能会出现 "Can not connect to MySQL server Too many connections"。

5.1 with关键字

以下三个例子演示了正确关闭一个文件

 

例子1 普通版

def m1():
    f = open("output.txt", "w")
    f.write("hello python")
    f.close()

这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用者释放。

例子2 进阶版:

def m2():
    f = open("output.txt", "w")
    try:
        f.write("hello python")
    except IOError:
        print("oops error")
    finally:  # 一定会执行finally
        f.close()

对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,该语句表示如果在 try 代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块。而无论如何,finally 块的代码最终都会被执行,文件一定会被关闭。缺点是增加了许多代码。

 

例子3 with版本:

def m3():
    with open("output.txt", "r") as f:
        f.write("hello Python")

with 关键字是一种更加简洁、优雅的方式。open 方法的返回值赋值给变量 f,当离开 with 代码块的时候(无论是否出现异常),系统会自动调用 f.close() 方法, with 的作用和使用 try/finally 语句是一样的

那么它的实现原理是什么?在讲 with 的原理前要涉及到另外一个概念,就是上下文管理器(Context Manager)

 

5.2. 上下文管理器(Context Manager)

任何实现了 __enter__() 方法__exit__() 方法对象都可称之为上下文管理器,上下文管理器对象可以使用 with 关键字。显然,文件(file)对象也实现了上下文管理器。

如,以下定义了一个包含 __enter__() 方法__exit__() 方法 的类 File:

class File():

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        print("will exit")
        self.f.close()

__enter__() 方法返回资源对象,这里就是你将要打开的那个文件对象,__exit__() 方法处理一些清除工作。

因为 File 类实现了上下文管理器,可以使用 with 语句,如下。

 

with File('out.txt', 'w') as fFile('out.txt', 'w') 返回对象的引用with 检查其是否是上下文管理器,若是则其会自动调用对象的__enter__() 方法,__enter__()的返回值给 f 。若产生了异常,会调用对象的__exit__() 方法。

with File('out.txt', 'w') as f:
    print("writing")
    f.write('hello, python')

启发UDP和TCP的Demo中,将其套接字、和使用套接字的函数封装成类,并实现 __enter__() 方法 和 __exit__()方法 。这样收发消息时,无论是否产生异常,都会自动调用套接字的close。

实现上下文管理器的另外方式:

Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 __enter__ 方法中执行,yield 之后的语句在 __exit__ 方法中执行。 yield 后面的值是函数的返回值。

如下: f = open(path, mode) 语句在 __enter__ 方法中执行,yield 之后的语句 f.close() 在 __exit__ 方法中执行,my_open方法返回 f

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f              # f 为方法的返回值
    f.close()


# 调用
with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")

with/上下文管理器小结

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。

----------实例来源于课件、网络等--------

-----------END-----------

发布了50 篇原创文章 · 获赞 10 · 访问量 6613

猜你喜欢

转载自blog.csdn.net/qq_23996069/article/details/104251467