Como sobrecarregar __init__ método baseado no tipo de argumento?
-
02-07-2019 - |
Pergunta
Vamos dizer que eu tenho uma classe que tem um membro da chamada de dados que é uma lista.
Eu quero ser capaz de inicializar a classe com, por exemplo, um nome de arquivo (que contém dados para inicializar a lista) ou com uma lista real.
Qual é a sua técnica para fazer isso?
Você apenas verificar o tipo de olhar para __class__
?
Existe algum truque que eu poderia estar faltando?
Estou acostumado a C ++, onde a sobrecarga por tipo de argumento é fácil.
Solução
Uma maneira muito mais limpa para obter construtores alternativas 'é usar classmethods. Por exemplo:
>>> 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')]
A razão do mais puro é que não há nenhuma dúvida sobre o que é esperado tipo, e você não é obrigado a adivinhar o que o chamador destinado a você a ver com o tipo de dados que você deu. O problema com isinstance(x, basestring)
é que não há nenhuma maneira para o chamador para dizer-lhe, por exemplo, que mesmo que o tipo não é um basestring, você deve tratá-lo como uma string (e não outra sequência.) E talvez o chamador gostaria usar o mesmo tipo para diferentes fins, às vezes como um único item, e às vezes como uma seqüência de itens. Sendo explícita tem qualquer dúvida de distância e leva a código mais robusto e mais clara.
Outras dicas
Excelente pergunta. Eu já abordou este problema também, e enquanto eu concordo que as "fábricas" (construtores de classe-Método) são um bom método, gostaria de sugerir uma outra, que também tenho encontrado muito útil:
Aqui está um exemplo (este é um método read
e não um construtor, mas a idéia é a mesma):
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
A ideia-chave é aqui é usar um excelente suporte do Python para argumentos nomeados para implementar isso. Agora, se eu quero ler os dados de um arquivo, eu digo:?
obj.read(filename="blob.txt")
E a lê-lo a partir de uma corda, eu digo:
obj.read(str="\x34\x55")
Desta forma, o usuário tem apenas um único método de chamada. Manuseá-lo dentro, como você viu, não é excessivamente complexa
Rápido e correção sujo
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
Depois, você pode chamá-lo com
MyData(astring)
MyData(None, alist)
MyData()
A melhor maneira seria usar isinstance e conversão de tipo. Se eu estou entendendo-lo bem, você quer este:
def __init__ (self, filename):
if isinstance (filename, basestring):
# filename is a string
else:
# try to convert to a list
self.path = list (filename)
com python3, você pode usar implementação de várias expedição com anotações de função como Python Cookbook escreveu:
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)
e funciona como:
>>> d = Date(2012, 12, 21)
>>> d.year
2012
>>> e = Date()
>>> e.year
2018
Você deve usar 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.).
Você provavelmente quer o isinstance
builtin função:
self.data = data if isinstance(data, list) else self.parse(data)
A minha solução preferida é:
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.
Em seguida, invocá-lo com qualquer MyClass()
ou MyClass([1,2,3])
.
Espero que ajude. Feliz codificação!
OK, ótimo. Eu só jogou juntos este exemplo com uma tupla, não um nome, mas isso é fácil. Obrigado a todos.
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 = MeusDados (um)
d = MeusDados (b)
c.GetData ()
d.GetData ()
[1, 2]
[2, 3]
Por que você não ir ainda mais Python?
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?