Я хочу создать класс на Python, чтобы добавлять и удалять атрибуты и методы. Как я могу это сделать?
О, и, пожалуйста, не спрашивайте, почему.
Я хочу создать класс на Python, чтобы добавлять и удалять атрибуты и методы. Как я могу это сделать?
О, и, пожалуйста, не спрашивайте, почему.
Я хочу создать класс на Python, чтобы добавить и удалить атрибуты и методы.
import types
class SpecialClass(object):
@classmethod
def removeVariable(cls, name):
return delattr(cls, name)
@classmethod
def addMethod(cls, func):
return setattr(cls, func.__name__, types.MethodType(func, cls))
def hello(self, n):
print n
instance = SpecialClass()
SpecialClass.addMethod(hello)
>>> SpecialClass.hello(5)
5
>>> instance.hello(6)
6
>>> SpecialClass.removeVariable("hello")
>>> instance.hello(7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'SpecialClass' object has no attribute 'hello'
>>> SpecialClass.hello(8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'SpecialClass' has no attribute 'hello'
В этом примере показаны различия между добавлением метода к классу и экземпляру.
>>> class Dog():
... def __init__(self, name):
... self.name = name
...
>>> skip = Dog('Skip')
>>> spot = Dog('Spot')
>>> def talk(self):
... print 'Hi, my name is ' + self.name
...
>>> Dog.talk = talk # add method to class
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk()
Hi, my name is Spot
>>> del Dog.talk # remove method from class
>>> skip.talk() # won't work anymore
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'
>>> import types
>>> f = types.MethodType(talk, skip, Dog)
>>> skip.talk = f # add method to specific instance
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk() # won't work, since we only modified skip
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'
Возможно, интересная альтернатива использованию types.MethodType
в:
>>> f = types.MethodType(talk, puppy, Dog)
>>> puppy.talk = f # add method to specific instance
будет использовать тот факт, что функции дескрипторы:
>>> puppy.talk = talk.__get__(puppy, Dog)
Я хочу создать класс на Python, чтобы добавлять и удалять атрибуты и методы. Как я могу это сделать?
Вы можете добавлять и удалять атрибуты и методы для любого класса, и они будут доступны для всех экземпляров класса:
>>> def method1(self):
pass
>>> def method1(self):
print "method1"
>>> def method2(self):
print "method2"
>>> class C():
pass
>>> c = C()
>>> c.method()
Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
c.method()
AttributeError: C instance has no attribute 'method'
>>> C.method = method1
>>> c.method()
method1
>>> C.method = method2
>>> c.method()
method2
>>> del C.method
>>> c.method()
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
c.method()
AttributeError: C instance has no attribute 'method'
>>> C.attribute = "foo"
>>> c.attribute
'foo'
>>> c.attribute = "bar"
>>> c.attribute
'bar'
вы можете просто назначить непосредственно классу (либо путем обращения к исходному имени класса, либо через __class__
):
class a : pass
ob=a()
ob.__class__.blah=lambda self,k: (3, self,k)
ob.blah(5)
ob2=a()
ob2.blah(7)
напечатает
(3, <__main__.a instance at 0x7f18e3c345f0>, 5)
(3, <__main__.a instance at 0x7f18e3c344d0>, 7)
Другая альтернатива, если вам нужно заменить оптовый класс, необходимо изменить атрибут class:
>>> class A(object):
... def foo(self):
... print 'A'
...
>>> class B(object):
... def foo(self):
... print 'Bar'
...
>>> a = A()
>>> a.foo()
A
>>> a.__class__ = B
>>> a.foo()
Bar
Просто:
f1 = lambda:0 #method for instances
f2 = lambda _:0 #method for class
class C: pass #class
c1,c2 = C(),C() #instances
print dir(c1),dir(c2)
#add to the Instances
c1.func = f1
c1.any = 1.23
print dir(c1),dir(c2)
print c1.func(),c1.any
del c1.func,c1.any
#add to the Class
C.func = f2
C.any = 1.23
print dir(c1),dir(c2)
print c1.func(),c1.any
print c2.func(),c2.any
что приводит к:
['__doc__', '__module__'] ['__doc__', '__module__']
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__']
0 1.23
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__', 'any', 'func']
0 1.23
0 1.23
Нужно ли, чтобы сам класс необходимо было изменить? Или цель просто заменить то, что object.method() делает в определенной точке во время выполнения?
Я прошу, потому что я обошел проблему фактического изменения класса в вызове метода patch для monkey в моей структуре с помощью getattribute и Runtime Decorator на моем объекте наследования Base.
Методы, полученные базовым объектом в getattribute, завертываются в Runtime_Decorator, который анализирует метод, вызывающий аргументы ключевого слова метода для исправлений декораторов/обезьян.
Это позволяет использовать синтаксис object.method(monkey_patch = "mypatch" ), object.method(decorator = "mydecorator" ) и даже object.method(decorators = my_decorator_list).
Это работает для любого индивидуального вызова метода (я не учитываю магические методы), делает это без фактического изменения атрибутов класса/экземпляра, может использовать произвольные, даже чужие методы для исправления и будет работать прозрачно на подслоях, которые наследуют от Base ( если они не переопределяют getattribute, конечно).
import trace
def monkey_patched(self, *args, **kwargs):
print self, "Tried to call a method, but it was monkey patched instead"
return "and now for something completely different"
class Base(object):
def __init__(self):
super(Base, self).__init__()
def testmethod(self):
print "%s test method" % self
def __getattribute__(self, attribute):
value = super(Base, self).__getattribute__(attribute)
if "__" not in attribute and callable(value):
value = Runtime_Decorator(value)
return value
class Runtime_Decorator(object):
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
if kwargs.has_key("monkey_patch"):
module_name, patch_name = self._resolve_string(kwargs.pop("monkey_patch"))
module = self._get_module(module_name)
monkey_patch = getattr(module, patch_name)
return monkey_patch(self.function.im_self, *args, **kwargs)
if kwargs.has_key('decorator'):
decorator_type = str(kwargs['decorator'])
module_name, decorator_name = self._resolve_string(decorator_type)
decorator = self._get_decorator(decorator_name, module_name)
wrapped_function = decorator(self.function)
del kwargs['decorator']
return wrapped_function(*args, **kwargs)
elif kwargs.has_key('decorators'):
decorators = []
for item in kwargs['decorators']:
module_name, decorator_name = self._resolve_string(item)
decorator = self._get_decorator(decorator_name, module_name)
decorators.append(decorator)
wrapped_function = self.function
for item in reversed(decorators):
wrapped_function = item(wrapped_function)
del kwargs['decorators']
return wrapped_function(*args, **kwargs)
else:
return self.function(*args, **kwargs)
def _resolve_string(self, string):
try: # attempt to split the string into a module and attribute
module_name, decorator_name = string.split(".")
except ValueError: # there was no ".", it just a single attribute
module_name = "__main__"
decorator_name = string
finally:
return module_name, decorator_name
def _get_module(self, module_name):
try: # attempt to load the module if it exists already
module = modules[module_name]
except KeyError: # import it if it doesn't
module = __import__(module_name)
finally:
return module
def _get_decorator(self, decorator_name, module_name):
module = self._get_module(module_name)
try: # attempt to procure the decorator class
decorator_wrap = getattr(module, decorator_name)
except AttributeError: # decorator not found in module
print("failed to locate decorators %s for function %s." %\
(kwargs["decorator"], self.function))
else:
return decorator_wrap # instantiate the class with self.function
class Tracer(object):
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
tracer = trace.Trace(trace=1)
tracer.runfunc(self.function, *args, **kwargs)
b = Base()
b.testmethod(monkey_patch="monkey_patched")
b.testmethod(decorator="Tracer")
#b.testmethod(monkey_patch="external_module.my_patch")
Недостатком этого подхода является getattribute перехватывает весь доступ к атрибутам, поэтому проверка и потенциальная упаковка методов происходит даже для атрибутов, которые не являются методами +, не будут использовать функцию для конкретный вызов. И использование getattribute вообще по своей сути несколько сложнее.
Фактическое влияние этих накладных расходов в моем опыте/для моих целей было незначительным, а на моей машине работает двухъядерный Celeron. В предыдущей реализации я использовал интроспективные методы для объекта init и связал Runtime_Decorator с методами. Выполнение этого способа устранило необходимость использования getattribute и уменьшило накладные расходы, упомянутые ранее... однако, он также разбивает рассол (возможно, не укроп) и менее динамичен, чем этот подход.
Единственные случаи использования, которые я на самом деле встречал "в дикой природе" с помощью этой техники, были с хронометрированием и трассировкой декораторов. Однако возможности, которые он открывает, чрезвычайно широки.
Если у вас есть существовавший ранее класс, который нельзя наследовать от другой базы (или использовать метод, которым он владеет определение класса или в его базовом классе), то все это просто не относится к вашей проблеме вообще, к сожалению.
Я не думаю, что установка/удаление не подлежащих вызову атрибутов для класса во время выполнения обязательно является сложной задачей? если вы не хотите, чтобы классы, которые наследовали от измененного класса, автоматически отражали изменения в себе... Это было бы "черным червем" червями по всему звуку.