Accesing superclass attribute during subclass construction in Python - python

Is it possible to access superclass's attributes during class construction?
Here's my code:
class A:
x = 1
class B(A):
x += 1 # <- error
The increment line x += ... is not valid, because x is not defined at this moment. You may suggest doing it in __init__, but I need to initialize the values before the constructor and want to minimize the code.
The code x = A.x + 1 will not work in my case because the A is generated in run-time. So the real code for B looks like
class A:
x: int
def class_gen(params):
class SubA(A):
x = 1
return SubA
class B(class_gen(some_params)):
x += 1
I've found a weird workaround:
x = A.__subclasses__()[-1].x + 1 (indeed the last subclass of A will be the generated super), but it looks too dirty and unstable.
Another workaround is declaring the a 'stub' class variable:
...
_Stub = class_gen(some_params)
class B(_Stub):
x = _Stub.x + 1
but is it also looks urgly. Is there a better way for a perfectionist?

I think you can accomplish it by using __new__ in the subclass.
class A:
x = 1
class B(A):
def __new__(cls, *args, **kwargs):
cls.x += 1
return super().__new__(cls, *args, **kwargs)
b = B()
print(b.x) # 2

There's no easy way to do this. Probably the best is to give your B class a metaclass that handles incrementing of the x attribute for you:
class XIncrementerMeta(type):
def __new__(mcls, name, bases, namespace):
namespace['x'] = bases[0].x + 1
return super().__new__(mcls, name, bases, namespace)
class B(class_gen("some_params"), metaclass=XIncrementerMeta):
pass
print(B.x) # prints 2
If you need each B-like class to have its own kind of manipulation of the x value, you could put it in a classmethod and have an inherited metaclass call it. Or you could even have an __init_subclass__ method in A that calls methods to set the value on its subclasses:
class A:
def __init_subclass__(cls):
cls.set_X()
class SubA(A):
#classmethod
def set_X(cls):
cls.x = 1
class B(SubA):
#classmethod
def set_X(cls):
super().set_X() # get SubA to set an initial value
cls.x += 1 # then update it
print(B.x) # prints 2

Related

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

How to call class method within namespace of the same class in python 3.x

When working with python instances, it is possible to access bound methods of the same class using self. This resolves to a method corresponding to the same class in hierarchy.
class A:
def f(self):
return 1
def __init__(self):
self.v = self.f()
class B(A):
def f(self):
return 2
b = B()
# b.v is set to 2
But, when working with class methods, there is no apparent way of accessing methods of the same class as above.
In my use case, f above needs to be a class method and I need to set class variable v according to f corresponding to the same class. Somewhat like:
class A:
#classmethod
def f(cls):
return 1
v = resolution_of_calling_class.f()
class B(A):
#classmethod
def f(cls):
return 2
# B.v should be 2
edit: v is actually an attribute defined by parent class, which should find a method overridden by child class
You just need to override __new__ method, since it is invoked before the __init__ and its purpose is to create an instance, that will be initialized by __init__.
class A:
def __new__(cls, *args, **kwargs):
cls.v = cls.f()
return super().__new__(cls, *args, **kwargs)
#classmethod
def f(cls):
return 1
class B(A):
#classmethod
def f(cls):
return 2
a = A()
print(a.v)
b = B()
print(b.v)
1
2
I am not 100% sure I understand what you are trying to do.
I used your code above and
class A:
#classmethod
def f(cls):
return 1
class B:
#classmethod
def f(cls):
return 2
print(B.f())
gives me 2 just as I expected it would. Should B be a child class of A as in the first example?

sum two different class attributes

