2010-10-10 4 views
4

У меня есть класс, в котором я добавляю некоторые атрибуты динамически и в какой-то момент хочу восстановить класс в нетронутом состоянии без добавленных атрибутов.Восстановить класс Python до исходного состояния

Ситуация:

class Foo(object): 
    pass 

Foo.x = 1 
# <insert python magic here> 
o = Foo() # o should not have any of the previously added attributes 
print o.x # Should raise exception 

Моя первая мысль была создать копию оригинального класса:

class _Foo(object): 
    pass 

Foo = _Foo 
Foo.x = 1 
Foo = _Foo # Clear added attributes 
o = Foo() 
print o.x # Should raise exception 

Но поскольку Foo просто ссылка на _foo любые атрибуты добавляются к оригиналу _Foo также. Я также пробовал

Foo = copy.deepcopy(_Foo) 

в случае, если это поможет, но, по-видимому, это не так.

осветление:

Пользователю не нужно заботиться о том, как реализуются класс. Поэтому он должен иметь те же функции «нормально определенного» класса, то есть интроспекции, встроенной помощи, подклассификации и т. Д. Это в значительной степени исключает что-либо на основе __getattr__

+0

Могу я спросить, почему вы считаете, что это полезно? –

+0

Поскольку у меня есть класс, где я динамически добавляю методы с префиксом по умолчанию (например, «m_» перед всеми добавленными материалами), когда модуль импортируется. Пользователь может захотеть использовать какой-либо другой префикс, поэтому мне нужно «повторно инициализировать» класс. – pafcu

+2

Это звучит как очень сломанная идея. –

ответ

1

Вы можете использовать инспектировать и поддерживать первоначальный список членов и не удалять все элементы, которые не в первоначальном списке

import inspect 
orig_members = [] 
for name, ref in inspect.getmembers(o): 
    orig_members.append(name) 
... 

Теперь, когда вам нужно восстановить обратно к исходному

for name, ref in inspect.getmembers(o): 
    if name in orig_members: 
    pass 
    else: 
    #delete ref here 
3

Вам необходимо записать исходное состояние и восстановить его явно. Если значение существовало до его изменения, восстановите это значение; в противном случае удалите установленное значение.

class Foo(object): 
    pass 

try: 
    original_value = getattr(Foo, 'x') 
    originally_existed = True 
except AttributeError: 
    originally_existed = False 

Foo.x = 1 

if originally_existed: 
    Foo.x = original_value 
else: 
    del Foo.x 

o = Foo() # o should not have any of the previously added attributes 
print o.x # Should raise exception 

Возможно, вы не хотите этого делать. Есть действительные случаи для патчей обезьян, но вы вообще не хотите пытаться обезьяну unpatch. Например, если два независимых бита кода обезьяны патчат один и тот же класс, один из них пытается отменить действие, не зная о другом, скорее всего, сломает вещи. Например, пример, когда это действительно полезно, см. https://stackoverflow.com/questions/3829742#3829849.

+0

Согласен. Вы не должны изменять внешние классы. Это класс, который модифицирует себя. Конечный пользователь не должен изменять какие-либо методы самостоятельно. Добавление методов более или менее прозрачно. Возможность изменения префикса необходима для будущего класса и вообще не нужна (методы добавляются на основе внешних данных). Альтернативой является не создание класса с префиксом по умолчанию, а принуждение пользователя каждый раз указывать его явно. Это приводит к большему количеству кода для пользователя, который может писать, больше возможностей для ошибок и вообще более раздражающего поведения. – pafcu

+0

Вы должны либо договориться о наличии конфигурации перед загрузкой модуля, так что вы можете установить правильные имена методов для начала или обработать имена методов динамически с помощью метода '__getattr__'. Однако, если вы действительно хотите установить методы, затем удалите их и установите их снова - тогда вы это сделаете. Если вы точно знаете, что методы не будут существовать, вы можете просто удалить их и не беспокоиться о сохранении исходного состояния. –

+0

Или, я полагаю, вы могли полностью игнорировать ответ. (Добро пожаловать в * Стандартное переполнение *.) –

0

Я не совсем понимаю, зачем вам это нужно, но я пойду. Обычное наследование, вероятно, не будет выполнено, потому что вы хотите «перезагрузить» прежнее состояние. Как about a proxy pattern?

class FooProxy(object): 
    def __init__(self, f): 
     self.f = foo 
     self.magic = {} 

    def set_magic(self, k, v): 
     self.magic[k] = v 

    def get_magic(self, k): 
     return self.magic.get(k) 

    def __getattr__(self, k): 
     return getattr(self.f, k) 

    def __setattr__(self, k, v): 
     setattr(self.f, k, v) 

    f = Foo() 
    p = FooProxy(f) 
    p.set_magic('m_bla', 123) 

использование е для обычного, «оригинальный» доступ, использовать р для проксированного доступа, он должен вести себя в основном как Foo. Re-прокси е с новой конфигурацией, если вам нужно

0

Я не знаю, если вы можете принять дополнительный файл модуля для класса, если вы можете:

my_class.py

class Foo(object): 
    pass 

Вы Основной сценарий :

import my_class 
Foo = my_class.Foo 
Foo.x = 1 
p = Foo() 
print p.x # Printing '1' 

# Some code.... 

reload(my_class) # reload to reset 
Foo = my_class.Foo 
o = Foo() 
print p.x # Printing '1' 
print o.__class__ == p.__class__ # Printing 'False' 
print o.x # Raising exception 

Я не уверен, есть ли побочный эффект. Кажется, что делает то, что хочет OP, хотя это действительно неудачное.

