Как создать классы в Python с сильно ограниченными экземплярами - PullRequest
2 голосов
/ 19 июля 2009

В Python есть примеры встроенных классов с сильно ограниченными экземплярами. Например, «None» является единственным экземпляром своего класса, и в классе bool есть только два объекта, «True» и «False» (я надеюсь, что пока я более или менее прав).

Другим хорошим примером являются целые числа: если a и b являются экземплярами типа int, то a == b означает, что a равно b.

Два вопроса:

  1. Как создать класс с одинаково ограниченными экземплярами? Например, мы могли бы попросить класс с ровно 5 экземплярами. Или может быть бесконечно много экземпляров, таких как тип int, но они не являются произвольными.

  2. если целые числа образуют класс, почему int () дает экземпляр 0? сравните это с определенным пользователем классом Cl, где Cl () даст экземпляр класса, а не конкретный уникальный экземпляр , например 0. Не должен int () возвращать неопределенный целочисленный объект, т.е. целое число без указанного значения?

Ответы [ 7 ]

5 голосов
/ 19 июля 2009

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

Краткий пример. Допустим, мы хотели использовать пары целых чисел для представления координат и иметь правильную семантику значений.

class point(object):
    memo = {}
    def __new__(cls, x, y):
        if (x, y) in cls.memo:         # if it already exists, 
            return cls.memo[(x, y)]    # return the existing instance
        else:                          # otherwise, 
            newPoint = object.__new__(cls) # create it, 
            newPoint.x = x             # initialize it, as you would in __init__
            newPoint.y = y             
            cls.memo[(x, y)] = newPoint # memoize it, 
            return newPoint            # and return it!

4 голосов
/ 19 июля 2009

Похоже, что # 1 уже получил хороший ответ, и я просто хочу объяснить принцип, связанный с # 2, который, по-видимому, был пропущен всеми респондентами: для большинства встроенных типов вызов типа без параметров ( «конструктор по умолчанию») возвращает экземпляр этого типа , который оценивается как ложный . Это означает пустой контейнер для типов контейнеров, число которых сравнивается равным нулю для типов номеров.

>>> import decimal
>>> decimal.Decimal()
Decimal("0")
>>> set()
set([])
>>> float()
0.0
>>> tuple()
()
>>> dict()
{}
>>> list()
[]
>>> str()
''
>>> bool()
False

См? Довольно регулярно! Более того, для изменяемых типов, как и для большинства контейнеров, вызов типа всегда возвращает новый экземпляр; для неизменяемых типов, таких как числа и строки, это не имеет значения (возможна внутренняя оптимизация, чтобы вернуть новую ссылку на существующий неизменяемый экземпляр, но реализация не обязана выполнять такую ​​оптимизацию, и если она это делает, то может и часто будет выполнять их выборочно), так как сравнивать экземпляры неизменяемого типа с is или эквивалентно id().

не всегда корректно.

Если вы разрабатываете тип, который некоторые экземпляры могут оценивать как ложные (при наличии в классе __len__ или __nonzero__ специальных методов), рекомендуется следовать той же схеме (иметь __init__ [или __new__). для неизменяемых], если вызывается без аргументов [[за пределами self для __init__ и, конечно же, 'cls' для __new__], подготовьте [[новый, если изменяемый]] "пустой" или "подобный нулю" экземпляр класса).

3 голосов
/ 19 июля 2009

По первому вопросу вы могли бы реализовать шаблон проектирования синглтон-класса http://en.wikipedia.org/wiki/Singleton_pattern, из которого следует ограничить количество экземпляров.

По второму вопросу, я думаю, что этот вид объясняет вашу проблему http://docs.python.org/library/stdtypes.html

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

Вот еще один ресурс ... http://docs.python.org/library/functions.html#built-in-funcs

2 голосов
/ 19 июля 2009

Чтобы ответить на более общий вопрос о том, как создавать ограниченные экземпляры, это зависит от ограничения. Оба приведенных выше примера являются своего рода «одиночками», хотя второй пример - это вариант, в котором вы можете иметь много экземпляров одного класса, но у вас будет только один на входное значение.

И то, и другое можно сделать, переопределив метод класса '__new__, так что класс создает экземпляры, если он еще не создан, и оба возвращают его, и сохраняют его как атрибут на классе (как было предложено выше). Однако, чуть менее хакерский способ - использовать метаклассы. Это классы, которые изменяют поведение классов, и синглтоны являются отличным примером того, когда использовать метаклассы. И самое замечательное в этом то, что вы можете повторно использовать метаклассы. Создав метакласс Singleton, вы можете использовать этот метакласс для всех ваших синглетонов.