Lets say I have 2 class and I want to add the second classes attributes to first class I can make like that:
class first:
def __init__(self):
self.value_one = 2
self.value_two = 5
self.value_third = 7 #second class don't have that attribute
def sum_class(self, cls):
for attribute in cls.__dict__:
x = getattr(cls, attribute)
y = getattr(self, attribute)
setattr(self, attribute, x+y)
class second:
def __init__(self):
self.value_one = 3
self.value_two = 1
But it doesn't look pythonic is there any better way to do it?
My Classes will have more than 10 attributes so I don't want to add one by one that could be easy but massy code like:
def sum(self, cls):
self.value_one += cls.value_one
self.value_two += cls.value_two
Also my third class may have:
class ClassB:
def __init__(self):
self.value_one = 2
self.value_third = 3
I also want to able to add this class into my first class
The only class that have a behvaiour similar to what you are looking for is the Counter class:
>>> c = Counter()
>>> c['a'] = 1.0
>>> c + Counter('a')
Counter({'a': 2.0})
So you could store these "attributes" inside a Counter and use __getattr__ to use normal attribute access:
from collections import Counter
class ClassWithCounter:
def __init__(self, **kwargs):
self.counter = Counter(kwargs)
def __getattr__(self, attr):
# this allows to use the syntax: first().value_one
try:
return self.counter[attr]
except KeyError:
raise AttributeError(attr)
class first(ClasswithCounter):
def __init__(self):
super(first, self).__init__(value_one=2, value_two=5, value_third=7)
def sum_class(self, cls):
self.counter += cls.counter
class second(ClassWithCounter):
def __init__(self):
super(second, self).__init__(value_one=3, value_two=1)
Note however that the purpose of Counter is just to count things, so there may be some situations where it gives you strange results.
If that is the case you can simply implement your own dictionary-like class and use it in place of Counter.
Also a suggestion: given that you are writing this for a game, you should consider whether this kind of update is good or not. Because in this way the original "base values" for the player gets lost.
I personally would keep the "base values" separate and keep track of the "modifiers" to such values (e.g. bonuses or maluses provided by items, or temporary effects).
This apporach allows you to implement things like "the damage of this spell isn't affected by bonus armor" (so you just use the base value when computing the damage). Your current approach makes this more cumbersome.
you can make it shorter by using:
def sum_class(self, cls):
[setattr(self, attr, getattr(cls, attr) + getattr(self, attr)) for attr in cls.__dict__]
Edit 1:
It was unclear what you wanted, but after you sad in comments you want something like classA.__dict__ + classB.__dict_, maybe you can use this:
class sum_class:
def __init__(self, class_1, class_2):
self.sum = class_1.__class__()
self.class_2 = class_2
for attr in self.class_2.__dict__:
if attr in self.sum.__dict__:
setattr(self.sum, attr, getattr(self.class_2, attr) + getattr(self.sum, attr))
else:
setattr(self.sum, attr, getattr(self.class_2, attr))
class first:
def __init__(self):
self.value_one = 2
self.value_two = 5
self.value_third = 7 #second class don't have that attribute
def __add__(self, cls):
return sum_class(self, cls).sum
class second:
def __init__(self):
self.value_one = 3
self.value_two = 1
def __add__(self, cls):
return sum_class(self, cls).sum
when classes are defined like that then you can use it like this:
>>> f = first()
>>> s = second()
>>> x = f + s
>>> x.value_one
5
>>> x.value_two
6
>>> x.value_third
7

Two possibilities(value and method) for class attribute in Python

Let's say I have a class. I want this class to have attribute x so that whenever I use the class without instantiating, I can both call the current value of x and when I change the value of x, to run a method that takes the value I put in x and does something else with it.
"Example" is a class:
[IN]: Example(y).x
[OUT]: 0
[IN]: Example(y).x=2
[OUT]: x(2)=6
[IN]: Example(y).x
[OUT]: 6
Sorry if this turns out to be banal. I tried searching and didn't come up with anything.
Example's x value must have a paired y value that belongs to the particular class.
You can override the __setattr__ method on your class:
class Test(object):
def foo(self, v):
return v + 1
def __setattr__(self, attribute, v):
return super(Test, self).__setattr__(attribute, self.foo(v))
t = Test()
t.a = 1 # t == 2
Here's how you do this:
values = {}
class Example(object):
def __init__(self,y):
self.y=y
#property
def value(self):
values[self.y]=some_data_object_method(self.y)
return some_data_object_method(self.y)
#value.setter
def value(self,new_value):
if new_value not in values.itervalues():
some_data_object_method(self.y) = new_value
Simple as that. Probably ended up being banal after all.

In Python, count the number of variables in a class or prevent adding new class variables

