探究如何给Python程序做hotfix

铁扇公主1 发布于 2017/05/26 15:06
阅读 101
收藏 0

【华为云1024程序员节·向云而生】预约直播 抽14件华为电子产品礼包!>>>

import

说到hotfix就要从import语句说起。
undefined
首先建立这样一个简单的文件用作测试。

 

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

from __future__ import print_function

 

class RefreshClass(object):

    def __init__(self):

        self.value = 1

 

    def print_info(self):

        print('RefreshClass value: {} ver1.0'.format(self.value))

 

version = 1.0

  

print(version)

下面启动一个python解释器。

 

Python

 

1

2

3

4

5

6

7

>>> import test_refresh as tr

1.0

>>> import test_refresh as tr

>>>> # edit  version=2.0

>>> import test_refresh as tr

>>> tr.version

1.0

重新import一个已经import过的模块,并不会重新执行文件(第二个import之后没有输出)。后面修改源文件并重新import后,对内存中tr.version的检查也验证了这一点。

为了能够重新加载修改后的源文件,我们需要明确的告诉Python解释器这一点。在Python中,sys.modules保存了已经加载过的模块。所以

 

Python

 

1

2

3

4

5

>>> del sys.modules['test_refresh']

>>> import test_refresh as tr

2.0

>>> tr.version

2.0

在将test_refresh从sys.modules中删除之后再进行import操作,就会重新加载源文件了。

另外,如果我们只能拿到模块的字符串名字,可以使用__import__函数。

 

Python

 

1

2

3

4

5

6

# edit version=3.0

>>> del sys.modules['test_refresh']

>>> tr = __import__('test_refresh')

3.0

>>> tr.version

3.0

 

reload

当我们面对的是一个之前已经import过的模块时,可以直接使用reload进行重新加载。

 

Python

 

1

2

3

4

5

6

# edit version = 4.0

>>> reload(tr)          

4.0

<module 'test_refresh' from 'test_refresh.py'>

>>> tr.version

4.0

 

初步尝试hotfix

知道了模块重新加载的方法后,我们在Python的交互式命令行中,尝试动态改变一个类的行为逻辑。

 

Python

 

1

2

3

4

5

6

7

8

from __future__ import print_function

 

class RefreshClass(object):

    def __init__(self):

        self.value = 1

 

    def print_info(self):

        print('RefreshClass value: {} ver1.0'.format(self.value))

这是测试类的当前状态。

我们创建一个该类的对象,验证下它的行为。

 

Python

 

1

2

3

4

5

>>> a = tr.RefreshClass()

>>> a.value

1

>>> a.print_info()

RefreshClass value: 1 ver1.0

符合预期。

接下来,修改类的print_info函数为ver2.0,并reload模块。

 

Python

 

1

2

3

4

5

6

7

8

# edit print_info ver2.0

>>> reload(tr)

4.0

<module 'test_refresh' from 'test_refresh.py'>

>>> a.value

1

>>> a.print_info()

RefreshClass value: 1 ver1.0

输出并没有如预期一样输出ver2.0……

那我们重新创建一个对象试试。

 

Python

 

1

2

3

4

5

>>> b = tr.RefreshClass()

>>> b.value

2

>>> b.print_info()

RefreshClass value: 2 ver2.0

新对象b的行为是符合重新加载后的逻辑的。这说明,reload确实更新了RefreshClass类的行为,但是对于已经实例化的RefreshClass类的对象,却没有进行更新。对象a中的行为还是指向了旧的RefreshClass类。

在Python中,一切皆是对象。不仅实例a是对象,a的类RefreshClass也是对象。
这时,要修改a的行为,就需要用到a的__class__属性,来强制使a的类行为指向重新加载后的RefreshClass对象。

 

Python

 

1

2

3

4

5

>>> a.__class__ = tr.RefreshClass

>>> a.value

1

>>> a.print_info()

