Форум сайта python.su
Не знаю как правильно назвать проблему.
Ситуация заключается в том, что нужно переопределить “стандартное” поведение класса.
Понятно что можно наследовать от него, и перекрыть необходимые методы. Но ТРЕБУЕТСЯ чтобы поведение изменилось не только в экземплярах нового класса, но и во всех экземплярах старого.
Пока ничего лучше не пришло в голову чем следующее:
class OldClass(object):
def __init__(self, x):
self.x = x
def get_x(self, y=1):
return self.x*y
def wrap(cls):
def new_get_x(self, y=1):
return self.x ** y
cls.get_x = new_get_x
return cls
OldClass = wrap(OldClass)
Офлайн
Это - грязный хак.
Легко смогу написать код, который такое чудо поломает, если у вас уже есть созданные экземпляры OldClass
Подумайте, быть может вам нужно что-то совсем другое.
Офлайн
zheromoЕе уже назвали: http://en.wikipedia.org/wiki/Monkey_patch
Не знаю как правильно назвать проблему.
есть ли какой способ сделать это “правильней”Способов сделать это хватает. Вот, например: http://pypi.python.org/pypi/collective.monkeypatcher
Офлайн
Вот мне сильно и не хочется так делать.
Есть возможность спроектировать OldClass как угодно с целью решить задачу: позволить в дальнейшем изменять поведение OldClass-а, т.к. нет возможности создания объектов от нового класса. Вполне подойдет ограничить список методов, которые можно будет переопределять.
Хотелось бы понять как.
Как вариант может написать типа фабрики реализаций, в которую можно будет поставлять нужную реализацию функционала (т.е. она будет четко определена) а уже сам класс-исполнитель будет пользоваться предоставленными ему реализациями функционала.
Хотелось бы посмотреть готовое “хорошее” решение.
Офлайн
Как-то вы запутанно изъясняетесь. Хороший пример очень помогает понять, о чем идет речь.
Может, имелась в виду Strategy? http://en.wikipedia.org/wiki/Strategy_pattern
Офлайн
Да, с небольшими особенностями
Должна быть одна большая реализация - которая будет по умолчанию.
И довольно много небольших реализаций, так как правило переопределять нужно небольшую часть поведения, т.е. сразу несколько небольших реализаций частично перекрывающих дефолтную.
Офлайн
zheromoВсе равно непонятно. То, что я процитировал, укладывается в обычную схему наследования: большая реализация - базовый класс, небольшие реализации, частично перекрывающие ее - наследники.
Должна быть одна большая реализация - которая будет по умолчанию.
И довольно много небольших реализаций, так как правило переопределять нужно небольшую часть поведения, т.е. сразу несколько небольших реализаций частично перекрывающих дефолтную.
Офлайн
Опишу задачу изначально на простом примере.
Например у нас есть модель User
class User(db.Model):
login = db.StringProperty()
email = db.StringProperty()
def get_avatar_path(self, size=48):
'''Путь к картинке с аватаром пользователя'''
return url_for('static', filename='avatars/' + self.login)
class GravatarUser(User):
def get_avatar_path(self, size=48):
default = super(GravatarUser, self).get_avatar_path(size)
hash = hashlib.md5(self.email.lower()).hexdigest()
gravatar_url = 'http://www.gravatar.com/avatar/' + hash + '?'
gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
return gravatar_url
import hashlib, urllib
_HANDLERS = {}
def has_realisation(rel_name):
def wrapper(func):
def inner(*argv, **kw):
def prev(i):
def g():
if i == len(handlers)-1:
return func(*argv, **kw)
return handlers[i+1](prev(i+1), *argv, **kw)
return g
handlers = _HANDLERS[rel_name]
if handlers:
return handlers[0](prev(0), *argv, **kw)
return func(*argv, **kw)
_HANDLERS[rel_name] = []
return inner
return wrapper
def is_realisation(rel_name):
def inner(func):
_HANDLERS[rel_name].insert(0, func)
return func
return inner
class User(object):
def __init__(self, login, email):
self.login = login
self.email = email
@has_realisation('avatar_path')
def get_avatar_path(self, size=48):
return '/static/avatars/%s/%d' % (self.login, size,)
user = User('test', 'test@example.com')
print user.get_avatar_path(96)
@is_realisation('avatar_path')
def gravatar(avatar, cls, size):
hash = hashlib.md5(cls.email.lower()).hexdigest()
gravatar_url = 'http://www.gravatar.com/avatar/' + hash + '?'
gravatar_url += urllib.urlencode({'d':avatar(), 's':str(size)})
return gravatar_url
print user.get_avatar_path(128)
Отредактировано (Фев. 15, 2011 22:19:35)
Офлайн
А если заменить наследование на композицию?
class Avatar:
def get_avatar_path(self, owner, size=48):
return url_for('static', filename='avatars/' + owner.login)
class Gravatar(Avatar):
def get_avatar_path(self, owner, size=48):
default = super(Gravatar, self).get_avatar_path(owner, size)
hash = hashlib.md5(owner.email.lower()).hexdigest()
gravatar_url = 'http://www.gravatar.com/avatar/' + hash + '?'
gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
return gravatar_url
class User(db.Model):
login = db.StringProperty()
email = db.StringProperty()
def __init__(self, avatar):
self.avatar = avatar
def get_avatar_path(self, size=48):
return self.avatar.get_avatar_path(self)
Офлайн
Угу, как вариант.
Если нужно еще гибче, то можно использовать фабрику:
from onemodule import Avatar
from anothermodule import Gravatar
from yetanothermodule import SuperPuperAvatar
def avatar_factory():
if <want to use Avatar>:
return Avatar(location='avatars/')
elif <want to use Gravatar>:
return Gravatar(baseurl='http://www.gravatar.com/avatar/')
else:
return SuperPuperAvatar(host='localhost', user='user', password='password')
---------------
from factory import avatar_factory
class User(db.Model):
.....
def get_avatar_path(self, size=48):
avatar = avatar_factory()
return avatar.get_avatar_path(size)
Отредактировано (Фев. 16, 2011 10:40:57)
Офлайн