LINUX.ORG.RU

[python2] как к объекту класса добавить метод с сохранением сигнатуры?


0

1

Сабж. Совсем здорово было б проделать то же самое с экземпляром класса и не обвалить при этом систему сборки мусора (что б не было циклич. ссылок).

Под методом понимается сторонняя ф-я принимающая первым аргументом self. Кроме сигнатуры (те имен аргументов исходной ф-ии, в т.ч. значения аргументов по умолчанию) надо бы еще сохранить строку документации.

★★★★★

Может быть заменить, а не добавить? А то непонятно причем здесь сохранение сигнатуры и докстрингов.

baverman ★★★
()
Ответ на: комментарий от baverman

Не, именно добавить. Сигнатура и докстринги должны совпасть с прицепляемым методом, потому что сигнатура и докстринги потом юзаются в в атовтодокументации и при биндинге в shell. Традиционные методы добавления через @classmethod наск я понимаю юзают обертку с *args, **kw_args, т.е. сигнатура сыпется нафик.

AIv ★★★★★
() автор топика
Ответ на: комментарий от AIv

>>> class A:
pass

def foo(self, test):

"'Doc string of foo"'
print self, test

a = A()

a.foo=types.MethodType(foo, a, A)


a.foo(«one»)


<__main__.A instance at 0x876e20c> one

a

<__main__.A instance at 0x876e20c>

a.foo.__doc__

'Doc string of foo'

arhibot
()
Ответ на: комментарий от arhibot

До кучи:

inspect.getargspec(a.foo)

ArgSpec(args=['self', 'test'], varargs=None, keywords=None, defaults=None)

arhibot
()
Ответ на: комментарий от AIv

Всё таки не понял в чем проблема.

class Boo():
    pass


def foo(self, arg):
    '''A very useful method'''
    print "Look, ma, I'm a Boo method"

Boo.foo = foo

Boo().foo(1)
help(Boo.foo)
baverman ★★★
()
Ответ на: комментарий от baverman

> Всё таки не понял в чем проблема.

метод нужно добавить именно к объекту (зачем такое нужно, я так и не понял), а не к классу.

anonymous
()

Манки-патчинг не в философии Pythonic. Ты делаешь что-то не так, раз у тебя такая идея появилась.
Не бойся обращаться со всеми объектами как со словарями: просто добавляй всё как хочешь, ничто не сломается. Функции просто принимают self в качестве первого аргумента, это всё! Там нет никакой магии.
Советую почитать вот это:
http://habrahabr.ru/blogs/python/114576/
http://habrahabr.ru/blogs/python/114585/
http://habrahabr.ru/blogs/python/114587/

tia
()
Ответ на: комментарий от anonymous

именно к объекту

Тогда что значит фраза «Совсем здорово было б проделать то же самое с экземпляром класса»? То есть ТС нужно и то и другое. Unbound метод можно добавить к классу напрямую, а к экземпляру через types.MethodType.

baverman ★★★
()
Ответ на: комментарий от tia

Манки-патчинг не в философии Pythonic

Чтобы сделать питоник апи, иногда надо побыть изворотливой мартышкой.

baverman ★★★
()
Ответ на: комментарий от baverman

Да, с объектом класса это я понимаю (вопрос писал поздно ночью, ступил). С экземпляром про добавление через MethodType «знал но забыл», но при таком добавлении валится сист. сборки мусора (получается кольцевая ссылка). Можно конечно извернутся и использовать напр. косвенную адресацию через глобальную таблицу, но я думал может кто знает более Ъ способ?

AIv ★★★★★
() автор топика
Ответ на: комментарий от AIv

GC должен подбирать такие ссылки.

import types
import gc
import weakref

refs = []

class Boo():
    pass


def foo(self, arg):
    '''A very useful method'''
    print "Look, ma, I.m a Boo method"

def do():
    boo = Boo()
    boo.foo = types.MethodType(foo, boo, Boo)
    boo.foo(1)

    refs.append(weakref.ref(boo))


do()
gc.collect()
assert refs[0]() == None

использовать напр. косвенную адресацию через глобальную таблицу

Можно заиспользовать тот же weakref. Чтобы сборка происходила при обнулении счетчика. Но имхо, пока не начало течь, можно и не волноваться.

baverman ★★★
()
Ответ на: комментарий от baverman

Про weakref не знал, спасибо.

Для экземпляра есть еще один способ через перегрузку __getattr__, из плюсов - экземпляр спокойно пиклится, из минусов - этих добавленных методов не видно через dir (что весьма хреново для интроспекции). Не знаете какого нить Ъ метода пропатчить dir? Можно конечно свою dir в builtin сунуть, но тогда при пиклинге начинаются косяки;-(

AIv ★★★★★
() автор топика

Ты что-то хочешь странного. Лучше раскажи, какая у тебя на самом деле проблема.

vasilenko ★★
()
Ответ на: комментарий от AIv

экземпляр спокойно пиклится

Тогда непонятен смысл добавления метода в инстанс, ведь после распикливания он потеряет поведение.

baverman ★★★
()
Ответ на: комментарий от baverman

Получается в итоге так.

Задача - создавать объекты с довешенным динамически кульком методов, все методы должны быть доступны через dir, сигнатуры методов сохранены, объекты должны корректно пиклится. Варианты решения:

1) Делаем порождающую ф-ю, в которой создаем класс, довешиваем методы и выдаем наружу экземпляр класса. dir работает, сборка мусора работает, пиклинг ес-но не работает. Можно заюзать get/setstate но пока непонятно как быть с самим исходным классом - его таки нужно в каком то виде создать снаружи ф-ии (что бы pickle его видел), но его изменения приведут к изменению всех экземпляров что недопустимо, да и get/setstate не очень ясно как писать.

