英文:
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)
因为 MyDict
是 dict
的子类,所以:
- 与另一个
MyDict
或dict
实例进行比较时,==
和is
的行为与dict
到dict
相同。这很好的地方在于它遵循最小惊讶原则。 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
ordict
, will behave the same as if it wasdict
todict
for==
andis
. The nice thing about that is that it follows the least surprise principle MyDict
is considered aMapping
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论