2010-05-21 5 views
1

Я строю классы, где я знаю предполагаемые типы атрибутов, но Python, конечно, этого не делает. Хотя это не-pythonic хочет сказать это, полагая, что I do хотите, есть ли идиоматический способ сделать это?Что такое pythonic способ добавления информации типа к атрибутам объекта?

Почему: Я читаю данные в иностранном формате (без информации о типе) с объектами-вложенными внутри-объектами. Его легко вставить в вложенные словари, но я хочу, чтобы он был в объектах моих типов классов, чтобы получить правильное поведение, а также данные. Например: предположим, что мой класс Book имеет атрибут isbn, который я буду заполнять объектом ISBNumber. Мои данные дают мне isbn как строку; Я хотел бы иметь возможность посмотреть на Book и сказать: «Это поле должно быть заполнено ISBNumber(theString)».

Бонус для меня, если решение можно применить к классам, которые я получаю от кого-то, не редактируя их код.

(я ограничен до 2,6, хотя заинтересованы в решениях для 3.x, если они существуют.)

+1

Включает ли это хлопание кого-либо с рыбой? – amelvin

+0

Не подходит ли рассол для этого - даже если бы его исходный код не ввел бы тип информации – Mark

+0

Моя проблема заключается в чтении, а не в написании и чтении (текстовом) формате сериализации, который я не контролирую (который не включает информацию о типе). Pickle (я полагаю) хранит всю информацию о типе, которую ему нужно рассыпать (имена модулей и классов). – Tikitu

ответ

2

Существует много способов добиться чего-то подобного. Если муфта формат ввода в объектной модели является приемлемым, то вы можете использовать дескрипторы для создания адаптеров типа:

class TypeAdaptingProperty(object): 
    def __init__(self, key, type_, factory=None): 
     self.key = key 
     self.type_ = type_ 
     if factory is None: 
      self.factory = type_ 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return getattr(instance, self.key) 

    def __set__(self, instance, value): 
     if not isinstance(value, self.type_): 
      value = self.factory(value) 
     setattr(instance, self.key, value) 

    def __delete__(self, instance): 
     delattr(instance, self.key) 

class Book(object): 
    isbn = TypeAdaptingProperty('isbn_', ISBNNumber) 

b = Book() 
b.isbn = 123 # Does the equivalent of b.isbn = ISBNNumber(123) 

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

Каркас может выглядеть примерно так:

class Adaptor(object): 
    """Any callable can be an adaptor. This base class just proxies calls 
    to an appropriately named method.""" 
    def __call__(self, input): 
     return self.adapt(input) 

class ObjectAdaptor(Adaptor): 
    """Adaptor to create objects adapting the input value to the 
    factory function/constructor arguments, and optionally setting 
    fields after construction.""" 
    def __init__(self, factory, args=(), kwargs={}, fields={}): 
     self.factory = factory 
     self.arg_adaptors = args 
     self.kwarg_adaptors = kwargs 
     self.field_adaptors = fields 

    def adapt(self, input): 
     args = (adaptor(input) for adaptor in self.arg_adaptors) 
     kwargs = dict((key, adaptor(input)) for key,adaptor in self.kwarg_adaptors.items()) 
     obj = self.factory(*args, **kwargs) 
     for key, adaptor in self.field_adaptors.items(): 
      setattr(obj, key, adaptor(input)) 
     return obj 

def TypeWrapper(type_): 
    """Converts the input to the specified type.""" 
    return ObjectAdaptor(type_, args=[lambda input:input]) 

class ListAdaptor(Adaptor): 
    """Converts a list of objects to a single type.""" 
    def __init__(self, item_adaptor): 
     self.item_adaptor = item_adaptor 
    def adapt(self, input): 
     return map(self.item_adaptor, input) 

class Pick(Adaptor): 
    """Picks a key from an input dictionary.""" 
    def __init__(self, key, inner_adaptor): 
     self.key = key 
     self.inner_adaptor = inner_adaptor 
    def adapt(self, input): 
     return self.inner_adaptor(input[self.key]) 

Переходники сообщение выглядеть примерно так:

book_message_adaptor = ObjectAdaptor(Book, kwargs={ 
    'isbn': Pick('isbn_number', TypeWrapper(ISBNNumber)), 
    'authors': Pick('authorlist', ListAdaptor(TypeWrapper(Author))) 
}) 

Обратите внимание, что названия структуры сообщения не может быть такой же, как модель объекта. Сам

обработки сообщений выглядит следующим образом:

message = {'isbn_number': 123, 'authorlist': ['foo', 'bar', 'baz']} 
book = book_message_adaptor(message) 
# Does the equivalent of: 
# Book(isbn=ISBNNumber(message['isbn_number']), 
#  authors=map(Author, message['author_list'])) 
+0

