如何创建一个可以通过其自身实例进行初始化的类

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

How to create a class which can be initialized by its own instance

问题

任务:

实现一个接受至少一个参数的类,可以通过原始数据进行初始化,也可以通过自己的实例进行初始化。

使用的最简示例:

arg = {}  # 真实对象所需的任何参数
instance1 = NewClass(arg)
instance2 = NewClass(instance1)
assert instance2 is instance1  # 或者至少是相等的

更复杂的使用示例:

from typing import Mapping, Union

class NewClass:
    """
    不完整

    应该以任务中描述的方式进行操作

    """
    def __init__(self, data: Mapping):
        self.data = data

    def cool_method(self):
        assert isinstance(self.data, Mapping)
        # 使用 self.data 进行某些操作
        return ...


class AnotherClass:
    """
    接受映射和 NewClass 实例,但需要在内部使用 NewClass

    """
    def __init__(self, obj: Union[Mapping, NewClass]):
        self.cool = NewClass(obj).cool_method()
        ...
英文:

Task:

Implement some class that accepts at least one argument and can be either initialized by original data, or its own instance.

Minimal example of usage:

arg = {}  # whatever necessary for the real object
instance1 = NewClass(arg)
instance2 = NewClass(instance1)
assert instance2 is instance1  # or at least, ==

More complex example of usage:

from typing import Mapping, Union


class NewClass:
    """
    Incomplete

    Should somehow act like described in the task

    """
    def __init__(self, data: Mapping):
        self.data = data

    def cool_method(self):
        assert isinstance(self.data, Mapping)
        # do smth with self.data
        return ...

    ...


class AnotherClass:
    """
    Accepts both mappings and NewClass instances,
    but needs NewClass internally

    """
    def __init__(self, obj: Union[Mapping, NewClass]):
        self.cool = NewClass(obj).cool_method()
        ...

答案1

得分: 1

One just have to make use of the __new__ method on the class, instead of __init__ to be able to change what is instantiated.

In this case, all you need is to write your NewClass like this:

from typing import Union, Mapping, Self

class NewClass:
    """
    acts like described in the task
    """
    # typing.Self is available in Python 3.11. 
    # For previous versions, just put the class name quoted
    # in a string: "NewClass" instead of Self
    def __new__(cls, data: Union[Mapping, Self]):
        if isinstance(data, NewClass):
            return data
        self = super().__new__(cls)
        self.data = data
        return self 

    def cool_method(self):
        assert isinstance(self.data, Mapping)
        # do smth with self.data
        return ...

Avoiding a metaclass is interesting because it avoids metaclasses conflicts, in larger projects, and it is an abstraction level most
projects simply do not need. Actually, static type checkers such as "Mypy" can't even figure out behavior changes coded into
the metaclasses.

On the other hand, __new__ is a common special method sibling to __init, readily available, just not used more commonly because Python also provides
__init, which suffices when the default behavior of __new, of always creating a new instance, is not the desired one.

For some reason I do not know, making use of a metaclass to create a "singleton" got wildly popular in tutorials and answers. It is a design pattern much less important and less used in Python than in languages which do not allow "stand alone" functions. Metaclasses are not needed for singletons either, by the way - one can just create a top-level instance of whatever class should have a single instance, and use that instance from that point on, instead of creating new instances. Other languages also restrict the existence of top-level, importable, instances, making that a need that was artificially imported into Python.

英文:

One just have to make use of the __new__ method on the class, instead of __init__ to be able to change what is instantiated.

In this case, all you need is to write your NewClass like this:

from typing import Union, Mapping, Self

class NewClass:
    """
    acts like described in the task

    """
    # typing.Self is available in Python 3.11. 
    # For previous versions, just put the class name quoted
    # in a string: `"NewClass"` instead of `Self`
    def __new__(cls, data: Union[Mapping, Self]):
        if isinstance(data, NewClass):
            return data
        self = super().__new__(cls)
        self.data = data
        return self 

    def cool_method(self):
        assert isinstance(self.data, Mapping)
        # do smth with self.data
        return ...

Avoiding a metaclass is interesting because it avoid metaclasses conflicts, in larger projects, and it is an abstraction level most
projects simply does not need. Actually, static type checkers such
as "Mypy" can't even figure out behavior changes coded into
the metaclasses.

