不正确地使用__new__来生成类?

Seg*_*ult 9 python factory factory-pattern

我正在创建一些类来处理各种类型的文件共享(nfs,afp,s3,本地磁盘)等文件名.我得到一个用户输入一个标识数据源(即"nfs://192.168.1.3""s3://mybucket/data")等的字符串.

我从具有公共代码的基类继承特定文件系统.我困惑的地方是对象创建.我所拥有的是以下内容:

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()
Run Code Online (Sandbox Code Playgroud)

我认为这将是一个很好的用途,__new__但我读到的大多数帖子的使用都不鼓励它.有没有更可接受的方法来解决这个问题?

mar*_*eau 17

认为用__new__()你想要的东西是不合适的.换句话说,我不同意这个问题的公认答案,即工厂功能始终是"最好的方法".

如果您真的想避免使用它,那么唯一的选项是元类或单独的工厂函数/方法.鉴于可用的选择,使__new__()方法成为一个 - 因为它默认是静态的 - 是一种非常明智的方法.

也就是说,下面是我认为代码的改进版本.我添加了几个类方法来帮助自动查找所有子类.这些支持最好的方式 - 现在添加子类不需要修改__new__()方法.这意味着它现在可以轻松扩展,因为它有效地支持了你可以称之为虚拟构造函数的东西.

类似的实现也可用于将实例的创建从__new__()方法移动到单独的(静态)工厂方法中 - 因此在某种意义上,所示的技术只是编码可扩展的通用工厂函数的一种相对简单的方式,无论它的名称是什么给出.

import os
import re

class FileSystem(object):
    class NoAccess(Exception): pass
    class Unknown(Exception): pass

    # Regex 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 (& 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>
Run Code Online (Sandbox Code Playgroud)


Kol*_*mar 7

在我看来,__new__以这种方式使用确实会让其他可能阅读您代码的人感到困惑。此外,它还需要一些黑客代码来区分猜测文件系统和用户输入以及创建Nfs及其LocalDrive相应的类。

为什么不为这种行为创建一个单独的函数呢?它甚至可以是类的静态方法FileSystem

class FileSystem(object):
    # other code ...

    @staticmethod
    def from_path(path):
        if path.upper().startswith('NFS://'): 
            return Nfs(path)
        else: 
            return LocalDrive(path)
Run Code Online (Sandbox Code Playgroud)

你这样称呼它:

data1 = FileSystem.from_path('nfs://192.168.1.18')
data2 = FileSystem.from_path('/var/log')
Run Code Online (Sandbox Code Playgroud)