Реализация прототипов ИЛИ создание экземпляров объектов класса - PullRequest
3 голосов
/ 15 июня 2010

updated 2010-06-15T09:45:00Z:

  • добавлен пример подхода «классы как экземпляры» и объяснение подхода «экземпляры как классы»;включил и сослался на ответ Алекса Мартелли;

Мне интересно, как реализовать наследование прототипов в Python.Казалось бы, есть два разных подхода к этой проблеме: классы как экземпляры и экземпляры как классы.

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

классы как экземпляры

Идея здесь состоит в том, чтобы использовать метакласс, чтобы экземпляры на самом деле были классами, а не объектами.Этот подход выглядит примерно так:

class ClassAsInstance(type):
    """ ClassAsInstance(type)\n
        >>> c = ClassAsInstance()
        >>> c.prop = 6

        It's sort of annoying to have to make everything a class method.
        >>> c.jef = classmethod(lambda self: self.prop)
        >>> c.jef()
        6
        >>> cc = c()
        >>> cc.jef()
        6

        But it works.
        >>> c.prop = 10
        >>> cc.jef()
        10
        >>> c.jef = classmethod(lambda self: self.prop * 2)
        >>> cc.jef()
        20
    """
    def __new__(self):
        return type(self.__name__ + " descendant", (self, ), {})

Я действительно не тестировал никаких сложных вещей с этим подходом, поэтому он может иметь ограничения.

экземпляры как классы

При таком подходе идея заключается в использовании конструктора type для создания классов из объектов.Это иллюстрируется в ответе Алекса Мартелли , хотя пример, который он использует для этого подхода, реализует копирование прототипов, а не позволяет потомкам наследовать более поздние изменения в своих прототипах.

Мой подход заключался в том, чтобы что-то делатькак это:

def createDescendant(obj):
    return type(obj.__class__.__name__ + " descendant", (obj.__class__, ), obj.__dict__)()

, который будет работать в виде javascript-y: изменения в данном объекте не будут влиять на его потомков, но изменения в родительском объекте __class__ (как в javascriptprototype) будет.Я понял, что это потому, что конструктор type копирует obj.__dict__, а не ссылается на него в какой-то схеме mro-ish.

Я попытался реализовать улучшенную версию, которая позволила быистинное наследование прототипа, при котором объекты наследуют обновления родительских объектов.Идея заключалась в том, чтобы назначить свойство __dict__ объекта-прототипа тому же свойству вновь созданного класса, которое становится классом объекта-потомка.

Однако это не сработало, так какЯ обнаружил, что __dict__ из type нельзя назначить;это ограничение также относится к классам, производным от type.Мне все еще любопытно, можно ли обойти эту проблему, создав объект, который «реализует протокол типов», как это делается с итерациями, последовательностями и т. Д., Но на самом деле не наследуется от type.Это может создать другие проблемы, такие как те, которые свойственны делегатскому подходу, который Алекс упоминает в первой части своего ответа.

делегирование

Алекс также предлагает третий подход, делегирование, при которомсостояние объекта распространяется на объекты-потомки с помощью магического метода __getattr__.Опять же, см. Ответ Алекса для примера, а также подробности об ограничениях этого подхода.

Требуется дополнительная информация о практичности этих подходов, а также альтернативные предложения.

Ответы [ 2 ]

4 голосов
/ 15 июня 2010

Если вам нужно, чтобы будущие изменения объекта-прототипа были прозрачно отражены во всех «потомках», то вы должны обратиться к явному делегированию. В обычных методах это легко сделать с помощью __getattr__, например, исходя из:

class FromPrototype(object):
  def __init__(self, proto):
    self._proto = proto
  def __getattr__(self, name):
    return getattr(self._proto, name)

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

Если вас не интересует «бесшовное наследование» будущих изменений в прототипе, но вы можете сделать снимок последнего во время «наследования прототипа», это проще:

import copy

def proto_inherit(proto):
  obj = copy.deepcopy(proto)
  obj.__class__ = type(obj.__class__.__name__, (obj.__class__,), {})
  return obj

каждый объект, построенный таким образом, имеет свой собственный класс, так что вы можете устанавливать специальные методы для класса (после получения объекта из proto_inherit), не затрагивая другие объекты (для обычных методов вы можете установить их либо в классе, либо в в этом случае, хотя всегда использование класса будет более регулярным и последовательным).

0 голосов
/ 13 декабря 2015

Это более надежная версия подхода к делегированию.Основными улучшениями являются

  1. , когда унаследованный член является методом, затем возвращается метод с той же базовой функцией, но связанный с исходным вызывающим объектом.Это решает проблему, которую @AlexMartelli поднимает в своем ответе:

    ... до тех пор, пока вы также наследуете состояние прототипа, а не только поведение.К сожалению, не переопределенные методы из прототипа не будут воспринимать любое состояние, которое могло быть переопределено в текущем объекте.

  2. Следуют соглашениям о кооперативном наследовании, чтобы не нарушать класснаследование на основе

Одно ограничение заключается в том, что класс Proto должен стоять на первом месте в порядке разрешения методов для корректной работы инициализации

Delegate.py

import types
import inspect

class Proto(object):
  def __new__(self, proto, *args, **kw):
    return super(Proto, self).__new__(self, *args, **kw)
  def __init__(self, proto, *args, **kw):
    self.proto = proto
    super(Proto, self).__init__(*args, **kw)
  def __getattr__(self, name):
    try:
      attr = getattr(self.proto, name)
      if (inspect.ismethod(attr) and attr.__self__ != None):
        attr = types.MethodType(attr.__func__, self)
      return attr
    except AttributeError:
      return super(Proto, self).__getattr__(name)

Delegate-demo.py

Вызов b.getY() ниже иллюстрирует точку Алекса и потерпит неудачу, если в его ответе будет использован класс FromPrototype вместо Proto

from delegate import Proto

class A(Proto):
  x = "This is X"
  def getY(self):
    return self._y

class B(Proto):
  _y = "This is Y"

class C(object):
  def __getattr__(self, name):
    return "So you want "+name

class D(B,C):
  pass

if __name__ == "__main__":
  a = A(None)
  b = B(a)
  print b.x
  print b.getY()
  d = D(a)
  print d.x
  print d.getY()
  print d.z
...