On the other hand, __new__ is a common special method sibling to __init__, readily available, just not used more commonly because Python also provides
__init__, which suffices when the default behavior of __new__, of
always creating a new instance, is not the desired one.

For some reason I do not know, making use of a metaclass to create a "singleton" got wildly popular in tutorials and answers. It is a design pattern much less important and less used in Python than in languages which do not allow "stand alone" functions. Metaclasses are not needed for singletons either, by the way - one can just create a top-level instance of whatever class should have a single instance, and use that instance from that point on, instead of creating new instances. Other languages also restrict the existence of top-level, importable, instances, making that a need that was artificially imported into Python.

答案2

得分: 0

元类解决方案:

适用于 Python 3.8

class SelfWrapperMeta(type):
    """
    元类,允许在用户类初始化时将先前创建的用户类实例返回为第一个位置参数

    在这种自包装情况下,其他参数将被忽略

    否则,用户类初始化将正常调用
    """
    def __call__(cls, arg, /, *args, **kwargs):
        if isinstance(arg, cls):
            return arg
        return super().__call__(arg, *args, **kwargs)

用法示例:

class A(metaclass=SelfWrapperMeta):
    def __init__(self, data):
        self.data = data

example = {}
a = A(example)
b = A(a)
c = A(example)
assert a is b
assert c is not a

注意: 代码部分未被翻译。

英文:

Metaclass solution:

Actual for python 3.8

class SelfWrapperMeta(type):
    """
    Metaclass, allowing to return previously created user class instance,
    if the user class init receives it as the first positional argument
    
    Other arguments are just ignored in that self-wrapping case
    
    Otherwise, the user class init calls normally

    """
    def __call__(cls, arg, /, *args, **kwargs):
        if isinstance(arg, cls):
            return arg
        return super().__call__(arg, *args, **kwargs)

Example of usage:

class A(metaclass=SelfWrapperMeta):
    def __init__(self, data):
        self.data = data

example = {}
a = A(example)
b = A(a)
c = A(example)
assert a is b
assert c is not a

答案3

得分: 0

Subclass dict

如果接受子类化 dict,那么可以使用一个非常简单的解决方案:

class MyDict(dict):
    pass

d = {1: 2}
d1 = MyDict(d)
d2 = MyDict(d1)

因为 MyDictdict 的子类,所以:

  • 与另一个 MyDictdict 实例进行比较时,==is 的行为与 dictdict 相同。这很好的地方在于它遵循最小惊讶原则。
  • MyDict 被视为 Mapping 类型

修改 __init__

以下是一些关于"对数据执行某些操作"的选项,可能需要混合匹配以适应您的解决方案。

数据未修改

您可能只是记录一个 MyDict 的创建:

class MyDict(dict):
    def __init__(self, data: Mapping):
        if isinstance(data, MyDict):
            print(f'制作 MyDict 与 {data}')
        super().__init__(data)

数据已修改

如果注入了一个键/值并且要求它们比较为真,那么可以理解您希望将该注入回传给数据参数。这种行为可能被认为是出乎意料的,因此可能不是一个好主意。

class MyDict(dict):
    def __init__(self, data: Mapping):
        data['_debug_entry'] = '涉及创建 MyDict 实例'
        super().__init__(data)
英文:

Subclass dict

If subclassing dict is acceptable then a very trivial solution can be used:

class MyDict(dict):
    pass

d = {1: 2}
d1 = MyDict(d)
d2 = MyDict(d1)

Because MyDict is a subclass of dict then:

  • Comparisons to another instance of MyDict or dict, will behave the same as if it was dict to dict for == and is. The nice thing about that is that it follows the least surprise principle
  • MyDict is considered a Mapping type

Modify __init__

Here's a few options for "doing something" to data, mixing and matching may be required for your solution.

Data Not Modified

You may be just logging a MyDict creation:

class MyDict(dict):
    def __init__(self, data: Mapping):
        if isinstance(data, MyDict):
            print(f'Making MyDict with {data}')
        super().__init__(data)

Data Modified

If a key/value is being injected and the requirement is they compare True then one can reason you want that injection back ported to the data arg. (It's arguable that this is surprising behaviour and therefore not a good idea)

class MyDict(dict):
    def __init__(self, data: Mapping):
        data['_debug_entry'] = 'involved in making MyDict instance'
        super().__init__(data)

huangapple
  • 本文由 发表于 2023年2月16日 19:25:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/75471585.html
匿名

发表评论

匿名网友

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

确定