Как подделать шрифт с помощью Python - PullRequest
12 голосов
/ 24 июля 2011

Недавно я разработал класс с именем DocumentWrapper вокруг некоторого объекта документа ORM в Python, чтобы прозрачно добавить к нему некоторые функции без какого-либо изменения интерфейса.

У меня только одна проблема с этим. Допустим, у меня есть объект User, завернутый в него. Вызов isinstance(some_var, User) вернет False, потому что some_var действительно является экземпляром DocumentWrapper.

Есть ли способ подделать тип объекта в Python, чтобы тот же вызов возвращал True?

Ответы [ 6 ]

14 голосов
/ 24 июля 2011

Вы можете использовать магический метод __instancecheck__, чтобы переопределить поведение по умолчанию isinstance:

@classmethod
def __instancecheck__(cls, instance):
    return isinstance(instance, User)

Это только если вы хотите, чтобы ваш объект был прозрачной оболочкой; то есть, если вы хотите, чтобы DocumentWrapper вел себя как User. В противном случае просто предоставьте обернутый класс как атрибут.

Это дополнение к Python 3; это пришло с абстрактными базовыми классами. Вы не можете сделать то же самое в Python 2.

7 голосов
/ 22 марта 2017

Переопределить __class__ в вашем классе оболочки DocumentWrapper:

class DocumentWrapper(object):

  @property
  def __class__(self):
    return User

>>> isinstance(DocumentWrapper(), User)
True

Таким образом, никаких модификаций для упакованного класса User не требуется.

Python Mock делает то же самое (см. Mock.py:612 в mock-2.0.0, не могу найти источники в Интернете для ссылки, извините).

6 голосов
/ 24 июля 2011

Тестирование типа объекта обычно является антипаттерном в python.В некоторых случаях имеет смысл проверить «тип утки» объекта, что-то вроде:

hasattr(some_var, "username")

Но даже это нежелательно, например, есть причины, по которым это выражение может возвращатьсяfalse, даже если оболочка использует магию с __getattribute__ для правильной передачи атрибута через прокси.

Обычно предпочтительно, чтобы переменные принимали только один абстрактный тип и, возможно, None.Различное поведение, основанное на разных входных данных, должно быть достигнуто путем передачи необязательно типизированных данных в разные переменные.Вы хотите сделать что-то вроде этого:

def dosomething(some_user=None, some_otherthing=None):
    if some_user is not None:
        #do the "User" type action
    elif some_otherthing is not None:
        #etc...
    else:
         raise ValueError("not enough arguments")

Конечно, все это предполагает, что у вас есть некоторый уровень контроля над кодом, который выполняет проверку типа.Предположим, это не так.для того, чтобы функция isinstance () вернула true, класс должен появиться в базах экземпляра, или класс должен иметь __instancecheck__.Поскольку вы не контролируете ни одну из этих вещей для класса, вы должны прибегнуть к некоторым махинациям в этом случае.Сделайте что-то вроде этого:

def wrap_user(instance):
    class wrapped_user(type(instance)):
        __metaclass__ = type
        def __init__(self):
            pass
        def __getattribute__(self, attr):
            self_dict = object.__getattribute__(type(self), '__dict__')
            if attr in self_dict:
                return self_dict[attr]
            return getattr(instance, attr)
        def extra_feature(self, foo):
            return instance.username + foo # or whatever
    return wrapped_user()

То, что мы делаем, - это динамическое создание нового класса в тот момент, когда нам нужно обернуть экземпляр и фактически наследовать от обернутого объекта __class__.Мы также переходим к дополнительным проблемам, связанным с переопределением __metaclass__, в случае, если у оригинала есть некоторые дополнительные поведения, с которыми мы фактически не хотим сталкиваться (например, ищем таблицу базы данных с определенным именем класса).Приятным удобством этого стиля является то, что нам никогда не нужно создавать какие-либо атрибуты экземпляра в классе-обертке, нет self.wrapped_object, поскольку это значение присутствует во время создания класса .

Редактировать: Как указано в комментариях, вышеупомянутое работает только для некоторых простых типов, если вам нужно проксировать более сложные атрибуты на целевом объекте (скажем, методы), тогда смотрите следующий ответ: Python - тип подделки продолжение

1 голос
/ 24 июля 2011

Вот решение с использованием метакласса, но вам нужно изменить упакованные классы:

>>> class DocumentWrapper:
    def __init__(self, wrapped_obj):
        self.wrapped_obj = wrapped_obj

>>> class MetaWrapper(abc.ABCMeta):
    def __instancecheck__(self, instance):
        try:
            return isinstance(instance.wrapped_obj, self)
        except:
            return isinstance(instance, self)

>>> class User(metaclass=MetaWrapper):
    pass

>>> user=DocumentWrapper(User())
>>> isinstance(user,User)
True
>>> class User2:
    pass

>>> user2=DocumentWrapper(User2())
>>> isinstance(user2,User2)
False
0 голосов
/ 24 июля 2011

Лучший способ - это унаследовать DocumentWrapper от самого пользователя или шаблона смешивания и выполнить множественное наследование от многих классов

 class DocumentWrapper(User, object)

Вы также можете подделать результаты isinstance (), манипулируя obj.__class__, но это магия глубокого уровня и не должна выполняться.

0 голосов
/ 24 июля 2011

Звучит так, будто вы хотите проверить тип объекта, который обертывает DocumentWrapper, а не тип самого DocumentWrapper. Если это так, то интерфейс к DocumentWrapper должен предоставлять этот тип. Например, вы можете добавить метод к вашему классу DocumentWrapper, который возвращает тип обернутого объекта, например. Но я не думаю, что обращение к isinstance неоднозначно, возвращая True, когда это не так, является правильным способом решения этой проблемы.

...