Хороший пример Python в Википедии: http://en.wikipedia.org/wiki/Singleton_pattern#Python

Вот вариант, который создаст другой экземпляр в зависимости от параметров: (Это не идеально. Если вы передадите параметр, который является диктом, он, например, потерпит неудачу. Но это только начало):

# Notice how you subclass not from object, but from type. You are in other words
# creating a new type of type.
class SingletonPerValue(type):
    def __init__(cls, name, bases, dict):
        super(SingletonPerValue, cls).__init__(name, bases, dict)
        # Here we store the created instances later.
        cls.instances = {}

    def __call__(cls, *args, **kw):
        # We make a tuple out of all parameters. This is so we can use it as a key
        # This will fail if you send in unhasheable parameters.
        params = args + tuple(kw.items())
        # Check in cls.instances if this combination of parameter has been used:
        if params not in cls.instances:
            # No, this is a new combination of parameters. Create a new instance,
            # and store it in the dictionary:
            cls.instances[params] = super(SingletonPerValue, cls).__call__(*args, **kw)

        return cls.instances[params]


class MyClass(object):
    # Say that this class should use a specific metaclass:
    __metaclass__ = SingletonPerValue

    def __init__(self, value):
        self.value = value

print 1, MyClass(1)
print 2, MyClass(2)
print 2, MyClass(2)
print 2, MyClass(2)
print 3, MyClass(3)

Но в Python есть и другие ограничения, чем создание экземпляров. Многие из них можно сделать с помощью метаклассов. У других есть ярлыки, вот класс, который позволяет вам устанавливать атрибуты 'items' и 'fruit', например.

class Constrained(object):
    __slots__ = ['items', 'fruit']

con = Constrained()
con.items = 6
con.fruit = "Banana"
con.yummy = True

Если вам нужны ограничения на атрибуты, но не такие сильные, вы можете переопределить __getattr__, __setattr__ and __delattr__, чтобы произошло много фантастических и ужасных вещей. :-) Существуют также пакеты, которые позволяют устанавливать ограничения на атрибуты и т. Д.

2 голосов
/ 19 июля 2009
  1. Создайте их заранее и верните один из них из __new__ вместо создания нового объекта или создайте кэшированные экземпляры ( weakrefs здесь удобно) и верните один из них вместо создания нового объекта .
  2. Целые числа особенные. Фактически это означает, что вы никогда не сможете использовать идентичность для сравнения, как если бы вы использовали идентичность для сравнения других объектов. Поскольку они неизменяемы и редко используются каким-либо образом, кроме контекста значения, это не составляет большой проблемы. Это сделано, насколько я могу судить, по причинам реализации больше, чем что-либо еще. (И поскольку нет четких указаний на то, что это неправильный путь, это хорошее решение.)

Синглетоны, такие как None: Создайте класс с именем, которое вы хотите присвоить переменной, а затем снова свяжите переменную с (единственным) экземпляром или впоследствии удалите класс. Это удобно, когда вы хотите эмулировать интерфейс, такой как getattr, где параметр является необязательным, но использование None отличается от не предоставления значения.

class raise_error(object): pass
raise_error = raise_error()

def example(d, key, default=raise_error):
  """Return d[key] if key in d, else return default or
  raise KeyError if default isn't supplied."""
  try:
    return d[key]
  except KeyError:
    if default is raise_error:
      raise
    return default
1 голос
/ 19 июля 2009

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

Обратите внимание, что a == b означает НЕ означает, что a is b.

Это верно только для первых нескольких целых чисел (например, первых сотен или около того - я точно не знаю), и это только деталь реализации CPython, которая фактически изменилась с переходом на Python 3.0.

Например:

>>> n1 = 4000
>>> n2 = 4000
>>> n1 == n2
True
>>> n1 is n2
False
1 голос
/ 19 июля 2009

Я думаю, что некоторые названия концепции, о которой вы думаете, это интернирование и неизменных объектов .

Что касается ответа на ваши конкретные вопросы, я думаю, что для # 1 вы можете найти свой ограниченный экземпляр в методе класса и вернуть его.

Что касается вопроса №2, я думаю, это просто вопрос того, как вы указываете свой класс. Неспецифический экземпляр класса int будет довольно бесполезен, поэтому просто укажите его, чтобы его невозможно было создать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...