2) Создаем экземпляр класса и методы довешиваем ему. Минусы - сборка мусора (придется тащить gc). Плюсы - при пральных get/setstate все вообще шикарно работает.

3) Делаем через __getattr__ и патчим dir - очень мутное решение, сделать видимо можно, но геморрррно.

Вроде ничего больше в голову не приходит...

AIv ★★★★★
() автор топика
Ответ на: комментарий от AIv

Задача - создавать объекты с довешенным динамически кульком методов

Почему методы уникальны для каждого экземпляра?

baverman ★★★
()
Ответ на: комментарий от baverman

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

AIv ★★★★★
() автор топика
Ответ на: комментарий от baverman
from sys import getrefcount
import gc
import types
import inspect

class A:
    pass

def foo(self, test):
    '''Doc string of foo'''
    print self, test


a = A()
print gc.get_referrers(foo)
print "Refs to a: %d" % getrefcount(a)
a.foo=types.MethodType(foo, a, A)
print "Refs to foo after add instancemethod: %d" % getrefcount(foo)
print "Refs to a.foo: %d" % getrefcount(a.foo)
del a
gc.collect()
print "Refs to foo: %d" % getrefcount(foo)
print gc.get_referrers(foo)

Запускаю:

[{'A': <class __main__.A at 0xb74994ac>, 'a': <__main__.A instance at 0xb749c12c>, 'getrefcount': <built-in function getrefcount>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'test.py', 'inspect': <module 'inspect' from '/usr/lib/python2.6/inspect.pyc'>, '__package__': None, 'gc': <module 'gc' (built-in)>, '__name__': '__main__', 'foo': <function foo at 0xb74958ec>, '__doc__': None, 'types': <module 'types' from '/usr/lib/python2.6/types.pyc'>}]
Refs to a: 2
Refs to foo after add instancemethod: 3
Refs to a.foo: 2
Refs to foo: 2
[{'A': <class __main__.A at 0xb74994ac>, 'getrefcount': <built-in function getrefcount>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'test.py', 'inspect': <module 'inspect' from '/usr/lib/python2.6/inspect.pyc'>, '__package__': None, 'gc': <module 'gc' (built-in)>, '__name__': '__main__', 'foo': <function foo at 0xb74958ec>, '__doc__': None, 'types': <module 'types' from '/usr/lib/python2.6/types.pyc'>}]
Где циклическая ссылка? Насколько я вижу --- всё почистилось

arhibot
()
Ответ на: комментарий от arhibot

gc как раз циклическими ссылками и занимается. Уберите gc - a останется висеть, поскольку в a.foo висит линк на a

AIv ★★★★★
() автор топика
Ответ на: комментарий от AIv

Вроде получилось:

import gc
import weakref
import inspect
import functools

refs = []

class Boo():
    pass

def weak_method(obj, func):
    self = weakref.ref(obj)
    
    args, vargs, kwargs, defaults  = inspect.getargspec(func)

    code = ("def %s%s:\n"
           "    return _orig_func%s\n" % (func.__name__, inspect.formatargspec(args[1:], vargs, kwargs, defaults),
               inspect.formatargspec(['self()'] + args[1:], vargs, kwargs, None)))

    result = {'_orig_func':func, 'self':self}
    exec code in result

    return functools.wraps(func)(result[func.__name__])


def foo(self, arg, arg1=10, **kwargs):
    '''A very useful method'''
    print "Look, ma, I'm a Boo method", arg, arg1

def do():
    boo = Boo()
    boo.foo = weak_method(boo, foo)
    boo.foo(1)
    boo.foo(1, 11)
    help(boo.foo)

    refs.append(weakref.ref(boo))


do()
assert refs[0]() == None
baverman ★★★
()
Ответ на: комментарий от baverman

Спасибо, снимаю шляпу... думал что знаю питон, но тут половина модулей незнакомая.

У меня были мысли про exec, но руками лень было аргументы доставать. Буду изучать;-)

AIv ★★★★★
() автор топика
Ответ на: комментарий от vasilenko

А я пока так и делаю, но это не сериализуемо.

AIv ★★★★★
() автор топика
Ответ на: комментарий от arhibot

Согласен. Написал из спортивного интереса. Чтобы такое использовать надо иметь очень серьезные причины.

baverman ★★★
()
Ответ на: комментарий от AIv

берите gc - a останется висеть, поскольку в a.foo висит линк на a

не останется, gc запущен всегда и срабатывает по дефолту каждые 100 инструкций. gc.collect() это просто способ принудительно запустить мусорщик.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

Забавно.... спасибо, не знал.

AIv ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.