¿Uso inapropiado de __new__ para generar clases?

Estoy creando algunas clases para tratar los nombres de archivos en varios tipos de recursos compartidos de archivos (nfs, afp, s3, disco local), etc. Recibo como usuario una cadena que identifica la fuente de datos (es decir, "nfs://192.168.1.3" o "s3://mybucket/data" ) etc.

Estoy subclasificando los sistemas de archivos específicos de una clase base que tiene código común. Donde estoy confundido es en la creación del objeto. Lo que tengo es lo siguiente:

 import os class FileSystem(object): class NoAccess(Exception): pass def __new__(cls,path): if cls is FileSystem: if path.upper().startswith('NFS://'): return super(FileSystem,cls).__new__(Nfs) else: return super(FileSystem,cls).__new__(LocalDrive) else: return super(FileSystem,cls).__new__(cls,path) def count_files(self): raise NotImplementedError class Nfs(FileSystem): def __init__ (self,path): pass def count_files(self): pass class LocalDrive(FileSystem): 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 len([x for x in os.listdir(self.path) if os.path.isfile(os.path.join(self.path, x))]) data1 = FileSystem('nfs://192.168.1.18') data2 = FileSystem('/var/log') print type(data1) print type(data2) print data2.count_files() 

Pensé que este sería un buen uso de __new__ pero la mayoría de las publicaciones que leí sobre su uso lo desalientan. ¿Hay una forma más aceptada de abordar este problema?

No creo que usar __new__() para hacer lo que quieres es incorrecto. En otras palabras, no estoy de acuerdo con la respuesta aceptada a esta pregunta de que las funciones de Factory siempre son la “mejor manera de hacerlo”.

Si realmente desea evitar su uso, las únicas opciones son metaclases o una función / método de fábrica independiente. Dadas las opciones disponibles, hacer que el __new__() uno, ya que es estático por defecto, es un enfoque perfectamente sensato.

Dicho esto, a continuación es lo que creo que es una versión mejorada de su código. He agregado un par de métodos de clase para ayudar a encontrar automáticamente todas las subclases. Estos admiten la forma más importante en la que es mejor, que ahora está agregando subclases no requiere modificar el __new__() . Esto significa que ahora es fácilmente extensible, ya que admite efectivamente lo que podríamos llamar constructores virtuales .

También se podría utilizar una implementación similar para mover la creación de instancias del método __new__ a un método de fábrica separado (estático), por lo que, en cierto sentido, la técnica que se muestra es solo una forma relativamente simple de codificar una función de fábrica genérica extensible, independientemente del nombre. esta dado

 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)) # ->  print(type(data2)) # ->  print(data2.count_files()) # ->  

En mi opinión, usar __new__ de tal manera es realmente confuso para otras personas que pueden leer su código. También requiere un código un tanto intrincado para distinguir el sistema de archivos de adivinación de la entrada del usuario y la creación de Nfs y LocalDrive con sus clases correspondientes.

¿Por qué no hacer una función separada con este comportamiento? Incluso puede ser un método estático de la clase FileSystem :

 class FileSystem(object): # other code ... @staticmethod def from_path(path): if path.upper().startswith('NFS://'): return Nfs(path) else: return LocalDrive(path) 

Y lo llamas así:

 data1 = FileSystem.from_path('nfs://192.168.1.18') data2 = FileSystem.from_path('/var/log')