获取必要的对象变量以重新创建具有__init__的对象

huangapple go评论69阅读模式
英文:

Get variables of object necessary to recreate object with __init__

问题

以下是您要翻译的内容:

如何获取在Python中用于重新创建对象的必要变量这些变量在`__init__`中定义

我有一个类
```python
class Example:
    def __init__(self, z, x=1):
        self.z = z
        self.x = x
        self.y = 0

    def change_y(self, y):
        self.y = y

    def calculate(self):
        return self.y * self.x * self.z

我想要获取重新创建此类所需的变量,在这种情况下是 xz。使用 pickle.dumps()vars() 然后 json.dumps() 会包含不必要的 y 变量(我不关心 y,因为它不涉及类的创建,对我来说它被视为“临时”的)。

在不以任何方式更改类的情况下,如何获取由__init__参数定义的变量(或者在x的情况下可能会定义)。

这是测试代码:

class Example:
    def __init__(self, z, x=1):
        self.z = z
        self.x = x
        self.y = 0

    def change_y(self, y):
        self.y = y

    def calculate(self):
        return self.y * self.x * self.z

e1 = Example(5)
e2 = Example(3, 2)

e1.change_y(4)
e2.change_y(1)

e1_data = magic_get_data(e1)  # 返回一个带有"var":value的字典
e2_data = magic_get_data(e2)

e3 = magic_make_from_data(Example, e1_data)  # e3.__dict__ == Example(5).__dict__ 需要为True
e4 = magic_make_from_data(Example, e2_data)  # e4.__dict__ == Example(3, 2).__dict__ 需要为True 

print(e3.__dict__ == Example(5).__dict__)
print(e4.__dict__ == Example(3, 2).__dict__)

应该输出:

True
True

注意:在上述代码中,我将类名更改为 Example,以保持一致性。

英文:

How do I get variables of object that are necessary to recreate object with __init__ python?

I have a class:

class example:
    def __init__(self, z, x=1):
        self.z = z
        self.x = x
        self.y = 0

    def change_y(self, y):
        self.y = y

    def calculate(self):
        return self.y*self.x*self.z

I want the variables required to recreate this class, in this case it is x and z. Using pickle.dumps() or vars() then json.dumps() gives the unnecessary y variable as well (I don't care about y because it isn't involved in creating the class, it is to me considered 'temporary').

Without changing the class in any way how can I get the variables defined(or could be defined, in the case of x) by the __init__ parameters.

Here is the testing code:

class Example:
    def __init__(self, z, x=1):
        self.z = z
        self.x = x
        self.y = 0

    def change_y(self, y):
        self.y = y

    def calculate(self):
        return self.y*self.x*self.z

e1 = Example(5)
e2 = Example(3, 2)

e1.change_y(4)
e2.change_y(1)

e1_data = magic_get_data(e1) # returns a dict with "var":value
e2_data = magic_get_data(e2)

e3 = magic_make_from_data(Example, e1_data) # e3.__dict__ == Example(5).__dict__ needs to be True
e4 = magic_make_from_data(Example, e2_data) # e4.__dict__ == Example(3, 2).__dict__ needs to be True 

print(e3.__dict__ == Example(5).__dict__)
print(e4.__dict__ == Example(3, 2).__dict__)

Should output:

True
True

答案1

得分: 1

如果构造函数的所有参数都存储在具有相同名称的属性中(例如:x 参数存储在 self.x 中,就像在您的示例中一样),那么您可以使用内置的 inspect 模块来获取类的构造函数签名。然后,您可以获取所有这些属性的值。就像这样:

def magic_get_data(obj):
    sig = [param[0] for param in inspect.signature(obj.__class__).parameters]
    return {param: obj.__dict__[param] for param in sig}

def magic_make_from_data(c, data):
    return c(**data)
英文:

If all parameters to the constructor are stored in attributes with the same name (ie: the x parameter is stored in self.x, just like in your example), then you can use the built-in inspect module to get the signature of the class' constructor. From there, you can get the value of all those attributes. Just like this:

def magic_get_data(obj):
    sig = [param[0] for param in inspect.signature(obj.__class__).parameters]
    return {param: obj.__dict__[param] for param in sig}


def magic_make_from_data(c, data):
    return c(**data)

答案2

得分: 1

不更改类的情况下,可以假设这是因为你不拥有这段代码,你可以通过为该类的__init__方法添加一个包装器来实现,该包装器将用于创建实例的参数存储在一个字典中,该字典将实例映射到参数:

class track_init:
    def __init__(self):
        self.args_of_instance = {}

    def __call__(self, cls):
        def __init__(this, *args, **kwargs):
            self.args_of_instance[this] = {'args': args, 'kwargs': kwargs}
            orig_init(this, *args, **kwargs)

        orig_init = cls.__init__
        cls.__init__ = __init__
        return cls

因此:

class Example:
    def __init__(self, z, x=1):
        self.z = z
        self.x = x
        self.y = 0

    def change_y(self, y):
        self.y = y

    def calculate(self):
        return self.y * self.x * self.z

track_examples = track_init()
Example = track_examples(Example)

magic_get_data = track_examples.args_of_instance.get
magic_make_from_data = lambda data: Example(*data['args'], **data['kwargs'])

e1 = Example(5)
e2 = Example(3, 2)

e1.change_y(4)
e2.change_y(1)

e1_data = magic_get_data(e1)
e2_data = magic_get_data(e2)

e3 = magic_make_from_data(e1_data)
e4 = magic_make_from_data(e2_data)

print(e3.__dict__ == Example(5).__dict__)
print(e4.__dict__ == Example(3, 2).__dict__)

将输出:

True
True

演示链接:https://replit.com/@blhsing/FormalHatefulAfkgaming

英文:

Without changing the class, presumably because you don't own the code, you can patch the class' __init__ method with a wrapper that stores the arguments used to create an instance in a dict that maps the instance to the arguments:

class track_init:
    def __init__(self):
        self.args_of_instance = {}

    def __call__(self, cls):
        def __init__(this, *args, **kwargs):
            self.args_of_instance[this] = {'args': args, 'kwargs': kwargs}
            orig_init(this, *args, **kwargs)

        orig_init = cls.__init__
        cls.__init__ = __init__
        return cls

so that:

class Example:
    def __init__(self, z, x=1):
        self.z = z
        self.x = x
        self.y = 0

    def change_y(self, y):
        self.y = y

    def calculate(self):
        return self.y*self.x*self.z

track_examples = track_init()
Example = track_examples(Example)

magic_get_data = track_examples.args_of_instance.get
magic_make_from_data = lambda data: Example(*data['args'], **data['kwargs'])

e1 = Example(5)
e2 = Example(3, 2)

e1.change_y(4)
e2.change_y(1)

e1_data = magic_get_data(e1)
e2_data = magic_get_data(e2)

e3 = magic_make_from_data(e1_data)
e4 = magic_make_from_data(e2_data)

print(e3.__dict__ == Example(5).__dict__)
print(e4.__dict__ == Example(3, 2).__dict__)

would output:

True
True

Demo: https://replit.com/@blhsing/FormalHatefulAfkgaming

答案3

得分: 1

根据我阅读的您的评论和原始问题,我认为您设想的方式无法解决您的问题。我了解您想要的约束条件:

  1. 您想从对象中提取数据,但不希望涉及对象的类(例如,通过定义某种 serialize 方法)
    • “我希望它适用于对此方法使用的任何类”
    • 如果没有对象的合作,您真正知道的只有它的实例变量以及其初始化程序的参数
  2. 您希望能够从提取的数据中实例化一个新对象,但不希望涉及对象的类(例如,通过定义某种 deserialize 方法)
    • 如果没有对象的合作,您只能依赖于调用其 __init__ 方法。
    • (分配给对象的 __dict__ 是可能的,但在一般情况下非常破碎,因为它绕过了类的 __init__

在像您的 class example 这样的情况下,您的问题在某些情况下是简单/容易的,其 __init__ 参数与其对象的实例变量(大部分)一一对应。 从根本上说,问题在于对象不仅仅是实例变量的抓袋。实例变量大多是封装的实现细节,仅在服务于构成对象 API 的公共实例方法时才存在。这就是它们与普通的 dict 不同的方式。

考虑以下示例:

class CounterExample:
  def __init__(self, x):
    self.y = x

您可以采取各种技巧列出 CounterExample__init__ 参数(以知道它有一个 x 参数),访问对象的 __dict__(以知道它有一个 y 实例变量)等等,但所有这些都不会从根本上帮助解决问题。您的代码绝对不可能知道参数 x 被分配给 ivar y

结论是,您始终会遇到边缘情况,需要进行以下一种妥协:

  1. 读取/写入对象的 __dict__ 并绕过它们的 __init__
    • 这会严重破坏那些不是愚蠢的 dict-like 值包的类
  2. 限制您可以支持的对象种类(例如,仅支持其初始化程序参数名称与实例变量名称一一对应的对象)
    • 即使如此,您仍然会遇到各种各样的错误。假设参数 x 最终只会被用作 self.x = x 是错误的。
  3. 要求类的作者合作,通过提供允许他们控制其对象如何序列化/反序列化的钩子
    • 实际上,这就是几乎每个序列化库(例如 pickle)都会做的事情。
英文:

From what I've read of your comments and original question, I don't think your problem is solvable in the way you envision it. I gather that you want these constraints:

  1. You want to extract data from an object, but without that object's class participation (e.g. by defining a serialize method of some sort)
    • "I want it to work for any class this method is used on"
    • Without an object's cooperation, all your really know about it is its instance variables, and the parameters to its initializer
  2. You want to be able to instantiate a new object from that extracted data, but without the object's class participation (e.g. by defining a deserialize method of some sort)
    • Without an object's cooperation, all you can rely on is calling its __init__ method.
    • (Assigning to the objects __dict__ is possible, but super broken in the general case, because it circumvents the class's __init__

Your problem is simple/easy in cases like your class example, whose __init__ params correspond (mostly) 1-to-1 with the instance variables of its objects. Fundamentally, the issue is that objects aren't just grab-bags of instance variables. Ivars are (mostly) encapsulated implementation details, that exist only in the service of the public instance methods that make up the object's API. This is how they differ from normal ol' dicts.

Consider this example:

class CounterExample:
  def __init__(self, x):
    self.y = x

You can do all sorts of tricks to list the parameters of CounterExample's __init__ (to know it has an x parameter), and access an object's __dict__ ( to know it has a y ivar), and so on, but none of it fundamentally helps. There is no way your code could possibly know that the parameter x gets assigned to the ivar y.

The conclusion here is that you will always have edge cases, which require one of these compromises:

  1. You read/write object's __dict__s, and bypass their __init__
    • This super-duper breaks classes that aren't dumb dict-like bags of values
  2. You limit what kinds of objects you can support (e.g. only those whose initializer parameter names correspond 1:1 to instance variable names)
    • Even still, you'll see all kinds of bugs here. It's wrong to assume that a param x just end up only being used as self.x = x.
  3. You require the class authors' cooperation, by giving them hooks that let them control how their objects are serialized/deserialized
    • In practice, this is what you see pretty much ever serialization library (e.g. pickle) does.

huangapple
  • 本文由 发表于 2023年5月29日 10:06:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76354301.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定