Динамическое создание экземпляра типа (self) без вызова __init__?

#python #class #copy

#python #класс #Копировать

Вопрос:

Это довольно сложно объяснить. У меня есть класс, который должен поддерживать метод copy_stateonly() . Он должен возвращать поврежденную версию объекта, которая содержит только (скопированные) элементы данных, которые я хочу. Я надеюсь, что этот пример объясняет это лучше:

 # everything inherits from this
class SuperBase:
    def __init__(self):
        self.state_var = 3 # this should be copied into future objects
        self.non_state_var = 0 # we don't want to copy this

    def copy_stateonly(self):
        newobj = # ??????????? create instance without calling __init__
        newobj.state_var = self.state_var
        return newobj

# some clases inherit from this
class Base(SuperBase):
    def __init__(self):
        SuperBase.__init__(self)
        self.isflying = True # we want to copy this, this is state
        self.sprite = "sprites/plane_generic.png" # we must drop this

    def copy_stateonly(self):
        newobj = SuperBase.copy_stateonly(self)
        newobj.isflying = self.isflying
        return newobj

class A144fighter(Base):
    def __init__(self, teamname): # note required __init__ argument
        Base.__init__(self)
        self.colors = ["black", "grey"] # we want to copy this, this is state
        self.name = teamname # we must drop this

    def copy_stateonly(self):
        newobj = Base.copy_stateonly(self)
        newobj.colors = self.colors[:]
        return newobj

plane = A144fighter("team_blue")
plane_state = plane.copy_stateonly() # this should return an A144fighter object with only state_var, flying and colors set.
  

Python 2.7

Ответ №1:

Я не знаю способа создания новых экземпляров классических классов (которые вы использовали в своем примере) без вызова __init__() . Новые экземпляры классов нового стиля (потомки object ) могут быть созданы с помощью

 object.__new__(cls)
  

где cls тип объекта, который вы хотели бы создать.

Альтернативой является использование copy.copy() для копирования, возможно перезаписи __getstate__() и __setstate__() для определения того, что должно быть скопировано.

Редактировать: Чтобы создать новый экземпляр классического класса cls без вызова __init__() , вы можете использовать следующий хак:

 class EmptyClass:
    pass

new_instance = EmptyClass()
new_instance.__class__ = cls
new_instance.__dict__.update(whatever)
  

Комментарии:

1. нет необходимости взламывать: import types; types.InstanceType(cls) (или import new; new.instance(cls) )

2. второй метод работает только тогда, когда оба класса имеют одинаковую компоновку, что верно для большинства классов, но не будет, когда, например, любой класс использует __slots__ .

3. @TokenMacGuy: Как я уже говорил, второй подход работает только для классических классов. __slots__ не имеют смысла в этом контексте.

4. На самом деле, этот метод работает даже для классов нового стиля (при условии, что EmptyClass действительно наследуется от object ).

5. @TokenMacGuy: Для классов нового стиля проще использовать __new__() , потому что в противном случае вам пришлось бы охватывать множество особых случаев ( __slots__ , встроенные типы среди базовых классов и т.д.)

Ответ №2:

Помните, что у каждого объекта есть атрибут с именем __class__ . Если вы сделаете <object>.__class__ это, вернется класс объекта object (если это имеет смысл). Объект класса можно вызывать, поэтому вы можете добавить круглые скобки в конец, чтобы создать новый экземпляр этого класса.

 newobj = self.__class__()
  

Комментарии:

1. Это все еще вызывает self.__class__.__init__ , который может иметь требуемые аргументы, как показано в примере, что приводит к ошибке.

2. Ах, я думал, вы просто не хотите вызывать его напрямую. Я бы не рекомендовал этого делать, но один из способов обойти это — сделать все аргументы необязательными (поверьте мне) и добавить еще один необязательный аргумент, который должен быть логическим значением. По умолчанию должно быть True. И вы могли бы установить для него значение False, если вы не хотите, чтобы «обязательные» аргументы были обязательными. Если для параметра установлено значение true, проверьте, установлены ли все «обязательные» переменные (т. Е. Они имеют значение None или что-то еще?). Опять же, я бы не рекомендовал это.

Ответ №3:

 # everything inherits from this
class SuperBase:
    def __init__(self):
        self.state_var = 3 # this should be copied into future objects
        self.non_state_var = 0 # we don't want to copy this

    def __getstate__(self):
        return { 'state_var' : self.state_var }

    def __str__(self):
        return self.__class__.__name__   '('   str(vars(self))   ')'

# some clases inherit from this
class Base(SuperBase):
    def __init__(self):
        SuperBase.__init__(self)
        self.isflying = True # we want to copy this, this is state
        self.sprite = "sprites/plane_generic.png" # we must drop this

    def __getstate__(self):
        state = SuperBase.__getstate__(self)
        state['isflying'] = self.isflying
        return state

class A144fighter(Base):
    def __init__(self, teamname): # note required __init__ argument
        Base.__init__(self)
        self.colors = ["black", "grey"] # we want to copy this, this is state
        self.name = teamname # we must drop this

    def __getstate__(self):
        state = Base.__getstate__(self)
        state['colors'] = self.colors[:]
        return state

plane = A144fighter("team_blue")
print plane

import copy
print copy.copy(plane)

# or manually:
import types
print types.InstanceType(plane.__class__, plane.__getstate__())