I am trying to answer the questions reading it as: "How can I make the return values of the operators of "set" to be of the type of my subclass of set. Ignoring the details of the given class and whether or not the example is broken to begin with. I came here from my own question which would be a duplicate, if my reading is correct.
This answer differs from some of the other answers as follows:
- The given class (subclass) gets changed only by adding a decorator
- therefore is general enough to not care about details of the given class (hasattr(s, 'foo'))
- The additional cost is paid once per class (when it's decorated), not for every instance.
- The only matter of the given example, that's specific to the "set" is the list of methods, which can be defined easily.
- Assumes, that the base class is NOT abstract and can be copy constructed itself (otherwise an __init__method needs to be implemented, that copies from an instance of the base class)
The library code, which can be put anywhere in the project or a module:
class Wrapfuncs:
def __init__(self, *funcs):
self._funcs = funcs
def __call__(self, cls):
def _wrap_method(method_name):
def method(*args, **kwargs):
result = getattr(cls.__base__, method_name)(*args, **kwargs)
return cls(result)
return method
for func in self._funcs:
setattr(cls, func, _wrap_method(func))
return cls
To use it with a set, we need the list of methods, that return a new instance:
returning_ops_funcs = ['difference', 'symmetric_difference', '__rsub__', '__or__', '__ior__', '__rxor__', '__iand__', '__ror__', '__xor__', '__sub__', 'intersection', 'union', '__ixor__', '__and__', '__isub__', 'copy']
and we can use it with our class:
@Wrapfuncs(*returning_ops_funcs)
class MySet(set):
pass
I am sparing the details of what could be special about this class.
I have tested the code with the following lines:
s1 = MySet([1, 2, 3])
s2 = MySet([2, 3, 4])
s3 = MySet([3, 4, 5])
print(s1&s2)
print(s1.intersection(s2))
print(s1 and s2)
print(s1|s2)
print(s1.union(s2))
print(s1|s2|s3)
print(s1.union(s2, s3))
print(s1 or s2)
print(s1-s2)
print(s1.difference(s2))
print(s1^s2)
print(s1.symmetric_difference(s2))
print(s1 & set(s2))
print(set(s1) & s2)
print(s1.copy())
which print:
MySet({2, 3})
MySet({2, 3})
MySet({2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3})
MySet({1})
MySet({1})
MySet({1, 4})
MySet({1, 4})
MySet({2, 3})
{2, 3}
MySet({1, 2, 3})
There is one case, in which the result is not optimal. This is, where the operator is used with an instance of the class as right hand operand and an instance of the builtin 'set' as first. I don't like this, but I believe this problem is common to all proposed solutions I have seen.
I have also thought of providing an example, where the collections.abc.Set is used.
While it could be done like this:
from collections.abc import Set, Hashable
@Wrapfuncs(*returning_ops_funcs)
class MySet(set, Set):
pass
I am not sure, whether it comes with the benefits, that @bjmc had in mind, or what the "some methods" are, that it gives you "for free".
This solution is targeted at using a base class to do the work and return instances of the subclass. A solution, that uses a member object to do the work could probably be generated in a similar way.