Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle constructors or methods with a different set (or type) of arguments in Python?

Tags:

python

Is there a way in Python, to have more than one constructor or more than one method with the same name, who differ in the number of arguments they accept or the type(s) of one or more argument(s)?

If not, what would be the best way to handle such situations?

For an example I made up a color class. This class should only work as a basic example to discuss this, there is lot's of unnecessary and/or redundant stuff in there.

It would be nice, if I could call the constructor with different objects (a list, an other color object or three integers...) and the constructor handles them accordingly. In this basic example it works in some cases with * args and * * kwargs, but using class methods is the only general way I came up with. What would be a "best practice" like solution for this?

The constructor aside, if I would like to implement an _ _ add _ _ method too, how can I get this method to accept all of this: A plain integer (which is added to all values), three integers (where the first is added to the red value and so forth) or another color object (where both red values are added together, etc.)?

EDIT

  • I added an alternative constructor (initializer, _ _ init _ _) that basicly does all the stuff I wanted.

  • But I stick with the first one and the factory methods. Seems clearer.

  • I also added an _ _ add _ _, which does all the things mentioned above but I'm not sure if it's good style. I try to use the iteration protocol and fall back to "single value mode" instead of checking for specific types. Maybe still ugly tho.

  • I have taken a look at _ _ new _ _, thanks for the links.

  • My first quick try with it fails: I filter the rgb values from the * args and * * kwargs (is it a class, a list, etc.) then call the superclass's _ _ new _ _ with the right args (just r,g,b) to pass it along to init. The call to the 'Super(cls, self)._ _ new _ _ (....)' works, but since I generate and return the same object as the one I call from (as intended), all the original args get passed to _ _ init _ _ (working as intended), so it bails.

  • I could get rid of the _ _ init _ _ completly and set the values in the _ _ new _ _ but I don't know... feels like I'm abusing stuff here ;-) I should take a good look at metaclasses and new first I guess.

Source:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class Color (object):

  # It's strict on what to accept, but I kinda like it that way.
  def __init__(self, r=0, g=0, b=0):
    self.r = r
    self.g = g
    self.b = b

  # Maybe this would be a better __init__?
  # The first may be more clear but this could handle way more cases...
  # I like the first more though. What do you think?
  #
  #def __init__(self, obj):
  #  self.r, self.g, self.b = list(obj)[:3]

  # This methods allows to use lists longer than 3 items (eg. rgba), where
  # 'Color(*alist)' would bail
  @classmethod
  def from_List(cls, alist):
    r, g, b = alist[:3]
    return cls(r, g, b)

  # So we could use dicts with more keys than rgb keys, where
  # 'Color(**adict)' would bail
  @classmethod
  def from_Dict(cls, adict):
    return cls(adict['r'], adict['g'], adict['b'])

  # This should theoreticaly work with every object that's iterable.
  # Maybe that's more intuitive duck typing than to rely on an object
  # to have an as_List() methode or similar.
  @classmethod
  def from_Object(cls, obj):
    return cls.from_List(list(obj))

  def __str__(self):
    return "<Color(%s, %s, %s)>" % (self.r, self.g, self.b)

  def _set_rgb(self, r, g, b):
    self.r = r
    self.g = g
    self.b = b
  def _get_rgb(self):
    return  (self.r, self.g, self.b)
  rgb = property(_get_rgb, _set_rgb)

  def as_List(self):
    return [self.r, self.g, self.b]

  def __iter__(self):
    return (c for c in (self.r, self.g, self.b))

  # We could add a single value (to all colorvalues) or a list of three
  # (or more) values (from any object supporting the iterator protocoll)
  # one for each colorvalue
  def __add__(self, obj):
    r, g, b = self.r, self.g, self.b
    try:
      ra, ga, ba = list(obj)[:3]
    except TypeError:
      ra = ga = ba = obj
    r += ra
    g += ga
    b += ba
    return Color(*Color.check_rgb(r, g, b))

  @staticmethod
  def check_rgb(*vals):
    ret = []
    for c in vals:
      c = int(c)
      c = min(c, 255)
      c = max(c, 0)
      ret.append(c)
    return ret

