Duck-typable类,ABC,继承,__ new__

时间:2014-04-09 10:32:40

标签: python class python-2.7 abc

我正在编写一个测试系统,其中包括一个数据源。在运行时,它会读取一堆仪器,但是为了测试和开发后端,我希望它能够读取文件,或者返回随机数。将来,我知道需要创建新的数据源,其操作尚不清楚。我正在尝试设置系统,以便我可以转移到一个新的部门,而不必回来支持它,所以想要成为Pythonic并尽可能少地躲避。主要要求是为源提供一致的API,而ABC似乎是明显的选择。源之间没有足够的共同点来在基类中继承任何有价值的块。

我不想要一个大的源模块来选择要做什么,我想要可以从中选择的小型独立源,因此可以保留原有的源模块。问题是,我希望能够选择与参数一起使用的源,因此我可以运行相同的测试脚本并轻松切换源。我忘记了我遇到__new__的确切方式,但这并不明显,很少有其他人听说过它。有用。但是,我正在尝试做的事情是明显的或Pythonic方式吗?有没有办法让我的同事更熟悉?我应该指出,我现在的工作略高于我对元编程的舒适程度,所以任何更复杂的东西都可能直接掠过我的脑袋。

from abc import ABCMeta, abstractmethod
import random

class BaseSource:
    __metaclass__ = ABCMeta

    @abstractmethod
    def get(self):
        pass    

class ManualSrc(BaseSource):
    def get(self):
        return float(raw_input('gimme a number - '))

class RandomSrc(BaseSource):
    def get(self):
        return random.random()

class Source(BaseSource):
    """generic Source choice"""
    def __new__(BaseSource, choice):
        if choice == 0:
            return ManualSrc()
        elif choice == 1:
            return RandomSrc()
        else:
            raise ValueError('source choice parameter {} not valid'.format(choice))

if __name__ == '__main__':
    for use_src in range(4):
        print 'using source choice {}'.format(use_src)
        src = Source(use_src)
        print src.get()

1 个答案:

答案 0 :(得分:1)

这不是一个答案......几乎是代码审查,所以我可能会等待不同的意见。

