python object building using multi-inheritance - python

I want to build an object dynamically which allow use to mix the class properties in whichever way they like base on multiple inheritance. This is the expected behaviour. These classes are dataclasses so there won't be many methods in them, mostly data properties.
class Foo():
def bar(self, x):
return x
class FooA(Foo):
def bar(self, x):
p = super().bar(x)
p += __class__.__name__
return p
class FooB(Foo):
def bar(self, x):
p = super().bar(x)
p += __class__.__name__
return p
class FooC(FooA, FooB):
def bar(self, x):
p = super().bar(x)
p += __class__.__name__
return p
f = FooC()
f.bar('S') # SFooBFooAFooC
However this code violate the DRY principle in broad daylight, hence I want to avoid the bar method completely, if there is no special operations in the current class.
Ideally I want something like
#bar_wrapper
class FooA(Foo):
pass
# OR
class FooA(Foo):
__metaclass__ = BarBase
Instead of this full implementation
class FooA(Foo):
def bar(self, x):
p = super().bar(x)
p += __class__.__name__
return p
Essentially is there a way that I extract the middle layer class information in a multi-level inheritance class through a decorator or metaclass (the two options that I can think of)? Anyone has any idea on how to do this?

Write a class decorator that adds the bar method to the class:
def bar_wrapper(cls):
def bar(self, x):
p = super(cls, self).bar(x)
p += cls.__name__
return p
bar.__module__ = cls.__module__
bar.__qualname__ = '{}.{}'.format(cls.__qualname__, bar.__name__)
cls.bar = bar
return cls
class Foo():
def bar(self, x):
return x
#bar_wrapper
class FooA(Foo):
pass
#bar_wrapper
class FooB(Foo):
pass
#bar_wrapper
class FooC(FooA, FooB):
pass
f = FooC()
print(f.bar('S')) # SFooBFooAFooC

Related

Can you make a class attribute a specific instance of that class within the class definition?

