Python | Get all static-class variables of all sublcasses in parent class - python

I want to get all class variable names of all sub-classes in my parent class (in python). While I managed to do that I'm not certain if there is a cleaner way to achieve that.
Pseudo code to explain the problem :
class Parent:
all_names = []
var_1 = 1
def __init__(self):
print(self.all_names)
class ChildA(Parent):
var_2 = 2
class ChildB(ChildA):
var_3 = 3
p = Parent()
ca = ChildA()
cb = ChildB()
>>> ["var_1"]
>>> ["var_1","var_2"]
>>> ["var_1","var_2","var_3"]
So what I am currently doing is that I use the __new__ function to set those values recursively :
class Parent(object):
test_1 = 1
signals = []
def __new__(cls, *args, **kwargs):
# create obj ref
_obj = super().__new__(cls, *args, **kwargs)
signals = []
# walk every base and add values
def __walk(base):
for key, value in vars(base).items():
if <condition>
signals.append(value.__name__)
for base in base.__bases__:
__walk(base)
__walk(cls)
signals.reverse() # to reorder the list
_obj.signals = signals
return _obj
For more context: I am trying to develop a Signal-System but to check whether an instance has a Signal I somehow need to add them to the Root-Parent-Class. Yes I could Make all Subclasses inherit from a helper class but I don't want to do that and instead have it all bundled in as few classes as possible.
Also if there are any bugs/risks with my implementation please let me know I just recently discovered __new__.
(Python 3.x)

You could walk up the method resolution order and check for any keys in each class's __dict__ that do not appear in object.__dict__ and are not callable or private keys.
class Parent:
var_p = 1
def __init__(self):
self.blah = 0
class_vars = []
for cls in self.__class__.mro()[:-1]:
class_vars.extend([
k for k, v in cls.__dict__.items()
if k not in object.__dict__ and not k.startswith('_')
and not callable(v)
])
print(class_vars)
def test(self):
return True
class ChildA(Parent):
var_a = 2
class ChildB(ChildA):
var_b = 3
p = Parent()
ca = ChildA()
cb = ChildB()
# prints:
['var_p']
['var_a', 'var_p']
['var_b', 'var_a', 'var_p']

Related

How to preserve the value of class properties

class A:
p = 1
def __init__(self, p=None, **kwargs):
self.p = p
class B(A):
p = 2
a = A()
print(a.p)
b = B()
print(b.p)
As a more sensible example consider:
class Mamal:
can_fly = False
class Bat(Mamal):
can_fly = True
In the examples above, I would like 1 and 2 be printed. However, it prints None for both, though I know why. What is the solution to preserve the default value of classes?
One solution I can think of is:
class A:
p = 1
def __init__(self, p=None, **kwargs):
if p: self.p = p
if q: self.q = q
...
and if I have many attributes I should do that for all of them!? another minor problem is that the user can't pass None to the class init.
Another solution could be like:
class A:
p = 1
def __init__(self, p=1, **kwargs):
self.p = p
self.q = q
...
However again if one instantiate b like:
b = B()
the value of b.p would be also 1, while I expect it to keep 2.
I use overriding classes attributes much, but I just don't know how to preserve them from being overwritten by default values of the same or parent class.
Yet, another solution is combination of the above, like:
class A:
p = 1
def __init__(self, p=1, **kwargs):
if p != 1: self.p = p
...
or using dataclass
from dataclasses import dataclass
#dataclass
class A:
p :int = 1
#dataclass
class B(A):
p:int = 2
Just would like to know what is usual approach and consequences.
UPDATE:
If you really absolutely need both your class and your instances to have this attribute, and also want to use the class attribute as the default for an instance, I would say the correct way is like this:
_sentinel = object()
class A:
p = 1
def __init__(self, p=_sentinel):
if p is not _sentinel:
self.p = p
class B(A):
p = 2
a = A()
print(a.p) # prints 1
b = B()
print(b.p) # prints 2
b2 = B(p=None)
print(b2.p) # prints None
The sentinel object is for when you do want to be able to pass None to the constructor for whatever reason. Since we compare identity in the __init__ method, it is (practically) guaranteed that if any value is passed, it will be assigned to the instance attribute, even if that value is None.
Original answer:
The problem seems to stem from a misunderstanding of how (class-)attribute work in Python.
When you do this:
class A:
p = 1
You define a class attribute. Instances of that class will automatically have that same attribute upon initialization, unless you overwrite it, which is exactly what you do here:
def __init__(self, p=None, **kwargs):
self.p = p
This overwrites the instance's attribute .p with the value p it receives in the __init__ method. In this case, since you defined a default value None and called the constructor without passing an argument, that is what was assigned to the instance's attribute.
If you want, you can simply omit the self.p assignment in the constructor. Then your instances will have the class' default upon initialization.
EDIT:
Depending on how you want to handle it, you can simply assign the value after initialization. But I doubt that is what you want. You probably don't need class attributes at all. Instead you may just want to define the default values in your __init__ method signature and assign them there.
If you really need that class attribute as well, you can do what you did, but more precisely by testing for if p is not None:.
I would set the default value of the p argument to the value that you want:
class A:
def __init__(self, p=1, **kwargs):
self.p = p
class B(A):
def __init__(self, p=2, **kwargs):
super().__init__(p, **kwargs)
a = A()
print(a.p)
b = B()
print(b.p)
Then from the constructor of B you can call the one from A by using super().__init__
You can use class properties from the class:
class A:
p = 1
class B(A):
p = 2
a = A()
print(a.p)
b = B()
print(b.p)
prints 1 and 2, like you wanted.
It is clearer to access them from the class directly, though:
print(A.p)
print(B.p)
You can set the instance one, without changing what is associated in the class.
class B(A):
def change(self, x):
self.p = x
b.change(3)
print(B.p) #2
print(b.p) #3

