Python class setter - 每次创建新实例时都会重新分配 class 属性吗?
Python class setter - are class attributes reassigned every time a new instance is created?
我正在尝试创建一个 class,它具有所有实例共享的基本属性,在初始化实例属性时用作模板。理想情况下,它将是 final/immutable,但这是 Python,所以我不会覆盖它。
class Foo:
class_template_attr = some_resource_intensive_operation()
def __init__(self, *args, **kwargs):
self.instance_attr = method_using_class_attribute()
def method_using_class_attribute():
# determine instance_specific_extras
return self.class_template_attr + instance_specific_extras
我想避免在每次实例化新的 Foo
时都重新生成 class_template_attr
,主要是因为这是一个资源密集型操作,而且还因为每个 Foo
所以它不应该被重新生成。我看了高低,一直没能确定class属性是否在创建新实例时重新计算
如果重新计算它们,我假设我可以定义一个 setter 来检查 class_template_attr
是否存在,然后创建它或传递属性(如果它已经存在)。我目前正在使用 metaclass 来执行此操作,但我想知道这是否是最 Pythonic 要做的事情(或者我一开始就做对了)。
class MetaFoo(type):
def __init__(cls, *args, **kwargs):
super.__init__(*args, **kwargs)
cls.class_template_attr = some_resource_intensive_operation()
@property
def class_template_attr(cls):
return cls.__class_template_attr
@class_template_attr.setter
def class_template_attr(cls, value):
if cls.__class_template_attr is None:
cls.__class_template_attr = value
else:
pass
def bar(input):
value = some_operation
return value
class Foo(metaclass=MetaFoo):
def __init__(self, object_specific_input):
self.instance_attr = bar(object_specific_input)
@property
def instance_attr(self):
return self.__instance_attr
@instance_attr.setter
def instance_attr(self, value):
self.__instance_attr = value
注意:我意识到实例属性的getter/setter在这里有点矫枉过正,但我正在使用它们出于特定目的,我不会为了 MRE 而深入。
我想知道你为什么认为如果你没有掌握classes和实例中最基本的属性,你就可以成功编写metaclasses。
事实上,你所描述的你需要的是 Python 中 object 的基本行为:你的属性是在 class body 是执行一次(通常在模块导入时),并且在所有实例中都可用。
此外,如果在一个实例中有代码执行类似以下操作:self.class_template_attr = "new value"
,那将仅在该实例中创建一个新属性,这将隐藏 class 属性 - 但仅此而已实例。原始值将保留所有其他实例的默认可见值。
现在,关于您的元class:您尝试在元class 本身上创建一个属性 并将其级联到classes - 那远射肯定是矫枉过正。
您的实施也存在一些问题 - 但以 non-error 开头:而“@属性”可以在 metaclass 上运行并可用作class object 上的 属性 可以工作,但您不会在任何文档中找到这方面的示例:由于事实上它们不应该以这种方式使用.它们起作用是因为 Python 的规则非常一致,然后,classes,作为 objects 和 metaclass 的实例,对描述符协议也不例外这就是“是什么让 property
工作”:metaclass 上的描述符将用于 class 上的属性检索。但是,metaclass 上的相同描述符不会 用于从 class.
的实例中检索属性
在实现上还有一些其他问题,但最糟糕的是过多的冗余。我要添加一个旁注,不要过多地依赖“私有”属性的双下划线前缀。它有时会有用,但肯定比具有适当的 public
、private
和 protected
修饰符的语言有用得多 - 它们更多地用于防止复杂的名称冲突class 使用多重继承和混入的层次结构,否则迟早会妨碍你。
Python
中的属性检索
关于myinstance.myattr
:
- Python 将首先检查该属性是否存在于 myinstance 的类中,如果存在,则它是否是描述符(属性值的 class 应该具有
__get__
方法。property
实现了那个)。如果是,则调用,结果值为 returned;
- Python 将检查实例本身的属性。通常,作为实例
__dict__
上的条目。如果是,则该值为 returned.
- Python 在实例的 class 中查找属性,不管它是描述符。
- 如果未找到,它将检查 class 是否具有
__getattr__
方法 - 将使用预期的属性名称调用该方法,并且 return 一个值或引发 AttributeError .
- 如果到目前为止没有找到任何内容,则搜索失败,并引发 AttributeError。
请注意,对于上面的步骤 (1)、(3) 和 (4),搜索的是 class,而不是它自己的元 class - 相反,搜索遵循继承链:在这些步骤的每一步中,都会搜索 superclasses(按照它们在 myinstance.__class__.__mro__
中显示的顺序)搜索属性。
对 read-only class 属性使用 property
如果设置普通 class 属性还不够,并且您希望防止实例意外地用 self.myattr = "foo"
覆盖实例值,可以创建只读 属性在 class body 上。无需求助于 metaclasses.
请注意,这仍然不会阻止属性在 class 本身中被覆盖(使用 self.__class__.myattr = "new value"
- 如果您想阻止 that , 那么你需要一个 metaclass 并自定义它的 __setattr__
方法。这就是 Pythonic 的“同意成人”的概念:如果一个人一路写 self.__class__.myattr = "new value"
,他们 非常 关心更改属性,无论 class 作者的意图如何,并愿意为此承担责任)。
无论如何,例如不可变属性,而且,作为额外的,你会得到延迟计算的昂贵值:它将在第一次实际需要时计算,而不是在模块导入时计算:这是一个交易关闭:您在应用程序启动时获得敏捷性,并在实际首次使用时支付计算成本:
class Foo:
@property
def my_attribute(self):
if not hasattr(self, "_my_attribute"):
self._my_attribute = some_resource_intensive_operation()
return self._my_attribute
现在 - 这就是所需要的。否“setter”:第一次读取值时,计算值并存储在“秘密”私有位置。在没有 setter 的情况下,尝试在实例中设置其值时会出现属性错误。不需要元classes。
我意识到我可以很容易地测试我自己的问题如下:
import random, string
def get_random_string():
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
random.seed(10)
class TestClassAttributeInstantiation:
class_attr = get_random_string()
def __init__(self):
self.inst_attr = get_random_string()
然后,在解释器中:
>>> TestClassAttributeInstantiation.class_attr
'OLPFVV'
>>> foo = TestClassAttributeInstantiation()
>>> foo.class_attr
'OLPFVV'
>>> foo.inst_attr
'QENIGY'
>>> bar = TestClassAttributeInstantiation()
>>> bar.class_attr
'OLPFVV'
>>> bar.inst_attr
'ZBWPJH'
如您所见,class_attr
是在定义 class 时设置的,而不是在实例化时设置的。这是显而易见的,因为所有实例化都有一个具有相同值的 class_attr
并且对 class 的引用也具有该值。
我正在尝试创建一个 class,它具有所有实例共享的基本属性,在初始化实例属性时用作模板。理想情况下,它将是 final/immutable,但这是 Python,所以我不会覆盖它。
class Foo:
class_template_attr = some_resource_intensive_operation()
def __init__(self, *args, **kwargs):
self.instance_attr = method_using_class_attribute()
def method_using_class_attribute():
# determine instance_specific_extras
return self.class_template_attr + instance_specific_extras
我想避免在每次实例化新的 Foo
时都重新生成 class_template_attr
,主要是因为这是一个资源密集型操作,而且还因为每个 Foo
所以它不应该被重新生成。我看了高低,一直没能确定class属性是否在创建新实例时重新计算
如果重新计算它们,我假设我可以定义一个 setter 来检查 class_template_attr
是否存在,然后创建它或传递属性(如果它已经存在)。我目前正在使用 metaclass 来执行此操作,但我想知道这是否是最 Pythonic 要做的事情(或者我一开始就做对了)。
class MetaFoo(type):
def __init__(cls, *args, **kwargs):
super.__init__(*args, **kwargs)
cls.class_template_attr = some_resource_intensive_operation()
@property
def class_template_attr(cls):
return cls.__class_template_attr
@class_template_attr.setter
def class_template_attr(cls, value):
if cls.__class_template_attr is None:
cls.__class_template_attr = value
else:
pass
def bar(input):
value = some_operation
return value
class Foo(metaclass=MetaFoo):
def __init__(self, object_specific_input):
self.instance_attr = bar(object_specific_input)
@property
def instance_attr(self):
return self.__instance_attr
@instance_attr.setter
def instance_attr(self, value):
self.__instance_attr = value
注意:我意识到实例属性的getter/setter在这里有点矫枉过正,但我正在使用它们出于特定目的,我不会为了 MRE 而深入。
我想知道你为什么认为如果你没有掌握classes和实例中最基本的属性,你就可以成功编写metaclasses。
事实上,你所描述的你需要的是 Python 中 object 的基本行为:你的属性是在 class body 是执行一次(通常在模块导入时),并且在所有实例中都可用。
此外,如果在一个实例中有代码执行类似以下操作:self.class_template_attr = "new value"
,那将仅在该实例中创建一个新属性,这将隐藏 class 属性 - 但仅此而已实例。原始值将保留所有其他实例的默认可见值。
现在,关于您的元class:您尝试在元class 本身上创建一个属性 并将其级联到classes - 那远射肯定是矫枉过正。
您的实施也存在一些问题 - 但以 non-error 开头:而“@属性”可以在 metaclass 上运行并可用作class object 上的 属性 可以工作,但您不会在任何文档中找到这方面的示例:由于事实上它们不应该以这种方式使用.它们起作用是因为 Python 的规则非常一致,然后,classes,作为 objects 和 metaclass 的实例,对描述符协议也不例外这就是“是什么让 property
工作”:metaclass 上的描述符将用于 class 上的属性检索。但是,metaclass 上的相同描述符不会 用于从 class.
在实现上还有一些其他问题,但最糟糕的是过多的冗余。我要添加一个旁注,不要过多地依赖“私有”属性的双下划线前缀。它有时会有用,但肯定比具有适当的 public
、private
和 protected
修饰符的语言有用得多 - 它们更多地用于防止复杂的名称冲突class 使用多重继承和混入的层次结构,否则迟早会妨碍你。
Python
中的属性检索关于myinstance.myattr
:
- Python 将首先检查该属性是否存在于 myinstance 的类中,如果存在,则它是否是描述符(属性值的 class 应该具有
__get__
方法。property
实现了那个)。如果是,则调用,结果值为 returned; - Python 将检查实例本身的属性。通常,作为实例
__dict__
上的条目。如果是,则该值为 returned. - Python 在实例的 class 中查找属性,不管它是描述符。
- 如果未找到,它将检查 class 是否具有
__getattr__
方法 - 将使用预期的属性名称调用该方法,并且 return 一个值或引发 AttributeError . - 如果到目前为止没有找到任何内容,则搜索失败,并引发 AttributeError。
请注意,对于上面的步骤 (1)、(3) 和 (4),搜索的是 class,而不是它自己的元 class - 相反,搜索遵循继承链:在这些步骤的每一步中,都会搜索 superclasses(按照它们在 myinstance.__class__.__mro__
中显示的顺序)搜索属性。
对 read-only class 属性使用 property
如果设置普通 class 属性还不够,并且您希望防止实例意外地用 self.myattr = "foo"
覆盖实例值,可以创建只读 属性在 class body 上。无需求助于 metaclasses.
请注意,这仍然不会阻止属性在 class 本身中被覆盖(使用 self.__class__.myattr = "new value"
- 如果您想阻止 that , 那么你需要一个 metaclass 并自定义它的 __setattr__
方法。这就是 Pythonic 的“同意成人”的概念:如果一个人一路写 self.__class__.myattr = "new value"
,他们 非常 关心更改属性,无论 class 作者的意图如何,并愿意为此承担责任)。
无论如何,例如不可变属性,而且,作为额外的,你会得到延迟计算的昂贵值:它将在第一次实际需要时计算,而不是在模块导入时计算:这是一个交易关闭:您在应用程序启动时获得敏捷性,并在实际首次使用时支付计算成本:
class Foo:
@property
def my_attribute(self):
if not hasattr(self, "_my_attribute"):
self._my_attribute = some_resource_intensive_operation()
return self._my_attribute
现在 - 这就是所需要的。否“setter”:第一次读取值时,计算值并存储在“秘密”私有位置。在没有 setter 的情况下,尝试在实例中设置其值时会出现属性错误。不需要元classes。
我意识到我可以很容易地测试我自己的问题如下:
import random, string
def get_random_string():
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
random.seed(10)
class TestClassAttributeInstantiation:
class_attr = get_random_string()
def __init__(self):
self.inst_attr = get_random_string()
然后,在解释器中:
>>> TestClassAttributeInstantiation.class_attr
'OLPFVV'
>>> foo = TestClassAttributeInstantiation()
>>> foo.class_attr
'OLPFVV'
>>> foo.inst_attr
'QENIGY'
>>> bar = TestClassAttributeInstantiation()
>>> bar.class_attr
'OLPFVV'
>>> bar.inst_attr
'ZBWPJH'
如您所见,class_attr
是在定义 class 时设置的,而不是在实例化时设置的。这是显而易见的,因为所有实例化都有一个具有相同值的 class_attr
并且对 class 的引用也具有该值。