Найти - Пользователи
Полная версия: Обратное наследование
Начало » Python для экспертов » Обратное наследование
1 2 3 4 5
zheromo
Не знаю как правильно назвать проблему.
Ситуация заключается в том, что нужно переопределить “стандартное” поведение класса.
Понятно что можно наследовать от него, и перекрыть необходимые методы. Но ТРЕБУЕТСЯ чтобы поведение изменилось не только в экземплярах нового класса, но и во всех экземплярах старого.

Пока ничего лучше не пришло в голову чем следующее:

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

Подумайте, быть может вам нужно что-то совсем другое.
Ed
zheromo
Не знаю как правильно назвать проблему.
Ее уже назвали: http://en.wikipedia.org/wiki/Monkey_patch

есть ли какой способ сделать это “правильней”
Способов сделать это хватает. Вот, например: http://pypi.python.org/pypi/collective.monkeypatcher
Поищите в гугле по ‘python monkey patching’ - найдете еще.

Способа сделать это ‘правильно’ нет. Правильно - не делать.
Прежде чем таки пойти этим путем подумайте хорошо надо ли оно вам.
Возможно проблему можно решить по-другому.
zheromo
Вот мне сильно и не хочется так делать.

Есть возможность спроектировать OldClass как угодно с целью решить задачу: позволить в дальнейшем изменять поведение OldClass-а, т.к. нет возможности создания объектов от нового класса. Вполне подойдет ограничить список методов, которые можно будет переопределять.

Хотелось бы понять как.

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

Хотелось бы посмотреть готовое “хорошее” решение.
Андрей Светлов
Как-то вы запутанно изъясняетесь. Хороший пример очень помогает понять, о чем идет речь.
Может, имелась в виду Strategy? http://en.wikipedia.org/wiki/Strategy_pattern
zheromo
Да, с небольшими особенностями
Должна быть одна большая реализация - которая будет по умолчанию.
И довольно много небольших реализаций, так как правило переопределять нужно небольшую часть поведения, т.е. сразу несколько небольших реализаций частично перекрывающих дефолтную.
Ed
zheromo
Должна быть одна большая реализация - которая будет по умолчанию.
И довольно много небольших реализаций, так как правило переопределять нужно небольшую часть поведения, т.е. сразу несколько небольших реализаций частично перекрывающих дефолтную.
Все равно непонятно. То, что я процитировал, укладывается в обычную схему наследования: большая реализация - базовый класс, небольшие реализации, частично перекрывающие ее - наследники.
Но вы же хотите, чтобы наследники изменяли поведение базового класса для объектов базового класса. Непонятно зачем. И еще непонятно почему нет возможности создания объектов наследников.
И на каком основании поведение базового класса должно меняться? После того, как создался объект наследника? После импорта класса-наследника? В каких-то других случаях?
Что делать, если создался следующий объект наследника другого типа? Должен ли он менять поведение уже созданных объектов базового класса и поведение всех объектов наследников всех типов?
Или только базовый класс? Или еще и классы наследников?

И таки да, пример бы сильно облегчил понимание. А еще лучше пример и объяснение задачи с самого начала, а не только той части, где у вас затруднения.
По виду у вас проблемы в архитектурной части, если она породила такую изуверскую потребность.
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)
Нам нужно например изменить поведение класса, чтобы метод get_avatar_path возвращал не url аватара из статической папки, а, например, урл на сервис gravatar.com.
Наследовать не получится - потому что все остальные части системы не будут об этом знать, т.к. знают только об User. После того, как создался объект наследника менять поведение не надо. Классы наследников - думаю да.

При обычном наследовании это было бы просто.

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)
PooH
А если заменить наследование на композицию?
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)
Ed
Угу, как вариант.
Если нужно еще гибче, то можно использовать фабрику:
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)
Такой подход позволяет менять источник аватаров и логику их выбора на ходу, определяя что нужно только в коде фабрики.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB