3

In python it's not possible to define the init function more than once, which, knowing how the language works, it's rather fair. When an object is created, the init is called, so, having two of them would create uncertainty. However in some designs such property is desirable. For example:

class Triangle(object):
  def __init__(self,vertices):
    self.v1 = vertices[0]
    self.v2 = vertices[1]
    self.v3 = vertices[2]

 def area(self):
   # calculate the are using vertices
   ...
   return result

class Triangle(object):
  def __init__(self,sides):
    self.v1 = sides[0]
    self.v2 = sides[1]
    self.v3 = sides[2]

 def area(self):
   # calculate the are using sides
   ...
   return result

in such cases we have the same number of attribute to initialize, and also they are correlated so that from one, you can obtain the other.True, in this specific example one could work with the fact that vertices are tuples whilst sides may be floats (or strings or something else) but of course it's not always the case.

One possible solution would be to delegate the initialization process to other functions like:

class Triangle(object):
  def __init__(self):
    self.v1 = None
    self.v2 = None
    self.v3 = None

  def byVertices(self,vertices):
    self.v1 = vertices[0]
    self.v2 = vertices[1]
    self.v3 = vertices[2]

 def sidesToVertices(self,sides):
    # converts sides to vertices
    ...
    return vertices

 def bySides(self,sides):
    vertices = sidesToVertices(sides)
    self.v1 = vertices[0]
    self.v2 = vertices[1]
    self.v3 = vertices[2]

 def area(self):
   # calculate the are using vertices
   ...
   return result

but it doesn't look very clean, and all the functionalities like "area" would have to check that the attributes are correctly instanciated (or adopt a try/catch) which is a lot of code, plus it undermines the readability of the project. Overall it looks like a cheap trick for the purpose.

Another option would be to tell the instance, what type of attributes you are going to initialize:

class Triangle(object):
  def __init__(self, data, type = "vertices"):
    if type == "sides":
      data = sideToVertices(self,sides)
    else if type == "vertices":
      pass
    else:
      raise(Exception)

   self.v1 = data[0]
   self.v2 = data[1]
   self.v3 = data[3]

 def sidesToVertices(self,sides):
    # converts sides to vertices
    ...
    return vertices



 def area(self):
   # calculate the are using vertices

This other approach seems preferrable, however i'm not sure how much "pythonic" is to introduce logic in the init. What are your tought on the matter ? Is there a better way to orchestrate the situation ?

2
  • 2
    A standard way to implement alternative constructors is to use classmethod Commented Jun 23, 2017 at 16:37
  • what about creating a second class called TriangleVertices that inherits the TriangleSides class and has a different init ? Commented Jun 23, 2017 at 16:38

3 Answers 3

5

Alternate constructors are the most common use case for class methods. Your "real" __init__ is often then the lowest common denominator for the various class methods.

class Triangle(object):
    def __init__(self, v1, v2, v3):
        self.v1 = v1
        self.v2 = v2
        self.v3 = v3

    # This is just here to demonstrate, since it is just
    # wrapping the built-in __new__ for no good reason.
    @classmethod
    def by_vertices(cls, vertices):
        # Make sure there are exactly three vertices, though :)
        return cls(*vertices)

    @staticmethod
    def sidesToVertices(sides):
        # converts sides to vertices
        return v1, v2, v3 

    @classmethod
    def by_sides(cls, sides):
        return cls(*sides_to_vertices(sides)) 

    def area(self):
        # calculate the are using vertices
        ...
        return result

The, to get an instance of Triangle, you can write any of the following:

t = Triangle(p1, p2, p3)
t = Triangle.by_vertices(p1, p2, p3)  # Same as the "direct" method
t = Triangle.by_sides(s1, s2, s3)

The only difference here is that Triangle(p1, p2, p3) hides the implicit call to Triangle.__new__, which is a class method just like by_vertices and by_sides. (In fact, you could simply define by_vertices = __new__.) In all three cases, Python implicitly calls Triangle.__init__ on whatever cls returns.

(Note that by_vertices generates a specific triangle, while by_sides could generate any number of "equivalent" triangles that differ only by position and rotation relative to the origin. Conversely, by_sides could be thought to generate the "real" triangle, with by_vertices specifying a triangle in a particular position. None of this is particularly relevant to the question at hand.)


Tangential background.

t = Triangle(v1, v2, v3) is the "normal" method, but what does this mean? Triangle is a class, not a function. To answer this, you need to know about metaclasses and the __call__ method.

__call__ is used to make an instance callable. my_obj(args) becomes syntactic sugar for my_obj.__call__(args), which itself, like all instance methods, is syntactic sugar (to a first approximation) for type(my_obj).__call__(my_obj, args).

You've heard that everything in Python is an object. This is true for classes as well; every class object is an instance of its metaclass, the type of types. The default is type, so Triangle(v1, v2, v3) would desugar to Triangle.__call__(v1, v2, v3) or type.__call__(Triangle, v1, v2, v3).

With that out of the way, what does type.__call__ do? Not much. It just calls the appropriate class method __new__ for its first argument. That is, type.__call__(Triangle, v1, v2, v3) == Triangle.__new__(v1, v2, v3). Python then implicitly calls __init__ on the return value of Triangle.__new__ if it is, in fact, an instance of Triangle. Thus, you can think of type.__call__ as being defined like

def __call__(cls, *args, **kwargs):
    obj = cls.__new__(*args, **kwargs)
    if isinstance(obj, cls):
       cls.__init__(obj, *args, **kwargs)
    return obj
Sign up to request clarification or add additional context in comments.

4 Comments

Why cls.__new__(...) instead of cls(...)?
but in this case, when creating an instance... you are forced to know the vertices beforehand, even if only have the sides... correct ? even using optional parameters, you are forced to initialize separetedly like : t1 = Triangle() -> t1.bySides(sides) ... right ? ( must be honest, i don't like that )
No, I forgot to add how they get used. To create a triangle given sides, you would write t = Triangle.bySides(s1, s2, s3) rather than using t = Triangle(...).
@Robᵩ Because ... reasons? I think I had started writing super.__new__, remembered there were no parent classes involved here, then never got rid of the explicit call to __new__ :)
1

You can use optional parameters and call the constructor with the parameters that are more convenient.

class Triangle(object):
    def __init__(self, vertices=None, sides=None):
        if vertices is not None:
            # Initialize with vertices
        elif sides is not None:
            # Initialize with sides
        else:
            # Error: neither parameter was passed!
my_sides = ...
my_vertices = ...

triangle_sides = Triangle(sides=my_sides)
triangle_vertices = Triangle(vertices=my_vertices)

1 Comment

true, but i would have to introduce some logic to make sure that not both parameters are passed. That's an interesting angle nonetheless
0
class Employee:
    def __init__(self, fname, lname, salary):
        self.fname = fname
        self.lname = lname
        self.salary = salary

    def __init__(self, *employee):
        self.fname = employee[0]
        self.lname = employee[1]


abdullah = Employee("Abdullah", "Aqib", 10000)
mustafa = Employee("Mustafa", "Ahmad")

print(abdullah.__dict__)
print(mustafa.__dict__)

1 Comment

Consider adding explanation to your code. This can result in more upvotes for your answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.