I have a scheme of cooperative classes based on collection.abc. When I subclass them, I want to be able to define just a couple of class attributes that then become the default values at instantiation, like so:
class MyFancyClass:
# Defines various attributes, as class attributes and/or in the
# __init__ method
def __init__(self, a=1, b=1):
self.a = a
self.b = b
class A(myFancyClass):
# Instances of A should have these values, even if they override
# a value set in MyFancyClass's __init__ method:
a = 2
b = 2
c = SomeHelperClass
Currently, in the __init__ of FancyClass, I do:
def __init__(self, *args, **kwargs):
for k, v in vars(type(self)).items():
if k.startswith("_"):
continue
if k not in kwargs:
kwargs[k] = v
super().__init__(*args, **kwargs)
That works fine, but if I make a class B that is a subclass of A, I lose those values defined for A, and I want to keep them.
So playing around, I got stuck here...
class InitExtras:
def __init__(self, *args, **kwargs):
for cls in type(self).__mro__:
if cls == InitExtras:
break
for k, v in vars(cls).items():
if k.startswith("_") or callable(v):
continue
if k not in kwargs:
print(f"adding\n{k=}\n{v=}\n")
kwargs[k] = v
super().__init__(*args, **kwargs)
class Base:
def __init__(self, *args, **kwargs):
print(f"{args = }")
print(f"{kwargs = }")
class A(Base):
def fun1(self):
pass
class B(A):
def fun2(self):
pass
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b = value
def __init__(self, *args, b=23, b2=32, **kwargs):
super().__init__(*args, **kwargs)
self.b = b
self.b2 = b2
class C(InitExtras, B):
b = 42
class D(C):
b2 = 420
class T:
pass
class E(C):
b2 = T
def fun3(self):
pass
This seem to do most of what I want, except that E().b2 is 32, not T. And if I remove the callable() filter, other stuff can get mixed in too, like extra functionalities one might define later to personalize classes even further if needed (fun3 in the example). I don't want to need to do a new __init__ each time.
So my question is, how to accomplish that?
I could solve it, I did by making a metaclass, and to distinguish between different class attributes I limit it to just properties
abc_recipes.py
from abc import ABCMeta, ABC, abstractmethod
class PropertyConfigMeta(ABCMeta):
def __new__(mcls, name, bases, namespace, /, **kwargs):
#list the properties that the new class would inherit
properties = {p for bcls in bases
for cls in bcls.__mro__
for p,v in vars(cls).items()
if isinstance(v,property)
}
#proceed to extract the attributes that would
#overwrite the properties inherited by non-property
new_default={}
new_namespace = {}
for k,v in namespace.items():
if k in properties:
if isinstance(v,property):
new_namespace[k] = v
else:
new_default[k] = v
else:
new_namespace[k] = v
cls = super().__new__(mcls, name, bases, new_namespace, **kwargs)
if hasattr(cls,"_new_default"):
cls._new_default = {**cls._new_default, **new_default}
else:
cls._new_default = new_default
return cls
class PropertyConfig(metaclass=PropertyConfigMeta):
"""cooperative class that transform
class A(SomeClass):
a = 1
b = 2
into
class A(SomeClass):
def __init__(self, *arg, a = 1, b = 2, **karg):
super().__init__(*arg, a = a, b = b, **karg)
so long as a and b are defined as properties in SomeClass
(or somewhere in the inheritance chain)
class SomeClass:
#property
def a(self):
...
#property
def b(self):
...
Use as
class A(PropertyConfig, SomeClass):
a = 1
b = 2
"""
def __init__(self,*arg,**kwargs):
for k,v in self._new_default.items():
if k not in kwargs:
kwargs[k]=v
super().__init__(*arg,**kwargs)
class ConfigClass(ABC):
"""Cooperative class that offer a default __repr__ method
based on the abstract property .config"""
#property
#abstractmethod
def config(self) -> dict:
"""configuration of this class"""
return {}
def __repr__(self):
return f"{type(self).__name__}({', '.join( f'{k}={v!r}' for k,v in self.config.items() )})"
sample use
import abc_recipes
class Base:
def __init__(self,*arg,**karg):
if arg:
print(f"{arg=}")
if karg:
print(f"{karg=}")
class A(Base):
pass
class B(abc_recipes.ConfigClass,A):
def __init__(self,*a, b=23, b2=32, **k):
super().__init__(*a,**k)
self.b = b
self.b2 = b2
#property
def b(self):
"b attribute"
#print("b getter")
return self._b
#b.setter
def b(self,v):
#print("b setter")
self._b=v
#property
def b2(self):
"b2 atrribute"
#print("b2 getter")
return self._b2
#b2.setter
def b2(self,v):
#print("b2 setter")
self._b2=v
#property
def config(self) -> dict:
"""configuration of this class"""
res = super().config
res.update(b=self.b, b2=self.b2)
return res
class C(abc_recipes.PropertyConfig,B):
b=42
pass
class D(C):
b2=420
pass
class T:
pass
class E(C):
b2 = T
pi = 3.14
class F(E):
#property
def b2(self):
#print("rewriten b2 getter")
return "rewriten b2"
#b2.setter
def b2(self, value):
#print("rewriten b2 setter")
pass
test
>>> F()
F(b=42, b2='rewriten b2')
>>> E()
E(b=42, b2=<class '__main__.T'>)
>>> D()
D(b=42, b2=420)
>>> C()
C(b=42, b2=32)
>>> B()
B(b=23, b2=32)
>>> e=E()
>>> e.pi
3.14
>>> f=F()
>>> f.pi
3.14
>>>
Related
I'm working with classes that have a lot of instance variables, and I want to have classes that inherit every instance variables from them. something like this:
class foo(object):
def __init__(self,thing1,thing2,thing3,thing4,thing5,thingetc):
self.1 = thing1
self.2 = thing2
self.3 = thing3
self.4 = thing4
self.5 = thing5
self.etc = thingetc
class bar(foo):
self.6 = []
a = bar
print a.3
obviously this won't work, but all the documentation that I can find on line is confusing. How do you inherit variables in cases like this?
Currently, your code is invalid syntax as a digit cannot be at the very front of a variable name. However, you can use *args with __dict__:
class foo:
def __init__(self, *args):
self.__dict__ = dict(zip(['var{}'.format(i) for i in range(1, len(args)+1)], args))
f = foo(*range(15))
print(f.var1)
print(f.var14)
Output:
0
13
Use this as a template for your inheritance, emphasis on the super() method:
class Foo:
def __init__(self):
self.name = 'Foo'
class Bar(Foo):
def __init__(self):
super().__init__()
b = Bar()
b.name
# outputs 'Foo'
For your specific type of class (that takes an unknown number of initialization arguments, i.e. *args):
class Foo:
def __init__(self, *args):
self.name = 'Foo'
for i, arg in enumerate(args):
setattr(self, 'thing_' + str(i), arg)
class Bar(Foo):
def __init__(self, *args):
super().__init__(*args)
b = Bar('hello', 'world')
b.name
# outputs 'Foo'
b.thing_0
# outputs 'hello'
b.thing_1
# outputs 'world'
Now I would personally use the **kwargs over *args for specifying unique instance attributes:
class Foo:
def __init__(self, **kwargs):
self.name = 'Foo'
for att in kwargs:
setattr(self, att, kwargs[att])
class Bar(Foo):
def __init__(self, **kwargs):
super().__init__(**kwargs)
b = Bar(value = 4, area = 3.14)
b.name
# outputs 'Foo'
b.value
# outputs 4
b.area
# outputs 3.14
Consider the code below:
class A(object):
attr = None
def __init__(self):
assert A.attr is not None
class B(object):
def __init__(self, attr):
A.attr = attr
class C(A):
def __init__(self):
super().__init__()
class D(A):
def __init__(self):
super().__init__()
nested_classes = {cls.__name__: cls for cls in {C, D}}
Above doesn't seem to work as I intend because:
>>> first_class = B("first")
>>> first_sub_class = first_class.C()
>>> first_sub_class.attr
'first'
>>> second_class = B("second")
>>> second_sub_class = second_class.C()
>>> second_sub_class.attr
'second'
>>> first_sub_class.attr
'second'
Is there a way to have first_sub_class.attr be first while having second_sub_class.attr be second? Possibly by having a metaclass whose scope is within B?
A few points:
I don't want to pass attr around, I want to set it while B is being initialized.
I don't want to circumnavigate the point above by using partial, since it breaks the rest of the code relying on __name__ or __qualname__ or alike.
I want to keep faithful to the current structure as much as possible.
To solve this problem just add the line self.attr = self.attr inside the __init__ function of A. Since you don't want to change the attributes of A, you will have to make the following changes:
class A(object):
attr = None
def __init__(self):
assert self.attr is not None # Don't refer to A, but to self to get the correct value
self.attr = self.attr
class B(object):
def __init__(self, attr):
self.attr = attr # Don't edit A, just save the value in own instance
def __getattribute__(self, item): # completely added, does some magic to ensure class.attr is set correctly
if item in B.nested_classes:
c = B.nested_classes[item]
c.attr = self.attr
return c
return super().__getattribute__(item)
class C(A):
def __init__(self):
super().__init__()
class D(A):
def __init__(self):
super().__init__()
nested_classes = {cls.__name__: cls for cls in {C, D}}
first_class = B("first")
first_sub_class = first_class.C()
print(first_sub_class.attr)
second_class = B("second")
second_sub_class = second_class.C()
print(second_sub_class.attr)
print(first_sub_class.attr)
You're insanely overcomplicating this:
class A:
def __init__(self, attr):
self.attr = attr
class C(A):
pass
class D(A):
pass
class B:
def __init__(self, attr):
self.attr = attr
def C(self):
return C(self.attr)
def D(self):
return D(self.attr)
Behaves exactly as desired:
>>> first_class = B("first")
>>> first_sub_class = first_class.C()
>>> first_sub_class.attr
'first'
>>> second_class = B("second")
>>> second_sub_class = second_class.C()
>>> second_sub_class.attr
'second'
>>> first_sub_class.attr
'first'
I am defining a base class in python like
class Base(object):
def __init__(self):
self._changed = False
and some derived classes:
class Car(Base):
def set_type(self, type_):
# do something
def set_mileage(self, mileage):
# do something
class Flower(base):
def set_name(self, name):
# do something
In this example I now want to set the attribute '_changed' to Truewhenever I call a set method of one of the derived classes. I simply could add the line
self._changed = True
to every set method, or use a decorator, but I am looking for a more convenient and automatic way to do this whenever a method is called whose name starts with 'set_'. I am thinking using __getattribute__ like in the following not tried (and not working example:
def __getattribute__(self, name):
if name.startswith('set_'):
self._changed = True
return self.__getattribute__(name)
So how to implement this in the correct way?
Update: A fully working example this time using a metaclass and descriptor with both setter and a getter:
class Field(object):
def __get__(self, ins, type):
return getattr(ins, self.field_name, None)
def __set__(self, ins, val):
setattr(ins, self.field_name, val)
ins._changed = True
class Meta(type):
def __new__(cls, clsname, bases, dct):
for k, v in dct.items():
if isinstance(v, Field):
v.field_name = '_' + k
return type.__new__(cls, clsname, bases, dct)
class Base(object):
__metaclass__ = Meta
def __init__(self):
self._changed = False
class Car(Base):
type = Field()
mileage = Field()
class Flower(Base):
name = Field()
Demo:
>>> c = Car()
>>> c._changed
False
>>> c.type = "4X4"
>>> c._changed
True
>>> c1 = Car()
>>> c1._changed
False
>>> c1.mileage = 100
>>> c1._changed
True
>>> c.type
'4X4'
>>> c1.mileage
100
>>> f = Flower()
>>> f._changed
False
>>> f.name = "Rose"
>>> f._changed
True
>>> f.name
'Rose'
I would use a decorator for this. Something like this (not tested):
def isGet(func):
def newFunc(self, var):
self._changed = True
func(self, var)
return
return newFunc
And then in any get method you want this behaviour, you simply do
#isGet
def set_mileage(self, mileage):
# dosomething
A metaclass would work here:
from types import FunctionType
from functools import wraps
class Setter(type):
def __new__(cls, clsname, bases, dct):
for item in dct:
if item.startswith("set_") and isinstance(dct[item], FunctionType):
dct[item] = cls.changer(dct[item])
return super(Setter, cls).__new__(cls, clsname, bases, dct)
#staticmethod
def changer(func):
#wraps(func)
def wrapper(self, *args, **kwargs):
self._changed = True
return func(self, *args, **kwargs)
return wrapper
class Base(object):
__metaclass__ = Setter
def __init__(self):
self._changed = False
Then just inherit from Base like you normally would.
Sample usage:
>>> from meta import Car
>>> c = Car()
>>> c._changed
False
>>> c.set_type("blah")
ok
>>> c._changed
True
The metaclass is just automatically decorating any method in your class' __dict__ that starts with set_.
I would like to do something like this:
class A:
def hello(): print "Hello"
# I do not want to explicitly setup a:
a = A()
# a = A() -> I want this to happen automatically when I access a
# My first try is this:
def a():
return A()
# Also, I do not want to call a as a function a(): it must be an object
# And it must stay alive and initialized
a.hello() # a is created, as object of class A
a.hello() # I do not want a second instantiation
How can I implement this? properties? cached-properties? They are only for classes: a is a module-level object.
Maybe something like this:
class A(object):
def hello(self):
print "Hello"
class LazyA(object):
def __init__(self):
self.instance = None
def __getattr__(self, k):
if self.instance is None:
self.instance = A()
return getattr(self.instance, k)
a = LazyA()
def lazyinit(cls):
class p(object):
def __init__(self, *args, **kws):
self._init = lambda: cls(*args, **kws)
self._obj = None
def __getattr__(self, k):
if not self._obj:
self._obj = self._init()
return getattr(self._obj, k)
return p
Example:
#lazyinit
class A(object):
def __init__(self, a, b):
print("initializing...")
self.x = a + b + 2
def foo(self):
return self.x
x = A(39, 1)
print x
print x.foo()
print x.foo()
Generalization of the answer by Pavel:
class LazyClass(object):
def __init__(self, myclass, *args, **kwargs):
self.instance = None
self.myclass = myclass
self.args = args
self.kwargs = kwargs
def __getattr__(self, k):
if self.instance is None:
self.instance = self.myclass(*self.args, **self.kwargs)
return getattr(self.instance, k)
class A(object):
def __init__ (self, name):
self.name = name
print "Created"
def hello(self):
print "Hello " + self.name
import unittest
class TestLazyClass(unittest.TestCase):
def setUp(self):
self.a = LazyClass(A, 'Daniel')
def test_it(self):
self.a.hello()
self.a.hello()
What is a simple example of decorating a class by defining the decorator as a class?
I'm trying to achieve what has been implemented in Python 2.6 using PEP 3129 except using classes not functions as Bruce Eckel explains here.
The following works:
class Decorator(object):
def __init__(self, arg):
self.arg = arg
def __call__(self, cls):
def wrappedClass(*args):
return cls(*args)
return type("TestClass", (cls,), dict(newMethod=self.newMethod, classattr=self.arg))
def newMethod(self, value):
return value * 2
#Decorator("decorated class")
class TestClass(object):
def __init__(self):
self.name = "TestClass"
print "init %s"%self.name
def TestMethodInTestClass(self):
print "test method in test class"
def newMethod(self, value):
return value * 3
Except, in the above, wrappedClass is not a class, but a function manipulated to return a class type. I would like to write the same callable as follows:
def __call__(self, cls):
class wrappedClass(cls):
def __init__(self):
... some code here ...
return wrappedClass
How would this be done?
I'm not entirely sure what goes into """... some code here ..."""
If you want to overwrite new_method(), just do it:
class Decorator(object):
def __init__(self, arg):
self.arg = arg
def __call__(self, cls):
class Wrapped(cls):
classattr = self.arg
def new_method(self, value):
return value * 2
return Wrapped
#Decorator("decorated class")
class TestClass(object):
def new_method(self, value):
return value * 3
If you don't want to alter __init__(), you don't need to overwrite it.
After this, the class NormalClass becomes a ClassWrapper instance:
def decorator(decor_arg):
class ClassWrapper:
def __init__(self, cls):
self.other_class = cls
def __call__(self,*cls_ars):
other = self.other_class(*cls_ars)
other.field += decor_arg
return other
return ClassWrapper
#decorator(" is now decorated.")
class NormalClass:
def __init__(self, name):
self.field = name
def __repr__(self):
return str(self.field)
Test:
if __name__ == "__main__":
A = NormalClass('A');
B = NormalClass('B');
print A
print B
print NormalClass.__class__
Output:
A is now decorated. <br>
B is now decorated. <br>
\__main__.classWrapper