Inheritance of dynamically added methods - python

I have the following class ClassA, for which I dynamically create a method returning string version of another method:
# module_a.py
class ClassA(object):
def width(self):
return 5
def height(self):
return 10
#classmethod
def add_str_method(cls, name):
method = getattr(cls, name)
def str_method(self):
return str(method(self))
setattr(cls, '{0}_str'.format(name), str_method)
for name in ['width', 'height']:
ClassA.add_str_method(name)
This part works perfectly fine as long as I don't subclass ClassA in a different module. But when I do, like in the example below, the dynamically added methods are not inherited.
# module_b.py
from module_a import ClassA
class ClassB(ClassA):
pass
What would be the proper way of adding methods dynamically such that they are automatically inherited by subclasses?

First you have to declare add_str_method as a #classmethod if you want to update the class A dynamically (not just an instance of A).
# file a.py
class A(object):
def __init__(self, a=5):
self._a = a
def a(self):
return self._a
#classmethod
def add_str_method(cls, name):
def str_method(self):
return str(getattr(self, name)())
setattr(cls, '{0}_str'.format(name), str_method)
for name in ['a']:
A.add_str_method(name)
In order to access the a method from A, and thus the variable _a attached to a particular instance, the str method has to be bounded to self, note this lines:
def str_method(self):
return str(getattr(self, name)())
Now, with this testing script it works as expected:
# file b.py
from a import A
class B(A):
pass
print(B(10).a_str()) # prints '10'

You'll have to add the procedure to the initialization (__init__) of classA:
class ClassA(object):
def __init__(self):
for name in ['width', 'height']:
self.add_str_method(name)
def width(self):
return 5
def height(self):
return 10
def add_str_method(cls, name):
method = getattr(self, name)
def str_method(self):
return str(method())
setattr(cls, '{0}_str'.format(name), str_method)
Now doing
from module_a import ClassA
class ClassB(ClassA):
pass
print(dir(ClassB))
Gives:
>>> ... 'height', 'width']

Related

How to Mock class with other class in Python unittest

I'm trying to mock a class in python with another class using unittest but the unittest.patch creates an instance from the mock class and replaces the original class with it. Here is the description
The Origin class is located in the file: src/libutil/util.py
class A:
def __init__(self) -> None:
self.a = self.g()
self.c = "parent"
def g(self):
return "HI from parent"
The mock class is located in the file tests/libraries/mocks/util.py
class B(A):
def __init__(self) -> None:
super().__init__()
def g(self):
return "Hi from child"
I'm mocking that using unittest as follows:
#pytest.fixture(scope="session", autouse=True)
def mock_util():
from tests.libraries.mocks.util import B
with mock.patch('libutil.util.A', new_callable=B, create=False) as util_mock:
yield util_mock
The problem is that the patch creates an instance from class B and replaces class A with it instead of replacing class A with class B itself. When I use a = libutil.util.A() that doesn't work and throws TypeError: 'B' object is not callable.
Can you help me in mocking class A with class B itself? Please note that the usage here is a simplified example.

Difference between inheriting from object and object.__class__ for python?

I can see code below
class MetaStrategy(StrategyBase.__class__): pass
I am not sure why not just write code like below
class MetaStrategy(StrategyBase): pass
Definition schematic
class StrategyBase(DataAccessor):
pass
class DataAccessor(LineIterator):
pass
class LineIterator(with_metaclass(MetaLineIterator, LineSeries)):
pass
def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, str('temporary_class'), (), {})
If you call self.__class__ from a subclass instance, self.__class__ will use that type of the subclass.
Any class that is expressly specified while using the class will be used naturally.
Take the example below:
class Foo(object):
def create_new(self):
return self.__class__()
def create_new2(self):
return Foo()
class Bar(Foo):
pass
b = Bar()
c = b.create_new()
print type(c) # We got an instance of Bar
d = b.create_new2()
print type(d) # we got an instance of Foo

getting circular import while trying hinting python

I have 2 class (that are defined in two different package).
An A object as a "set" of B objects that all refer to the said A object.
Here is how it looks like :
the a.py :
from b import B
class A():
def __init__(self, data):
self.data = data
self.Bs = {}
def add_B(self, id, data_B):
self.Bs[id] = B(data_B, self)
the b.py :
class B():
def __init__(self, data, a_instance):
self.data = data
self.a = a_instance
so everything works preety good, but I'd like to hint python that the a_instance is indeed a class A object to have autocompletion in visual studio code.
At first i've tried to add from a import A and modify def __init__(self, data, a_instance : A): in the b.py file, but i've obviously got a circular import error
So I've been trying to use the typing package, and so added those lines to the a.py file :
from typing import NewType
A_type = NewType('A_type', A)
But I'm steel getting a circular import error.
Can Anyone explain me what I'm doing wrong ?
thanks for the help
PS: My classes actually have some complex methods and are defined in _a.py (resp. _b.py) and the __init__.py just import the class A and declare the A_type (resp. just import the class B)
Use
typing.TYPE_CHECKING, a variable that's never true at runtime
the string form of a type annotation to refer to a name that is not in scope at runtime:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from a import A
class B:
def __init__(self, data, a_instance: "A"):
...
However, if you can restructure your code in a way that avoids circular imports altogether, all the better.
You could try an abstract class with attributes of A and B, then implement each accordingly.
from collections.abc import abstractmethod, ABCMeta
class ABInerface(metaclass=ABCMeta):
#property
#abstractmethod
def data(self):
pass
#data.setter
#abstractmethod
def data(self, value):
pass
#property
#abstractmethod
def Bs(self):
pass
#Bs.setter
#abstractmethod
def Bs(self, value):
pass
#property
#abstractmethod
def a(self):
pass
#a.setter
#abstractmethod
def a(self, value):
pass
#abstractmethod
def add_B(self, id, data_B):
pass
Then, create each class by extending the Interface meta class.
class B(ABInerface):
def __init__(self, data, a_instance):
self.data = data
self.a = a_instance
class A(ABInerface):
def __init__(self, data):
self.data = data
def add_B(self, id, data_B):
self.Bs[_id] = B(data_B, self)