+0

Я настоятельно рекомендую избегать использования 'reload' в производственном коде. Вы не восстанавливаете один и тот же объект, а загружаете новый по старому имени. Любые ссылки и экземпляры класса перед перезагрузкой могут привести к поломке. –

5

Я согласен с Гленном в том, что это ужасно сломанная идея. В любом случае, вот как вы это сделаете с декоратором. Спасибо за сообщение Гленна, а также за то, что вы напомнили мне, что вы можете удалить элементы из словаря класса, а не напрямую. Вот код.

def resetable(cls): 
    cls._resetable_cache_ = cls.__dict__.copy() 
    return cls 

def reset(cls): 
    cache = cls._resetable_cache_ # raises AttributeError on class without decorator 
    for key in [key for key in cls.__dict__ if key not in cache]: 
     delattr(cls, key) 
    for key, value in cache.items(): # reset the items to original values 
     try: 
      setattr(cls, key, value) 
     except AttributeError: 
      pass 

Я разорванный о том, чтобы сбросить значения ловя попытки обновления, не назначаемые атрибутов с try, как я показал, или создание списка таких атрибутов. Я оставлю это вам.

А вот польза:

@resetable # use resetable on a class that you want to do this with 
class Foo(object): 
    pass 

Foo.x = 1 
print Foo.x 
reset(Foo) 
o = Foo() 
print o.x # raises AttributeError as expected 
0

В вашем втором примере вы делаете ссылку на класс, а не экземпляр.

Foo = _foo # Ссылка

Если вместо этого сделал копию экземпляра, что вы хотите сделать, это именно так, как он будет работать. Вы можете изменить экземпляр, который хотите, и «вернуть» его, создав новый экземпляр.

Foo = _foo()

#/USR/бен/питон

класс FooClass (объект): проход

FooInstance = FooClass() # Создать экземпляр

FooInstance.x = 100 # Изменить экземпляр

print dir (FooClass) # Проверить, что FooClass не имеет атрибута 'x'

FooInstance = FooClass() # Создает новый экземпляр

печати FooInstance.x # Exception

+0

Нет, это не будет сделано, так как я теряю, например. возможность делать помощь (Foo) в подсказке python, не может подкласса от Foo и т. д. – pafcu

+0

На самом деле вы можете делать обе эти вещи. help (FooInstance) представит вам справку FooClass. Что касается подклассификации, это новое требование, не упомянутое в вопросе. Вы хотите прочитать метапрограммирование с помощью python. Попробуйте эти. http://www2.lib.uchicago.edu/~keith/courses/python/class/5/ и http://tim.oreilly.com/pub/a/python/2003/04/17/metaclasses.html – synthesizerpatel

-1

Для создания глубокой копии класса вы можете использовать функцию new.classobj

class Foo: 
    pass 

import new, copy 
FooSaved = new.classobj(Foo.__name__, Foo.__bases__, copy.deepcopy(Foo.__dict__)) 

# ...play with original class Foo... 

# revert changes 
Foo = FooSaved 

UPD : модуль new устарел. Вместо этого вы должны использовать types.ClassType с теми же аргументах

+0

это классы старого стиля, и ваши предложения будут работать только для таких классов.Это не просто модуль, который устарел, это целая чертова схема, которую вы предлагаете. – aaronasterling

0

Я не понимаю, что вы пытаетесь сделать, но имейте в виду, что вы не должны добавлять атрибуты класса для того, чтобы сделать его выглядеть вы добавили атрибуты в класс.

Вы можете дать классу метод __getattr__, который будет вызываться для любого отсутствующего атрибута. Где она получает значение от до вас:

class MyTrickyClass(object): 
    self.magic_prefix = "m_" 
    self.other_attribute_source = SomeOtherObject() 

    def __getattr__(self, name): 
     if name.startswith(self.magic_prefix): 
      stripped = name[len(self.magic_prefix):] 
      return getattr(self.other_attribute_source, stripped) 
     raise AttributeError 

m = MyTrickyClass() 
assert hasattr(m, "m_other") 
MyTrickyClass.magic_prefix = "f_" 
assert hasattr(m, "f_other") 
+0

Я думаю, мне следовало бы более ясно, что я хочу, чтобы весь процесс был максимально прозрачным, то есть пользователю не нужно было знать, как этот класс реализуется. Переход по маршруту __getattr__ приводит к многочисленным проблемам, таким как отсутствие интроспекции, которая путала бы пользователей. – pafcu

0

Если весь материал, добавленный начинается с заданным отличительным префиксом, вы можете искать объект __dict__ для членов с префиксом, и удалить их, когда пришло время восстановить.

0

Самый простой способ я нашел это:

def foo_maker(): 
    class Foo(object): 
     pass 
    return Foo 
Foo = foo_maker() 
Foo.x = 1 
Foo = foo_maker() # Foo is now clean again 
o = Foo() # Does not have any of the previously added attributes 
print o.x # Raises exception 

е dit: Как указано в комментариях, на самом деле не сброс класса, но на практике такой же эффект.

+0

, который не восстанавливает класс до его первоначального значения. Это совершенно новый класс с тем же именем. – aaronasterling

+0

@AaronMcSmooth: Конечно, но у него есть все свойства, которые я хотел бы иметь. Внешнему пользователю нет разницы. – pafcu

+0

Создайте экземпляр модифицированного класса 'Foo'. Замените класс 'Foo' на« чистую »версию. У старого экземпляра все еще есть новые атрибуты. Это совсем не то же самое. Даже на футбольном поле. – aaronasterling

Смежные вопросы