RefreshClass value: 1 ver2.0

由于value是绑定在实例a上的,所以它的值并不会随RefreshClass的改变而改变。这也符合hotfix的预期逻辑:更新内存中实例的行为逻辑,但是不更新它们的数据。

接下来,我们还可以通过print_info函数的imfunc属性,验证在更改了_class__属性后,函数确实更新成了新版本。

 

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# edit print_info ver3.0

>>> reload(tr)

4.0

<module 'test_refresh' from 'test_refresh.py'>

>>> a.print_info.im_func

<function print_info at 0x7f50beeb2c08>

>>> c = tr.RefreshClass()

>>> c.print_info()

RefreshClass value: 3 ver3.0

>>> c.print_info.im_func

<function print_info at 0x7f50beeb2cf8>

>>> a.__class__ = tr.RefreshClass

>>> a.print_info.im_func

<function print_info at 0x7f50beeb2cf8>

>>> a.print_info()

RefreshClass value: 1 ver3.0

 

触发hotfix

上面的操作都是在Python的交互式解释器中运行的。下面我们将尝试使一个运行中的Python程序进行热更新。

这里遇到一个问题:作为Python程序入口的那个文件,不是以module的形式存在的,因此不能用上面的方式进行hotfix。所以我们需要保持入口文件的尽量简洁,而将绝大多数的逻辑功能交给其他的模块执行。

要触发一个正在运行中的Python程序进行热更新,我们需要有一种方式和Python程序通信。直接使用OS的标识文件是一个简单易行的方法。

 

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

from __future__ import print_function

 

import os

import time

import refresh_class

 

 

rc = refresh_class.RefreshClass()

while True:

    if os.path.exists('refresh.signal'):

        reload(refresh_class)

        rc.__class__ = refresh_class.RefreshClass

    time.sleep(5)

    rc.print_info()

 

 

Python

 

1

2

3

4

5

6

class RefreshClass(object):

    def __init__(self):

        self.value = 1

 

    def print_info(self):

        print('RefreshClass value: {} ver1.0'.format(self.value))

每次我们修改完refresh_class.py文件,就创建一个refresh.signal文件。当refresh执行完毕,删除此文件即可。

这种做法一般来讲,会导致多次重新加载(因为一般不能及时的删除refresh.signal文件)。

所以,我们考虑使用Linux下的信号量,来同Python程序通信。

 

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

from __future__ import print_function

 

import time

import signal

 

import refresh_class

 

rc = refresh_class.RefreshClass()

 

 

def handl_refresh(signum, frame):

    reload(refresh_class)

    rc.__class__ = refresh_class.RefreshClass

 

 

signal.signal(signal.SIGUSR1, handl_refresh)

while True:

    time.sleep(5)

    rc.print_info()

我们在Python中注册了信号量SIGUSR1的handler,在其中热更新RefreshClass。

那么只需在另一个terminal中,输入:

kill -SIGUSR1 pid

即可向pid进程发送信号量SIGUSR1。

当然,还有其他方法可以触发hotfix,比如使用PIPE,或者直接开一个socket监听,自己设计消息格式来触发hotfix。

总结

以上进行Python热更新的方式,原理简单明了,就是利用了Python提供的import/reload机制。但是这种方式,需要去替换每一个类的实例的__class__成员。这就往往需要在某处保存目前内存中存在的所有对象(或者能够索引到所有活动对象的根对象),并且在类的设计上,需要所有类的基类提供一个通用的refresh方法,在其中进行__class__的替换工作。对于复杂的类组合方式,这种方法比较容易在热更新的时候漏掉某些实例。

怎么样才能学好python学好python你需要一个良好的环境,一个优质的开发交流群,群里都是那种相互帮助的人才是可以的,我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是301,还有056,最后是051,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种分享。如果你看了觉得还可以的麻烦给我点个赞谢谢
  

加载中
返回顶部
顶部