How to fake a type using Python

I recently developed a class called DocumentWrapper around some Python ORM document object to transparently add some functions to it without changing its interface.

I have only one problem with this. Say I have a User object wrapped in it. Calling isinstance(some_var, User) will return False because some_var indeed an instance of DocumentWrapper .

Is there a way to fake an object type in Python to have the same return True call?

+7
source share
6 answers

Object type testing is usually antipattern in python. In some cases, it makes sense to test the "type of duck" object, something like:

 hasattr(some_var, "username") 

But even this is undesirable, for example, there are reasons why this expression can return false, although the shell uses some magic with __getattribute__ to correctly proxy the attribute.

It is usually preferred that variables accept only one abstract type and possibly None . Different behaviors based on different inputs must be achieved by passing optionally typed data in different variables. You want to do something like this:

 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") 

Of course, all of this assumes that you have some level of control over the code that performs type checking. Suppose this is not the case. for "isinstance ()" to return true, the class should appear in the instance database, or the class must have __instancecheck__ . Since you do not control any of these things for the class, you need to resort to some fraud on the instance. Do something like this:

 def wrap_user(instance): class wrapped_user(type(instance)): __metaclass__ = type def __new__(cls): pass 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() 

What we do is create a new class dynamically at the time when we need to wrap the instance and actually inherit from the wrapped __class__ object. We also move on to the additional __metaclass__ override problem if the original had some additional behavior that we actually don't encounter (for example, finding a database table with a specific class name). A good convenience of this style is that we never need to create instance attributes in the wrapper class, there is no self.wrapped_object , since this value is present at the time the class is created.

Edit: as noted in the comments, the above only works for some simple types, if you need to proxy more complex attributes of the target object (say, methods), and then see the following answer: Python - Fake Type Continued

+7
source

You can use the __instancecheck__ magic method to override the default isinstance :

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

This is only if you want your object to be a transparent wrapper; that is, if you want DocumentWrapper to behave like a User . Otherwise, simply print the wrapped class as an attribute.

This is an addition to Python 3; he came with abstract base classes. You cannot do the same in Python 2.

+11
source

Override __class__ in the DocumentWrapper wrapper class:

 class DocumentWrapper(object): @property def __class__(self): return User >>> isinstance(DocumentWrapper(), User) True 

Thus, no changes are required in the wrapped User class.

Python Mock does the same (see mock.py:612 in mock-2.0.0, could not find sources on the Internet to link to them, sorry).

+4
source

Here is a solution using a metaclass, but you need to change the wrapped classes:

 >>> 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 
+1
source

It looks like you want to check the type of an object on DocumentWrapper wrappers, and not on the DocumentWrapper type. If it is right, then the interface for DocumentWrapper should expose this type. You can add a method to your DocumentWrapper class, which, for example, returns the type of the wrapped object. But I don’t think that making the isinstance call ambiguous by returning True when it is not is the right way to solve this problem.

0
source

The best way is to inherit the DocumentWrapper from the User itself or a mixing pattern and perform multiple inheritance from many classes

  class DocumentWrapper(User, object) 

You can also fake isinstance () results by manipulating obj.__class__ , but this is deep level magic and should not be executed.

0
source

All Articles