In python, is there a way to prevent adding new class variables after defining the object?
For example:
class foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
bar = foo()
try:
bar.d = 4
except Exception, e:
print "I want this to always print"
Alternatively, is there a way to count the number of variables in an object?
class foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
def count(self):
...
bar = foo()
if bar.count() == 3:
print "I want this to always print"
The only way I thought of doing this was using a dictionary or list:
class foo:
def __int__(self):
self.dict = {'foo':1, 'bar':2}
self.len = 2
def chk():
return self.len == len(self.list)
However, doing this feels rather cumbersome for python. (obj.dict['foo']). I'd prefer just obj.foo if possible.
I want to have this so that I never accidentally declare a variable when I mean to change an existing one.
f = foo()
f.somename = 3
...
f.simename = 4 #this is a typo
if f.somename == 3:
solve_everything()
I suggest using __setattr__ to avoid the oddities of __slots__.
You always have to be careful when messing with __setattr__, since it takes care of setting all instance attributes, including those you set in __init__. Therefore it has to have some way of knowing when to allow the setting of an attribute, and when to deny it. In this solution I've designated a special attribute that controls whether new attributes are allowed or not:
class A(object):
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
self.freeze = True
def __setattr__(self, attr, value):
if getattr(self, "freeze", False) and not hasattr(self, attr):
raise AttributeError("You shall not set attributes!")
super(A, self).__setattr__(attr, value)
Testing:
a = A()
try:
a.d = 89
except AttributeError:
print "It works!"
else:
print "It doesn't work."
a.c = 42
print a.a
print a.c
a.freeze = False
a.d = 28
a.freeze = True
print a.d
Result:
It works!
1
42
28
Also see gnibblers answer that wraps this concept neatly up in a class decorator, so it doesn't clutter up the class definition and can be reused in several classes without duplicating code.
EDIT:
Coming back to this answer a year later, I realize a context manager might solve this problem even better. Here's a modified version of gnibbler's class decorator:
from contextlib import contextmanager
#contextmanager
def declare_attributes(self):
self._allow_declarations = True
try:
yield
finally:
self._allow_declarations = False
def restrict_attributes(cls):
cls.declare_attributes = declare_attributes
def _setattr(self, attr, value):
disallow_declarations = not getattr(self, "_allow_declarations", False)
if disallow_declarations and attr != "_allow_declarations":
if not hasattr(self, attr):
raise AttributeError("You shall not set attributes!")
super(cls, self).__setattr__(attr, value)
cls.__setattr__ = _setattr
return cls
And here's how to use it:
#restrict_attributes
class A(object):
def __init__(self):
with self.declare_attributes():
self.a = 1
self.b = 2
self.c = 3
So whenever you want to set new attributes, just use the with statement as above. It can also be done from outside the instance:
a = A()
try:
a.d = 89
except AttributeError:
print "It works!"
else:
print "It doesn't work."
a.c = 42
print a.a
print a.c
with a.declare_attributes():
a.d = 28
print a.d
In python, is there a way to prevent adding new class variables after defining the object?
Yes. __slots__. But do carefully read the notes.
How about a class decorator based on lazyr's answer
def freeze(cls):
_init = cls.__init__
def init(self, *args, **kw):
_init(self, *args, **kw)
self.freeze = True
cls.__init__ = init
def _setattr(self, attr, value):
if getattr(self, "freeze", None) and (attr=="freeze" or not hasattr(self, attr)):
raise AttributeError("You shall not set attributes!")
super(cls, self).__setattr__(attr, value)
cls.__setattr__ = _setattr
return cls
#freeze
class foo(object):
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
bar = foo()
try:
bar.d = 4
except Exception, e:
print "I want this to always print"
Preventing adding new attibutes using __slots__ class attribute:
class foo(object):
__slots__ = ['a', 'b', 'c']
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
bar = foo()
try:
bar.d = 4
except Exception as e:
print(e,"I want this to always print")
Counting attributes:
print(len([attr for attr in dir(bar) if attr[0] != '_' ]))
use this to count no.of attributes of an instance:
>>> class foo:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
>>> bar=foo()
>>> bar.__dict__
{'a': 1, 'c': 3, 'b': 2}
>>> len(bar.__dict__) #returns no. of attributes of bar
3
Do you mean new class variables or new instance variables? The latter looks like what you mean and is much easier to do.
Per Ignacio Vazquez-Abrams's answer, __slots__ is probably what you want. Just do __slots__ = ('a', 'b', 'c') inside of your class and that will prevent any other attributes from being created. Note that this only applies to instances of your class -- class-level attributes can still be set, and subclasses can add whatever attributes they please. And he is right -- there are some oddities, so read the linked documentation before you start sprinkling slots everywhere.
If you aren't using slots, return len(vars(self)) works as a body for your suggested count method.
As an alternative to slots, you could define a __setattr__ that rejects any attribute not on a "known good" list, or to reject any new attributes after a frozen attribute is set to True at the end of __init__, etc. This is harder to get right, but more flexible.
If you actually want your instances to be completely read-only after initialization, and you are using a recent version of Python, consider defining a namedtuple or subclass thereof. Tuple subclasses also have some limitations though; if you need to go this route I can expand on it, but I'd stick with slots unless you have a reason to do otherwise.
Suppose you now want your class to have a fixed set of both mutable and immutable attributes? I've hacked gnibbler's answer to make class attributes immutable after init:
def frozenclass(cls):
""" Modify a class to permit no new attributes after instantiation.
Class attributes are immutable after init.
The passed class must have a superclass (e.g., inherit from 'object').
"""
_init = cls.__init__
def init(self, *args, **kw):
_init(self, *args, **kw)
self.freeze = True
cls.__init__ = init
def _setattr(self, attr, value):
if getattr(self, "freeze", None):
if attr=="freeze" or not hasattr(self, attr):
raise AttributeError("You shall not create attributes!")
if hasattr(type(self), attr):
raise AttributeError("You shall not modify immutable attributes!")
super(cls, self).__setattr__(attr, value)
cls.__setattr__ = _setattr
return cls
And an example:
#frozenclass
class myClass(object):
""" A demo class."""
# The following are immutable after init:
a = None
b = None
c = None
def __init__(self, a, b, c, d=None, e=None, f=None):
# Set the immutable attributes (just this once, only during init)
self.a = a
self.b = b
self.c = c
# Create and set the mutable attributes (modifyable after init)
self.d = d
self.e = e
self.f = f

Categories