Python 3: Using OOP to change Class to Integer on Conditional Statement - python

Apologies for incorrect lingo, I am still new to this.
I want to make a class initialiser that, using a conditional, will decide whether or not the instance of said class will collapse into a simple integer.
Simplified Unworking Example:
class A(object):
def __init__(self,a,b):
self.a = a
self.b = b
if self.b == 0:
return int(a)
def __repr__(self):
return str(a)+":"+str(b)
DoesntBecomeAnInt = A(3,4)
WillBecomeAnInt = A(3,0)
print(DoesntBecomeAnInt,WillBecomeAnInt)
##Desired Output:
##3:4, 3
Any help would be very much appreciated!

You should use the magic method __new__ for this. __new__ is used as a factory where you can decide which class should be instantiated.
class A(object):
def __new__(self, a):
return int(a)
A(4)
> 4
A(4).__class__
> <type 'int'>

class A:
def __new__(cls, a, b):
if b == 0:
return a
return super().__new__(cls)
def __init__(self, a, b):
print('Initilizing')
self.a = a
self.b = b
def __repr__(self):
return str(self.a)+":"+str(self.b)
__new__ is the method used to control the creation of new objects (hence the name). Here we check if b is zero in __new__ and return an instance of the appropriate type.
In action:
>>> DoesntBecomeAnInt = A(3,4)
Initilizing
>>> WillBecomeAnInt = A(3,0)
>>> print(DoesntBecomeAnInt,WillBecomeAnInt)
3:4 3

You don't.
The behavior you desire is completely unexpected and somewhat bizarre. Calling A() is expected to return an instance of A. Doing anything else is confusing and unintuitive, which makes it difficult to read and understand any code invoking it.
Alternative
If you really need this behavior, create a factory method:
def make_thing(a, b):
if 0 == b:
return int(a)
else:
return A(a, b)
Obviously, you need a better name than make_thing, but without any context, I can't give you any suggestions.
Avoid the problem if possible
Since A is not a number and is generally not compatible with int, it is also somewhat strange to store both int and A in the same variable.
If all you're doing is converting to a string, then you don't need a class at all. A simple method outside of a class is the better alternative:
def a_and_b_to_string(a, b):
if b == 0:
return str(int(a))
else:
return str(a) + ":" + str(b)
If you're doing more than that, your calling code will probably end up looking something like this:
x = make_thing(input1, input2)
if isinstance(x, A):
result = x.some_method_from_a() # Or some other calculation requiring an A
else:
result = 5 * x # Or some other calculation requiring an `int`
This is somewhat silly: you write a method to choose the data type and then have to write specialized code for each possible result. You're not getting any benefits from having a function that returns the separate types here. I can think of two simpler alternatives:
Just move the check to the calling code:
if input2 == 0:
temp = A(input1, input2)
result = temp.some_method_from_a() # Or some other calculation requiring an A
else:
result = 5 * int(input1) # Or some other calculation requiring an int
If you go this route, you should also modify A.__init__ to throw a ValueError if b == 0, since that would be an invalid state for an A to be in.
Modify A so that it works properly regardless of whether b is 0:
class A(object):
def __init__(self,a,b):
self.a = a
self.b = b
def some_method_from_a():
if self.b == 0:
# Some calculation involving only a
return int(self.a) * 5
else:
# Some other more complex calculation involving both a and b
return self.a * self.b * 6
def __repr__(self):
if self.b == 0:
return str(int(self.a))
else:
return str(self.a) + ":" + str(self.b)
Then
x = A(a, b)
result = x.some_method_from_a()
But again, it's hard to provide recommendations without knowing how you're actually using it.

Using dataclasses
# A(...) should either raise an exception or
# return an instance of A to avoid confusion.
# create can do whatever you want it to.
import dataclasses
#dataclasses.dataclass
class A:
print('Initializing')
a : int
b : int
def __repr__(self):
if self.b == 0:
return str(int(self.a))
else:
return str(self.a) + ":" + str(self.b)
#classmethod
def create(cls, a, b):
if b == 0:
return a
return cls(a, b)
DoesntBecomeAnInt = A.create(3,4)
WillBecomeAnInt = A.create(3,0)
print(f'{DoesntBecomeAnInt}\n{WillBecomeAnInt}')
Initializing
3:4
3
[Program finished]

Related

Is it allowed to add output of a method into self?

