How to increment Methods within a Class? - python-3.x

I would like to increment methods within a python class but I don't know how to do it. Code will be easier to understand than words so here it is...
I do this because I want to use the methods later. I've tried first to create an incremental list of functions that I could use later, but it doesn't work. this is why I'm trying to do it this way.
What I would like as a result -> Works:
class MyClass:
def __init__(self, val):
self.value = val
def method_1(self):
print(self.value * 1)
def method_2(self):
print(self.value * 2)
def method_3(self):
print(self.value * 3)
def method_4(self):
print(self.value * 4)
mc = MyClass(5)
mc.method_1()
mc.method_2()
mc.method_3()
mc.method_4()
What I would like as a way to write it -> Doesn't work:
class MyClass:
def __init__(self,val):
self.value = val
for i in range(0,5):
def method_i(self):
print(self.value * (i + 1))
mc = MyClass(5)
mc.method_1()
mc.method_2()
mc.method_3()
mc.method_4()

This is possible, but not advisable:
class MyClass:
def __init__(self, val):
self.value = val
for i in range(5):
def method_xxx(self, i=i): # avoid late binding problem
print(self.value * (i + 1))
# Correct names:
method_xxx.__name__ = method_xxx.__name__.replace("xxx", str(i + 1))
method_xxx.__qualname__ = method_xxx.__qualname__.replace("xxx", str(i + 1))
# bind to class:
setattr(MyClass, "method_{}".format(i + 1), method_i)
Because interpolation is not really possible, we need to define the method under a different name first (if we want to avoid eval & Co.). Definition outside the class itself is necessary because the class body is executed before the class is created, so its name is not available yet.
Instead, it's a much better idea to just give the number as an argument to a more general method:
class MyClass:
...
def method(self, i):
print(self.value + i)

The short answer is that you should not. It is not advisable to use variable numbering instead of, say, lists.
If the methods are indeed all doing the same thing up to a parameter, you probably want to make this a single method.
class MyClass:
...
def method(self, i):
print(self.value * i)
If the method have a no recognizable pattern, then you will need to define them anyway.

Related

dynamic inheritance with type and super

