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