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

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

If not, what is the best way to deal with such situations?

As an example, I have compiled a class of colors. This class should only work as a basic example to discuss this, there is a lot of unnecessary and / or redundant material.

It would be nice if I could call a constructor with different objects (a list, another colored object or three integers ...), and the constructor processes 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 across. What would be “ best practice ” as a solution to this?

The constructor aside, if I wanted to implement the _ _ add _ _ method too, how can I get this method to accept all of this: a simple integer (which is added to all the values), three integers (where the first is added to the red value, etc. e.) or another colored object (where both red values ​​are added together, etc.)?

EDIT

  • I added an alternative constructor (initializer, _ _ init _ _), which basically does everything I wanted.

  • But I stick to the first and factory methods. It seems clearer.

  • I also added _ _ add _ _, which does everything mentioned above, but I'm not sure if this is a good style. I am trying to use the iteration protocol and returning to "single value mode" instead of checking for specific types. Maybe still ugly.

  • I looked at _ _ new _ _, thanks for the links.

  • My first attempt to compromise: I filter the rgb values ​​from * args and * * kwargs (this is a class, list, etc.), then call the superclass _ _ new _ _ with right args (just r, g, b) so that pass it to init. The call "Super (cls, self) ._ _ new _ _ (....)" works, but since I generate and return the same object as the one I am calling from (optional), all the original arguments go to _ _ init _ _ (work as intended), so it is freed.

  • I could completely get rid of _ _ init _ _ and set the values ​​to _ _ new _ _, but I don’t know ... it feels like I am abusing here ;-) I should take a good look at the metaclasses and new ones, as I assume.

Source:

#!/usr/bin/env python # -*- coding: UTF-8 -*- class Color (object): # It 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 iterable. # Maybe that 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)> 
+14
python
Dec 10 '08 at 16:42
source share
7 answers

In general, use factory methods tagged as @classmethod s. They will also work correctly in subclasses. From a design point of view, they are more clear, especially when they are given a good name.

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

+9
Dec 10 '08 at 19:22
source share

You may have factory methods, that's fine. But why not just call it what it is?

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

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

 Color(*Color2.as_list()) 

Explicit is better than implicit - Python Zen

+10
Dec 10 '08 at 17:14
source share

Python does not accept multiple methods with the same name, period. One method does one thing.

I have seen different approaches recommended for how to handle these ... classmethods (as you indicated above) or factory. I use keywords the most.

 class Color (object): def __init__(self, **parms): if parms.get('list'): self.r, self.g, self.b = parms['list'] elif parms.get('color'): color = parms['color'] self.r = color.r self.g = color.g self.b = color.b else: self.r = parms['red'] self.g = parms['green'] self.b = parms['blue'] c1 = Color(red=220, green=0, blue=270) c2 = Color(list=[220, 0, 70]) c3 = Color(color=c1) 

This is consistent with the Python way of being explicit and readable, and it also makes it easy to add new arguments if necessary.

EDIT: Plus, I don't have to look at the actual constructor code to understand the arguments. An explanation is provided by the keyword.

+7
Dec 10 '08 at 18:02
source share

In __add__ problem is:

First, you cannot get “three integers,” I assume you mean 3-tuples of integers?

In this case, you will not be able to get around a few isinstance calls:

 def __add__(self, other): if isinstance(other, Color): ... elif isinstance(other, (int, long)): ... elif len(other) == 3 and all(isinstance(e, (int, long)) for e in other): ... else: raise TypeError("Can only add Color to Color, int or three-tuple") 

You can also add __radd__ implementations so you can handle

 1 + Color(1, 2, 3) 

but it's just

 def __radd__(self, other): return self.__add__(other) 

although strictly, it will never be called when type(other) is Color .

Also, don't forget __iadd__ support += .

+2
Dec 10 '08 at 19:30
source share

First, I recommend using factory methods.

If you really need one method, give it something to send parameter processing.

 def __init__(self, method, *args, **kw): getattr(self, '_init_'+method)(*args, **kw) def _init_coponents(self, r, g, b): ... def _init_fromColor(self, color): ... 

And use like:

 c1 = Color('components', 0, 0, 0,) c2 = Color('fromColor', c1) 

While this parameter adds another parameter, it is still better than type tests, and it saves explicit data. It provides excellent box exceptions for illegal calls and is easily extensible even in subclasses.

+1
Oct. 16 2018-11-11T00:
source share

Python always completely replaces methods with the same name. Unlike C #, which, if I remember correctly, will create methods with the same name parameters to enter different arguments.

If the keywords have only one option, for example 3 or 4 arguments of the same type, I would say that using the preset of the last argument or all of them will be a way.

However, if you need lists, tuples, and other types, you should probably go to a list of arbitrary arguments and check the contents of this in a function

 def function(*args): if type(args[0]) is int: dothis() #and so on 
0
Dec 10 '08 at 18:02
source share

You can check the type of argument passed inside your constructor:

 def __init__(self, r = 0, g = 0, b = 0): # if r is a list if (type(r) == type([1,2,3])): r, g, b = r[0], r[1], r[2] # if r is a color if (type(r) == type(self)): r, g, b = rr, rg, rb self.r = r self.g = g self.b = b 

Maybe this will help.

-one
Dec 10 '08 at 17:05
source share



All Articles