How to get the class from which a method was called? - python

The get_calling_class function must pass the following tests by returning the class of the method that called the A.f method:
class A:
def f(self): return get_calling_class()
class B(A):
def g(self): return self.f()
class C(B):
def h(self): return self.f()
c = C()
assert c.g() == B
assert c.h() == C

Walking the stack should give the answer.
The answer should ideally be, in the caller's stack frame.
The problem is, the stack frames only record the function
names (like so: 'f', 'g', 'h', etc.) Any information about
classes is lost. Trying to reverse-engineer the lost info,
by navigating the class hierarchy (in parallel with the
stack frame), did not get me very far, and got complicated.
So, here is a different approach:
Inject the class info into the stack frame
(e.g. with local variables),
and read that, from the called function.
import inspect
class A:
def f(self):
frame = inspect.currentframe()
callerFrame = frame.f_back
callerLocals = callerFrame.f_locals
return callerLocals['cls']
class B(A):
def g(self):
cls = B
return self.f()
def f(self):
cls = B
return super().f()
class C(B):
def h(self):
cls = C
return super(B, self).f()
def f(self):
cls = C
return super().f()
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
Related:
get-fully-qualified-method-name-from-inspect-stack
Without modifying the definition of subclasses:
Added an "external" decorator, to wrap class methods.
(At least as a temporary solution.)
import inspect
class Injector:
def __init__(self, nameStr, valueStr):
self.nameStr = nameStr
self.valueStr = valueStr
# Should inject directly in f's local scope / stack frame.
# As is, it just adds another stack frame on top of f.
def injectInLocals(self, f):
def decorate(*args, **kwargs):
exec(f'{self.nameStr} = {self.valueStr}')
return f(*args, **kwargs)
return decorate
class A:
def f(self):
frame = inspect.currentframe()
callerDecoratorFrame = frame.f_back.f_back # Note:twice
callerDecoratorLocals = callerDecoratorFrame.f_locals
return callerDecoratorLocals['cls']
class B(A):
def g(self): return self.f()
def f(self): return super().f()
class C(B):
def h(self): return super(B, self).f()
def f(self): return super().f()
bInjector = Injector('cls', B.__name__)
B.g = bInjector.injectInLocals(B.g)
B.f = bInjector.injectInLocals(B.f)
cInjector = Injector('cls', C.__name__)
C.h = cInjector.injectInLocals(C.h)
C.f = cInjector.injectInLocals(C.f)
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
I found this link very interesting
(but didn't take advantage of metaclasses here):
what-are-metaclasses-in-python
Maybe someone could even replace the function definitions*,
with functions whose code is a duplicate of the original;
but with added locals/information, directly in their scope.
*
Maybe after the class definitions have completed;
maybe during class creation (using a metaclass).

Related

Python: pass whole self of class to init of new class

I have a class and a sub-class, I'd like to pass the whole of the self of the class to the sub-class. I can pass self over to the new class explicitly easily enough, e.g.
class foo:
def __init__(self, a, b):
self.a = a
self.b = b
self.c = 'foo'
def foo_method(self):
print('a foo method')
class bar(foo):
def __init__(self, foo_object):
self.a = foo_object.a
self.b = foo_object.b
self.c = foo_object.c
def bar_method(self):
print('a bar method')
foo_object = foo(a = 'a', b = 'b')
bar_object = bar(foo_object)
bar_object.a
Is there a more succinct way to pass these over? Something like:
class bar(foo):
def __init__(self, foo_object):
self = self.foo_object
Update:
Thanks https://stackoverflow.com/users/10104112/bastien-antoine, the following solution worked:
class bar(foo):
def __init__(self, foo_object):
self.__dict__ = foo_object.__dict__.copy()
def bar_method(self):
print('a bar method with ' + str(self.c))
Have you tried the copy builtins library?
Otherwise I think you can easily implement your own .copy() method that would copy the values from the old object __dict__ to the new one. Something like this:
class MyObject:
a = None
def set_default_values(self):
self.a = 1
def copy(self, old):
if type(self) == type(old):
self.__dict__ = old.__dict__.copy()
else:
raise TypeError('Wrong type')
if __name__ == "__main__":
obj_1 = MyObject()
print(obj_1.a)
obj_1.set_default_values()
print(obj_1.a)
obj_2 = MyObject()
print(obj_2.a)
obj_2.copy(obj_1)
print(obj_2.a)
Note that I've added a type checking to be sure that you copy attributes that would exist otherwise, but I think simply self.__dict__ = old.__dict__.copy() would work fine, thought you might end up with attributes you might not suppose to have in the new object.
Hope this helps!
I think that you can do that with
class bar(foo):
def __init__(self):
super(bar, self).__init__()
with this code, you ran the init function for the subclass

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?

Calling different parent-class methods with one decorator

So basically my problem seems like this.
class A():
def func(self):
return 3
class B():
def func(self):
return 4
class AA(A):
def func(self):
return super(AA, self).func
class BB(B):
def func(self):
return super(BB, self).func
The func function is doing some work and one of the things it does is getting some attribute(or running method or whatever) from it's parent class.
Since func originally does the same logic at both cases (except that only parent class changes) I'd like to do this with decorators.
Is it possible? if so how to do it? Do I have somehow to pass parent-class as a argument?
I'll be very grateful for answers it's been bothering me for a while now.
There is no need to use super to access data attributes of a parent class.
Neither does a class need a parent in order for access to data attributes to work.
You can use a mixin to do the job:
# A and B stay the same - they still have a c attribute
class A():
c = 3
class B():
c = 4 # I've changed B to make it clear below
#Instead have a mixin which defines func()
class Mixin:
def func(self):
# func has its behaviour here
return self.c
class AA(Mixin, A):
pass
class BB(Mixin, B):
pass
a = AA()
b = BB()
print(a.func())
print(b.func())
Output:
3
4
You could do it with a single class decorator by defining a generic method inside of it that does what you want, and then adding it to the class being decorated. Here's what I mean:
def my_decorator(cls):
def call_super_func(self):
return super(type(self), self).func()
setattr(cls, 'call_super_func', call_super_func)
return cls
class A():
def func(self):
print('in A.func')
return 3
class B():
def func(self):
print('in B.func')
return 4
#my_decorator
class AA(A):
def func(self):
print('in AA.func')
return self.call_super_func()
#my_decorator
class BB(B):
def func(self):
print('in BB.func')
return self.call_super_func()
aa = AA()
aa.func()
bb = BB()
bb.func()
Output:
in AA.func
in A.func
in BB.func
in B.func
Of course you could eliminate the need to do this by just defining baseclass for A and B that has a call_super_func() method in it that they would then both inherit.

Call grandparent method in grand child class

Sorry, may be this is silly question, but its very confusing to me. Let's suppose we have the following classes:
class A():
def say(self):
print("A")
class B(A):
def say(self):
print("B")
class C(B):
def say(self,*args, **kwargs):
return super(C, self).say(*args, **kwargs)
I am accessing parent method in child, and it prints B, but I want to access method from class A as we are getting access from class B.
I know we can add super in class B, but I don't want to modify class B. so is there any option to get method from A directly in class C?
You can by calling A.say(self) like this:
class A():
def say(self):
print("A")
class B(A):
def say(self):
print("B")
class C(B):
def say(self):
A.say(self)
B.say(self)
print("C")
Then to test it out from a terminal:
>>> a = A()
>>> a.say()
A
>>> b = B()
>>> b.say()
B
>>> c = C()
>>> c.say()
A
B
C
Note: I dropped the args and kwargs because the A and B classes didn't use those arguments. If you wanted to make say take those all the way up though simply call A.say(self, *args, **kwargs) and if A.say returns something you can return it too

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