0

By defining the __instancecheck__() special method of a class's metaclass, one can control when an object tests as a member of that class. What if you have a predefined class P (say, a builtin class) and you want to conditionally control whether a given instance of your class C tests as an object of type P? Can this be done?

Here is a silly example:

class C:
    def __init__ (self, x, y):
        self.x = x
        self.y = y

Suppose I want C to test as an object of class numbers.Number, but only if x > y and y % 3 == 0. (I said it would be a silly example.)

from numbers import Number
assert isinstance(C(4, 3), Number)
assert not isinstance(C(1, 3), Number)
assert not isinstance(C(4, 2), Number)

Can this be done? How?

Hammerite
  • 21,755
  • 6
  • 70
  • 91
  • 1
    possible duplicate of [class method \_\_instancecheck\_\_ does not work](http://stackoverflow.com/questions/13135712/class-method-instancecheck-does-not-work) – Viktor Kerkez Aug 18 '13 at 20:59
  • 1
    This question is not a duplicate of that other question. The two questions are qualitatively different. – Hammerite Aug 18 '13 at 21:38
  • 1
    I don't know what you're really trying to do, but it seems more reasonable to have a factory function that returns either a `C_Number` instance (which "is" a numbers.Number) or a `C_NotNumber` instance (which "is not" a numbers.Number). The is-a-number property would then be static, but given that "normal" numbers are immutable, that seems like a sensible restriction. – torek Aug 18 '13 at 22:30
  • You should make that an answer. – Hammerite Aug 19 '13 at 01:29

1 Answers1

1

The method __instancecheck__() is defined on the class you want to test intances against -- in this case, the class numbers.Number. This class uses abc.ABCMeta as its metaclass (see the source code). The mechanism ABCMeta provides to add further classes that should be considered instances of the class is the register() method. To achieve exactly what you asked for, you would have to replace the metaclass of numbers.Number by some custom subclass of ABCMeta that overrides __instancecheck__() to provide the functionality you want. This would be possible in CPython by assigning a new class to numbers.Number.__class__, but this wouldn't be guaranteed to work in other python implementations.

import abc
import numbers

class C(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def is_number(self):
        return self.x > self.y and not self.y % 3

class NumberMeta(abc.ABCMeta):
    def __instancecheck__(self, obj):
        if type(obj) is C:
            return obj.is_number()
        return abc.ABCMeta.__instancecheck__(obj)

numbers.Number.__class__ = NumberMeta

assert isinstance(C(4, 3), Number)
assert not isinstance(C(1, 3), Number)
assert not isinstance(C(4, 2), Number)

That said, actually doing something like this seems a terrible idea. Why do you think you need this?

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841