我已经(亲自说话......这里没有客观确认)__new__通常用于创建class的实例,当您使用自己的__metaclass__时(es)(在SO中检查this answer,在Python的元类中检查this great thread

在您的示例中,因为如果您添加新来源(新的WhateverSrc()内容),您还需要编辑__new__课程的Source方法,看起来使用从BaseSource继承的类创建其他来源有点过分。另外,问题是: Source类真的是BaseSource吗?据我所知,不是真的... Source是一个来源工厂, 对?如果是这样的话,你可以试试this implementation,如果你愿意的话(链接就是我在第二段中提到的答案,所以我没有太多的优点,而且#34;找到&# 34;虽然工厂听起来很像Java风格。再次,这里只是个人意见。

而不是Source(BaseSource)课程的方式,我采用简单的create_source方法:

## [ . . . ]

class RandomSrc(BaseSource):
    def get(self):
        return random.random()

def create_source(choice):
    if choice == 0:
        return ManualSrc()
    elif choice == 1:
        return RandomSrc()
    else:
        raise ValueError('source choice parameter {} not valid'.format(choice))

if __name__ == '__main__':
    for use_src in range(4):
        print 'using source choice {}'.format(use_src)
        src = create_source(use_src)
        print src.get()

如果您需要新来源,请编辑create_source方法,例如:

## [ . . . ]

class RandomSrc(BaseSource):
    def get(self):
        return random.random()

class WhateverSrc(BaseSource):
    def get(self):
        return "Foo Bar??"

def create_source(choice):
    if choice == 0:
        return ManualSrc()
    elif choice == 1:
        return RandomSrc()
    elif choice == 2:
        return WhateverSrc()
    else:
        raise ValueError('source choice parameter {} not valid'.format(choice))

或者甚至更多......完全忘掉@abstractmethod,然后获得一堆或常规的具体课程。如果有人创建了一个没有实现*Src方法的新get类,那么该人将会看到一个非常具有描述性的失败......

import random

class ManualSrc(object):
    def get(self):
        return float(raw_input('gimme a number - '))

class RandomSrc(object):
    def get(self):
        return random.random()

class BreakingSrc(object):
    pass

def create_source(choice):
    if choice == 0:
        return ManualSrc()
    elif choice == 1:
        return RandomSrc()
    elif choice == 2:
        return BreakingSrc()
    else:
        raise ValueError('source choice parameter {} not valid'.format(choice))

if __name__ == '__main__':
    for use_src in range(4):
        print 'using source choice {}'.format(use_src)
        src = create_source(use_src)
        print src.get()

输出:

using source choice 0
gimme a number - 1
1.0
using source choice 1
0.702223268052
using source choice 2
Traceback (most recent call last):
  File "./stack26.py", line 28, in <module>
    print src.get()
AttributeError: 'BreakingSrc' object has no attribute 'get'

所有这一切......使用元类,您可以在定义class Whatever(参见this answer)时在某种列表或字典中注册一个类,这也可以为您提供一些想法 : - )

在您的情况下,按照通过元类注册类的想法,下面的代码段可行,但正如您所看到的,代码变得越来越混乱:

from abc import ABCMeta, abstractmethod
import random
import inspect

available_srcs = []

def register(newclass):
    if inspect.isabstract(newclass):
        print ("newclass %s is abstract, and has abstract"
                " methods: %s. Refusing to register"
                % (newclass, newclass.__abstractmethods__))
        return
    if newclass not in available_srcs:
        available_srcs.append(newclass)
        print "Registered %s as available source" % newclass

class MyMetaClass(ABCMeta):
    def __new__(cls, clsname, bases, attrs):
        newclass = super(MyMetaClass, cls).__new__(cls, clsname, bases, attrs)
        register(newclass)  # here is your register function
        return newclass

class BaseSource(object):
    __metaclass__ = MyMetaClass

    @abstractmethod
    def get(self):
        pass    

class ManualSrc(BaseSource):
    def get(self):
        return float(raw_input('gimme a number - '))

class RandomSrc(BaseSource):
    def get(self):
        return random.random()

if __name__ == '__main__':
    for use_src in range(4):
        print 'using source choice {}'.format(use_src)
        src = available_srcs[use_src]()
        print src.get()

编辑1

OP(Neil_UK)在对此答案的评论中提出这将更加混乱,将某些不是类的东西大写,或调用非大写的名称来实例化特定对象?

在开始之前,以下示例充分利用了内置的typevars函数。在继续之前,您应确保熟悉他们的工作。

对我来说(这只是我的观点,因为大写或非大写的函数名在Python中语法上都是正确的),在大写字母中使用函数会更令人困惑。请记住,您实际上并没有返回(尽管可以,因为class(es)也是type类型的实例)你是什么&#39 ;返回是实例,并且返回实例的函数(根据PEP8 naming convention的小写)没有任何问题。这就是记录模块的作用,例如:

>>> import logging
>>> log = logging.getLogger('hello')
>>> vars(log)
{'name': 'hello', 'parent': <logging.RootLogger object at 0x17ce850>, 'handlers': [], 'level': 0, 'disabled': 0, 'manager': <logging.Manager object at 0x17ce910>, 'propagate': 1, 'filters': []}
>>> type(log)
<class 'logging.Logger'>

回到你的特定场景:如果我对你的代码一无所知(如果我只是在某处导入CreateSource),我知道我必须像这样使用CreateSourcesrc = CreateSource(use_src)我自动认为srcCreateSource类的一个实例,而且我在use_src参数中传递的整数将存储在某个属性中。检查上面复制的logging示例... 'hello'字符串恰好是通过name创建的log实例的getLogger属性功能。好的...... getLogger函数没什么奇怪的。

让我们举一个极端的例子。我知道你并没有做过我将要做的事情,(我认为你的事实上是一个有效的问题),但也许这有助于证明我的意思。

请考虑以下代码:

 a = A()
 a.x = 5
 print "a.x is %s" % a.x

我刚看到你,你认为它会在那里发生什么?您认为自己正在创建A类的空实例,并将其x属性设置为5,因此您需要print输出a.x is 5,对吧?

错误。这里有什么(完全正确的Python):

class B(object):
    def __init__(self):
        self.x = 10
    @property
    def x(self):
        return "I ain't returning x but something weird, and x is %s... FYI"\
                % self._x
    @x.setter
    def x(self, x):
        self._x = int(self._x if hasattr(self, '_x') else 0 + 2 * x)

def A():
    return B()

所以a实际上是class B的一个实例,因为Python提供了#34;掩盖&#34;通过properties获得吸气剂和制定者,我创造了一个根本不直观的可怕混乱。在与Python打交道时,你会听到很多时候,你实际上可以做某事的事实并不意味着你应该这样做。我个人总是引用本叔叔的话:以强大的力量来承担很大的责任(好吧......或Voltaire,但是,我发现引用了叔叔的冷静, whaddup !!?< / em> : - D

这就是说,你可能想在https://codereview.stackexchange.com/创建一个用户我确信有很多知识渊博的人可以比我更好地回答这类问题。

哦,之前我提到class也是一个实例。等等,喔?是的。功能也是实例!!看看这个:

>>> class C(object):
...     pass
... 
>>> vars(C)
dict_proxy({'__dict__': <attribute '__dict__' of 'C' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'C' objects>, '__doc__': None})
>>> type(C)
<type 'type'>
>>> def get_me_a_c_class():
...     return C
... 
>>> my_class = get_me_a_c_class()
>>> my_instance = my_class()
>>> type(my_instance)
<class '__main__.C'>
>>> type(get_me_a_c_class)
<type 'function'>
>>> vars(get_me_a_c_class)
{}
>>> get_me_a_c_class.random_attribute = 5
>>> print "Did I just put an attribute to a FUNCTION??: %s" % get_me_a_c_class.random_attribute
Did I just put an attribute to a FUNCTION??: 5

在我处理Python的几年里,我发现它严重依赖于程序员的常识。虽然我最初犹豫不决,认为这种范式不会导致可怕的混乱,但事实证明它并没有(在大多数情况下; - )