В Python я видел рекомендацию использовать удержание или перенос для расширения функциональности объекта или класса, а не наследования. В частности, я думаю, что Алекс Мартелли говорил об этом в своем выступлении Python Design Patterns . Я видел этот шаблон, используемый в библиотеках для внедрения зависимостей, например pycontainer .
Одна проблема, с которой я столкнулся, заключается в том, что когда мне приходится взаимодействовать с кодом, который использует
isinstance anti-pattern , этот шаблон завершается ошибкой, поскольку объект удержания / переноса не проходит тест isinstance
. Как настроить объект удержания / обтекания, чтобы обойти ненужную проверку типов? Можно ли сделать это в общем? В некотором смысле мне нужно что-то для экземпляров классов, аналогичное декораторам с сохранением сигнатур (например, simple_decorator или декоратор Микеле Симионато ).
Квалификация: я не утверждаю, что все isinstance
использование неуместно; несколько ответов дают хорошие замечания по этому поводу. Тем не менее, следует признать, что использование isinstance
накладывает существенные ограничения на взаимодействие объектов - оно заставляет наследование быть источником полиморфизма, а не поведения .
Кажется, есть некоторая путаница относительно того, как именно / почему это проблема, поэтому позвольте мне привести простой пример (в широком смысле взято из pycontainer ). Допустим, у нас есть класс Foo, а также FooFactory. Для примера предположим, что мы хотим иметь возможность создавать экземпляры объектов Foo, которые регистрируют каждый вызов функции, или не думают об AOP. Кроме того, мы хотим сделать это без какого-либо изменения класса / источника Foo (например, на самом деле мы можем реализовывать универсальную фабрику, которая может добавить возможность регистрации в любой экземпляр класса на лету). Первый удар в этом может быть:
class Foo(object):
def bar():
print 'We\'re out of Red Leicester.'
class LogWrapped(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
attr = getattr(self.wrapped, name)
if not callable(attr):
return attr
else:
def fun(*args, **kwargs):
print 'Calling ', name
attr(*args, **kwargs)
print 'Called ', name
return fun
class FooFactory(object):
def get_foo(with_logging = False):
if not with_logging:
return Foo()
else:
return LogWrapped(Foo())
foo_fact = FooFactory()
my_foo = foo_fact.get_foo(True)
isinstance(my_foo, Foo) # False!
Могут быть причины, по которым вы можете захотеть делать все именно так (использовать декораторы и т. Д.), Но имейте в виду:
- Мы не хотим трогать класс Foo. Предположим, мы пишем фреймворковый код, который может использоваться клиентами, о которых мы пока не знаем.
- Смысл в том, чтобы вернуть объект, который по сути является Foo, но с добавленной функциональностью. Это должно выглядеть как Foo - насколько это возможно - для любого другого клиентского кода, ожидающего Foo. Отсюда и желание обойти
isinstance
.
- Да, я знаю, что мне не нужен фабричный класс (упреждающая защита здесь).