Класс factory для создания простых структурированных классов?
При исследовании 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'
-
Есть ли аналогичный механизм в Python для этого? (Я обычно использую словари).
-
Как мне получить функцию Struct()
для создания правильных аргументов __init__()
. (в этом случае я хотел бы выполнить person1 = Person('John', 'Doe')
Именованные аргументы, если это возможно: person1 = Person(surname='Doe', forename='John')
Я хотел бы в интересующем вопросе ответить на вопрос 2, даже если для этого есть лучший механизм Python.
Ответы
Ответ 1
Обновление варианта 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)
>>>
Update
Взглянув на то, как namedtuple()
делает это в collections.py. Класс создается и расширяется как строка и оценивается. Также имеет поддержку для травления и т.д. И т.д.
Ответ 2
Если вы используете 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
Ответ 3
Если вы используете 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() factory, чтобы назначить __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)
Ответ 4
Как говорили другие, назвали кортежи в 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/
Ответ 5
Это продолжение ответа Сайда (и, вероятно, только интересное для людей, которые хотят копать глубже).
У меня возникла проблема с использованием 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'
Ответ 6
Существует namedtuple
>>> from collections import namedtuple
>>> Person = namedtuple("Person", ("forename", "surname"))
>>> john = Person("John", "Doe")
>>> john.forename
'John'
>>> john.surname
'Doe'