class ColorAlpha(Color):

  def __init__(self, r=0, g=0, b=0, alpha=255):
    Color.__init__(self, r, g, b)
    self.alpha = alpha

  def __str__(self):
    return "<Color(%s, %s, %s, %s)>" % (self.r, self.g, self.b, self.alpha)

  # ...

if __name__ == '__main__':
  l = (220, 0, 70)
  la = (57, 58, 61, 255)
  d = {'r': 220, 'g': 0, 'b':70}
  da = {'r': 57, 'g': 58, 'b':61, 'a':255}
  c = Color(); print c # <Color(0, 0, 0)>
  ca = ColorAlpha(*la); print ca # <Color(57, 58, 61, 255)>
  print '---'
  c = Color(220, 0, 70); print c # <Color(220, 0, 70)>
  c = Color(*l); print c # <Color(220, 0, 70)>
  #c = Color(*la); print c # -> Fail
  c = Color(**d); print c # <Color(220, 0, 70)>
  #c = Color(**da); print c # -> Fail
  print '---'
  c = Color.from_Object(c); print c # <Color(220, 0, 70)>
  c = Color.from_Object(ca); print c # <Color(57, 58, 61, 255)>
  c = Color.from_List(l); print c # <Color(220, 0, 70)>
  c = Color.from_List(la); print c # <Color(57, 58, 61, 255)>
  c = Color.from_Dict(d); print c # <Color(220, 0, 70)>
  c = Color.from_Dict(da); print c # <Color(57, 58, 61, 255)>
  print '---'
  print 'Check =', Color.check_rgb('1', 0x29a, -23, 40)
  # Check = [1, 255, 0, 40]
  print '%s + %s = %s' % (c, 10, c + 10)
  # <Color(57, 58, 61)> + 10 = <Color(67, 68, 71)>
  print '%s + %s = %s' % (c, ca, c + ca)
  # <Color(57, 58, 61)> + <Color(57, 58, 61, 255)> = <Color(114, 116, 122)>
like image 669
Brutus Avatar asked Dec 10 '08 16:12

Brutus


People also ask

What are different types of constructors in Python?

In Python, there are two different types of constructors. Non-parameterized Constructor: The constructors in Python which have no parametres present is known as a non parameterized constructor. Parameterized Constructor: A constructor that has a parametre pre defined is known as a parameterized constructor.

What is a constructor argument in Python?

A constructor is a special method in a class used to create and initialize an object of a class. There are different types of constructors. Constructor is invoked automatically when an object of a class is created.

What is constructor and method in Python?

Python Constructor. A constructor is a special type of method (function) which is used to initialize the instance members of the class. In C++ or Java, the constructor has the same name as its class, but it treats constructor differently in Python. It is used to create an object. Constructors can be of two types.

How do you make a different constructor in Python?

Providing Multiple Constructors With @classmethod in Python. A powerful technique for providing multiple constructors in Python is to use @classmethod . This decorator allows you to turn a regular method into a class method. Unlike regular methods, class methods don't take the current instance, self , as an argument.


2 Answers

You can have the factory methods, it is fine. But why not just call it as it is?

Color(r, g, b)
Color(*[r, g, b])
Color(**{'r': r, 'g': g, 'b': b})

This is the python way. As for the from object constructor, I would prefer something like:

Color(*Color2.as_list())

Explicit is better than implicit - Python Zen

like image 57
muhuk Avatar answered Sep 28 '22 07:09

muhuk


In general, use factory methods, marked up as @classmethods. They'll also work correctly on subclasses. From a design perspective, they are more explicit, especially when given a good name.

In this case, mixing everything together is probably more convenient, but it also makes the contract for your constructor more difficult.

like image 33
Torsten Marek Avatar answered Sep 28 '22 06:09

Torsten Marek