将类型信息添加到对象属性的pythonic方法是什么?

时间:2010-05-21 09:27:07

标签: python types

我正在构建我知道属性的预期类型的​​类,但Python当然不会。虽然想要告诉它是不科幻的,但假设我想要,是否有一种惯用的方法呢?

为什么:我正在读取涉及对象嵌套内部对象的外来格式数据(没有类型信息)。将它放入嵌套字典很容易,但我希望它在我的类类型的对象中,以获得正确的行为以及数据。例如:假设我的班级Book有一个属性isbn,我将填充ISBNumber个对象。我的数据给了我isbn作为一个字符串;我希望能够查看Book并说“该字段应由ISBNumber(theString)填充。”

如果解决方案可以应用于我从其他人那里获得的课程而不需要编辑他们的代码,那么对我来说是一种欢乐。

(我限制在2.6,但如果它们存在则对3.x的解决方案感兴趣。)

5 个答案:

答案 0 :(得分: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']))

答案 1 :(得分: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'))]

当然,你的对象可能不只是构建和重建自己......而且还有关于Tamás注意到的eval的警告。

答案 2 :(得分: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会导致任意代码执行的可能性,这非常危险。

答案 3 :(得分:1)

这可能不是你想要的,但Enthought traits API确实能让你为Python类属性添加显式类型。它可能适合你。

答案 4 :(得分:0)

有两个非常好的库:

  1. atom
  2. traitlets
  3. 它们都允许您将属性限制为特定类型,并在属性更改时提供通知方式。

      MATCH( first, last )AGAINST('+"some" +"guy"' IN BOOLEAN MODE )