请考虑以下数据类。我想防止使用__init__
方法直接创建对象。
from __future__ import annotations
from dataclasses import dataclass, field
@dataclass
class C:
a: int
@classmethod
def create_from_f1(cls, a: int) -> C:
# do something
return cls(a)
@classmethod
def create_from_f2(cls, a: int, b: int) -> C:
# do something
return cls(a+b)
# more constructors follow
c0 = C.create_from_f1(1) # ok
c1 = C() # should raise an exception
c2 = C(1) # should raise an exception
例如,我想强制使用我定义的其他构造函数,并在直接将对象创建为c = C(..)
时引发异常或警告。
到目前为止,我尝试过的工作如下。
@dataclass
class C:
a : int = field(init=False)
@classmethod
def create_from(cls, a: int) -> C:
# do something
c = cls()
c.a = a
return c
在init=False
中使用field
可以防止a
成为生成的__init__
的参数,因此这部分解决了问题,因为c = C(1)
引发了异常。
另外,我不喜欢它作为解决方案。
是否有直接方法可以禁止从类外部调用 init 方法?
答案 0 :(得分:1)
__init__
方法不负责从类创建实例。如果要限制类的实例化,则应覆盖__new__
方法。但是,如果您覆盖__new__
方法,也会影响任何形式的实例化,这意味着您的classmethod
将不再起作用。因此,由于通常不是Python风格的将实例创建委托给另一个函数,因此最好在__new__
方法中执行此操作。详细原因可以在doc中找到:
被称为创建类
cls. __new__()
的新实例的是一个静态方法(特殊情况,因此您无需这样声明),该方法将请求实例的类作为其第一个参数。其余参数是传递给对象构造函数表达式(对类的调用)的参数。__new__()
的返回值应该是新的对象实例(通常是cls的实例)。典型实现通过使用带有适当参数的
__new__()
调用超类的super().__new__(cls[, ...])
方法来创建该类的新实例,然后在返回之前根据需要修改新创建的实例。如果
__new__()
返回cls
的实例,则新实例的__init__()
方法将像__init__(self[, ...])
一样被调用,其中self是新实例,其余参数是与传递给__new__()
的相同。如果
__new__()
未返回cls的实例,则将不会调用新实例的__init__()
方法。
__new__()
主要用于允许不可变类型(例如int,str或tuple)的子类自定义实例创建。在自定义元类中也通常会覆盖它,以自定义类的创建。
答案 1 :(得分:1)
由于这不是强加于实例创建的标准限制,因此额外增加一两行代码来帮助其他开发人员了解正在发生的事情/为什么禁止这样做很值得。秉承“我们都是成年人”的精神,__init__
的隐藏参数可能在易于理解和易于实现之间取得很好的平衡:
class Foo:
@classmethod
def create_from_f1(cls, a):
return cls(a, _is_direct=False)
@classmethod
def create_from_f2(cls, a, b):
return cls(a+b, _is_direct=False)
def __init__(self, a, _is_direct=True):
# don't initialize me directly
if _is_direct:
raise TypeError("create with Foo.create_from_*")
self.a = a
在没有经过create_from_*
的情况下创建实例当然仍然是可能,但是开发人员必须有意识地绕过障碍进行操作。
答案 2 :(得分:1)
尝试在Python中将构造函数设为私有不是一件很Python的事情。 Python的哲学之一是“我们都是成年人”。也就是说,您不会尝试隐藏__init__
方法,但会记录用户可能想使用便利构造器之一的情况。但是,如果用户认为自己确实知道自己在做什么,那么欢迎尝试。
您可以在标准库中看到这一理念。使用inspect.Signature
。类构造函数采用Parameter
的列表,创建起来相当复杂。这不是要求用户创建签名实例的标准方法。而是提供了一个名为signature
的函数,该函数以Callable作为参数,并完成了从CPython中的各种不同函数类型创建参数实例并将其编组到Signature
对象中的所有工作。>
那是这样的:
@dataclass
class C:
"""
The class C represents blah. Instances of C should be created using the C.create_from_<x>
family of functions.
"""
a: int
b: str
c: float
@classmethod
def create_from_int(cls, x: int):
return cls(foo(x), bar(x), baz(x))
答案 3 :(得分:0)
不是创建两个构造函数,然后禁止其中之一,并强制使用类方法,为什么不简单地仅提供所需的构造函数呢?
class C:
def __init__(self, a: int):
# do something
self.a = a
这看起来比原始代码简单得多,并且可以执行请求的操作。
答案 4 :(得分:0)
正如Dunes' answer所解释的,这不是您通常想要做的。但是由于仍然有可能,因此是这样:
dataclasses import dataclass
@dataclass
class C:
a: int
def __post_init__(self):
# __init__ will call this method automatically
raise TypeError("Don't create instances of this class by hand!")
@classmethod
def create_from_f1(cls, a: int):
# disable the post_init by hand ...
tmp = cls.__post_init__
cls.__post_init__ = lambda *args, **kwargs: None
ret = cls(a)
cls.__post_init__ = tmp
# ... and restore it once we are done
return ret
print(C.create_from_f1(1)) # works
print(C(1)) # raises a descriptive TypeError
我可能不必说句柄代码看起来绝对令人发指,并且它也使得不可能将__post_init__
用于其他任何事情,这是非常不幸的。但这是回答帖子中问题的一种方法。