Python: изменение методов и атрибутов во время выполнения - PullRequest
69 голосов
/ 08 июня 2009

Я хочу создать класс в Python, чтобы я мог добавлять и удалять атрибуты и методы. Как мне это сделать?

О, и, пожалуйста, не спрашивайте, почему.

Ответы [ 8 ]

114 голосов
/ 08 июня 2009

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

>>> 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'
45 голосов
/ 08 июня 2009

Я хочу создать в 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'
28 голосов
/ 08 июня 2009

Возможно, интересная альтернатива использованию types.MethodType в:

>>> f = types.MethodType(talk, puppy, Dog)
>>> puppy.talk = f # add method to specific instance

будет использовать тот факт, что функции являются дескрипторами :

>>> puppy.talk = talk.__get__(puppy, Dog)
5 голосов
/ 08 июня 2009

Я хочу создать класс в 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'
4 голосов
/ 11 апреля 2013

вы можете просто назначить непосредственно классу (либо путем доступа к исходному имени класса или через __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)
0 голосов
/ 14 сентября 2014

Обязательно ли нужно модифицировать сам класс? Или цель просто заменить то, что object.method () делает в определенный момент во время выполнения?

Я спрашиваю, потому что я обошел проблему фактической модификации класса, чтобы он вызывал специфичные для патчей вызовы методов в моей структуре с помощью 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's 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 hooks all доступ к атрибутам, поэтому проверка и потенциальная упаковка методов происходит даже для атрибутов, которые не являются методами +, не будет используя функцию для конкретного звонка. А использование getattribute вообще немного сложное.

Фактическое влияние этих накладных расходов на мой опыт / для моих целей было незначительным, и моя машина работает на двухъядерном Celeron. В предыдущей реализации я использовал интроспективные методы для объекта init и затем связывал Runtime_Decorator с методами. Выполнение этих действий избавило от необходимости использовать getattribute и уменьшило накладные расходы, упомянутые ранее ... однако, это также нарушает рассол (возможно, не укроп) и менее динамично, чем этот подход.

Единственные случаи использования, с которыми я действительно столкнулся "в дикой природе" с этой техникой, были с декораторами синхронизации и трассировки. Однако возможности, которые он открывает, чрезвычайно широки.

Если у вас есть ранее существующий класс, который нельзя сделать наследующим от другой базы (или использовать технику, которая определена его собственным классом или в его базовом классе '), то, к сожалению, все это вообще не относится к вашей проблеме. .

Не думаю, что установка / удаление не вызываемых атрибутов в классе во время выполнения обязательно так сложно? если только вы не хотите, чтобы классы, которые наследуются от измененного класса, также автоматически отражали изменения сами по себе ... Хотя это звучит целым червем "просто невозможно" по звуку этого.

0 голосов
/ 21 января 2013

Просто:

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
0 голосов
/ 04 ноября 2012

Другая альтернатива, если вам нужно заменить класс оптом, это изменить атрибут 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
...