Ответ 1
Я не думаю, что использование __new__()
для выполнения того, что вы хотите, является неправильным. Другими словами, я не согласен с принятым ответом на этот вопрос о том, что функции Factory всегда являются "лучшим способом сделать это".
Если вы действительно хотите избежать его использования, то единственными опциями являются метаклассы или отдельная фабричная функция/метод. Учитывая доступные варианты, использование __new__()
поскольку он статичен по умолчанию, является вполне разумным подходом.
Тем не менее, ниже я думаю, что это улучшенная версия вашего кода. Я добавил пару методов класса, чтобы помочь автоматически найти все подклассы. Они поддерживают самый важный способ, которым это лучше - который теперь добавляет подклассы, не требует изменения __new__()
. Это означает, что теперь его легко расширять, поскольку он эффективно поддерживает то, что вы могли бы назвать виртуальными конструкторами.
Аналогичную реализацию можно также использовать для перемещения создания экземпляров из метода __new__
в отдельный (статический) фабричный метод - так что в некотором смысле показанная методика является просто относительно простым способом кодирования расширяемой универсальной фабричной функции независимо от того, какое имя это дано.
import os
import re
class FileSystem(object):
class NoAccess(Exception): pass
class Unknown(Exception): pass
# Pattern for matching "xxx://" where x is any character except for ":".
_PATH_PREFIX_PATTERN = re.compile(r'\s*([^:]+)://')
@classmethod
def _get_all_subclasses(cls):
""" Recursive generator of all class' subclasses. """
for subclass in cls.__subclasses__():
yield subclass
for subclass in subclass._get_all_subclasses():
yield subclass
@classmethod
def _get_prefix(cls, s):
""" Extract any file system prefix at beginning of string s and
return a lowercase version of it or None when there isn't one.
"""
match = cls._PATH_PREFIX_PATTERN.match(s)
return match.group(1).lower() if match else None
def __new__(cls, path):
""" Create instance of appropriate subclass using path prefix. """
path_prefix = cls._get_prefix(path)
for subclass in cls._get_all_subclasses():
if subclass.prefix == path_prefix:
# Using "object" base class method avoids recursion here.
return object.__new__(subclass)
else: # no subclass with matching prefix found (and no default defined)
raise FileSystem.Unknown(
'path "{}" has no known file system prefix'.format(path))
def count_files(self):
raise NotImplementedError
class Nfs(FileSystem):
prefix = 'nfs'
def __init__ (self, path):
pass
def count_files(self):
pass
class LocalDrive(FileSystem):
prefix = None # Default when no file system prefix is found.
def __init__(self, path):
if not os.access(path, os.R_OK):
raise FileSystem.NoAccess('Cannot read directory')
self.path = path
def count_files(self):
return sum(os.path.isfile(os.path.join(self.path, filename))
for filename in os.listdir(self.path))
if __name__ == '__main__':
data1 = FileSystem('nfs://192.168.1.18')
data2 = FileSystem('c:/') # Change as necessary for testing.
print(type(data1)) # -> <class '__main__.Nfs'>
print(type(data2)) # -> <class '__main__.LocalDrive'>
print(data2.count_files()) # -> <some number>