Как-то возможно * изменить * код Python (например, в Lisp или Erlang)
Мне было интересно, можно ли как-то модифицировать код Python вживую, сохраняя все состояние экземпляров объектов и методов, как я думаю, возможно в Lisp или Erlang (*)?
Скажем, у меня есть активные сеансы Python, где я создал экземпляр класса foo
из самонаписанного модуля:
class foo():
@classmethod
def do_something(self):
print "this is good"
Командная строка Python:
>>> f =foo()
>>> f.do_something()
Теперь я хотел бы изменить оператор печати на что-то другое (например, print "this is better"
). Если я отредактирую файл модуля, чтобы сделать это, и перезагрузите его, я должен повторно создать экземпляр объекта f
. Есть ли способ просто позвонить f.do_something()
снова, не вызывая сначала f=foo()
?
Итак, я должен это сделать:
>>> reload my_module
>>> f =foo()
>>> f.do_something() # with changed print statement
Но я хочу сделать это:
>>> reload my_module
>>> f.do_something() # with changed print statement
(*) Я основываю это утверждение на крутом фильме Erlang и этот фрагмент Практический общий Lisp: "Когда ошибка, проявленная в дикой природе - на 100 миллионов миль от Земли, команде удалось диагностировать и исправлять текущий код, позволяя завершить эксперименты.
Изменить: Я думал об этом немного больше, и, может быть, то, что я хочу, по своей сути ошибочно относится к применению OO (то есть к состоянию класса и методам). Я думаю, что Erlang позволяет это, потому что, насколько я помню, это больше касается отдельных коммуникационных объектов, поэтому живое обновление кода объекта имеет больше смысла. Я не уверен, хотя, так что все еще открыт для ответов.
Edit2: Возможно, лучший способ описать то, что я хочу, - это повторить то, что я сказал в комментарии в сообщении ниже: "Когда вызывается, методы просто должны указывать на новые определения методов/места".
Ответы
Ответ 1
Да, вы можете, довольно просто. Вы хотите изменить только экземпляр f
, а не класс foo
, правильно?
>>> class foo():
@classmethod
def do_something(self):
print "this is good"
>>> f = foo()
>>> f.do_something()
this is good
>>> def new_ds():
print "this is better"
>>> f.do_something = new_ds
>>> f.do_something()
this is better
>>> f2 = foo()
>>> f2.do_something() #unchanged
this is good
ИЗМЕНИТЬ
Это почти наверняка меньше желаемого из-за изменения объема, но изменения, подобные этому, имели место сразу после перезагрузки
testmod.py - изначально
class foo():
@classmethod
def do_something(self):
outside_func()
def outside_func():
print "this is good"
testmod.py - после изменения
class foo():
@classmethod
def do_something(self):
outside_func()
def outside_func():
print "this is better"
Переводчик
>>> import testmod
>>> f = testmod.foo()
>>> f.do_something()
this is good
>>> reload(testmod)
<module 'testmod' from 'C:\Python26\testmod.py'>
>>> f.do_something()
this is better
Ответ 2
Вы можете создать декоратор класса или метакласс, который гарантирует, что класс старых объектов будет изменен при перезагрузке класса. Здесь работает (по крайней мере, для меня) пример, хотя я бы не предложил вам использовать его как есть, но использовать его как вдохновение для создания чего-то, что соответствует вашим намерениям и потребностям. (Он также не тестируется на классах, которые не определяют __init__
, поэтому будьте осторожны.)
import sys
import weakref
class _ObSet(weakref.WeakValueDictionary):
def add(self, ob):
self[id(ob)] = ob
def remove(self, ob):
del self[id(ob)]
def __iter__(self):
return self.itervalues()
def reloadable(cls):
# Change the __init__ of the old class to store the instances
# in cls.__instances (you might stick this into a class as a
# static method to avoid name collisions)
if '__init__' in vars(cls):
old_init = vars(cls)['__init__']
def __init__(self, *a, **kw):
self.__class__.__instances.add(self)
old_init(self, *a, **kw)
cls.__init__ = __init__
elif '__new__' in vars(cls):
old_new = vars(cls)['__new__']
def __new__(cls, *a, **kw):
self = old_new(cls, *a, **kw)
cls.__instances.add(self)
return self
cls.__new__ = __new__
else:
def __init__(self, *a, **kw):
self.__class__.__instances.add(self)
super(cls, self).__init__(*a, **kw)
cls.__init__ = __init__
cls.__instances = _ObSet()
module = sys.modules.get(cls.__module__)
if module is None:
return cls
old_cls = getattr(module, cls.__name__, None)
if old_cls is None:
return cls
# Change the bases of all subclasses of the old class
for ob in old_cls.__instances:
if ob.__class__ is old_cls:
ob.__class__ = cls
# Change the class of all instances of the old class
for child_cls in old_cls.__subclasses__():
child_cls.__bases__ = tuple(cls if base is old_cls else base
for base in child_cls.__bases__)
return cls
Вот пример того, как он используется:
from reloading import reloadable
@reloadable
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
class B1(A):
def __init__(self, c, *a):
super(B1, self).__init__(*a)
self.c = c
@reloadable
class B2(A):
def __init__(self, c, *a):
super(B2, self).__init__(*a)
self.c = c
И как это работает:
>>> import test_reload
>>> a = test_reload.A(1, 2)
>>> b1 = test_reload.B1(1, 2, 3)
>>> b2 = test_reload.B2(1, 4, 6)
>>> isinstance(a, test_reload.A)
True
>>> isinstance(b1, test_reload.A)
True
>>> isinstance(b1, test_reload.B1)
True
>>> isinstance(b2, test_reload.A)
True
>>> isinstance(b2, test_reload.B2)
True
>>> reload(test_reload)
<module 'test_reload' from 'test_reload.pyc'>
>>> isinstance(a, test_reload.A)
True
>>> isinstance(b1, test_reload.A)
True
>>> isinstance(b1, test_reload.B1) # will fail, not @reloadable
False
>>> isinstance(b2, test_reload.A)
True
>>> isinstance(b2, test_reload.B2)
True
>>> a.a, a.b
(1, 2)
>>> b1.a, b1.b, b1.c
(2, 3, 1)
>>> b2.a, b2.b, b2.c
(4, 6, 1)
Ответ 3
Это показывает, что вы можете изменить существующий класс и внести эти изменения в экземпляры этого класса. Ключ состоит в том, чтобы изменить существующий класс, а не (re) создать новый класс с тем же именем, что и старый класс.
>>> class foo():
... @classmethod
... def do_something(self):
... print "this is good"
...
>>> f = foo()
>>> f.do_something()
this is good
>>> def do_something_else(self):
... print "this is better"
...
>>> foo.do_something = do_something_else
>>> f.do_something()
this is better