类工厂用来生成简单的类结构?

13 投票
8 回答
8928 浏览
提问于 2025-04-15 13:32

在研究Ruby的时候,我发现了一种创建简单的类似于结构体的类的方法:

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')

我想知道第二个问题的答案,即使有更好的Python机制来实现这个。

8 个回答

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() 工厂修改为分配 __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)
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上使用的移植版

3

这是对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中实现的。这个类是以字符串的形式创建并扩展的,然后进行评估。同时它还支持序列化等功能。

撰写回答