开发者

How can I write a class that behaves exactly like another dynamically determined class? (Python)

I want to create a 'File' object that returns 'Line' objects when the ReadLine() method is invoked instead of just strings. I also want to be able to initialize the File object with either a string containing the absolute path of a text document, or with a list of a strings, and have the resulting instance behave identically in either case. The only way that I could figure out how to do this is by wrapping a File object around either a FileDoc or a FileList object, depending on the input type. Here is an abbreviated version of the solution I have so far:

class Line(object):
    def __init__(self, line, count, fpath):
        self.text = line
        self.count = count
        self.fname = fpath.split('/')[-1]

class FileBase(object):
    def __init__(self):
        pass

    def Open(self):
        self.count = 0

    def Readline(self):
        pass

    def Get_count(self):
        return self.count

    def Set_count(self, val):
        self.count = val

class FileList(FileBase):
    def __init__(self, lines):
        self.lines = lines
        self.Open()

    def ReadLine(self):
        self.count += 1
        try:
            return Line(line=self.lines[self.count - 1], count=self.count - 1, fpath='list')
        except IndexError:
            raise StopIteration

class FileDoc(FileBase):
    def __init__(self, fpath):
        self.fpath = fpath
        self.Open()

    def Open(self):
        self.count = 0
        self.file = open(self.fpath, 'r')

    def ReadLine(self):
        self.count += 1
        return Line(line=self.file.next(), count=self.count - 1, fpath=self.fpath)

class File(FileBase):
    def __init__(self, input):
        if type(input) == type(''):
            self.actual = FileDoc(input)
        elif type(input) == type([]):
            self.actual = FileList(input)
        else:
            raise NonRecognizedInputError

    def Open(self):
        self.actual.Open()

    def ReadLine(self):
        return self.actual.ReadLine()

    def Get_count(self):
       开发者_如何转开发 return self.actual.count

    def Set_count(self, val):
        self.actual.count = val

However, this seems clunky and non-Pythonic as I have to use the Get_count() and Set_count() methods to access the .count member of the File object, instead of just being able to access it directly with instance.count. Is there a more elegant solution, one which would allow me to access the .count member as a member instead of with getters and setters?

Also, for bonus points, I'm still trying to figure out the whole inheritance thing. Is there a better way to structure the relationships between the classes?


To simplify the count property, use the property decorator:

@property
def count(self):
    return self._count  # or return self.actual.count

@count.setter
def count(self, value):
    self._count = value  # or self.actual.count = value

Or if you don't want it to be a decorator:

count = property(Get_count, Set_count)

As for your inheritance scheme, I think it's okay; since you're using File to hide most of the details, it shouldn't be too hard to change it later if need be. @MannyD's comment is a good idea for restructuring; note that, for instance, file objects are iterables, just like lists.

As a side note, FileLine may be better as a collections.namedtuple (just saying):

Line = collections.namedtuple('Line', 'line count path')


Personally I think that you class hierarchy is way too complicated. I'm not sure how you use it in your code, but the word File seems to be overused. The list of strings is not file at all, hence I'd create something like LineReader to be able to read from different sources and than feed it with either a file or a list iterator.

Consider the following code:

class Line(object):
    def __init__(self, line, count, fpath):
        self.text = line
        self.count = count
        self.fname = fpath

class LineReader(object):
    def __init__(self, iterator, fname):
        self.iterator = iterator
        self.fname = fname
        self.count = 0

    def ReadLine(self):
        line = Line(self.iterator.next(), self.count, self.fname)
        self.count += 1        
        return line

class LineSource(object):
    def __init__(self, input):
        if type(input) == type(''):
            self.reader = LineReader(open(input), input.split('/')[-1])
        elif type(input) == type([]):
            self.reader = LineReader(iter(input), 'list')
        else:
            raise NonRecognizedInputError

    def ReadLine(self):
        return self.reader.ReadLine()

Looks far less complicated for me and does the job. I have no idea why you need access to counter as it's written to Line object. You can use a property as @li.davidm recommends, but only if you really have a reason to change the internal counter of file reader while reading the lines.


Unless there is a reason to enforce the inheritance hierarchy, I'd consider using a factory pattern and take advantage of the fact that Python is dynamically typed:

def FileFactory(input):
    if isinstance(input, types.StringTypes):
        return FileDoc(input)
    if isinstance(input, (types.ListType, types.TupleType)):
        return FileList(input)
    raise NonRecognizedInputError()

There is no need for the File() class as it does not offer anything over the base classes so in this case it is just extra code to maintain. Also, rather than comparing for a specific type, it's usually better to use isinstance(object, type) to work with derived classes as well.

On a side note, I'd suggest following the PEP 8 style guide as it will make your code easier to read for others.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