2016-04-26 3 views
1

Я пишу веб-сайт генератор с различными классами, которые представляют содержание веб-страниц, таких как Page, NewsPost, Tag, Category и т.д.Контекстуально инъекционные статические свойства класса в Python

Я хотел бы быть в состоянии строить эти объекты прямо, и у меня нет проблем с этим.

Однако я также хотел бы построить эти объекты в определенном контексте - скажем, в контексте веб-сайта с определенным корневым 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 сек страницы, а также, что не желательно.

Как бы я мог достичь этого? Метаклассы? Или какой-то классной фабричной функции?

+0

Вы либо делаете его экземпляром, либо атрибутом/свойством класса, нет других вариантов, даже если вы вводите магию метакласса. Скорее всего, вам нужен атрибут экземпляра, и некоторые из вашего кода должны быть перемещены из методов класса в методы экземпляра, чтобы иметь доступ к этим переменным экземпляра. – pasztorpisti

+1

Перенос переменных (неконстантных данных) в статические/глобальные/общие/любые места - это плохая практика вообще (даже на других языках программирования). Обычно это указывает на неподходящий дизайн, который нуждается в переосмыслении. – pasztorpisti

+0

Я действительно думаю, что вы правы, но учитывая (недостаток) важность и небольшой объем проекта, я решил, что это будет стоить того, если бы это было осуществимо. – Andreas

ответ

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.cm и cm2.Page.cm имеют разные значения, потому что это два отдельных класса (или объекты класса).

Примечание: Хотя в python это можно решить, создавая объекты подкласса динамически, проблемы обычно имеют лучшие решения. Создание классов/подклассов динамически является предупреждающим знаком (HACK).

Я по-прежнему убежден, что вы не должны создавать подкласс страницы для каждого экземпляра менеджера контента. Вместо этого я просто использовал бы экземпляры глобальных классов ContentManager и Page, соединяя их со ссылками друг на друга соответствующим образом и помещая данные и код в атрибуты/методы экземпляра.

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}) 

После того, как вы получили все это установить, достаточно просто сделать именно то, что вы пытаетесь сделать, с см как переменная класса.

+0

Существуют ли какие-либо практические различия между использованием функции 'type' и определением самого класса внутри метода' __init__'? – Andreas

+0

Нет! Полностью стилистический. –

Смежные вопросы