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.
精彩评论