问题
I'm writing some code for a program that will be able to run some software, read the inputs/outputs and do some data processing along the way. For example: (this is not the real case, but just to give an idea)
class MusicFile(object):
extension = [""]
def setup(self):
#set up the data
def runsoftware(self):
#play the song
class MP3(MusicFile):
extension = [".mp3"]
def setup(self):
#setupMP3file
def runsoftware(self):
#runMP3software
I have about 4 general classes, and then file extensions will be defined and treated differently. Each class has a defined number of methods that are required for it to work. For now, only one or two file extensions will be supported, but I want to leave a structure done where adding more extensions is easy (perhaps intuitive) for others to do.
I've been coding for not so long and I was given this task, which I want to do the best possible way.
Is the use of metaclasses appropriate for this case? In order to set up some strict classes that must contain the methods I have previously defined, that way it will be uniform across all of them. Should I stick to just simple inheritance?
I want to be able to maybe have all the subclasses of MusicFile
registered to be able to instantiate the right one (given a file path, for example).
I have read about metaclasses, and they seem like a good fit for this case, but since I've read also that they don't need to be used if you don't know them very well, I want to ask for your expert opinion.
回答1:
This answer only applies for Python 3.6+. If you have the option to upgrade to the most recent Python version, there are numerous reasons why you should and the following may be one of them.
Python 3.6 introduced the __init_subclass__
hook that is exectued after a class is subclassed.
class MusicFile:
_register = {}
def __init_subclass__(cls, **kwargs):
if not hasattr(cls, 'extension'):
raise ValueError(cls.__name__ + ' has no extension')
MusicFile._register.update({e: cls for e in cls.extension})
@classmethod
def get_class_by_extension(cls, ext):
return cls._register.get(ext)
Example
class MP3(MusicFile):
extension = ['mp3']
class MIDI(MusicFile):
extension = ['midi']
MusicFile.get_class_by_extension('mp3') # <class '__main__.MP3'>
MusicFile.get_class_by_extension('midi') # <class '__main__.MIDI'>
Note that this is very similar to the factory approach suggested by bipll, but in a more straightforward and maintainable way.
回答2:
I want to be able to maybe have all the subclasses of MusicFile registered to be able to instantiate the right one (given a file path, for example).
So you probably want to implement a Factory?
This factory can probably be auto-built with metaclasses. One serious issue I see is using class's own extension
member to register it, so to me, it is easier done with metaclasses' eternal competition, decorators:
class MusicFile(object):
known_types = {}
@staticmethod
def processor(ext):
return MusicFile.known_types[ext]()
def file_types(*exts):
def registered_class(cls):
for ext in exts: MusicFile.known_types[ext] = cls
return cls
return registered_class
@file_types('mp3')
class Mp3(MusicFile):
def greet(self):
print 'Hi .mp3!'
@file_types('mid', 'midi')
class Midi(MusicFile):
def greet(self):
print 'Hi, Music Instruments Digital Interface!'
pcsor = MusicFile.processor('mp3')
pcsor.greet() # Hi .mp3!
pcsor = MusicFile.processor('mid')
pcsor.greet() # Hi, Music Instruments Digital Interface!
pcsor = MusicFile.processor('midi')
pcsor.greet() # Hi, Music Instruments Digital Interface!
pcsor = MusicFile.processor('s3m') # KeyError: 's3m'
pcsor.greet()
来源:https://stackoverflow.com/questions/51923555/metaclass-vs-inheritance-for-predefined-class-creation