python: simulating the multiple inheritance of class variables

I have a class hierarchy where some methods work with a list of properties defined at the class level.
Let's say that for class A I have A.X = [propA1, propA2] and for subclass C I need C.X = [propA1, propA2, propC]. Subclasses inherit the properties from the parents, so it would make sense to write class methods with super() calls, each one using the properties of their own class.
However, it is a bit cumbersome. I can deal with all properties in a single method in the base class. So it really feels more natural to define a class variable containing an array of new properties for each subclass and manually go down the cls.__mro__ to retrieve all properties.
What I've come up with (below) seems to work relatively transparently but is it idiomatic? Is there a more typical coding pattern for this? Is there a way to avoid decorating all subclasses?
class Base(object):
pass
class InheritClassVariable:
def __init__(self, var, base):
self.var = var
self.base = base
def __call__(self, cls):
name = self.var
uname = '_' + name
bases = [B for B in cls.__mro__ if issubclass(B, self.base)]
setattr(cls, uname, getattr(cls, name, []))
value = [item for B in bases for item in getattr(B, uname, [])]
setattr(cls, name, value)
return cls
#InheritClassVariable('X', Base)
class A(Base):
X = ['propA1', 'propA2']
#InheritClassVariable('X', Base)
class B(Base):
X = ['propB']
#InheritClassVariable('X', Base)
class C(A):
X = ['propC']
#InheritClassVariable('X', Base)
class D(C,B,A):
X = ['propD']
if __name__ == "__main__":
print(f"D.X = {D.X}")
A commentator mentioned metaclasses, something I didn't know of. I looked it up and found out there is an __init_subclass__ method that is meant to avoid some of the uses of metaclasses.
That being known, I could simplify my code to (edited):
def InheritConstantArray(varname, value=[]):
"""Return a class making the 'varname' array to be inherited in subclasses"""
basename = f"InheritConstantArray{varname}"
def init_subclass(cls):
# it seems __class__ won't work here. I still don't understand
# why. All I know is eval() is dirty so I do a lookup.
allbases = cls.mro()
base = [b for b in allbases if b.__name__ == basename][0]
# collaborate with other classes using __init_subclass__().
# if we want sevaral variables to be inherited.
super(base, cls).__init_subclass__()
# base._X[cls] will store original cls.X
uvarname = f'_{varname}' if varname[0] != '_' else f'{varname}_'
if not hasattr(base, uvarname):
setattr(base, uvarname, {base: value})
stored_values = getattr(base, uvarname)
stored_values[cls] = cls.__dict__.get(varname, [])
# Then we set cls.X value from base classes
bases = [b for b in allbases if issubclass(b, base)]
values = [i for b in bases for i in stored_values.get(b, [])]
print(cls, base)
setattr(cls, varname, values)
dct = {varname: value, '__init_subclass__': init_subclass}
base = type(basename, (object,), dct)
return base
class A(InheritConstantArray('X')):
X = ['A']
class B(A):
X = ['B']
class C(A):
X = ['C']
class D(B,C,InheritConstantArray('Y')):
X = ['D']
Y = ['d']
class E(D):
X = ['E']
Y = ['e']
class F(D):
X = ['F']
Y = ['f']
class G(E,F):
X = ['G']
Y = ['g']
if __name__ == "__main__":
print(f"A.X = {A.X}")
print(f"B.X = {B.X}")
print(f"C.X = {C.X}")
print(f"D.X = {D.X} {D.Y}")
print(f"E.X = {E.X} {E.Y}")
print(f"F.X = {F.X} {F.Y}")
print(f"G.X = {G.X} {G.Y}")
I'm still unsure if it's the standard way to do it, though. (Yes, there is a very strong rationale to have class variables and multiple inheritance in my real-world problem.)

Dynamically retrieve meta information about python classes declared in module/script

