【问题标题】:Class factory to produce simple struct-like classes?类工厂生产简单的类似结构的类?
【发布时间】:2010-11-18 21:17:36
【问题描述】:

在研究 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 机制来执行此操作。

【问题讨论】:

  • Python 中没有结构类型,因为您很少需要它。大多数时候,一个元组或一个字典就足够了,对于更复杂的情况,使用一个真实的类。不要尝试用 Python 编写 Ruby 或 Java 代码 - 改用 Python 习语。

标签: python struct class-factory


【解决方案1】:

如果您使用的是 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'

编辑:根据 cmets,有一个 backport for earlier versions of Python

【讨论】:

【解决方案2】:

如果你正在运行 python 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() 工厂更改为分配__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)

【讨论】:

  • +1 这是我正在寻找的信息。无论我是否使用 Python
  • 我错了还是你定义 Struct() 创建类的方式只有 class 成员?那么新类的所有实例不会为它们共享相同的值吗?你不需要在 kwargs 中提供一个 __init__ 键和函数来提供实例变量吗?!
  • Struct() 函数定义了 default 值。您可以通过像往常一样手动设置值来覆盖它们,即 my_instance.attribute = value
  • 我不是这个意思。当你有一个构造类的多个实例,并改变一个属性值时,它不会改变所有其他的吗?
  • 还有一件事:如果你使用 Struct() 的第二个定义(使用 slots 的那个),我的 Python 2.6 告诉我返回的类的实例是只读的! MyStruct2 = Struct('forename','lastname'); m = MyStruct2(); m.forename = "Jack" -> AttributeError: 'MyStruct2' 对象属性 'forename' 是只读的。 - 鉴于 OP 的要求,恕我直言,这使得这种方法毫无用处。
【解决方案3】:

正如其他人所说,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/

【讨论】:

    【解决方案4】:

    这是对 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'
    

    【讨论】:

    • +1 看来您在思考这个[主要是]理论上的问题上花费的时间比我多。我会试一试并玩弄它。谢谢。
    • 您缺少括号:Struct1() 函数的最后一个字符。
    【解决方案5】:

    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 中是如何做到这一点的。该类被创建并扩展为字符串并进行评估。还支持酸洗等。

    【讨论】:

    • +1 我特别喜欢将实例创建时间的位置参数映射到类创建时间的位置参数的方式,使用前者作为值,后者作为键(setattr(self, args[i], iargs[i])).
    • 我想可以进行加法来计算 args 的数量,看看传递给 Struct() 的数字是否与传递给 __init__() 的数字匹配。
    【解决方案6】:

    namedtuple

    >>> from collections import namedtuple
    >>> Person = namedtuple("Person", ("forename", "surname"))
    >>> john = Person("John", "Doe")
    >>> john.forename 
    'John'
    >>> john.surname 
    'Doe'
    

    【讨论】:

      【解决方案7】:

      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/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-13
        • 1970-01-01
        • 2018-10-28
        • 2021-05-28
        • 2018-01-15
        • 1970-01-01
        相关资源
        最近更新 更多