Python: зміна методів та атрибутів під час виконання


76

Я хочу створити клас на Python, який я можу додавати та видаляти атрибути та методи. Як я можу це досягти?

О, і, будь ласка, не питайте чому.



3
Ви хочете дізнатись, як робити штампування качок у python? en.wikipedia.org/wiki/Duck_punching
baudtack

2
проголосували за прохання не питати чому
oulenz

Відповіді:


47

Я хочу створити клас на 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'

9
Зверніть увагу, що це додає метод класу до SpecialClass. Він не додає метод, який буде доступний для всіх майбутніх екземплярів SpecialClass. (Я цікаво , якщо є спосіб зробити що .) _
М. Elkstein

Це було б дійсно цікаво.
technillogue

123

Цей приклад показує різницю між додаванням методу до класу та до екземпляра.

>>> 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'

13
Зверніть увагу, що ви можете робити це лише для класів , а не екземплярів . Якщо ви виконуєте puppy.talk = talk, обговорення не буде "пов'язаним методом", тобто воно не отримає неявного аргументу "self".
Пол Фішер

10
Доповнити коментар Пола: якщо ви хочете, щоб мавпа виправляла метод екземпляру: "типи імпорту; f = типи.MethodType (розмова, щеня, собака); puppy.talk = f"
Джаррет Харді

5
+1 Паоло за демонстрацію динамічного ефекту призначення та видалення атрибутів методу класу.
Джаррет Харді

3
Дякую за чудові коментарі, хлопці, я оновив відповідь, щоб показати відмінності.
Паоло Бергантіно

8
Дуже приємний відредагований приклад ... він майже повинен входити в документи Python API для модуля типів, які вкрай недостатні.
Джаррет Харді

28

Можливо, цікава альтернатива використанню types.MethodTypeв:

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

було б використовувати той факт, що функції є дескрипторами :

>>> puppy.talk = talk.__get__(puppy, Dog)

2
Щось я чогось навчився :) Але я думаю, що це виглядає менш читабельним.
Ніколас Думазет

+1 Хороший альтернативний синтаксис, як ти кажеш. Мені цікаво: чи є якісь особливі переваги у цього підходу чи у використанні "типів"? Зрештою, вони дають однаковий результат і внутрішні прив'язки AFAICAT. Чи ефективно type.MethodType створює дескриптор, чи там більше на роботі?
Джаррет Харді

@NicDumZ, так, __ штучки ніколи насправді не виглядають добре. @Jarret, в якийсь момент у дизайні Python 3 розгорнулася розмова про скасування модуля `` типи '', але він залишився, скоротившись з 37 записів до 12 (`` новий '' модуль справді пішов, так! -). Семантично вони насправді однакові: MethodType повертає той самий тип об’єкта, який є результатом get - екземпляр <type 'instancemethod'>.
Алекс Мартеллі,

5

Я хочу створити клас на 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

ви можете просто призначити безпосередньо класу (або шляхом доступу до оригінальної назви класу, або через __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

інша альтернатива, якщо вам потрібно замінити клас оптом, це змінити атрибут класу :

>>> 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

Цікаво, але суть полягала в зміні методів @ runtime. Це виглядає як ідея для IoC-контейнера з переключенням часу виконання :)
Migol

Так, клас перемикання дозволяє модифікувати методи оптом, особливо коли ви додаєте це з тим фактом, що python дозволяє багаторазове успадкування, а класи python можна змінювати, це може призвести до деяких досить потужних динамічних методів або дуже неможливого коду.
Lie Ryan

0

Просто:

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

Чи потрібно обов'язково модифікувати сам клас? Або мета просто замінити те, що 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 (за умови, що вони не не відміняю гетатрибут, звичайно).

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 гаків всього доступ до атрибутів, тому перевірка і потенціал обгортання методів відбуваються навіть для атрибутів, які не є методами + не використовуватиме цю функцію для конкретного виклику в питанні. І використання гетаттрибуту взагалі за своєю суттю дещо складне.

Фактичний вплив цих накладних витрат на мій досвід / для моїх цілей був незначним, і на моїй машині працює двоядерний Celeron. У попередній реалізації я використовував самоаналіз методів на об'єкті init і тоді прив'язував Runtime_Decorator до методів. Роблячи речі таким чином, виключається необхідність використання гетаттрибута та зменшуються накладні витрати, про які згадувалося раніше ... однак, це також розбиває соління (можливо, не кріп) і є менш динамічним, ніж цей підхід.

Єдині випадки використання, з якими я справді стикався "в дикій природі" за допомогою цієї техніки, були декоратори хронометражу та трасування. Однак можливості, які він відкриває, надзвичайно широкі.

Якщо у вас є вже існуючий клас, який неможливо змусити успадковувати з іншої бази (або використовувати техніку, яка є власним визначенням класу або в базовому класі '), то все це просто не стосується вашої проблеми, на жаль.

Я не думаю, що встановлення / видалення атрибутів, що не викликаються, для класу під час виконання обов’язково є настільки складним завданням? за винятком випадків, коли ви хочете, щоб класи, які успадковуються від модифікованого класу, також автоматично відображали зміни в них самих ... Це все одно було б черв'яками "нічого не може" за звуком цього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.