Overwrite base class attribute with #property of the same name

I am trying to subclass a python class and overwrite a regular attribute with a #property function. The catch is that I can't modify the parent class, and the api for the child class needs to look the same as the parent class (but behave differently). (So my question is different from this one in which the parent class also used a #property method to access the underlying attribute.)
The simplest possible example is
# assume this class can't be overwritten
class Parent(object):
def __init__(self, a):
self.attr = a
# how do I make this work?
class Child(Parent):
def __init__(self, a):
super(Child, self).__init__(a)
# overwrite access to attr with a function
#property
def attr(self):
return super(Child, self).attr**2
c = Child(4)
print c.attr # should be 16
This produces an error when the parent init method is called.
<ipython-input-15-356fb0400868> in __init__(self, a)
2 class Parent(object):
3 def __init__(self, a):
----> 4 self.attr = a
5
6 # how do I make this work?
AttributeError: can't set attribute
Hopefully it is clear what I want to do and why. But I can't figure out how.
This is easily fixed by adding a setter method
class Child(Parent):
def __init__(self, a):
self._attr = None
super(Child, self).__init__(a)
# overwrite access to a with a function
#property
def attr(self):
return self._attr**2
#attr.setter
def attr(self, value):
self._attr = value

Is there a way to make code run when a class is derived from a particular parent class in Python?

For example, if I create the class Foo, then later derive the subclass Bar, I want the myCode() method of Foo to run.
class Foo(object):
x = 0
def __init__(self):
pass
def myCode(self):
if(self.x == 0):
raise Exception("nope")
class Bar(Foo): #This is where I want myCode() to execute
def baz(self):
pass
This should happen any time a class is derived from the base class Foo. Is it possible to do this in Python? I'm using Python 3 if it matters.
Note: In my real code, Foo is actually an abstract base class.
Edit: I also need access to derived class member data and methods in myCode().
Use a metaclass:
class MetaClass:
def __init__(cls, name, bases, dictionary):
if name is not 'Parent':
print('Subclass created with name: %s' % name)
super().__init__(name, bases, dictionary)
class Parent(metaclass=MetaClass):
pass
class Subclass(Parent):
pass
Output:
Subclass created with name: Subclass
Metaclasses control how classes themselves are created. Subclass inherits its metaclass from Parent, and thus that code gets run when it is defined.
Edit: As for your use case with an abstract base class, off the top of my head I think you'd just need to define your metaclass as a subclass of ABCMeta, but I didn't test that.
May this code can help you:
class Foo:
def myCode(self):
print('myCode within Foo')
def __init__(self):
if type(self) != Foo:
self.myCode()
class Bar(Foo):
def __init__(self):
super(Bar, self).__init__()
def baz(self):
pass
Test:
>>>
>>> f = Foo()
>>> b = Bar()
myCode within Foo
>>>
This works:
class MyMeta(type):
def __new__(cls, name, parents, dct):
if name is not 'Foo':
if 'x' not in dct:
raise Exception("Nein!")
elif 'x' in dct and dct['x'] == 0:
raise Exception("Nope!")
return super(MyMeta, cls).__new__(cls, name, parents, dct)
Output:
class Bar(Foo):
x = 0
> Exception: Nope!
This is my specific use case if anyone wants to comment on whether or not this is appropriate:
class MagmaMeta(type):
def __new__(cls, name, parents, dct):
# Check that Magma instances are valid.
if name is not 'Magma':
if 'CAYLEY_TABLE' not in dct:
raise Exception("Cannot create Magma instance without CAYLEY_TABLE")
else:
# Check for square CAYLEY_TABLE
for row in CAYLEY_TABLE:
if not len(row) == len(dct['CAYLEY_TABLE']):
raise Exception("CAYLEY_TABLE must be a square array")
# Create SET and ORDER from CAYLEY_TABLE
dct['SET'] = set([])
for rows in CAYLEY_TABLE:
for x in rows:
dct['SET'].add(x)
dct['ORDER'] = len(dct['SET'])
return super(MyMeta, cls).__new__(cls, name, parents, dct)

Categories