在Python

时间:2016-04-26 18:59:03

标签: python metaprogramming

我正在编写一个网站生成器,其中包含代表网页内容的各种类,例如PageNewsPostTagCategory等。< / p>

我希望能够清楚地构建这些对象,而且我对此没有任何问题。

但是,我还想在特定的上下文中构建这些对象 - 比如具有特定根URL的网站的上下文。我们假设我将此上下文放入类ContentManager的实例中。这是我最终希望最终得到的代码:

page = Page(title='Test Page', content='hello world!')
assert page.cm == None

cm = ContentManager(root_url='//localhost')
page = cm.Page(title='Test Page', content='hello world!') 
assert page.cm == cm

如果page.cm__init__中设置的每个实例属性,我可以轻松管理这个,但我需要调用cm.Page上需要访问cm的类方法对象,所以它必须是一个静态属性。

如果我只是将它设置为Page类的静态属性,它最终也会影响其他 ContentManager页面,这是不可取的。< / p>

我将如何实现这一目标?元类?或某种类工厂功能?

2 个答案:

答案 0 :(得分:1)

将所有其他内容放在一边,您只需要动态构建一个类来绑定ContentManager的每个实例;我们可以使用内置的type函数来做到这一点,它可以用一个参数给我们一个对象的类型,或者用三个参数(类名,基类和类字典)构造一个新课。

以下是您的情况可能会如何展示的样本:

class Page(object):
    # This is just a default value if we construct a Page
    # outside the context of a ContentManager
    cm = None
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    @classmethod
    def do_class_thing(cls):
        return cls.cm

class ContentManager(object):

    def __init__(self, root_url):
        self.url = root_url

        """
        This is where the magic happens. We're telling type() to
        construct a class, with the class name ContentManagerPage,
        have it inherit from the above explicitly-declared Page
        class, and then overriding its __dict__ such that the class
        cm variable is set to be the ContentManager we're
        constructing it from.
        """

        self.Page = type(str('ContentManagerPage'), (Page,), {'cm': self})

一旦你完成了所有这些设置,它就足够简单,可以做你正在尝试做的事情,用cm作为类变量。

答案 1 :(得分:1)

一个解决方案可能是为每个Page实例创建ContentManage的子类:

class Page:
    cm = None

    def __init__(self, title, content):
        self.title = title
        self.content = content


class ContentManager:
    def __init__(self, root_url):
        class PerContentManagerPage(Page):
            cm = self

        self.Page = PerContentManagerPage


page0 = Page(title='Test Page', content='hello world!')

cm = ContentManager(root_url='//localhost')
page = cm.Page(title='Test Page', content='hello world!')

cm2 = ContentManager(root_url='//localhost')
page2 = cm2.Page(title='Test Page 2', content='hello world!')

assert page0.cm is None
assert page.cm == cm
assert page2.cm == cm2

在python中,类也是一个对象(它的元类的一个实例)。每次实例化Page时,此解决方案都会创建ContentManager的新子类。这意味着cm.Page类与cm2.Page类不同,但它们都是Page的子类。这就是为什么cm.Page.cmcm2.Page.cm可能有不同的值,因为它们是两个独立的类(或类对象)。

注意:虽然在python中可以通过动态创建子类对象来解决,但问题通常有更好的解决方案。动态创建类/子类是一个警告标志( HACK )。

我仍然相信你不应该为每个内容管理器实例创建一个页面子类。相反,我只是使用全局ContentManagerPage类的实例,通过以适当的方式将它们与彼此的引用相连接,并将数据和代码放入实例属性/方法中。