1

I'm trying to write my own "fraction" class that takes in two numerical objects: the numerator and denominator. However, depending on the data type for the two arguments, I'd like the object to initialize in different ways.

For example, if I declare x = Frac(2,5) I want x to stay a Frac type. Whereas if I declare y = Frac(2.1, 5) I want y to be cast to a float with the value of 0.42, rather than Frac(21,50).

What would be the right way to go about something like this?

This feels very similar to the scenario in which a tuple with a single object simply returns the original object. This means that x = ("cat") sets x to be a str type, rather than a tuple type.

lsdr
  • 1,245
  • 1
  • 15
  • 28
Patch
  • 113
  • 5
  • 1
    Your tuple example is just an artifact of syntax. `x = ("cat",)` creates a one element tuple. You can't do what your describing using class instantiation directly, you need another function outside of the class. – jordanm Mar 11 '20 at 16:48
  • Does [this answer](https://stackoverflow.com/a/10034173/20875) to a similar question helps you? Basically it shows you how to define a `__float__` function in your `Frac` class that would allow you to coerce the type as you like. – lsdr Mar 11 '20 at 16:49
  • @jordanm That can't be the entire picture. When I execute `>>> x = ("cat")`, `>>> isinstance(x, tuple)`, and `>>> isinstance(x, str)` in succession, Python returns `False` and then `True`. – Patch Mar 11 '20 at 17:11
  • @patch `x = ("foo")` is just not how you assign a one element tuple due to syntax. In the `x = ("foo")` example, the `()` are for operation ordering. Consider `x = (1+1) * 2` – jordanm Mar 11 '20 at 17:13
  • @ jordanm I see; that actually makes a lot of sense. But then I still don't know why `x = ()` leads to `isinstance(x, tuple)` returning `True`. By the reasoning you gave above, shouldn't `x` just be a `None` object? – Patch Mar 11 '20 at 17:18

1 Answers1

4

There are two options.

1. Factory method

x = Frac.create_by_type(2, 5)
y = Frac.create_by_type(2.1, 5)

Implementation example:

class Frac:
    ...
    @classmethod
    def create_by_type(cls, a, b):
        if isinstance(a, float):
            return a / b
        return cls(a, b)

If you want to use Frac constructor directly,

2. Overriding Frac.__new__ method

class Frac:
    def __new__(cls, a, b):
        if isinstance(a, float):
            return a / b
        return super().__new__(cls)

    def __init__(self, a, b):
        self.a = a
        self.b = b


f1 = Frac(2, 5)
f2 = Frac(2.1, 5)

print(type(f1))
print(type(f2))
print(f1.a, f1.b)

output:

<class '__main__.Frac'>
<class 'float'>
2 5

But overriding __new__ method might be tricky, so I'm not sure to recommend it.

Boseong Choi
  • 2,566
  • 9
  • 22