I have a class with several methods. Outputs of a method are used in other methods. I don't want to pass these variables as input argument to other methods (to make code more simple).
I can add output of this method into self so I have access to these variables in other methods.
But, I want to be sure that it is a standard implementation. I am worried that it may cause unpredictable error. If you have experience in this regard, please let me know if the following example is a correct implementation or not.
class MyClass:
def method_1(self, A):
return A + 1
def method_2(self):
return self.B + 10
def method_3(self, C):
self.B = self.method_1(C)
result = self.method_2()
return result
z = MyClass()
z.method_3(1)
In the above example, I don't need to pass self.B into method_2. This code works but I want to be sure that it is a standard way.
The real program I working on is complicated, so I made a simple example for this question.
Yup it is more or less correct but the standard way of doing something like this is having a __init__() method and using function annotations.
class MyClass:
def __init__(self) -> None:
self.B = 0
def method_1(self, A: int) -> int:
return A + 1
def method_2(self) -> int:
return self.B + 10
def method_3(self, C: int) -> int:
self.B = self.method_1(C)
result = self.method_2()
return result
z = MyClass()
z.method_3(1)
Where method_2() relies on an attribute that may be unset, make it private so that people aren't tempted to use it. For example, what if I did this?
>>> z = MyClass()
>>> z.method_2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "tmp.py", line 9, in method_2
return self.B + 10
AttributeError: 'MyClass' object has no attribute 'B'
For that matter, it's probably best to make the attribute private too. So:
class MyClass:
def method_1(self, A):
return A + 1
def _method_2(self):
return self._B + 10
def method_3(self, C):
self._B = self.method_1(C)
result = self._method_2()
return result
By the way, where method_1() doesn't use self, consider making it a staticmethod.

check if inner functions were created by the same function

lets supose that i have something like:
def a(b):
def c(d):
# do someting
# ...
# ...
return something_else
return c
and then this:
q = a(54)
w = a(97)
e = a(12)
is there any way to know if for example q and w where both created by a ? or is there anything that i can make to check it ?
i though about doing an empty class and then inheit from that one overwriting __call__ and using issubclass but it seems a little bit hacky (and will raise if its actually a normal function), and maybe there is a better option.
thanks.
You could duck punch it:
def a(b):
def c(d):
# do stuff
return stuff
c.created_by = 'a'
return c
So then when you use it...
q = a(54)
w = a(97)
q.created_by == w.created_by # True
Not judging since I don't know why you're trying to do this, or really understand the problem, but personally I think this is a bigger hack than doing it via classes:
class A(object):
def __init__(self, b):
self.b = b
class C(A):
def do_stuff(self, d):
return self.b * d
q = C(54)
w = C(97)
isinstance(q, A) # True

Is it possible to define an integer-like object in Python that can also store instance variables?

Is it possible to define a data object in python that behaves like a normal integer when used in mathematical operations or comparisons, but is also able to store instance variables?
In other words, it should be possible to do the following things:
pseudo_integer = PseudoInteger(5, hidden_object="Hello World!")
print(5 + pseudo_integer) # Prints "10"
print(pseudo_integer == 5) # Prints "True"
print(pseudo_integer.hidden_object) # Prints "Hello World!"
So, all answers above are fine, but probably you don't want to re-define all existing methods.
Normally, in python you can just subclass any built-in class (type). But with immutable types (and integers in python are immutable) is slightly tricky. TL;DR:
class PseudoInt(int):
def __new__(cls, x, hidden, *args, **kwargs):
instance = int.__new__(cls, x, *args, **kwargs)
instance.hidden = hidden
return instance
x = PseudoInt(5, 'secret')
x.hidden # 'secret'
x + 4 # 9
x * 3 # 15
Normally, you should reload __init__ method, but with immutable bases you should use __new__. You can read more about data model in corresponding docs section
All this is viable only if you need single signature for constructing your object. If its fine to have 1 call for creating, and dedicated calls to populate object with attributes - Kevin's answer is all you need
Yes, it is. You can create your own custom class. Python has many magic methods to help you archive that.
Check the code:
class PseudoInteger:
def __init__(self, x, s):
self.x = x
self.s = s
def __add__(self, num):
return self.x + num
def __eq__(self, num):
return self.x == num
a = PseudoInteger(5, 'hello, world')
print(a + 3)
print(a == 5)
print(a == 2)
Or you can just inherit from int, after creating an instance, you are able to assign attributes to the inherited int object. You can't assign attributes to int directly, because int does not support item assignment :
class PseudoInteger(int):
pass
a = PseudoInteger(5)
a.hidden = 'hello, world'
print(a)
print(a == 5)
print(a + 3)
print(a.hidden)
You simply need a class for this:
class PseudoInteger(object):
def __init__(self, num, hidden=None):
self.num = num
self.hidden = hidden
def __add__(self, otherVal):
if isinstance(otherVal, PseudoInteger):
return self.num + otherVal.num
else:
return self.num + otherVal
p = PseudoInteger(4, 'Tiger')
q = PseudoInteger(6, 'lion')
print (p+q)
print (p+4)
This prints out:
10
8
You have to add the other operations (division, substraction, eq, ...) you need to the class on your own :)
Look into implementing the __add__ and __eq__ methods for your PseudoInteger class