I have a set of classes which I would like to be able to dynamically initialise from dict.
class A(object):
var = 1
class B(object):
val = 2
class C(object):
var = 1
val = 3
BASE = {'a': A, 'b': B, 'c': C}
I might use these in some dynamic function such as
def create_and_obtain(kind, **kwargs):
base = BASE[kind]()
for kwarg in kwargs.keys():
print(getattr(base, kwarg))
>>> create_and_obtain('c', var=None, val=None)
1
3
If I have a lot of classes, which are added, amended and removed from time to time, I must ensure that my BASE dict is kept up to date. Is there a way of dynamically constructing BASE according to the declared classes in the script? Can the classes themselves have any meta_attributes added that could be used to extend the definition of BASE beyond this simplistic example?
For example could I add some meta tags like:
class A(object):
...
_meta_dict_key_ = 'apples'
class B(object):
...
_meta_dict_key_ = 'bananas'
class C(object):
...
_meta_dict_key_ = 'coconuts'
so that BASE is dynamically constructed as:
BASE = {'apples': A, 'bananas': B, 'coconuts':C}
Python has class decorators and so a clean way might be this:
BASE = {}
def register(klass): # Uses an attribute of the class
global BASE
BASE[klass.name] = klass
return klass
def label(name): # Provide an explicit name
def deco(klass):
global BASE
BASE[name] = klass
return klass
return deco
#register
class A(object):
var = 1
name = 'bananas'
#label('apples')
class B(object):
val = 2
#register
class C(object):
var = 1
val = 3
name = 'cakes'
You can iterate through the output of the locals() function at the end of the file and check _meta_dict_key. If there is such key in the object, you can use it to construct base dict.
In the python, all code that declared on the file top-level executes only once on the first import so it is safe to put such code there.

Creating an object with a reference to the object that created it

I have a program where an object creates another object. However, the second object that gets created needs to be able to access the first. Is this possible?
EG (pseudocode)
class parentObject():
parentVar = 1
# Create Child
x = childObject()
class childObject():
#Assign Var to the Var of the childs parent
childVar = parent.parentVar
>>> x.childVar = 1
is there a straitforward way to do this?
UPDATE:
I don't want to inheret the class, I need to be able to access the actual object that created it, as each object created from that class has different values.
Why not inherit the class?
class parentObject():
parentVar = 1
class childObject(parentObject):
childVar = parentObject.parentVar
>>> x = childObject()
>>> print(x.childVar)
1
If you are going to have different instances of the class, you should do it as this instead:
class parentObject(object):
def __init__(self):
self.parentVar = 1
class childObject(parentObject):
def __init__(self):
super(childObject, self).__init__()
self.childVar = self.parentVar
>>> x = childObject()
>>> print(x.childVar)
1
If you want a reference to the "parent" class, but inheritance is illogical, consider sending self in to the constructor:
class Room:
def __init__(self, name):
self.name = name
self.furniture = []
def add_chair(self):
self.furniture.append(Chair(self))
def __str__(self):
return '{} with {}'.format(self.name, self.furniture)
class Chair:
def __init__(self, room):
self.room = room
def __str__(self):
return 'Chair in {}'.format(self.room.name)
r = Room('Kitchen')
r.add_chair()
r.add_chair()
print r
print r.furniture[0]
Output:
Kitchen with [<__main__.Chair instance at 0x01F45F58>, <__main__.Chair instance at 0x01F45F80>]
Chair in Kitchen

Built-in non-data version of property?

class Books():
def __init__(self):
self.__dict__['referTable'] = 1
#property
def referTable(self):
return 2
book = Books()
print(book.referTable)
print(book.__dict__['referTable'])
Running:
vic#ubuntu:~/Desktop$ python3 test.py
2
1
Books.referTable being a data descriptor is not shadowed by book.__dict__['referTable']:
The property() function is implemented as a data descriptor.
Accordingly, instances cannot override the behavior of a property.
To shadow it, instead of property built-in descriptor i must use my own descriptor. Is there a built in descriptor like property but which is non-data?
To expand on my comment, why not simply something like this:
>>> class Books():
... def __init__(self):
... self.__dict__['referTable'] = 1
... #property
... def referTable(self):
... try:
... return self.__dict__['referTable']
... except KeyError:
... return 2
...
>>> a = Books()
>>> a.referTable
1
>>> del a.__dict__['referTable']
>>> a.referTable
2
Now, I'd like to note that I don't think this is good design, and you'd be much better off using a private variable rather than accessing __dict__ directly. E.g:
class Books():
def __init__(self):
self._referTable = 1
#property
def referTable(self):
return self._referTable if self._referTable else 2
In short, the answer is no, there is no alternative to property() that works in the way you want in the Python standard library.
There is something very similar to a built-in non-data descriptor -- the class attribute:
class Books():
referTable = 'default'
def __init__(self, referTable=None):
if referTable is not None:
self.referTable = referTable
book = Books()
print(book.referTable)
# default
book.referTable = 'something specific'
print(book.referTable)
# something specific
If you need something more like a property (for example, you want a function to do some heavy-lifting the first time, but then use that first value for all future references), then you will need to build it yourself:
class OneTime(object):
def __init__(self, method):
self.name = method.__name__
self.method = method
def __get__(self, inst, cls):
if inst is None:
return self
result = self.method(inst)
inst.__dict__[self.name] = result
return result
class Books(object):
#OneTime
def referTable(self):
print 'calculating'
return 1 * 2 * 3 * 4 * 5
b = Books()
print b.__dict__
print b.referTable
print b.__dict__
print b.referTable
With the following results:
{}
calculating
120
{'referTable': 120}
120

Categories

Resources