13

在研究 Ruby 时,我遇到了这个来创建一个简单的类似 Struct 的类:

Person = Struct.new(:forname, :surname)
person1 = Person.new('John', 'Doe')
puts person1  #<struct Person forname="John", surname="Doe">

这对我提出了一些 Python 问题。我已经在 Python 中编写了这个机制的 [非常] 基本克隆:

def Struct(*args):
    class NewStruct:
        def __init__(self):
            for arg in args:
                self.__dict__[arg] = None

    return NewStruct

>>> Person = Struct('forename', 'surname')
>>> person1 = Person()
>>> person2 = Person()
>>> person1.forename, person1.surname = 'John','Doe'
>>> person2.forename, person2.surname = 'Foo','Bar'
>>> person1.forename
'John'
>>> person2.forename
'Foo'
  1. Python中是否已经有类似的机制来处理这个问题?(我通常只使用字典)。

  2. 我将如何获得Struct()创建正确__init__()参数的函数。(在这种情况下,如果可能,我想执行person1 = Person('John', 'Doe')命名参数:person1 = Person(surname='Doe', forename='John')

出于兴趣,我希望回答问题 2,即使有更好的 Python 机制来执行此操作。

4

7 回答 7

17

如果您使用的是 Python 2.6,请尝试使用标准库namedtuple类。

>>> from collections import namedtuple
>>> Person = namedtuple('Person', ('forename', 'surname'))
>>> person1 = Person('John', 'Doe')
>>> person2 = Person(forename='Adam', surname='Monroe')
>>> person1.forename
'John'
>>> person2.surname
'Monroe'

编辑:根据评论,早期版本的 Python 有一个 backport

于 2009-08-12T08:01:06.463 回答
11

如果你正在运行 python <2.6 或者想扩展你的类来做更多的事情,我建议使用type()内置的。与您的解决方案相比,这具有优势,因为设置__dict__发生在类创建而不是实例化时。它也没有定义方法,因此如果类由于某种原因再次__init__调用,也不会导致奇怪的行为。__init__例如:

def Struct(*args, **kwargs):
    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), kwargs)

像这样使用:

>>> MyStruct = Struct("forename", "lastname")

相当于:

class MyStruct(object):
    forename = None
    lastname = None

虽然这样:

>>> TestStruct = Struct("forename", age=18, name="TestStruct")

相当于:

class TestStruct(object):
    forename = None
    age = 18

更新

此外,您可以编辑此代码以非常容易地防止分配除指定变量之外的其他变量。只需将 Struct() 工厂更改为 assign __slots__

def Struct(*args, **kwargs):
    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    kwargs['__slots__'] = kwargs.keys()
    return type(name, (object,), kwargs)
于 2009-08-12T08:43:38.203 回答
5

正如其他人所说,Python 2.6/3.x 中的命名元组。对于旧版本,我通常使用 Stuff 类:

class Stuff(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

john = Stuff(forename='John', surname='Doe')

但是,这并不能保护您免受拼写错误的影响。ActiveState 上还有一个命名元组的配方:

http://code.activestate.com/recipes/500261/

于 2009-08-12T08:26:24.790 回答
3

这是对 Cide 的回答的跟进(可能只对想要深入挖掘的人感兴趣)。

我在使用 Cide 更新的 Struct() 定义(使用 __slots__ 的定义)时遇到了问题。问题是返回类的实例具有只读属性:

>>> MS = Struct('forename','lastname')
>>> m=MS()
>>> m.forename='Jack'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyStruct' object attribute 'forename' is read-only

当存在同名的类属性时,似乎 __slots__ 正在阻止实例级属性。我试图通过提供一个 __init__ 方法来克服这个问题,因此可以在对象创建时设置实例属性:

def Struct1(*args, **kwargs):
    def init(self):
        for k,v in kwargs.items():
            setattr(self, k, v)
    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

作为一个净效应,构造的类只看到 __init__ 方法和 __slots__ 成员,它按预期工作:

>>> MS1 = Struct1('forename','lastname')
>>> m=MS1()
>>> m.forename='Jack'
>>> m.forename
'Jack'
于 2009-08-13T12:01:17.093 回答
2

ThomasH 变体的更新:

def Struct(*args, **kwargs):
    def init(self, *iargs, **ikwargs):
        for k,v in kwargs.items():
            setattr(self, k, v)
        for i in range(len(iargs)):
            setattr(self, args[i], iargs[i])
        for k,v in ikwargs.items():
            setattr(self, k, v)

    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

这允许参数(和命名参数)传入__init__()(没有任何验证 - 看起来很粗糙):

>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>> 

更新

看看如何namedtuple()collections.py中做到这一点。该类被创建并扩展为字符串并进行评估。还支持酸洗等。

于 2009-08-13T22:54:16.673 回答
1

namedtuple

>>> from collections import namedtuple
>>> Person = namedtuple("Person", ("forename", "surname"))
>>> john = Person("John", "Doe")
>>> john.forename 
'John'
>>> john.surname 
'Doe'
于 2009-08-12T08:00:34.467 回答
0

Python 包 esu 带来了一个可以提供几乎相同功能的结构:

from esu import Struct

Customer = Struct(
        'Customer',
        'name', 'address',
        methods={
            'greeting': lambda self: "Hello {}".format(self.__dict__['name'])
        })

dave = Customer()
dave.name = 'Dave'
dave.greeting() # => Hello Dave

来自https://torokmark.github.io/post/python-struct/

于 2020-11-09T10:28:24.200 回答