Was wondering if there was a way to set a class attribute to a specific instance from within the class definition. For example,
class Value:
def __init__(self, x):
self.x = x
# Something like
# half = Value(0.5)
>>> Value.half.x
0.5
>>> Value.half.half.x
0.5
I'm also aware I can easily set it outside the class that seems a bit more bulky and error prone, like this
class Value:
def __init__(self, x):
self.x = x
Value.half = Value(0.5)
>>> Value.half.x
0.5
>>> Value.half.half.x
0.5
No. At the time the body of the class is being evaluated, the class doesn't yet exist. A class statement is a declarative syntax for calling a metaclass:
class Value:
def __init__(self, x):
self.x = x
is roughly equivalent to
def init(self, x):
self.x = x
Value = type('Value', (object,), {'__init__': init})
Your class attribute would have to be a member of the dict passed as the third argument, which has to be fully defined before type is called.
not quite, but you can make a class method that return a new instance of your class in whatever way you want with the classmethod decorator
>>> class Value:
def __init__(self, x):
self.x=x
def __repr__(self):
return f"{type(self).__name__}({self.x})"
#classmethod
def half(cls):
return cls(0.5)
>>> Value(10)
Value(10)
>>> Value.half()
Value(0.5)
>>>
look like in py3.9 you can combine it with the property decorator to accomplish just that, see linked documentation above (but I don't have it at the moment)
Simply, you can't because the class hasn't yet existed. But you can use either metaclass or class decorator to achieve the same goal as the following shows:
#Metaclass
class Meta(type):
def __init__(cls, clsname, clsbases, clsdict):
cls.half = cls(0.5)
class Value(metaclass=Meta):
def __init__(self, x):
self.x = x
#Decorator
def decorator(cls):
cls.half = cls(0.5)
return cls
#decorator
class Value2:
def __init__(self, x):
self.x = x
print(Value.half.half.x)
print(Value.half.x)
print(Value2.half.half.x)
print(Value2.half.x)

Define partial specialization of Class

I have a bunch of classes that all vary only by a string and a function, like so:
class ClassOne:
__init__(self, x):
self._x = x
self._my_str = 'hello'
def greet():
fun1(self._x)
return self._my_str
class ClassTwo:
__init__(self, x):
self._x = x
self._my_str = 'howdy'
def greet():
fun2(self._x)
return self._my_str
I would love to be able to define
ClassOne = ClassTemplate('hello', fun1)
ClassTwo = ClassTemplate('howdy', fun2)
So that ClassOne and ClassTwo still act as normal classes, i.e. can be imported into other modules and the likes. I realise this is probably a standard technique, but not knowing its name I cannot manage to Google it (that also explains my inability to properly name the question)
I realise I can take them in as part of the init, but the choice of function and string is not obvious for the user at all, so I would prefer to name them.
Edit: It seems people misunderstand what I seek. Continuing the example I want to be able to do
first_instance = ClassOne(3.14)
second_instance = ClassTwo(2.71)
first_instance.greet()
I.e. ClassOne and ClassTwo need to be proper class definitions.
_my_str can be class attribute, which you can set using
__init_subclass__ provided by an appropriate base class. We'll make fun a static method so that the user doesn't have to define fun specially.
class BaseClass:
def __init_subclass__(cls, my_str=None, fun=None):
cls._my_str = my_str
cls._fun = staticmethod(fun)
def __init__(self, x):
self._x = x
def greet(self):
self._fun(self._x)
return self._my_str
class ClassOne(BaseClass, my_str='hello', fun=fun1):
pass
class ClassTwo(BaseClass, my_str='howdy', fun=fun2):
pass
(If you just used cls._fun = fun, then self._fun(self._x) would be equivalent to type(self)._fun(self, self._x), rather than the intended type(self)._fun(self._x).)
Alternately, you can simply declare the class attributes explicitly, though now the caller is responsible for correctly defining fun as a static method.
class BaseClass:
def __init__(self, x):
self._x = x
def greet(self):
self._fun(self._x)
return self._my_str
class ClassOne(BaseClass):
_my_str = 'hello'
fun = staticmethod(fun1)
class ClassTwo(BaseClass):
my_str = 'howdy'
fun = staticmethod(fun2)
In either case, you can defer setting the class attributes:
class ClassThree(BaseClass):
pass
# time passes
ClassThree._my_str = "g'day"
ClassThree.fun = staticmethod(fun3)
You can make the classes take in parameters when creating them
Update:
To name parameters, set a default value
class ClassThing:
def __init__(self, x, string=None, function=None):
self.x = x #whatever x is
self.string = string
self.function = function
def greet(self):
self.function(self.x)
return self.string
Then you can do
>>> classthing1 = ClassThing(32, string = "hello1", function = lambda x: print(f"LOL {x}"))
>>> classthing2 = ClassThing(129, string = "hello2", function = lambda x: print(f"LOL lmao {x}"))
>>> classthing1.greet()
LOL hello1
32
>>> classthing2.greet()
LOL lmao hello2
129
Edited because I was way off
As you don't want to pass the function in the init you can do this
class BaseClass:
def __init__(self, x):
self._x = x
self._my_str = 'hello'
self.fun = None
def greet(self):
self.fun(self._x)
return self._my_str
def fun(x):
print(x)
def ClassTemplate(s, f):
res = BaseClass(s)
res.fun = f
return resr
a = ClassTemplate('toto', fun)
a.greet()

Limit of decorator as class compared to decorator as function

I want to make sure that I understood correctly how decorator as class works.
Let's say i have a decorator as a function that add an attribute to an object
def addx(obj):
obj.x = 10
return obj
#addx
class A:
pass
assert A.x == 10
Is it possible to write the same decorator as a class decorator? since the class decorator can't return the object itself with __init__
class addx:
def __init__(self, obj):
obj.x = 10
# ???
You could write an equivalent class-based decorator like this...
class addx:
def __new__(self, obj):
obj.x = 10
return obj
#addx
class A:
pass
assert A.x == 10
...but I don't think this really gets you anything. The utility of a class-based decorator becomes more apparent when your goal is to modify objects of class A, rather than class A itself. Compare the following two decorators, one function based and one class based:
def addx_func(kls):
def wrapper():
res = kls()
res.x = 10
return res
return wrapper
class addx_class:
def __init__(self, kls):
self.kls = kls
def __call__(self):
res = self.kls()
res.x = 10
return res
#addx_func
class A:
pass
#addx_class
class B:
pass
a = A()
assert a.x == 10
b = B()
assert b.x == 10

Use methods of parent class A from parent class B

I have a class A:
class A(object):
def pprint(x):
print(x)
Then I have a class B:
class B(object):
def pprint(x):
x += 1
# find a way to call A.pprint(x)
Then I have a child class:
class Child(B, A):
pass
Which should be used:
child = Child()
child.pprint(1)
>>> 2
I can make changes to B but not to A. I cannot refer to A directly in B. B will never be instantiated directly, always via children class.
After the explanation - what you need is not super() you need something like sibling_super() to find the next class in the multiple inheritance chain. You can poll Python's MRO for that, for example:
class A(object):
def pprint(self, x): # just to make it valid, assuming it is valid in the real code
print(x)
class B(object):
#staticmethod
def sibling_super(cls, instance):
mro = instance.__class__.mro()
return mro[mro.index(cls) + 1]
def pprint(self, x):
x += 1
self.sibling_super(B, self).pprint(self, x)
class Child(B, A):
pass
child = Child()
child.pprint(1) # 2
You have a couple of options for accessing the A method from the B class without having B inherit from A.
First, you could create a staticmethod and call it from B.
class A(object):
#staticmethod
def pprint(x):
print(x)
class B(object):
def pprint(self, x):
print(x + 1)
A.pprint(x)
Or you could inherit A in B like this:
class A(object):
def pprint(self, x):
print(x)
class B(A):
def pprint(self, x):
print(x + 1)
super(B, self).pprint(x)
Then for your Child class only inherit from B:
class Child(B):
pass
>>> c = Child()
>>> c.pprint(1)
2
1
OK, newest solution.
import inspect
class C(B, A):
def pprint(self, x):
a_class = inspect.getmro(Child)[-2]
a_class.pprint(self, x)
Since object will be the last result in inspect.getmro(Child) we skip that one to get the one before the last one, which is A. We then call that class's pprint method. You could also, to be more sure, if you know the __name__ of the class you want to call, iterate over the results from inspect.getmro(Child) and find the one that you want.

Python generic method scope

I've got a class that wraps functions with some metadata, in particular a parental relationship with other instances:
class Foo(object):
def __init__(self, func, parent):
self.func = func
self.parent = parent
self.parent_func = self.parent.func
In a few cases, I would like to use Foo to wrap a function that internally calls another Foo's function:
def f(x): return str(x).title()
def g(x): return self.parent_func(x)
a = Foo(f)
b = Foo(g, a)
print b.func("april is the cruellest month")
>>> April Is The Cruellest Month
Problem is that g isn't actually a method until b runs Foo.__init__, so it doesn't have a self.
I'm assuming there's something rather fundamental I'm missing about scoping, object methods, or functions' first-class citizenship status, and would greatly appreciate a point in the right direction.
EDIT: Looks like my above genericized example threw folks off, so I'm adding a more specific example below. The idea of this class is that each instance is an integer property (primality, perfection, its list of factors, etc), and contains a function that tests an integer for the property (returning a bool or an answer, as the case base be).
def f(n): # returns list of factors of n
def s(n): return len(self.parent_func(n))==2 # checks if n is semiprime
factors = Foo(f)
semiprime = Foo(s, factors)
It seems like your question boils down to "how can I dynamically add a method to an object", the the short answer is don't do it (1). Objects can have attributes which can be functions, and that's fine, but these functions do not become methods and don't behave like methods. For example if foo.attr is sum then foo.attr(x) is the same as sum(x) not sum(foo, x).
Your question has a certain functional "aroma" to it, if you wanted to drop the class/object stuff and go the fully functional route you could do something like this:
def identity(x):
return x
def f(n):
return [i for i in range(1, 10) if (n % i == 0)]
def s(factors):
return (len(factors) == 2)
def foo(func, helper=identity):
def innerfunc(n):
return func(helper(n))
return innerfunc
a = foo(f)
print a(6)
# [1, 2, 3, 6]
b = foo(s, a)
print b(5)
# True
If that doesn't appeal to you, I would suggest thinking of the func and parent attributes on your Foo class as data attached to your objects, not as methods, and work out the problem from there. The logic associated with your class should live inside proper methods. These methods can refer to the data as needed. Here's my very simple example:
class Foo(object):
def __init__(self, func, parent=None):
self.func = func
self.parent = parent
def run(self, n):
if self.parent is None:
return self.func(n)
else:
return self.func(self.parent.run(n))
a = Foo(f)
print a.run(6)
# [1, 2, 3, 6]
b = Foo(s, a)
print b.run(5)
# True
(1) Methods belong to a class not an object, so the question should really be how can I attach something to my object that behaves like a method.
As Matthew said, "parental relationship" would point to inheritance. But if you want/have to do it this way, you could use functools.partial:
from functools import partial
class Foo(object):
def __init__(self, func, parent=None):
self.func = partial(func, self)
self.parent = parent
self.parent_func = self.parent.func if parent is not None else None
def f(self, x):
return str(x).title()
def g(self, x):
return self.parent_func(x)
if __name__ == '__main__':
a = Foo(f)
b = Foo(g, a)
print b.func("april is the cruellest month")
When you call a object method, it is called with self as first parameter.
def f(self,x): return str(x).title()
def g(self,x): return self.parent_func(x)

Categories