听起来您想要make_lottery_set_type 返回一个新的class,大概是LotteryGameType 的子类,而不是返回该类型的实例。
这实际上在 Python 中很容易做到。类定义只是普通代码,您可以在任何地方运行,甚至在函数中间。他们可以在运行时访问本地环境。类本身是“一等值”,这意味着您可以传递它们并从函数中返回它们。所以:
def make_lottery_set_type(name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
class NewLotteryGameType(LotteryGameType):
def __init__(self, numbers):
super().__init__(name, size, minmax[0], minmax[1])
self.numbers = numbers
return NewLotteryGameType
如果你想添加其他方法,这与向任何其他类添加方法相同。例如:
def make_lottery_set_type(name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
class NewLotteryGameType(LotteryGameType):
def __init__(self, numbers):
super().__init__(name, size, minmax[0], minmax[1])
self.numbers = numbers
def __eq__(self, rhs):
return set(self.numbers) == set(rhs.numbers)
return NewLotteryGameType
所以:
>>> SuperLotto = make_lottery_set_type('SuperLotto', 6, (1,50))
>>> super1 = SuperLotto([1,2,3,4,5,6])
>>> super2 = SuperLotto([6,5,4,3,2,1])
>>> super3 = SuperLotto([7,8,9,10,11,12])
>>> super1 == super2
True
>>> super1 == super3
False
(显然,如果 set-equality 不适合您的使用规则,您可以随意定义 __eq__。)
如果您尝试检查您生成的值,它们看起来并不像您希望的那样漂亮。例如,您可能更愿意在以下位置看到SuperLotto 而不是NewLotteryGameType:
>>> super1
<__main__.NewLotteryGameType at 0x10259e490>
>>> SuperLotto.__name__
'NewLotteryGameType'
为此,只需添加NewLotteryGameType.__name__ = name。您可能还想从父类或其他各种东西中复制文档字符串。
更一般地,查看functools.update_wrapper(它是为封装函数而不是类,但许多细节是相同的)以获得灵感,以及来自您的 Python 版本的 inspect 模块文档类可以具有的属性。
在评论中,你问:
唯一的问题是我想让NewLotteryGameType继承LotteryGameType的name、set_size、min_set_number、max_set_number等参数。所以假设我想在 Python Shell 中输入 NewLotteryGameType.set_size。我希望它还给我 6.
这是矛盾的。如果你想继承LotteryGameType的实例属性……好吧,你已经做到了。例如:
>>> super1.set_size
6
如果您希望它们可以从类中访问,那么它们不能是实例属性,它们必须是类属性。只是将set_size 更改为LotteryGameType 的类属性并继承它是行不通的,因为类属性的全部意义在于该类的所有实例或其任何子类共享相同的值,并且子类都需要不同的值。
但你可以这样做:
class LotteryGameType:
def __init__(self, min_set_number, max_set_number):
self.min_set_number = min_set_number
self.max_set_number = max_set_number
def make_lottery_set_type(lottery_name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
class NewLotteryGameType(LotteryGameType):
name = lottery_name
set_size = size
def __init__(self, numbers):
super().__init__(minmax[0], minmax[1])
self.numbers = numbers
def __eq__(self, rhs):
return set(self.numbers) == set(rhs.numbers)
return NewLotteryGameType
(请注意,由于作用域的工作方式,我必须将第一个 make_ 参数重命名为 lottery_name,因此它与类属性 name 不同。)现在,name 和 set_size 是不是实例属性,也不是LotteryGameType 的类属性——但它们是每个NewLotteryGameType 的类属性。所以:
>>> SuperLotto = make_lottery_set_type('SuperLotto', 6, (1,50))
>>> SuperDuperLotto = make_lottery_set_type('SuperDuperLotto', 8, (1,100))
>>> SuperLotto.set_size
6
>>> SuperDuperLotto.set_size
8
如果您创建这些类型的实例会怎样?好吧,Python 在实例中查找属性,然后在派生最多的类中,然后是基类。因此,只要您不创建具有相同名称的实例属性(请注意,我从 LotteryGameType.__init__ 方法中删除了额外的参数和设置实例属性的代码),它就可以满足您的需求:
>>> super1 = SuperLotto([1,2,3,4,5,6])
>>> super1.set_size
6
>>> duper1 = SuperDuperLotto([1,2,3,4,5,6,7,8])
>>> duper1.set_size
8
当然这意味着LotteryGameType 本身不再是可用的类型;只有它的子类可用。但这可能就是你想要的,对吧?您甚至可以考虑将其明确设为 abstract base class,以确保没有人不小心尝试直接使用 LotteryGameType 实例。
如果您有勇气,您可能想阅读元类,看看如何将整个设计调整为使用 LotteryGameMetaclass,因此每个新类都是该元类的实例,而不是(抽象)基类。 3.4 中新的enum 模块的源代码,或几乎等效的外部flufl.enum 包,可能是很好的示例代码。然后,您可以同时使用它们,看看它们的相似之处和不同之处。