Comment surcharger la méthode __init__ en fonction du type d'argument?
-
02-07-2019 - |
Question
Disons que j'ai une classe qui a un membre appelé data qui est une liste.
Je veux pouvoir initialiser la classe avec, par exemple, un nom de fichier (qui contient des données pour initialiser la liste) ou avec une liste réelle.
Quelle est votre technique pour faire cela?
Vérifiez-vous simplement le type en consultant __ classe __
?
Y a-t-il une astuce qui pourrait me manquer?
Je suis habitué au C ++ où la surcharge par type d'argument est facile.
La solution
Une méthode beaucoup plus simple pour obtenir des "constructeurs alternatifs" consiste à utiliser des méthodes de classe. Par exemple:
>>> class MyData:
... def __init__(self, data):
... "Initialize MyData from a sequence"
... self.data = data
...
... @classmethod
... def fromfilename(cls, filename):
... "Initialize MyData from a file"
... data = open(filename).readlines()
... return cls(data)
...
... @classmethod
... def fromdict(cls, datadict):
... "Initialize MyData from a dict's items"
... return cls(datadict.items())
...
>>> MyData([1, 2, 3]).data
[1, 2, 3]
>>> MyData.fromfilename("/tmp/foobar").data
['foo\n', 'bar\n', 'baz\n']
>>> MyData.fromdict({"spam": "ham"}).data
[('spam', 'ham')]
La raison pour laquelle il est plus judicieux est qu'il n'y a aucun doute sur le type attendu, et vous n'êtes pas obligé de deviner ce que l'appelant vous a demandé de faire avec le type de données qu'il vous a fourni. Le problème avec isinstance (x, baseestring)
est qu’il n’ya aucun moyen pour l’appelant de vous dire, par exemple, que même si le type n’est pas une basestring, vous devez le traiter comme une chaîne ( et pas une autre séquence.) Et l’appelant voudra peut-être utiliser le même type à des fins différentes, parfois comme un seul élément et parfois comme une séquence d’éléments. Être explicite enlève tout doute et conduit à un code plus robuste et plus clair.
Autres conseils
Excellente question. J'ai également abordé ce problème, et bien que je convienne que les "usines" (constructeurs de classe) sont une bonne méthode, je voudrais en proposer une autre, que j’ai également trouvée très utile:
Voici un exemple (c'est une méthode read
et non un constructeur, mais l'idée est la même):
def read(self, str=None, filename=None, addr=0):
""" Read binary data and return a store object. The data
store is also saved in the interal 'data' attribute.
The data can either be taken from a string (str
argument) or a file (provide a filename, which will
be read in binary mode). If both are provided, the str
will be used. If neither is provided, an ArgumentError
is raised.
"""
if str is None:
if filename is None:
raise ArgumentError('Please supply a string or a filename')
file = open(filename, 'rb')
str = file.read()
file.close()
...
... # rest of code
L'idée principale est d'utiliser ici l'excellent support de Python pour les arguments nommés afin de l'implémenter. Maintenant, si je veux lire les données d’un fichier, je dis:
obj.read(filename="blob.txt")
Et pour le lire à partir d'une chaîne, je dis:
obj.read(str="\x34\x55")
Ainsi, l'utilisateur n'a qu'une seule méthode à appeler. Le manipuler à l'intérieur, comme vous l'avez vu, n'est pas trop complexe
Correctif rapide et incorrect
class MyData:
def __init__(string=None,list=None):
if string is not None:
#do stuff
elif list is not None:
#do other stuff
else:
#make data empty
Ensuite, vous pouvez l'appeler avec
MyData(astring)
MyData(None, alist)
MyData()
Un meilleur moyen serait d’utiliser une conversion d’instance et de type. Si je vous comprends bien, vous voulez ceci:
def __init__ (self, filename):
if isinstance (filename, basestring):
# filename is a string
else:
# try to convert to a list
self.path = list (filename)
avec python3, vous pouvez utiliser Mise en place de plusieurs Envoi avec les annotations de fonction , écrit Python Cookbook:
import time
class Date(metaclass=MultipleMeta):
def __init__(self, year:int, month:int, day:int):
self.year = year
self.month = month
self.day = day
def __init__(self):
t = time.localtime()
self.__init__(t.tm_year, t.tm_mon, t.tm_mday)
et cela fonctionne comme:
>>> d = Date(2012, 12, 21)
>>> d.year
2012
>>> e = Date()
>>> e.year
2018
Vous devriez utiliser isinstance
isinstance(...)
isinstance(object, class-or-type-or-tuple) -> bool
Return whether an object is an instance of a class or of a subclass thereof.
With a type as second argument, return whether that is the object's type.
The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
isinstance(x, A) or isinstance(x, B) or ... (etc.).
Vous voulez probablement que la fonction intégrée isinstance
:
self.data = data if isinstance(data, list) else self.parse(data)
Ma solution préférée est:
class MyClass:
_data = []
__init__(self,data=None):
# do init stuff
if not data: return
self._data = list(data) # list() copies the list, instead of pointing to it.
Invoquez-le ensuite avec MyClass ()
ou MyClass ([1,2,3])
.
J'espère que ça aide. Bon codage!
OK, génial. Je viens de lancer cet exemple avec un tuple, pas un nom de fichier, mais c'est facile. Merci à tous.
class MyData:
def __init__(self, data):
self.myList = []
if isinstance(data, tuple):
for i in data:
self.myList.append(i)
else:
self.myList = data
def GetData(self):
print self.myList
a = [1,2]
b = (2,3)
c = MyData (a)
d = MyData (b)
c.GetData ()
d.GetData ()
[1, 2]
[2, 3]
Pourquoi ne vas-tu pas encore plus pythonique?
class AutoList:
def __init__(self, inp):
try: ## Assume an opened-file...
self.data = inp.read()
except AttributeError:
try: ## Assume an existent filename...
with open(inp, 'r') as fd:
self.data = fd.read()
except:
self.data = inp ## Who cares what that might be?