I'm looking for a way to dynamically inherit a parent class with its attributes and methods, by using type for class creation and super for inheritance, like so:
class A:
def __init__(self,a,b):
self.a = a
self.b = b
def some_method(self,q):
return (self.a + self.b)**q
def B_init(self,**kwargs):
super().__init__(**kwargs)
def another_method(self,):
return 1
def class_B_factory(parent_class):
return type(
'B',
(parent_class, some_other_parent_class),
{'__init__':B_init,
'another_method':another_method
}
)
And then be able to call...
model = class_B_factory(A)(a = 1, b = 5)
print(model.some_method(2)) # outputs to (1 + 5)**2 = 36
I'm not sure how to proceed. I don't think I'll need a custom metaclass since I'm pretty sure you can't call the parent class' __init__ method while also creating self in the process. I also tried overriding the default __init__ method outside the scope of class_B_factory like so:
def class_B_factory(parent_class):
return type(
'B',
(parent_class, some_other_parent_class),
{'another_method':another_method
}
)
B = class_B_factory(A)
def B_init(self,**kwargs):
super(B,self).__init__(**kwargs)
B.__init__ = B_init
model = B(a = 1, b = 5)
because I figured type doesn't need __init__ right away, as it is only needed during instantiation. But then I get TypeError: __init__() got an unexpected keyword argument error, which seems like it didn't work, and its not clean anyway.
EDIT: I tried defining the methods outside the factory via the following but I am still unsuccessful. Not sure how to fix it. Python has trouble instantiating maybe?
class A:
...
def B_init(self, produced_class = None, **kwargs):
super(produced_class,self).__init__(**kwargs)
def another_method(self, q, parent_class = None):
if parent_class is not None:
return 3 * parent_class.some_method(self,q) # I expect any parent_class passed to have a method called some_method
return 1
def class_B_factory(parent_class, additional_methods):
methods = {}
for name, method in additional_methods.items():
if "parent_class" in signature(method).parameters:
method = partial(method, parent_class = parent_class) # freeze the parent_class argument, which is a cool feature
methods[name] = method
newcls = type(
'B',
(parent_class,),
methods # would not contain B_init
)
newcls.__init__ = partial(B_init, produced_class = newcls) # freeze the produced class that I am trying to fabricate into B_init here
return newcls
model = class_B_factory(parent_class = A, additional_methods = {"another_method": another_method})
print(signature(model.__init__).parameters) # displays OrderedDict([('self', <Parameter "self">),...]) so it contains self!
some_instance_of_model = model(a = 1, b = 5) # throws TypeError: B_init() missing 1 required positional argument: 'self'
The parameterless form of super() relies on it being physically placed inside a class body - the Python machinnery them will, under the hood, create a __class__ cell variable referring that "physical" class (roughly equivalent to a non-local variable), and place it as the first parameter in the super() call.
For methods not written inside class statements, one have to resort to explicitly placing the parameters to super, and these are the child class, and the instance (self).
The easier way to do that in your code is to define the methods inside your factory function, so they can share a non-local variable containing the newly created class in the super call: ​
def class_B_factory(parent_class):
def B_init(self,**kwargs):
nonlocal newcls # <- a bit redundant, but shows how it is used here
​super(newcls, self).__init__(**kwargs)
def another_method(self,):
​​return 1
​ newcls = type(
​'B',
​(parent_class, some_other_parent_class),
​{'__init__':B_init,
​'another_method':another_method
​}
return newcls
If you have to define the methods outside of the factory function (which is likely), you have to pass the parent class into them in some form. The most straightforward would be to add a named-parameter (say __class__ or "parent_class"), and use functools.partial inside the factory to pass the parent_class to all methods in a lazy way:
from functools import partial
from inspect import signature
class A:
...
# the "parent_class" argument name is given a special treatement in the factory function:
def B_init(self, *, parent_class=None, **kwargs):
nonlocal newcls # <- a bit redundant, but shows how it is used here
​super([parent_class, self).__init__(**kwargs)
def another_method(self,):
​​return 1
def class_B_factory(parent_class, additional_methods, ...):
methods = {}
for name, method in additional_methods.items():
if "parent_class" in signature(method).parameters:
method = partial(method, parent_class=parent_class)
# we populate another dict instead of replacing methods
# so that we create a copy and don't modify the dict at the calling place.
methods[name] = method
​ newcls = type(
​'B',
​(parent_class, some_other_parent_class),
methods
)
return newcls
new_cls = class_B_factory(B, {"__init__": B_init, "another_method": another_method})

Simplest way to multithread using class variables?

I'm trying to implement a function in my class that calculates information from array A and outputs the result in array B. Array A and array B are both variables of a class, as is the function. Something along these lines:
class MyClass:
def __init__(self, A):
self.A = A
self.B = np.zeros((A.shape[0], A.shape[1])
def my_function(self, i):
self.B += self.A[i]
def main(self):
for i in range(A.shape[2]):
my_function(i)
example = np.random.rand(256, 256, 1000)
my_class = MyClass(example)
my_result = my_class.B
Obviously this function is oversimplified but the question revolves about how to use multiprocess with variables self.A and self.B. I've tried something like this but it didn't work at all:
class MyClass:
def __init__(self, A):
self.A = A
self.B = np.zeros((A.shape[0], A.shape[1])
def my_function(self, i):
return self.A[i]
def main(self):
with multiprocessing.Pool() as p:
position = range(self.A.shape[2])
for i, result in enumerate(p.map(my_function, position))
self.B += result
You can get your example code to work doing something like...
class MyClass:
def __init__(self, A):
self.A = A
self.B = np.zeros((A.shape[0], A.shape[1]))
def my_function(self, i):
return self.A[:,:,i]
def run(self):
with Pool() as p:
position = range(self.A.shape[2])
for result in p.imap(self.my_function, position, chunksize=self.A.shape[2]):
self.B += result
example = np.random.rand(256, 256, 1000)
my_class = MyClass(example)
st = time.time()
my_class.run()
print(time.time() - st)
The problem with multiprocessing is that it has to fork new processes and then serialize (via pickle) the data going into and out of them. For simple code like this, the overhead is much more than the actual function you're completing.
Setting chunksize to the size of your iterable is just a way to assure that python doesn't fork process pools more than once and thus reduce the overhead. For this example the multiprocessed code is still slower than doing it single process, however if you have a more complex function, the MP version could be faster.
As a general rule, I try to never put the multiprocessed function/data inside of the class. This leads to a lot of extra overhead in the fork/pickle/unpickle process. You can move your function outside with something like...
# Simple gobal data / function
data = None
def my_function(i):
global data
return data[:,:,i]
class MyClass:
def __init__(self, A):
global data
data = A # move class data to global
self.A = A
self.B = np.zeros((A.shape[0], A.shape[1]))
def run(self):
with Pool() as p:
position = range(self.A.shape[2])
for result in p.imap(my_function, position, chunksize=self.A.shape[2]):
self.B += result
example = np.random.rand(256, 256, 1000)
my_class = MyClass(example)
st = time.time()
my_class.run()
print(time.time() - st)
For a simple function like this multiprocessing will still be slower, but if your actual function has a lot of complexity this can speed things up.

Calling classmethod multiple times in python

I am trying to create a classmethod which can be called again and again, however it only works once and stops. Here is the code:
class NewBytes(bytes):
def __init__(self, var):
self.var= var
#classmethod
def rip(cls):
return cls(var[2:])
a = b"12asd5789"
x = NewBytes(a)
print(x, x.rip(), x.rip().rip(), x.rip().rip().rip())
Here is what I got from this:
b'12asd5789' b'asd5789' b'asd5789' b'asd5789'
However, what I want to have is:
b'12asd5789' b'asd5789' b'd5789' b'789'
Thanks in advance.
Probably you don't actually want a class method, since you need access to instance state here.
class NewBytes(bytes):
def __init__(self, x):
self.x = x
def rip(self):
return type(self)(self.x[2:])
My previous answer of using self.x doesnt make sense since this is a class method (too quick to answer). I think this is a case of the XY problem, see the below example of how to use a class method.
class Test(object):
x = "hey there whats up this is a long string"
#classmethod
def TestFunction(cls):
cls.x = cls.x[3:]
print(cls.x)
print(Test().x)
Test().TestFunction()
Test().TestFunction()
Test().TestFunction()
Test().TestFunction()
Test().TestFunction()

Python overriding default attribute assignment

for a specific framework i work with, i need to define object attributes as special classes, for example, instead of writing this:
class A:
def __init__(self):
self.some_int = 2
i would need to write:
class A:
def __init__(self):
self.some_int = SpecialIntWrapper(name = "some_int", value = 2)
I would like to somehow override operators/methods so that typing the first code (self.some_int = 2) will call SpecialIntWrapper behind the scenes, with the attribute name and value.
is this possible?
Basically there are two ways - via a #property decorator (preferable unless you want to affect arbitrary names)
class MyClass:
def __init__(self):
self.some_int = 2
# if you know the name of the property define it as a property - a getter
#property
def some_int(self):
return self._some_int
# and a setter
#some_int.setter
def some_int(self, value):
self._some_int = SpecialIntWrapper("some_int", value)
or overloading the __setattr__ magic method
class MyClass:
def __init__(self):
self.some_int = 2
def __setattr__(self, name, value):
# in general if you dont know the names of the properties
# beforehand you can somehow filter them here
if name == "some_int":
super().__setattr__(name, SpecialIntWrapper(name=name, value=value))
else:
# to use the setattr in a default way, just call it via super(Python 3)
super().__setattr__(name, value)
either way the some_int will be initialized to the SpecialIntWrapper instance
>>>print(MyClass().some_int)
<__main__.SpecialIntWrapper object at 0x03721810>
Something like this
class SpecialIntWrapper:
def __init__(self, name, value):
pass
class MyClass:
def __init__(self):
self.some_int = 3
def __setattr__(self, key, value):
if key == 'some_int':
self.__dict__[key] = SpecialIntWrapper(key, value)
else:
self.__dict__[key] = value
print(MyClass().some_int)
# >>> <__main__.SpecialIntWrapper object at 0x1076f1748>

How do I define a variable for all class methods use?

I need to define a variables that is to be used in all the methods of one class.
class A:
def method1(self):
for i in range(N):
*do something with M*
def method2(self):
for i in range(N):
*do other thing with M*
and so on.
All I need to do is define N and M variables somewhere in the begining of class describing. I tried to define it in constructor like this:
class A:
def __init__(N, M):
self.N=N
self.M=M
def method1(self):
...
in tend to call A with this parameters just once. But there was another error in case I don't really now how to use constructor for now. Then I add this variables as parameters of each method:
...
def method1(self, N, M):
...
And that time it worked perfectly. But I don't want to input values each time I call A.methodX() and I'm 100% sure there is one or two ways that accords my requests.
You were on the right track so I wrote a short version of what I suspect you wanted to do with a little 'debug' code embedded. Note the instantiation of the class aa = A(1,2)
class A:
def __init__(self, N, M):
self.N = N
self.M = M
def method1(self):
print ("iterate N")
for i in range(self.N):
print (str(i) + "\n")
def method2(self):
print ("iterate M")
for i in range(self.M):
print (str(i) + "\n")
aa = A(1, 2)
aa.method1()
aa.method2()
Note that what I've done here is create instance variables - you can also create class variables by instantiating them outside of the methods. So iVar becomes the class variable.
class A:
iVar = 0
def __init__(self, N, M):
self.N = N
self.M = M
......

Resources