Python 禁止从类外部使用默认构造函数

Python 禁止从类外部使用默认构造函数,python,class,python-3.7,python-dataclasses,Python,Class,Python 3.7,Python Dataclasses,考虑以下数据类。我想阻止使用direclty方法创建对象 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) @

考虑以下数据类。我想阻止使用direclty方法创建对象

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
,我防止
a
成为生成的
\uuuuuu init\uuuuu
的参数,因此这部分解决了问题,因为
c=c(1)
引发异常。
而且,我不喜欢把它作为解决方案


有没有一种直接的方法可以禁止从类外部调用init方法?

方法不负责从类创建实例。如果希望限制类的实例化,则应重写
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu新方法。但是,如果覆盖
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。因此,由于将实例创建委托给另一个函数通常不是Pythonic的,因此最好在
\uuuuu new\uuuu
方法中执行此操作。具体原因如下所示:

调用以创建类
cls的新实例__new_u()
是一个静态方法(有特殊的大小写,因此不需要声明),它将请求实例的类作为其第一个参数。其余的参数是传递给对象构造函数表达式(类调用)的参数。
\uuuu new\uuuu()
的返回值应该是新对象实例(通常是cls的实例)

典型的实现通过调用超类的
\uuuu new\uuu()
方法,使用
super()。\uuu new\uu(cls[,…])
和适当的参数来创建类的新实例,然后在返回它之前根据需要修改新创建的实例

如果
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

如果
\uuuu new\uuuuuuuuuuuuuuu()
未返回cls实例,则不会调用新实例的
\uuuuuuuuuuu init\uuuuuuuuuuuuuu()
方法

\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu()
主要用于允许不可变类型的子类(如int、str或tuple)自定义实例创建。为了自定义类的创建,它通常在自定义元类中被重写


由于这不是对实例创建施加的标准限制,因此额外增加一两行代码来帮助其他开发人员了解发生了什么/为什么禁止这样做可能是值得的。本着“我们都是同意的成年人”的精神,您的
\uuuu init\uuuu
的一个隐藏参数可能是易于理解和易于实施之间的一个很好的平衡:

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\u from.*
就可以创建实例,但是开发人员必须在知情的情况下绕过您的障碍才能做到这一点。

在Python中尝试将构造函数私有化并不是一件非常Python式的事情。Python的哲学之一是“我们都是同意的成年人”。也就是说,您不会试图隐藏
\uuuu init\uuu
方法,但您会记录用户可能希望使用其中一个方便的构造函数。但如果用户认为他们真的知道自己在做什么,那么欢迎他们尝试

您可以在标准库中看到这一理念的实际应用。具有类构造函数获取一个
参数列表
,这是一个相当复杂的创建过程。这不是用户创建签名实例的标准方式。而是提供了一个被调用的函数,该函数以一个可调用函数作为参数,并完成从CPython中的各种不同函数类型创建参数实例并将其编组到
签名
对象中的所有工作

也就是说,你可以这样做:

@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))
@dataclass
C类:
"""
类C表示blah。应使用C.create_from_创建C的实例
函数族。
"""
a:整数
b:str
c:浮球
@类方法
def从_int创建_(cls,x:int):
返回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

我可能不需要说句柄代码看起来非常可怕,而且它还使得无法将
\uuu post\uu init\uuu
用于其他任何东西,这是非常不幸的。但这是在你的帖子中回答这个问题的一种方式

当在
create\u from
中创建实例时,
\u new\u
不是也被调用吗?在我看来,无论何时创建实例,都会引发此异常。@chris,当然,我会在一分钟内对此进行解释。为了避免调用方在不了解其操作的情况下意外地传递参数,请将其设置为仅关键字:
def\uu init\uuuu(self,a,*,\u is\u direct=True):