how to implement a function like sum(2)(3)(4)......(n) in python?

how to implement a function that will be invoked in the following way sum_numbers(2)(3)(4)......(n) in python?
the result should be 2+3+4+.....+n
The hint that I have is since functions are object in pythons there is way to do those using a nested function but I am not sure.
def sum_number(x):
def sum_number_2(y):
def sum_number_3(z):
....................
def sum_number_n(n)
return n
return sum_number_n
return sum_number_3
return sum_number_2
return sum_number
But instead of writing so many nested functions we should be able to do it in couple nested functions to compute sum of n values when invoked in the following way sum_numbers(2)(3)(4)......(n)
Use Python's data model features to convert the result into the desired type.
class sum_number(object):
def __init__(self, val):
self.val = val
def __call__(self, val):
self.val += val
return self
def __float__(self):
return float(self.val)
def __int__(self):
return int(self.val)
print '{}'.format(int(sum_number(2)(3)(8)))
print '{}'.format(float(sum_number(2)(3)(8)))
You could create a subclass of int that is callable:
class sum_numbers (int):
def __new__ (cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
def __call__ (self, val):
return sum_numbers(self + val)
That way, you have full compatibility with a normal integer (since objects of that type are normal integers), so the following examples work:
>>> sum_numbers(2)(3)(4)(5)
14
>>> isinstance(sum_numbers(2)(3), int)
True
>>> sum_numbers(2)(3) + 4
9
Of course, you may want to override additional methods, e.g. __add__ so that adding a normal integer will still return an object of your type. Otherwise, you would have to call the type with the result, e.g.:
>>> sum_numbers(sum_numbers(2)(3) + 5)(6)
16
If your function is returning another function, you can't just chain calls together and expect a human readable result. If you want a function that does what you want without the final result, this works:
def sum(x):
def f(y):
return sum(x+y)
return f
If you're fine with printing out the operations you can try this:
def sum(x):
print(x)
def f(y):
return sum(x+y)
return f
If you absolutely, absolutely need a return value then this is a dirty, horrible hack you could try:
def sum(x, v):
v[0] = x
def f(y, v):
return sum(x+y, v)
return f
v = [0]
sum(1,v)(2,v)(3,v)
print(v[0]) # Should return 6
Here's another solution that uses classes:
class sum(object):
def __init__(self, x=0):
self.x=x
def __call__(self, *y):
if len(y) > 0:
self.x += y[0]
return self
return self.x
print(sum(1)(2)(3)()) # Prints 6
What you're asking for is not possible in Python since you aren't providing a way to determine the end of the call chain, as cricket_007 mentions in the comments. However, if you do provide a way to indicate that there are no more calls then the function is easy to code. One way to indicate the end of the chain is to make the last call with no arguments.
I'm using rsum (recursive sum) as the name of the function in my code because sum is a built-in function and unnecessarily shadowing the Python built-ins is not a good coding practice: it makes the code potentially confusing, or at least harder to read because you have to keep remembering that the name isn't referring to what you normally expect it to refer to, and can lead to subtle bugs.
def rsum(val=None, tot=0):
if val is None:
return tot
tot += val
return lambda val=None, tot=tot: rsum(val, tot)
print rsum(42)()
print rsum(1)(2)()
print rsum(4)(3)(2)(1)()
print rsum(4100)(310000)(9)(50)()
output
42
3
10
314159
class MetaSum(type):
def __repr__(cls):
sum_str = str(cls.sum)
cls.sum = 0
return sum_str
def __call__(cls, *args):
for arg in args:
cls.sum += arg
return cls
class sum_numbers(object, metaclass = MetaSum):
sum = 0
print (sum_numbers(2)(3)(4)) # this only works in python 3

Make my_average(a, b) work with any a and b for which f_add and d_div are defined. As well as builtins

In short: what I want is for the majority of mathematical functions I've written (e.g., my_average(a, b)) to work with any a and b for which an f_add and f_div have been defined. Without overloading + and / and without breaking my_average(built_in_type, built_in_type) In python 3.
Specifically, I'm working with instances of a Pigment object I have created. Overloading operators for these objects in not straightforward. For instance:
The difference (for distance purposes) between two instances might be a.lab - b.lab. (The Lab colorspace has a good correlation between perceptive and Euclidian distance.)
The sum (for mixing purposes) of two instances might be a.srgb + b.srgb. (The srgb colorspace is linear and appropriate for mathematical manipulation.)
For other purposes, sum and difference might mean something else.
So, duck typing in my existing modules wont work.
pigment_instance.distance(self, b)
pigment_instance.mix(self, b)
are fine as long as I don't mind re-writing every function (as a method) I need every time I have a new object like this. What I'd like to do is rewrite my functions just once more to be more robust.
I've tried a few things:
class Averager():
__init__(self, f_mix, f_distance):
self.f_mix = f_mix
...
def __call__(self, a, b):
# return the average calculated with self.f_something functions
That works OK, but I just end up burying an entire module in a class.
def mix(a, b, f_mix=lambda x, y: x + y, f_distance=lambda x, y: x - y)
# or, same as above with decorators.
Again, works OK, but I've got to keep the long default arguments or supply an f_add every time I want to calculate 2+2.
def pigment_functions(f_mix, f_distance):
return [
functools.partial(mix, f_mix=somefunc, f_distance=somefunc),
functools.partial(distance, f_mix=somefunc, f_distance=somefunc)]
mix, difference = pigment_functions(f_mix, f_distance)
A similar choice to the second.
def mix(a, b):
try: a + b
except TypeError: # look for some global f_mix
Also works OK, but I've got global variables and a mess inside every function
Which of these (or something else) makes sense?
if you have my_average(a, b) that is implemented in terms of add and div functions e.g.:
def my_average(a, b):
return div(add(a, b), 2)
then to provide different implementations for different types, you could use functools.singledispatch:
import functools
#singledispatch
def div(x, y:int): # default implementation
raise NotImplementedError('for type: {}'.format(type(x)))
#div.register(Divisible) # anything with __truediv__ method
def _(x, y):
return x / y
#singledispatch
def add(a, b):
raise NotImplementedError('for type: {}'.format(type(a)))
#add.register(Addable) # anything with __add__ method
def _(a, b):
return a + b
where Addable, Divisable could be defined as:
from abc import ABCMeta, abstractmethod
class Divisible(metaclass=ABCMeta):
"""Anything with __truediv__ method."""
__slots__ = ()
__hash__ = None # disable default hashing
#abstractmethod
def __truediv__(self, other):
"""Return self / other."""
#classmethod
def __subclasshook__(cls, C):
if cls is Divisible:
if any("__truediv__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
class Addable(metaclass=ABCMeta):
"""Anything with __add__ method."""
__slots__ = ()
__hash__ = None # disable default hashing
#abstractmethod
def __add__(self, other):
"""Return self + other."""
#classmethod
def __subclasshook__(cls, C):
if cls is Addable:
if any("__add__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Example
>>> isinstance(1, Addable) # has __add__ method
True
>>> isinstance(1, Divisible) # has __truediv__ method
True
>>> my_average(1, 2)
1.5
>>> class A:
... def __radd__(self, other):
... return D(other + 1)
...
>>> isinstance(A(), Addable)
False
>>> _ = Addable.register(A) # register explicitly
>>> isinstance(A(), Addable)
True
>>> class D:
... def __init__(self, number):
... self.number = number
... def __truediv__(self, other):
... return self.number / other
...
>>> isinstance(D(1), Divisible) # via issubclass hook
True
>>> my_average(1, A())
1.0
>>> my_average(A(), 1) # no A.__div__
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'A' and 'int'
Builtin numbers such as int define __add__, __truediv__ method so they are supported automatically. As class A shows, you could use classes even if they don't define the specific methods such as __add__ by calling .register method explicitly if they still can be used in the given implementation.
Use add.register and div.register to define implementations for other types if necessary e.g.:
#div.register(str)
def _(x, y):
return x % y
After that:
>>> my_average("%s", "b") # -> `("%s" + "b") % 2`
'2b'
This might be an idea:
import operator
f_add = {}
def add(a,b):
return f_add.get(type(a),operator.add)(a,b)
# example
class RGB:
def __init__(self, r,g,b):
self.r, self.g, self.b = (r,g,b)
def __str__(self):
return '<%s,%s,%s>'%(self.r,self.g,self.b)
f_add[RGB] = lambda a,b: RGB(a.r+b.r,a.g+b.g,a.b+b.b)
print(add(RGB(0.4,0.7,0.1), RGB(0.1, 0.2, 0.5)))
print(add(4,5))

Categories