英文:
How to provide two different ways to instantiate
问题
假设我有一个名为AmbiguousClass
的类,它有两个属性a
和b
(假设它们都是int
类型,但也可以更一般化)。它们通过某个可逆方程相关联,因此我可以根据b
计算出a
,反之亦然。
我想要给用户提供一种方式来实例化AmbiguousClass
,用户可以根据自己方便的方式提供a
或b
中的任意一个。请注意,两种实例化变量的方式可以具有相同的签名,因此这不是典型的多态性示例。不提供参数或同时提供两个参数应该导致错误/警告。
我最好的猜测是这样的:
class AmbiguousClass():
def __init__(self, a=None, b=None):
# 如果没有提供参数,我们会引发错误(无法实例化)
if a is None and b is None:
raise SomeCustomError()
# 如果同时提供了a和b,这似乎是多余的,会发出警告
elif a is not None and b is not None:
warnings.warn(f"你确定需要同时指定a和b吗?")
self.a = a
self.b = b
# 如果提供了a,我们从a计算b
elif a is not None:
self.a = a
self.b = calculateB(self.a)
# 如果提供了b:
elif b is not None:
self.b = b
self.a = calculateA(self.b)
然后,用户可以通过指定提供的关键字来实例化该类:
var1, var2 = AmbiguousClass(a=3), AmbiguousClass(b=6)
然而,这种方式感觉有点笨拙,特别是如果用户决定在不提供关键字的情况下提供参数(默认将是a
,但从用户的角度来看并不清楚,可能会导致意外行为)。
如何在__init__
函数中以清晰的方式实现这一点,并避免意外行为呢?
英文:
Let's say I have a class AmbiguousClass
which has two attributes, a
and b
(let's say they are both int
, but it could be more general). They are related by some invertible equation, so that I can calculate a from b and reciprocally.
I want to give the user the possibility to instantiate an AmbiguousClass
by providing either a
or b
, depending on what is easier for them. Note that both ways to instantiate the variable can have the same signature, so this is not the typical polymorphism example. Providing none or both should result in an error/warning.
My best guess was something like this:
class AmbiguousClass():
def __init__(self, a=None, b=None):
#if no parameter is provided, we raise an error (can not instantiate)
if a is None and b is None:
raise SomeCustomError()
#if both a and b are provided, this seems redundant, a warning is raised
elif a is not None and b is not None:
warnings.warn(f"are you sure that you need to specify both a and b?")
self.a = a
self.b = b
# if a is provided, we calculate b from it
elif a is not None:
self.a = a
self.b = calculateB(self.a)
# if b is provided:
elif b is not None:
self.b =b
self.a = calculateA(self.b)
And then, the user would have to instantiate the class by specifying which keyword he is providing:
var1, var2 = AmbiguousClass(a=3), AmbiguousClass(b=6)
However, this feels a bit clunky, especially if the user decides to provide an argument without providing a keyword (it will by default be a
, but that is not clear from the user's point of view and might lead to unexpected behaviors).
How can I do this within the __init__
function in a way that is clear and will avoid unexpected behavior?
答案1
得分: 4
我会强制使用只接受一个参数的自定义构造函数,并清楚地指示提供了哪个参数,从而避免歧义。
class AmbiguousClass:
def __init__(self, a, b, _is_from_cls=False):
if not _is_from_cls:
raise TypeError(
"不能直接实例化 AmbiguousClass。请使用类方法 'from_a' 或 'from_b'。"
)
self.a = a
self.b = b
@classmethod
def from_a(cls, a):
b = calculateB(a)
return cls(a=a, b=b, _is_from_cls=True)
@classmethod
def from_b(cls, b):
a = calculateA(b)
return cls(a=a, b=b, _is_from_cls=True)
var1 = AmbiguousClass.from_a(a=3)
var2 = AmbiguousClass.from_b(b=6)
var3 = AmbiguousClass(3, 6) # 会引发 TypeError
var4 = AmbiguousClass(3, 6, True) # 不会引发 TypeError,但用户明确覆盖了默认参数。
英文:
I would enforce using custom constructors that only take a single parameter and clearly indicate which parameter is provided, thus avoiding ambiguity.
class AmbiguousClass:
def __init__(self, a, b, _is_from_cls=False):
if not _is_from_cls:
raise TypeError(
"Cannot instantiate AmbiguousClass directly."
" Use classmethods 'from_a' or 'from_b' instead."
)
self.a = a
self.b = b
@classmethod
def from_a(cls, a):
b = calculateB(a)
return cls(a=a, b=b, _is_from_cls=True)
@classmethod
def from_b(cls, b):
a = calculateA(b)
return cls(a=a, b=b, _is_from_cls=True)
var1 = AmbiguousClass.from_a(a=3)
var2 = AmbiguousClass.from_b(b=6)
var3 = AmbiguousClass(3, 6) # will raise a TypeError
var4 = AmbiguousClass(3, 6, True) # will not raise a TypeError but the user explicitly overwrites the default parameter.
答案2
得分: 1
你可以在构造函数中强制使用关键字参数。
这可能是个人偏好的问题,但我喜欢尽可能地进行“惰性”处理,也就是说,在类构造过程中只做最基本的操作。
可以像这样实现:
import warnings
class SomeCustomException(Exception):
...
class AmbiguousClass:
def __init__(self, *, a=None, b=None):
if a is None and b is None:
raise SomeCustomException('必须至少指定一个参数')
if a is not None and b is not None:
warnings.warn('确定需要同时指定 a 和 b 吗?')
self._a = a
self._b = b
def calculateA(self):
return 999
def calculateB(self):
return 666
@property
def a(self):
if self._a is None:
self._a = self.calculateA()
return self._a
@property
def b(self):
if self._b is None:
self._b = self.calculateB()
return self._b
print(AmbiguousClass(a=3).b)
这段代码中,构造函数使用了关键字参数a
和b
,如果两者都为None
,则抛出SomeCustomException
异常。如果同时指定了a
和b
,则会发出警告。属性a
和b
使用了@property
装饰器,当访问它们时,如果对应的私有变量为None
,则会调用calculateA()
和calculateB()
方法进行计算。最后打印出了AmbiguousClass(a=3).b
的结果。
英文:
You can enforce the use of keywords in the constructor.
This may be a matter of preference but I like to do as much "lazy" processing as possible - i.e., do the bare minimum during class construction
Something like this:
import warnings
class SomeCustomException(Exception):
...
class AmbiguousClass:
def __init__(self, *, a=None, b=None):
if a is None and b is None:
raise SomeCustomException('At least one parameter must be specified')
if a is not None and b is not None:
warnings.warn('Are you sure that you need to specify both a and b?')
self._a = a
self._b = b
def calculateA(self):
return 999
def calculateB(self):
return 666
@property
def a(self):
if self._a is None:
self._a = self.calculateA()
return self._a
@property
def b(self):
if self._b is None:
self._b = self.calculateB()
return self._b
print(AmbiguousClass(a=3).b)
答案3
得分: 1
更好的做法是一开始就不允许任何歧义。也就是说,强制使用关键字参数,并禁止调用者同时提供两个参数--没有任何好的理由可以这样做。
可以使用*
语法来强制使用关键字参数,而使用布尔比较可以禁止同时提供两个参数:
class UnambiguousClass:
def __init__(self, *, a=None, b=None):
assert (a is None) != (b is None), "必须提供且仅提供 `a` 和 `b` 中的一个。"
if a is None:
self.a = calculateA(b)
self.b = b
else:
self.a = a
self.b = calculateB(a)
英文:
A better practice would be to not allow any ambiguity at all in the first place. That is, enforce the use of keyword arguments, and disallow the caller to provide both arguments at the same time--there is simply no good reason for it.
Enforcing keyword arguments can be done with the *
syntax, while disallowing both arguments to be provided can be done by using a boolean comparison:
class UnambiguousClass:
def __init__(self, *, a=None, b=None):
assert (a is None) != (b is None), "Exactly one of `a` and `b` must be provided."
if a is None:
self.a = calculateA(b)
self.b = b
else:
self.a = a
self.b = calculateB(a)
答案4
得分: -1
使用两种不同方式进行模棱两可类(AmbiguousClass)的实例化的Pythonic方法
当设计一个类(比如AmbiguousClass
)时,实例化可以使用两个不同的属性(a
和b
),同时确保两者不会同时提供,你可以实现一个更Pythonic和更清晰的实现方式。为此,我们可以利用类方法作为替代构造函数,允许用户更直观地提供a
或b
,而不仅仅依赖关键字参数。
下面是使用类方法的更Pythonic的方法:
class AmbiguousClass():
def __init__(self, a=None, b=None):
# 如果没有提供参数,则抛出错误(无法实例化)
if a is None and b is None:
raise ValueError("必须提供'a'或'b'之一。")
# 如果同时提供了a和b,则发出警告
elif a is not None and b is not None:
warnings.warn("确定需要同时指定'a'和'b'吗?")
# 如果提供了a,则从a计算b
elif a is not None:
self.a = a
self.b = self.calculateB(self.a)
# 如果提供了b:
elif b is not None:
self.b = b
self.a = self.calculateA(self.b)
@classmethod
def from_a(cls, a):
# 使用'a'进行实例化
return cls(a=a)
@classmethod
def from_b(cls, b):
# 使用'b'进行实例化
return cls(b=b)
@staticmethod
def calculateB(a):
# 从'a'计算'b'的实现
pass
@staticmethod
def calculateA(b):
# 从'b'计算'a'的实现
pass
通过这种实现方式,用户现在可以使用替代构造函数from_a()
和from_b()
来实例化AmbiguousClass
,这样可以清楚地指定他们提供的属性。这种方法有助于避免在提供参数时没有指定关键字而导致的意外行为。
下面是如何实例化该类的示例:
# 使用from_a()构造函数
var1 = AmbiguousClass.from_a(3)
# 使用from_b()构造函数
var2 = AmbiguousClass.from_b(6)
这种方法提高了可读性,减少了意外错误的机会,为使用a
或b
实例化AmbiguousClass
提供了更Pythonic和用户友好的方式。
英文:
Pythonic Approach for AmbiguousClass Instantiation with Two Different Ways
When designing a class like AmbiguousClass
, where the instantiation can be done using two different attributes (a
and b
), while ensuring both are not provided simultaneously, you can indeed achieve a more Pythonic and cleaner implementation. To do this, we can utilize class methods as alternative constructors, allowing the user to provide a
or b
more intuitively without relying solely on keyword arguments.
Here's a more Pythonic approach using class methods:
class AmbiguousClass():
def __init__(self, a=None, b=None):
# If no parameter is provided, raise an error (cannot instantiate)
if a is None and b is None:
raise ValueError("Either 'a' or 'b' must be provided.")
# If both a and b are provided, raise a warning
elif a is not None and b is not None:
warnings.warn("Are you sure that you need to specify both 'a' and 'b'?")
# If a is provided, calculate b from it
elif a is not None:
self.a = a
self.b = self.calculateB(self.a)
# If b is provided:
elif b is not None:
self.b = b
self.a = self.calculateA(self.b)
@classmethod
def from_a(cls, a):
# Instantiate using 'a'
return cls(a=a)
@classmethod
def from_b(cls, b):
# Instantiate using 'b'
return cls(b=b)
@staticmethod
def calculateB(a):
# Your implementation to calculate 'b' from 'a'
pass
@staticmethod
def calculateA(b):
# Your implementation to calculate 'a' from 'b'
pass
With this implementation, The use can now instantiate AmbiguousClass
using the alternative constructors from_a()
and from_b()
, which provides clarity on what attribute they are providing. This approach helps to avoid unexpected behavior when you provide an argument without specifying the keyword.
Here's how you can now instantiate the class:
# Using the from_a() constructor
var1 = AmbiguousClass.from_a(3)
# Using the from_b() constructor
var2 = AmbiguousClass.from_b(6)
This approach promotes better readability and reduces the chances of unintentional errors, offering a more Pythonic and user-friendly way to instantiate AmbiguousClass
with either a
or b
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论