Уведомления

Группа в Telegram: @pythonsu

#1 Фев. 14, 2011 22:07:28

zheromo
От:
Зарегистрирован: 2010-10-02
Сообщения: 356
Репутация: +  2  -
Профиль   Отправить e-mail  

Обратное наследование

Не знаю как правильно назвать проблему.
Ситуация заключается в том, что нужно переопределить “стандартное” поведение класса.
Понятно что можно наследовать от него, и перекрыть необходимые методы. Но ТРЕБУЕТСЯ чтобы поведение изменилось не только в экземплярах нового класса, но и во всех экземплярах старого.

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

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)
Собственно вопрос: есть ли какой способ сделать это “правильней”, может быть путем создания какого либо способа документированного изменения поведения? А то этот вариант мне что-то не нравится, или он вполне нормален?



Офлайн

#2 Фев. 15, 2011 00:15:09

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Обратное наследование

Это - грязный хак.
Легко смогу написать код, который такое чудо поломает, если у вас уже есть созданные экземпляры OldClass

Подумайте, быть может вам нужно что-то совсем другое.



Офлайн

#3 Фев. 15, 2011 00:20:55

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

Обратное наследование

zheromo
Не знаю как правильно назвать проблему.
Ее уже назвали: http://en.wikipedia.org/wiki/Monkey_patch

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

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



Офлайн

#4 Фев. 15, 2011 01:00:55

zheromo
От:
Зарегистрирован: 2010-10-02
Сообщения: 356
Репутация: +  2  -
Профиль   Отправить e-mail  

Обратное наследование

Вот мне сильно и не хочется так делать.

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

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

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

Хотелось бы посмотреть готовое “хорошее” решение.



Офлайн

#5 Фев. 15, 2011 03:13:35

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Обратное наследование

Как-то вы запутанно изъясняетесь. Хороший пример очень помогает понять, о чем идет речь.
Может, имелась в виду Strategy? http://en.wikipedia.org/wiki/Strategy_pattern



Офлайн

#6 Фев. 15, 2011 09:59:37

zheromo
От:
Зарегистрирован: 2010-10-02
Сообщения: 356
Репутация: +  2  -
Профиль   Отправить e-mail  

Обратное наследование

Да, с небольшими особенностями
Должна быть одна большая реализация - которая будет по умолчанию.
И довольно много небольших реализаций, так как правило переопределять нужно небольшую часть поведения, т.е. сразу несколько небольших реализаций частично перекрывающих дефолтную.



Офлайн

#7 Фев. 15, 2011 10:55:17

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

Обратное наследование

zheromo
Должна быть одна большая реализация - которая будет по умолчанию.
И довольно много небольших реализаций, так как правило переопределять нужно небольшую часть поведения, т.е. сразу несколько небольших реализаций частично перекрывающих дефолтную.
Все равно непонятно. То, что я процитировал, укладывается в обычную схему наследования: большая реализация - базовый класс, небольшие реализации, частично перекрывающие ее - наследники.
Но вы же хотите, чтобы наследники изменяли поведение базового класса для объектов базового класса. Непонятно зачем. И еще непонятно почему нет возможности создания объектов наследников.
И на каком основании поведение базового класса должно меняться? После того, как создался объект наследника? После импорта класса-наследника? В каких-то других случаях?
Что делать, если создался следующий объект наследника другого типа? Должен ли он менять поведение уже созданных объектов базового класса и поведение всех объектов наследников всех типов?
Или только базовый класс? Или еще и классы наследников?

И таки да, пример бы сильно облегчил понимание. А еще лучше пример и объяснение задачи с самого начала, а не только той части, где у вас затруднения.
По виду у вас проблемы в архитектурной части, если она породила такую изуверскую потребность.



Офлайн

#8 Фев. 15, 2011 22:16:00

zheromo
От:
Зарегистрирован: 2010-10-02
Сообщения: 356
Репутация: +  2  -
Профиль   Отправить e-mail  

Обратное наследование

Опишу задачу изначально на простом примере.
Например у нас есть модель 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)



Отредактировано (Фев. 15, 2011 22:19:35)

Офлайн

#9 Фев. 16, 2011 06:37:17

PooH
От:
Зарегистрирован: 2006-12-05
Сообщения: 1948
Репутация: +  72  -
Профиль   Отправить e-mail  

Обратное наследование

А если заменить наследование на композицию?

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)



Вот здесь один из первых отарков съел лаборанта. Это был такой умный отарк, что понимал даже теорию относительности. Он разговаривал с лаборантом, а потом бросился на него и загрыз…

Офлайн

#10 Фев. 16, 2011 10:33:33

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

Обратное наследование

Угу, как вариант.
Если нужно еще гибче, то можно использовать фабрику:

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)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version