禁止从 class 外部使用默认构造函数
Forbid the usage of the default constructor from outside the class
考虑以下数据class。我想防止使用 __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
with init=False
in field
我防止 a
成为生成的 __init__
的参数,所以这部分解决了 c = C(1)
引发的问题一个例外。
另外,我不喜欢它作为解决方案。
是否有直接的方法来禁止从 class 外部调用 init 方法?
__init__
方法不负责从 class 创建实例。如果您想限制 class 的实例化,您应该覆盖 __new__
方法。但是如果你重写 __new__
方法也会影响任何形式的实例化,这意味着你的 classmethod
将不再起作用。因此,由于将实例创建委托给另一个函数通常不是 Pythonic,因此最好在 __new__
方法中执行此操作。详细原因可以在 doc:
中找到
Called to create a new instance of class cls. __new__()
is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression (the call to the class). The return value of __new__()
should be the new object instance (usually an instance of cls).
Typical implementations create a new instance of the class by invoking the superclass’s __new__()
method using super().__new__(cls[, ...])
with appropriate arguments and then modifying the newly-created instance as necessary before returning it.
If __new__()
returns an instance of cls
, then the new instance’s __init__()
method will be invoked like __init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to __new__()
.
If __new__()
does not return an instance of cls, then the new instance’s __init__()
method will not be invoked.
__new__()
is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.
由于这不是对实例创建施加的标准限制,因此额外的一两行代码来帮助其他开发人员了解正在发生的事情/为什么禁止这样做可能是值得的。秉承 "We are all consenting adults" 的精神,__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_*
创建实例,但开发人员必须有意识地绕过您的障碍才能做到这一点。
试图在 Python 中将构造函数设为私有并不是一件非常 pythonic 的事情。 Python 的哲学之一是 "we're all consenting adults"。也就是说,您不会试图隐藏 __init__
方法,但您确实记录了用户可能想改用其中一种便捷的构造函数。但如果用户认为他们真的知道自己在做什么,那么欢迎他们尝试。
您可以在标准库中看到这一理念的实际应用。提供了 inspect.Signature
. The class constructor takes a list of Parameter
, which a fairly complicated to create. This is not the standard way a user is expected to create a Signature instance. Rather a function called signature
,它接受一个可调用对象作为参数,并完成从 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))
正如 所解释的,这不是您通常想要做的事情。但无论如何这是可能的,方法如下:
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
我可能不需要说 handle 代码看起来绝对令人发指,而且它也使得无法将 __post_init__
用于其他任何事情,这是非常不幸的。但这是回答 post 中问题的一种方式。
考虑以下数据class。我想防止使用 __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
with init=False
in field
我防止 a
成为生成的 __init__
的参数,所以这部分解决了 c = C(1)
引发的问题一个例外。
另外,我不喜欢它作为解决方案。
是否有直接的方法来禁止从 class 外部调用 init 方法?
__init__
方法不负责从 class 创建实例。如果您想限制 class 的实例化,您应该覆盖 __new__
方法。但是如果你重写 __new__
方法也会影响任何形式的实例化,这意味着你的 classmethod
将不再起作用。因此,由于将实例创建委托给另一个函数通常不是 Pythonic,因此最好在 __new__
方法中执行此操作。详细原因可以在 doc:
Called to create a new instance of class
cls. __new__()
is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression (the call to the class). The return value of__new__()
should be the new object instance (usually an instance of cls).Typical implementations create a new instance of the class by invoking the superclass’s
__new__()
method usingsuper().__new__(cls[, ...])
with appropriate arguments and then modifying the newly-created instance as necessary before returning it.If
__new__()
returns an instance ofcls
, then the new instance’s__init__()
method will be invoked like__init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to__new__()
.If
__new__()
does not return an instance of cls, then the new instance’s__init__()
method will not be invoked.
__new__()
is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.
由于这不是对实例创建施加的标准限制,因此额外的一两行代码来帮助其他开发人员了解正在发生的事情/为什么禁止这样做可能是值得的。秉承 "We are all consenting adults" 的精神,__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_*
创建实例,但开发人员必须有意识地绕过您的障碍才能做到这一点。
试图在 Python 中将构造函数设为私有并不是一件非常 pythonic 的事情。 Python 的哲学之一是 "we're all consenting adults"。也就是说,您不会试图隐藏 __init__
方法,但您确实记录了用户可能想改用其中一种便捷的构造函数。但如果用户认为他们真的知道自己在做什么,那么欢迎他们尝试。
您可以在标准库中看到这一理念的实际应用。提供了 inspect.Signature
. The class constructor takes a list of Parameter
, which a fairly complicated to create. This is not the standard way a user is expected to create a Signature instance. Rather a function called signature
,它接受一个可调用对象作为参数,并完成从 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))
正如
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
我可能不需要说 handle 代码看起来绝对令人发指,而且它也使得无法将 __post_init__
用于其他任何事情,这是非常不幸的。但这是回答 post 中问题的一种方式。