Как перегрузить метод __init__ на основе типа аргумента?
-
02-07-2019 - |
Вопрос
Допустим, у меня есть класс, в котором есть элемент под названием data, представляющий собой список.
Я хочу иметь возможность инициализировать класс, например, именем файла (которое содержит данные для инициализации списка) или фактическим списком.
Какова ваша техника для этого?
Вы просто проверяете тип, взглянув на __class__
?
Есть ли какой-то трюк, который я, возможно, упускаю?
Я привык к C ++, где перегрузка по типу аргумента выполняется легко.
Решение
Гораздо более аккуратный способ получить "альтернативные конструкторы" - это использовать методы классов.Например:
>>> 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')]
Причина, по которой это более аккуратно, заключается в том, что нет сомнений в том, какой тип ожидается, и вы не вынуждены гадать, что вызывающий абонент намеревался, чтобы вы сделали с типом данных, который он вам предоставил.Проблема с isinstance(x, basestring)
заключается в том, что вызывающий объект не может сообщить вам, например, что даже если тип не является базовой строкой, вы должны рассматривать его как строку (а не другую последовательность).) И, возможно, вызывающий хотел бы использовать один и тот же тип для разных целей, иногда как отдельный элемент, а иногда как последовательность элементов.Явность устраняет все сомнения и приводит к более надежному и понятному коду.
Другие советы
Отличный вопрос.Я также рассматривал эту проблему, и хотя я согласен с тем, что "фабрики" (конструкторы методов классов) - хороший метод, я хотел бы предложить другой, который я также нашел очень полезным:
Вот пример (это read
метод, а не конструктор, но идея та же):
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
Ключевая идея здесь заключается в использовании превосходной поддержки именованных аргументов Python для реализации этого.Теперь, если я хочу прочитать данные из файла, я говорю:
obj.read(filename="blob.txt")
И чтобы прочитать это из строки, я говорю:
obj.read(str="\x34\x55")
Таким образом, у пользователя есть только один метод для вызова.Обращаться с ним внутри, как вы видели, не слишком сложно
Быстрое и грязное решение
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
Тогда вы можете вызвать это с помощью
MyData(astring)
MyData(None, alist)
MyData()
Лучшим способом было бы использовать isinstance и преобразование типов.Если я правильно тебя понимаю, ты хочешь этого:
def __init__ (self, filename):
if isinstance (filename, basestring):
# filename is a string
else:
# try to convert to a list
self.path = list (filename)
с помощью python3 вы можете использовать Реализация множественной отправки с аннотациями функций как написано в Поваренной книге Python:
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)
и это работает следующим образом:
>>> d = Date(2012, 12, 21)
>>> d.year
2012
>>> e = Date()
>>> e.year
2018
Вам следует использовать 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.).
Вы, вероятно, хотите, чтобы isinstance
встроенная функция:
self.data = data if isinstance(data, list) else self.parse(data)
Мое предпочтительное решение - это:
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.
Затем вызовите его с помощью либо MyClass()
или MyClass([1,2,3])
.
Надеюсь, это поможет.Счастливого Кодирования!
Ладно, отлично.Я просто объединил этот пример с кортежем, а не с именем файла, но это просто.Спасибо всем.
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 = Мои данные (a)
d = Мои данные (b)
c.getData()
d.getData()
[1, 2]
[2, 3]
Почему бы тебе не стать еще более питоническим?
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?