Схема адаптера: niiiice. Ожидайте, что это, вероятно, * ответ *; слишком поспешно приняв его, я оставлю это день или около того, чтобы быть уверенным. (Все еще нахожу свой путь с помощью StackOverflow.) – Tikitu

1

Это - в принципе, по крайней мере - __repr__ функции какой объекта предполагается включить: однозначная реконструкция объекта.

Если ваш класс определен как:

class ISBN(object): 
    def __init__(self, isbn): 
     self.isbn = isbn 

    def __repr__(self): 
     return 'ISBN(%r)' % self.isbn 

Тогда вы один eval от воссоздавая исходный объект. См. Также pickle module.

риффов на класс ISBN затем дает книгу класс

class Book(object): 
    def __init__(self, title, author, isbn): 
     self.title = title 
     self.author = author 
     self.isbn = isbn 

    def __repr__(self): 
     return 'Book(%r, %r, %r)' % (self.title, self.author, self.isbn) 

good_book = Book("Cat's Cradle", 'Kurt Vonnegut, Jr.', ISBN('038533348X')) 
bad_book = Book('The Carpetbaggers', 'Harold Robbins', ISBN('0765351463')) 
library = [good_book, bad_book] 
print library 
# => [Book("Cat's Cradle", 'Kurt Vonnegut, Jr.', ISBN('038533348X')), 
     Book('The Carpetbaggers', 'Harold Robbins', ISBN('0765351463'))] 
reconstruct = eval(str(library)) 
print reconstruct 
# => [Book("Cat's Cradle", 'Kurt Vonnegut, Jr.', ISBN('038533348X')), 
     Book('The Carpetbaggers', 'Harold Robbins', ISBN('0765351463'))] 

Конечно ваши объекты могли бы сделать больше, чем просто построить и реконструировать себя ... и предостережение о том, что Eval Тамаш отметил.

+0

Я не контролирую Запись данные сериализации, только чтение it. К сожалению. (Данные книги, возможно, даже не были получены на Python, например, в чей-то хелл-API доступа к данным.) – Tikitu

1

Я предполагаю, что вы уже рассмотрели модуль pickle и не подходит для ваших целей. В этом случае, вы можете прикрепить атрибут к классу, который определяет тип для каждого атрибута:

class MyClass(object): 
    _types = {"isbn": ISBNNumber} 

После реконструкции, вы итерацию над _types и попытаться принудить тип:

for name, type_name in MyClass._types.iteritems(): 
    if hasattr(obj, name): 
     value = getattr(obj, name) 
     if not isinstance(value, type_name): 
      setattr(obj, name, type_name(value)) 

В приведенный выше пример, obj - это объект, который реконструируется, и я предполагаю, что атрибуты уже назначены в строчном формате (или что бы вы ни получили от неэтериализации).

Если вы хотите, чтобы это работало со сторонними классами, где вы не можете изменить исходный код, вы можете прикрепить атрибут _types к классу во время выполнения после его импорта из другого места. Например:

>>> from ConfigParser import ConfigParser 
>>> cp = ConfigParser() 
>>> cp._types 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: ConfigParser instance has no attribute '_types' 
>>> ConfigParser._types = {"name": "whatever"} 
>>> cp._types 
{"name": "whatever"} 

Я также согласен с использованием __repr__ и eval, если у вас есть полный контроль над тем, что вы получите от ваших входных файлов; однако, если задействован какой-либо пользовательский ввод, использование eval приводит к возможности произвольного выполнения кода, что весьма опасно.

+0

Спасибо, я сделаю это. Pickle и repr/eval не хороши, потому что я читаю данные в иностранном формате (как отметил мбс, извините за путаницу). – Tikitu

+0

Argh, слишком быстро принять (все еще нахожу свой путь с помощью StackOverflow). Другой ответ решает проблему вложенности (если ISBNumber был сложным классом с самими атрибутами) с более сложной структурой. Теперь (наказанный) я оставлю это день или около того, прежде чем принимать решение. – Tikitu

1

Возможно, это не совсем то, что вы хотите, но Enthought traits API дает вам возможность добавлять явные команды ввода в атрибуты класса Python. Это может сработать для вас.

0

Там две очень хорошие библиотеки для этого:

  1. atom
  2. traitlets

Они оба позволяют ограничить атрибуты к определенному типу и provi средство уведомления при изменении атрибута.

from atom.api import Atom, Unicode, Range, Bool, observe 
class Person(Atom): 
    """ A simple class representing a person object. 

    """ 
    last_name = Unicode() 

    first_name = Unicode() 

    age = Range(low=0) 

    debug = Bool(False) 
Смежные вопросы