Как перегрузить метод __init__ в зависимости от типа аргумента? - PullRequest
284 голосов
/ 26 сентября 2008

Допустим, у меня есть класс, в котором есть член с именем data, представляющий собой список.

Я хочу иметь возможность инициализировать класс, например, именем файла (который содержит данные для инициализации списка) или с фактическим списком.

Какая у вас техника для этого?

Вы просто проверяете тип, глядя на __class__?

Есть какой-то трюк, который я мог бы пропустить?

Я привык к C ++, где перегрузка по типу аргумента проста.

Ответы [ 10 ]

391 голосов
/ 27 сентября 2008

Гораздо более аккуратный способ получить «альтернативные конструкторы» - это использовать методы классов. Например:

>>> 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) заключается в том, что вызывающий абонент не может, например, сказать вам, что, хотя тип не является базовой строкой, вы должны рассматривать его как строку (а не другую последовательность). И, возможно, вызывающую функцию хотел бы использовать один и тот же тип для разных целей, иногда как отдельный элемент, а иногда как последовательность элементов. Ясность снимает все сомнения и приводит к более надежному и ясному коду.

33 голосов
/ 17 октября 2008

Отличный вопрос. Я также решил эту проблему, и хотя я согласен с тем, что «фабрики» (конструкторы методов класса) являются хорошим методом, я хотел бы предложить другой, который я также нашел очень полезным:

Вот пример (это 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")

Таким образом, у пользователя есть только один метод для вызова. Работа с ним внутри, как вы видели, не слишком сложна

10 голосов
/ 19 апреля 2012

Быстрое и грязное исправление

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()
8 голосов
/ 26 сентября 2008

Лучшим способом было бы использовать isinstance и преобразование типов. Если я вас правильно понимаю, вы хотите это:

def __init__ (self, filename):
    if isinstance (filename, basestring):
        # filename is a string
    else:
        # try to convert to a list
        self.path = list (filename)
5 голосов
/ 20 апреля 2018

с python3 вы можете использовать Реализация множественной отправки с аннотациями функций , как написано в 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)

и работает как:

>>> d = Date(2012, 12, 21)
>>> d.year
2012
>>> e = Date()
>>> e.year
2018
4 голосов
/ 26 сентября 2008

Вы должны использовать 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.).
2 голосов
/ 26 сентября 2008

Возможно, вам нужна встроенная функция isinstance:

self.data = data if isinstance(data, list) else self.parse(data)
0 голосов
/ 24 сентября 2014

Мое предпочтительное решение:

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]).

Надеюсь, это поможет. Удачного кодирования!

0 голосов
/ 01 мая 2014

Почему бы тебе не пойти еще более питонно?

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?
0 голосов
/ 27 сентября 2008

ОК, отлично. Я просто собрал этот пример с помощью кортежа, а не имени файла, но это легко. Спасибо всем.

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]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...