英文:
Python: Dynamically add properties to class instance, properties return function value with inputs
问题
我一直在查看关于动态属性设置的所有Stackoverflow答案,但出于某种原因,似乎无法使其工作。
我有一个名为Evolution_Base
的类,在其init
中创建了一个Value_Differences
的实例。Value_Differences
应该基于我传递的列表动态创建properties
,并返回来自_get_df_change
的函数值:
from pandas import DataFrame
from dataclasses import dataclass
import pandas as pd
class Evolution_Base():
def __init__(self, res_date_0: DataFrame, res_date_1: DataFrame):
@dataclass
class Results_Data():
res_date_0_df: DataFrame
res_date_1_df: DataFrame
self.res = Results_Data(res_date_0_df=res_date_0, res_date_1_df=res_date_1)
property_list = ['abc', 'xyz']
self.difference = Value_Differences(parent=self, property_list=property_list)
# 共享函数
def _get_df_change(self, df_name, operator='-'):
df_0 = getattr(self.res.res_date_0_df, df_name.lower())
df_1 = getattr(self.res.res_date_1_df, df_name.lower())
return self._df_change(df_1, df_0, operator=operator)
def _df_change(self, df_1: pd.DataFrame, df_0: pd.DataFrame, operator='-') -> pd.DataFrame:
"""
返回 df_1 <operator | 默认 = -> df_0
"""
# 数值掩码
m_1 = df_1.select_dtypes('number')
m_0 = df_0.select_dtypes('number')
def label_me(x):
x.columns = ['t_1', 't_0']
return x
if operator == '-':
return label_me(df_1[m_1] - df_0[m_0])
elif operator == '+':
return label_me(df_1[m_1] + df_0[m_0])
class Value_Differences():
def __init__(self, parent: Evolution_Base, property_list=[]):
self._parent = parent
for name in property_list:
def func(self, prop_name):
return self._parent._get_df_change(name)
# 我尝试过以下方法...
setattr(self, name, property(fget=lambda cls_self: func(cls_self, name)))
setattr(self, name, property(func(self, name)))
setattr(self, name, property(func))
这让我很疯狂... 感谢任何帮助!
我的期望结果是:
evolution = Evolution_Base(df_1, df_2)
evolution.difference.abc == evolution._df_change('abc')
evolution.difference.xyz == evolution._df_change('xyz')
编辑:真正的简单问题是,如何为属性函数设置setattr
?
英文:
I've been going through all the Stackoverflow answers on dynamic property setting, but for whatever reason I can't seem to get this to work.
I have a class, Evolution_Base
, that in its init
creates an instance of Value_Differences
. Value_Differences
should be dynamically creating properties
, based on the list I pass, that returns the function value from _get_df_change
:
from pandas import DataFrame
from dataclasses import dataclass
import pandas as pd
class Evolution_Base():
def __init__(self, res_date_0 : DataFrame , res_date_1 : DataFrame):
@dataclass
class Results_Data():
res_date_0_df : DataFrame
res_date_1_df : DataFrame
self.res = Results_Data(res_date_0_df= res_date_0,
res_date_1_df= res_date_1)
property_list = ['abc', 'xyz']
self.difference = Value_Differences(parent = self, property_list=property_list)
# Shared Functions
def _get_df_change(self, df_name, operator = '-'):
df_0 = getattr(self.res.res_date_0_df, df_name.lower())
df_1 = getattr(self.res.res_date_1_df, df_name.lower())
return self._df_change(df_1, df_0, operator=operator)
def _df_change(self, df_1 : pd.DataFrame, df_0 : pd.DataFrame, operator = '-') -> pd.DataFrame:
"""
Returns df_1 <operator | default = -> df_0
"""
# is_numeric mask
m_1 = df_1.select_dtypes('number')
m_0 = df_0.select_dtypes('number')
def label_me(x):
x.columns = ['t_1', 't_0']
return x
if operator == '-':
return label_me(df_1[m_1] - df_0[m_0])
elif operator == '+':
return label_me(df_1[m_1] + df_0[m_0])
class Value_Differences():
def __init__(self, parent : Evolution_Base, property_list = []):
self._parent = parent
for name in property_list:
def func(self, prop_name):
return self._parent._get_df_change(name)
# I've tried the following...
setattr(self, name, property(fget = lambda cls_self: func(cls_self, name)))
setattr(self, name, property(func(self, name)))
setattr(self, name, property(func))
Its driving me nuts... Any help appreciated!
My desired outcome is for:
evolution = Evolution_Base(df_1, df_2)
evolution.difference.abc == evolution._df_change('abc')
evolution.difference.xyz == evolution._df_change('xyz')
EDIT: The simple question is really, how do I setattr for a property function?
答案1
得分: 4
这是一个相当复杂的问题。"Impossible is a big call, but I will say this: they don't intend you to do this." 这句话中,"Impossible is a big call" 的意思是 "不可能" 是一个相当大胆的说法,意思是说虽然这很困难,但并不是不可能的。"they don't intend you to do this" 意思是他们并不打算让你这样做。
在代码部分,作者讨论了 Python 中的类和属性的一些内部机制,以及如何使用 property
来自定义属性的 getter 函数。作者还提到了 Python 中的元类(Metaclass)和类(Class)之间的关系,以及类的实例化过程。
总的来说,这段文本讨论了 Python 中的一些高级概念和内部机制,以及如何使用 property
来实现自定义属性的 getter 函数。
英文:
This is quite the rabbit hole. Impossible is a big call, but I will say this: they don't intend you to do this. The 'Pythonic' way of achieving your example use case is the __getattr__
method. You could also override the __dir__
method to insert your custom attributes for discoverability.
This is the code for that:
class Value_Differences():
def __init__(self, parent : Evolution_Base, property_list = []):
self._parent = parent
self._property_list = property_list
def __dir__(self):
return sorted(set(
dir(super(Value_Differences, self)) + \
list(self.__dict__.keys()) + self._property_list))
def __getattr__(self, __name: str):
if __name in self._property_list:
return self._parent._get_df_change(__name)
But that wasn't the question, and respect for a really, really interesting question. This is one of those things that you look at and say 'hmm, should be possible' and can get almost to a solution. I initially thought what you asked for was technically possible, just very hacky to achieve. But it turns out that it would be very, very weird hackery if it was possible.
Two small foundational things to start with:
- Remind ourselves of the hierarchy of Python objects that the runtime is working with when defining and instantiating classes:
- The metaclass (defaulting to
type
), which is used to build classes. I'm going to refer to this as the Metaclass Type Object (MTO). - The class definition, which is used to build objects. I'm going to refer to this as the Class Type Object (CTO).
- And the class instance or object, which I'll refer to as the Class Instance Object (CIO).
MTOs are subclasses of type
. CTOs are subclasses of object
. CIOs are instances of CTOs, but instantiated by MTOs.
- Python runs code inside class definitions as if it was running a function:
class Class1:
print("1")
def __init__(self, v1):
print("4")
print("2")
print("3")
c1 = Class1("x")
print("5")
gives 1, 2, 3, 4, 5
Put these two things together with:
class Class1:
def attr1_get(self):
return 'attr1 value'
attr1 = property(attr1_get)
we are defining a function attr1_get as part of the class definition. We are then running an inline piece of code that creates an object of type property
. Note that this is just the name of the object's type - it isn't a property as you would describe it. Just an object with some attributes, being references to various functions. We then assign that object to an attribute in the class we are defining.
In the terms I used above, once that code is run we have a CTO instantiated as an object in memory that contains an attribute attr1
of type property
(an object subclass, containing a bunch of attributes itself - one of which is a reference to the function attr1_get
).
That can be used to instantiate an object, the CIO.
This is where the MTO comes in. You instantiate the property object while defining the CTO so that when the runtime applies the MTO to create the CIO from the CTO, an attribute on the CIO will be formed with a custom getter function for that attribute rather than the 'standard' getter function the runtime would use. The property
object means something to the type
object when it is building a new object
.
So when we run:
c1 = Class1()
we don't get a CIO c1
with an attribute attr1
that is an object of type property
. The metaclass of type type
formed a set of references against the attribute's internal state to all the functions we stored in the property
object. Note that this is happening inside the runtime, and you can't call this directly from your code - you just tell the type
metaclass to do it by using the property
wrapper object.
So if you directly assign a property()
result to an attribute of a CIO, you have a Pythonic object assigned that references some functions, but the internal state for the runtime to use to reference the getter, setter, etc. is not set up. The getter of an attribute that contains a property object is the standard getter and so returns the object instance, and not the result of the functions it wraps,
This next bit of code demonstrates how this flows:
print("Let's begin")
class MetaClass1(type):
print("Starting to define MetaClass1")
def __new__(cls, name, bases, dct):
x = super().__new__(cls, name, bases, dct)
print("Metaclass1 __new__({})".format(str(cls)))
return x
print("__new__ of MetaClass1 is defined")
def __init__(cls, name, bases, dct):
print("Metaclass1 __init__({})".format(str(cls)))
print("__init__ of MetaClass1 is defined")
print("Metaclass is defined")
class Class1(object,metaclass=MetaClass1):
print("Starting to define Class1")
def __new__(cls, *args, **kwargs):
print("Class1 __new__({})".format(str(cls)))
return super(Class1, cls).__new__(cls, *args, **kwargs)
print("__new__ of Class1 is defined")
def __init__(self):
print("Class1 __init__({})".format(str(self)))
print("__init__ of Class1 is defined")
def g1(self):
return 'attr1 value'
print("g1 of Class1 is defined")
attr1 = property(g1)
print("Class1.attr1 = ", attr1)
print("attr1 of Class1 is defined")
def addProperty(self, name, getter):
setattr(self, name, property(getter))
print("self.", name, " = ", getattr(self, name))
print("addProperty of Class1 is defined")
print("Class is defined")
c1 = Class1()
print("Instance is created")
print(c1.attr1)
def g2(cls):
return 'attr2 value'
c1.addProperty('attr2', g2)
print(c1.attr2)
I have put all those print statements there to demonstrate the order in which things happen very clearly.
In the middle, you see:
g1 of Class1 is defined
Class1.attr1 = <property object at 0x105115c10>
attr1 of Class1 is defined
We have created an object of type property
and assigned it to a class attribute.
Continuing:
addProperty of Class1 is defined
Metaclass1 __new__(<class '__main__.MetaClass1'>)
Metaclass1 __init__(<class '__main__.Class1'>)
Class is defined
The metaclass got instantiated, being passed first itself (__new__
) and then the class it will work on (__init__
). This happened right as we stepped out of the class definition. I have only included the metaclass to show what will happen with the type
metaclass by default.
Then:
Class1 __new__(<class '__main__.Class1'>)
Class1 __init__(<__main__.Class1 object at 0x105124c10>)
Instance is created
attr1 value
self. attr2 = <property object at 0x105115cb0>
<property object at 0x105115cb0>
Class1
is instantiated, providing first its type to __new__
and then its instance to __init__
.
We see that attr1
is instantiated properly, but attr2
is not. That is because setattr
is being called once the class instance is already constructed and is just saying attr2
is an instance of the class property
and not defining attr2
as the actual runtime construct of a property.
Which is made more clear if we run:
print(c1.attr2.fget(c1))
print(c1.attr1.fget(c1))
attr2
(a property object) isn't aware of the class or instance of the containing attribute's parent. The function it wraps still needs to be given the instance to work on.
attr1
doesn't know what to do with that, because as far as it is concerned it is a string object, and has no concept of how the runtime is mapping its getter.
答案2
得分: 4
作为要翻译的部分
## As asked
> how do I setattr for a property *function*?
To be usable as a `property`, the accessor function needs to be wrapped as a property and then assigned as an attribute **of the class, not** the instance.
That function, meanwhile, needs to have a single **unbound** parameter - which will be an instance of the class, but is **not** necessarily the current `self`. Its logic needs to use the current value of `name`, but [late binding will be an issue](https://stackoverflow.com/questions/2295290) because of the desire to [create lambdas in a loop](https://stackoverflow.com/questions/3431676).
A clear and simple way to work around this is to define a helper function accepting the `Value_Differences` instance and the `name` to use, and then [bind](https://stackoverflow.com/questions/277922/) the `name` value eagerly.
Naively:
```python
from functools import partial
def _get_from_parent(name, instance):
return instance._parent._get_df_change(name)
class Value_Differences:
def __init__(self, parent: Evolution_Base, property_list = []):
self._parent = parent
for name in property_list:
setattr(Value_Differences, name, property(
fget = partial(_get_from_parent, name)
))
However, this of course has the issue that every instance of Value_Differences
will set properties on the class, thus modifying what properties are available for each other instance. Further, in the case where there are many instances that should have the same properties, the setup work will be repeated at each instance creation.
The apparent goal
It seems that what is really sought, is the ability to create classes dynamically, such that a list of property names is provided and a corresponding class pops into existence, with code filled in for the properties implementing a certain logic.
There are multiple approaches to this.
Factory A: Adding properties to an instantiated template
Just like how functions can be nested within each other and the inner function will be an object that can be modified and return
ed (as is common when creating a decorator), a class body can appear within a function and a new class object (with the same name) is created every time the function runs. (The code in the OP already does this, for the Results_Data
dataclass.)
def example():
class Template:
pass
return Template
>>> TemplateA, TemplateB = example(), example()
>>> TemplateA is TemplateB
False
>>> isinstance(TemplateA(), TemplateB)
False
>>> isinstance(TemplateB(), TemplateA)
False
So, a "factory" for value-difference classes could look like
from functools import partial
def _make_value_comparer(property_names, access_func):
class ValueDifferences:
def __init__(self, parent):
self._parent = parent
for name in property_names:
setattr(Value_Differences, name, property(
fget = partial(access_func, name)
))
return ValueDifferences
Notice that instead of hard-coding a helper, this factory expects to be provided with a function that implements the access logic. That function takes two parameters: a property name, and the ValueDifferences
instance. (They're in that order because it's more convenient for functools.partial
usage.)
Factory B: Using the type
constructor directly
The built-in type
in Python has two entirely separate functions.
With one argument, it discloses the type of an object.
With three arguments, it creates a new type. The class
syntax is in fact syntactic sugar for a call to this builtin. The arguments are:
- a string name (will be set as the
__name__
attribute) - a list of classes to use as superclasses (will be set as
__bases__
) - a dict mapping attribute names to their values (including methods and properties - will become the
__dict__
, roughly)
In this style, the same factory could look something like:
from functools import partial
def _make_value_comparer(property_names, access_func):
methods = {
name: property(fget = partial(access_func, name)
for name in property_names
}
methods['__init__'] = lambda self, parent: setattr(self, '_parent', parent)
return type('ValueDifferences', [], methods)
Using the factory
In either of the above cases, EvolutionBase
would be modified in the same way.
Presumably, every EvolutionBase
should use the same ValueDifferences
class (i.e., the one that specifically defines abc
and xyz
properties), so the EvolutionBase
class can cache that class as a class attribute, and use it later:
class Evolution_Base():
def _get_from_parent(name, mvd):
# mvd._parent will be an instance of Evolution_Base.
return mvd._parent._get_df_change(name)
_MyValueDifferences = _make_value_comparer(['abc', 'xyz'], _get_from_parent)
def __init__(self, res_date_0 : DataFrame , res_date_1 : DataFrame):
@dataclass
class Results_Data():
res_date_0_df : DataFrame
res_date_1_df : DataFrame
self.res = Results_Data(res_date_0_df= res_date_0,
res_date_1_df= res_date_1)
self.difference = _MyValueDifferences(parent = self)
Notice that the cached _MyValueDifferences
class no longer requires a list of property names to be constructed. That's because it was already provided when the class was created. The actual thing that varies per instance of _MyValueDifferences
, is the parent
, so that's all that gets passed.
Simpler approaches
It seems that the goal is to have a class whose instances are tightly associated with instances of Evolution_Base
, providing properties specifically named abc
and xyz
that are computed using the Evolution_Base
's data.
That could just be hard-coded as a nested class:
class Evolution_Base:
class EBValueDifferences:
def __init__(self, parent):
self._parent = parent
@property
def abc(self):
return self._parent._get_df_change('abc')
@property
def xyz(self):
return self._parent._get_df_change('xyz')
def __init__(self, res_date_0 : DataFrame , res_date_1 : DataFrame):
@dataclass
class Results_Data():
res
<details>
<summary>英文:</summary>
## As asked
> how do I setattr for a property *function*?
To be usable as a `property`, the accessor function needs to be wrapped as a property and then assigned as an attribute **of the class, not** the instance.
That function, meanwhile, needs to have a single **unbound** parameter - which will be an instance of the class, but is **not** necessarily the current `self`. Its logic needs to use the current value of `name`, but [late binding will be an issue](https://stackoverflow.com/questions/2295290) because of the desire to [create lambdas in a loop](https://stackoverflow.com/questions/3431676).
A clear and simple way to work around this is to define a helper function accepting the `Value_Differences` instance and the `name` to use, and then [bind](https://stackoverflow.com/questions/277922/) the `name` value eagerly.
Naively:
from functools import partial
def _get_from_parent(name, instance):
return instance._parent._get_df_change(name)
class Value_Differences:
def init(self, parent: Evolution_Base, property_list = []):
self._parent = parent
for name in property_list:
setattr(Value_Differences, name, property(
fget = partial(_get_from_parent, name)
))
However, this of course has the issue that **every** instance of `Value_Differences` will set properties **on the class**, thus modifying what properties are available **for each other instance**. Further, in the case where there are many instances that should have the same properties, *the setup work will be repeated at each instance creation*.
----
## The apparent goal
It seems that what is really sought, is the ability to **create classes dynamically**, such that a list of property names is provided and a corresponding class pops into existence, with code filled in for the properties implementing a certain logic.
There are multiple approaches to this.
### Factory A: Adding properties to an instantiated template
Just like how functions can be nested within each other and the inner function will be an object that can be modified and `return`ed (as is common [when creating a decorator](https://stackoverflow.com/questions/739654)), a class body can appear within a function and a new class object (with the same name) is created every time the function runs. (The code in the OP already does this, for the `Results_Data` dataclass.)
def example():
class Template:
pass
return Template
>>> TemplateA, TemplateB = example(), example()
>>> TemplateA is TemplateB
False
>>> isinstance(TemplateA(), TemplateB)
False
>>> isinstance(TemplateB(), TemplateA)
False
So, a "factory" for value-difference classes could look like
from functools import partial
def _make_value_comparer(property_names, access_func):
class ValueDifferences:
def init(self, parent):
self._parent = parent
for name in property_names:
setattr(Value_Differences, name, property(
fget = partial(access_func, name)
))
return ValueDifferences
Notice that instead of hard-coding a helper, this factory expects to be provided with a function that implements the access logic. That function takes two parameters: a property name, and the `ValueDifferences` instance. (They're in that order because it's more convenient for `functools.partial` usage.)
### Factory B: Using the `type` constructor directly
The built-in `type` in Python has two entirely separate functions.
With one argument, it discloses the type of an object.
With three arguments, it creates a new type. The `class` syntax is in fact syntactic sugar for a call to this builtin. The arguments are:
* a string name (will be set as the `__name__` attribute)
* a list of classes to use as superclasses (will be set as `__bases__`)
* a dict mapping attribute names to their values (including methods and properties - will become the `__dict__`, roughly)
In this style, the same factory could look something like:
from functools import partial
def _make_value_comparer(property_names, access_func):
methods = {
name: property(fget = partial(access_func, name)
for name in property_names
}
methods['init'] = lambda self, parent: setattr(self, '_parent', parent)
return type('ValueDifferences', [], methods)
### Using the factory
In either of the above cases, `EvolutionBase` would be modified in the same way.
Presumably, every `EvolutionBase` should use the same `ValueDifferences` class (i.e., the one that specifically defines `abc` and `xyz` properties), so the `EvolutionBase` class can cache that class as a class attribute, and use it later:
class Evolution_Base():
def _get_from_parent(name, mvd):
# mvd._parent will be an instance of Evolution_Base.
return mvd._parent._get_df_change(name)
_MyValueDifferences = _make_value_comparer(['abc', 'xyz'], _get_from_parent)
def __init__(self, res_date_0 : DataFrame , res_date_1 : DataFrame):
@dataclass
class Results_Data():
res_date_0_df : DataFrame
res_date_1_df : DataFrame
self.res = Results_Data(res_date_0_df= res_date_0,
res_date_1_df= res_date_1)
self.difference = _MyValueDifferences(parent = self)
Notice that the cached `_MyValueDifferences` class no longer requires a list of property names to be constructed. That's because it was already provided **when the class** was created. The actual thing that varies per instance of `_MyValueDifferences`, is the `parent`, so that's all that gets passed.
----
## Simpler approaches
It seems that the goal is to have a class whose instances are tightly associated with instances of `Evolution_Base`, providing properties specifically named `abc` and `xyz` that are computed using the `Evolution_Base`'s data.
That could just be hard-coded as a nested class:
class Evolution_Base:
class EBValueDifferences:
def init(self, parent):
self._parent = parent
@property
def abc(self):
return self._parent._get_df_change('abc')
@property
def xyz(self):
return self._parent._get_df_change('xyz')
def __init__(self, res_date_0 : DataFrame , res_date_1 : DataFrame):
@dataclass
class Results_Data():
res_date_0_df : DataFrame
res_date_1_df : DataFrame
self.res = Results_Data(res_date_0_df = res_date_0,
res_date_1_df = res_date_1)
self.difference = EBValueDifferences(self)
# _get_df_change etc. as before
Even simpler, provide corresponding properties directly on `Evolution_Base`:
class Evolution_Base:
@property
def abc_difference(self):
return self._get_df_change('abc')
@property
def xyz_difference(self):
return self._get_df_change('xyz')
def __init__(self, res_date_0 : DataFrame , res_date_1 : DataFrame):
@dataclass
class Results_Data():
res_date_0_df : DataFrame
res_date_1_df : DataFrame
self.res = Results_Data(res_date_0_df = res_date_0,
res_date_1_df = res_date_1)
# _get_df_change etc. as before
client code now calls my_evolution_base.abc_difference
instead of my_evolution_base.difference.abc
If there are a lot of such properties, they could be attached using a **much simpler** dynamic approach (that would still be reusable for other classes that define a `_get_df_change`):
def add_df_change_property(name, cls):
setattr(
cls, f'{name}_difference',
property(fget = lambda instance: instance._get_df_change(name))
)
which can also be adapted for use as a decorator:
from functools import partial
def exposes_df_change(name):
return partial(add_df_change_property, name)
@exposes_df_change('abc')
@exposes_df_change('def')
class Evolution_Base:
# self.difference
can be removed, no other changes needed
</details>
# 答案3
**得分**: 3
你尝试的东西之所以不起作用的根本原因是,属性(描述符的用例)必须按设计存储为类变量,而不是实例属性。
摘自[描述符文档](https://docs.python.org/3/howto/descriptor.html#simple-example-a-descriptor-that-returns-a-constant):
> 为了使用描述符,它必须存储为**类变量**在
> 另一个类中:
要创建一个具有动态命名属性并访问父类的类,一种优雅的方法是在主类的方法内创建该类,并使用`setattr`创建具有动态名称和属性对象的类属性。在方法的闭包中创建的类会自动访问父实例的`self`对象,避免了在尝试中像您所做的那样管理笨重的`_parent`属性:
```python
class Evolution_Base:
def __init__(self, property_list):
self.property_list = property_list
self._difference = None
@property
def difference(self):
if not self._difference:
class Value_Differences:
pass
for name in self.property_list:
# 使用默认值存储每次迭代中name的值
def func(obj, prop_name=name):
return self._get_df_change(prop_name) # 通过闭包访问self
setattr(Value_Differences, name, property(func))
self._difference = Value_Differences()
return self._difference
def _get_df_change(self, df_name):
return f'df change of {df_name}' # 演示目的的简化返回值
# 这样:
evolution = Evolution_Base(['abc', 'xyz'])
print(evolution.difference.abc)
print(evolution.difference.xyz)
将输出:
df change of abc
df change of xyz
演示链接:https://replit.com/@blhsing/ExtralargeNaturalCoordinate
英文:
The fundamental reason why what you tried doesn't work is that a property, a use case of a descriptor, by design must be stored as a class variable, not as an instance attribute.
Excerpt from the documentation of descriptor:
> To use the descriptor, it must be stored as a class variable in
> another class:
To create a class with dynamically named properties that has access to a parent class, one elegant approach is to create the class within a method of the main class, and use setattr
to create class attributes with dynamic names and property objects. A class created in the closure of a method automatically has access to the self
object of the parent instance, avoiding having to manage a clunky _parent
attribute like you do in your attempt:
class Evolution_Base:
def __init__(self, property_list):
self.property_list = property_list
self._difference = None
@property
def difference(self):
if not self._difference:
class Value_Differences:
pass
for name in self.property_list:
# use default value to store the value of name in each iteration
def func(obj, prop_name=name):
return self._get_df_change(prop_name) # access self via closure
setattr(Value_Differences, name, property(func))
self._difference = Value_Differences()
return self._difference
def _get_df_change(self, df_name):
return f'df change of {df_name}' # simplified return value for demo purposes
so that:
evolution = Evolution_Base(['abc', 'xyz'])
print(evolution.difference.abc)
print(evolution.difference.xyz)
would output:
df change of abc
df change of xyz
Demo: https://replit.com/@blhsing/ExtralargeNaturalCoordinate
答案4
得分: 2
回应您的问题,您可以创建一个类:
class FooBar:
def __init__(self, props):
def make_prop(name):
return property(lambda accessor_self: self._prop_impl(name))
self.accessor = type(
'Accessor',
tuple(),
{p: make_prop(p) for p in props}
)()
def _prop_impl(self, arg):
return arg
o = FooBar(['foo', 'bar'])
assert o.accessor.foo == o._prop_impl('foo')
assert o.accessor.bar == o._prop_impl('bar')
此外,缓存创建的类将有助于使等效对象更加相似,并消除与相等比较可能相关的问题。
话虽如此,我不确定是否需要这样做。用属性访问(o.a
)替换方法调用语法(o.f('a')
)的好处很小。我认为这可能会在多个方面产生不利影响:动态属性很容易引起混淆,难以文档化等等。最后,在动态Python的疯狂世界中,这些都不能严格保证,它们传达了错误的信息:访问是廉价的,不涉及计算,也许您可以尝试写入它。
英文:
Responding directly to your question, you can create a class:
class FooBar:
def __init__(self, props):
def make_prop(name):
return property(lambda accessor_self: self._prop_impl(name))
self.accessor = type(
'Accessor',
tuple(),
{p: make_prop(p) for p in props}
)()
def _prop_impl(self, arg):
return arg
o = FooBar(['foo', 'bar'])
assert o.accessor.foo == o._prop_impl('foo')
assert o.accessor.bar == o._prop_impl('bar')
Further, it would be beneficiary to cache created class to make equivalent objects more similar and eliminate potential issues with equality comparison.
That said, I am not sure if this is desired. There's little benefit of replacing method call syntax (o.f('a')
) with property access (o.a
). I believe it can be detrimental on multiple accounts: dynamic properties are confusing, harder to document, etc., finally while none of this is strictly guaranteed in crazy world of dynamic python -- they kind of communicate wrong message: that the access is cheap and does not involve computation and that perhaps you can attempt to write to it.
答案5
得分: 1
我认为当您在循环中定义函数func时,它会封闭当前name变量的值,而不是在访问属性时的name变量值。为了修复这个问题,您可以使用lambda函数来创建一个闭包,捕获在定义属性时的name的值。
class Value_Differences():
def __init__(self, parent: Evolution_Base, property_list=[]):
self._parent = parent
for name in property_list:
setattr(self, name, property(fget=lambda self, name=name: self._parent._get_df_change(name)))
这对您有帮助吗?
英文:
I think that when you define the function func in the loop, it closes over the current value of the name variable, not the value of the name variable at the time the property is accessed. To fix this, you can use a lambda function to create a closure that captures the value of name at the time the property is defined.
class Value_Differences():
def __init__(self, parent : Evolution_Base, property_list = []):
self._parent = parent
for name in property_list:
setattr(self, name, property(fget = lambda self, name=name: self._parent._get_df_change(name)))
Does this help you ?
答案6
得分: 0
这是您提供的代码的翻译:
第一个示例:
简单的问题实际上是,如何为属性函数设置setattr?
在Python中,我们可以像这样设置动态属性:
class DynamicProperties():
def __init__(self, property_list):
self.property_list = property_list
def add_properties(self):
for name in self.property_list:
setattr(self.__class__, name, property(fget=lambda self: 1))
dync = DynamicProperties(['a', 'b'])
dync.add_properties()
print(dync.a) # 打印 1
print(dync.b) # 打印 1
第二个示例:
纠正我如果我错了,但从审查您的代码来看,您想创建动态属性,然后将它们的值设置为在同一类中的特定函数调用,其中传递的数据是在构造函数“__init__”中传递的属性。这是可以实现的,以下是一个示例:
class DynamicProperties():
def __init__(self, property_list, data1, data2):
self.property_list = property_list
self.data1 = data1
self.data2 = data2
def add_properties(self):
for name in self.property_list:
setattr(self.__class__, name, property(fget=lambda self: self.change(self.data1, self.data2) ))
def change(self, data1, data2):
return data1 - data2
dync = DynamicProperties(['a', 'b'], 1, 2)
dync.add_properties()
print(dync.a == dync.change(1, 2)) # 打印 true
print(dync.b == dync.change(1,2)) # 打印 true
希望这有所帮助。
英文:
The simple question is really, how do I setattr for a property function?
In python we can set dynamic attributes like this:
class DynamicProperties():
def __init__(self, property_list):
self.property_list = property_list
def add_properties(self):
for name in self.property_list:
setattr(self.__class__, name, property(fget=lambda self: 1))
dync = DynamicProperties(['a', 'b'])
dync.add_properties()
print(dync.a) # prints 1
print(dync.b) # prints 1
Correct me if I am wrong but from reviewing your code, you want to create a dynamic attributes then set their value to a specific function call within the same class, where the passed in data is passed in attributes in the constructor " init " this is achievable, an example:
class DynamicProperties():
def __init__(self, property_list, data1, data2):
self.property_list = property_list
self.data1 = data1
self.data2 = data2
def add_properties(self):
for name in self.property_list:
setattr(self.__class__, name, property(fget=lambda self: self.change(self.data1, self.data2) ))
def change(self, data1, data2):
return data1 - data2
dync = DynamicProperties(['a', 'b'], 1, 2)
dync.add_properties()
print(dync.a == dync.change(1, 2)) # prints true
print(dync.b == dync.change(1,2)) # prints true
答案7
得分: 0
你只需为成员添加更多复杂性,__getattr__ / __setattr__
会给你字符串,因此可以根据需要进行解释。这样做的最大“问题”是返回可能不一致,将其传递回期望对象具有特定行为的库可能会引发软错误。
这个示例与你的不同,但具有相同的概念,通过成员来操作列。要获取具有更改的副本,不需要使用 set,只需使用副本,修改并返回,可以创建具有所需内容的新实例。
例如,此行中的 __getattr__
将执行以下操作:
- 检查并解释字符串
xyz_mull_0
- 验证成员和操作数是否存在
- 复制
data_a
- 修改复制品并返回它
var = data_a.xyz_mull_0()
这看起来比实际情况要复杂一些,对于具有相同实例成员的情况,清楚地了解它正在做什么,但 _of
修饰符需要一个回调,这是因为 __getattr__
只能有一个参数,所以它需要保存 attr
并返回一个回调,该回调将与其他实例一起调用,然后将回调到 __getattr__
并完成函数的其余部分。
import re
class FlexibleFrame:
operand_mod = {
'sub': lambda a, b: a - b,
'add': lambda a, b: a + b,
'div': lambda a, b: a / b,
'mod': lambda a, b: a % b,
'mull': lambda a, b: a * b,
}
@staticmethod
def add_operand(name, func):
if name not in FlexibleFrame.operand_mod.keys():
FlexibleFrame.operand_mod[name] = func
# This makes this class subscriptable
def __getitem__(self, item):
return self.__dict__[item]
# Uses:
# -> object.value
# -> object.member()
# -> object.<name>_<operand>_<name|int>()
# -> object.<name>_<operand>_<name|int>_<flow>()
def __getattr__(self, attr):
if re.match(r'^[a-zA-Z]+_[a-zA-Z]+_[a-zA-Z0-9]+(_of)?$', attr):
seg = attr.split('_')
var_a, operand, var_b = seg[0:3]
# If there is a _of: the second operand is from the other
# instance, the _of is removed and a callback is returned
if len(seg) == 4:
self.__attr_ref = '_'.join(seg[0:3])
return self.__getattr_of
# Checks if this was a _of attribute and resets it
if self.__back_ref is not None:
other = self.__back_ref
self.__back_ref = None
self.__attr_ref = None
else:
other = self
if var_a not in self.__dict__:
raise AttributeError(
f'No match of {var_a} in (primary) {__class__.__name__}'
)
if operand not in FlexibleFrame.operand_mod.keys():
raise AttributeError(
f'No match of operand {operand}'
)
# The return is a copy of self, if not the instance
# is getting modified making x = a.b() useless
ret = FlexibleFrame(**self.__dict__)
# Checks if the second operand is an int
if re.match(r'^\d+$', var_b) :
ref_b_num = int(var_b)
for i in range(len(self[var_a])):
ret[var_a][i] = FlexibleFrame.operand_mod[operand](
self[var_a][i], ref_b_num
)
elif var_b in other.__dict__:
for i in range(len(self[var_a])):
# out_index = operand[type](in_a_index, in_b_index)
ret[var_a][i] = FlexibleFrame.operand_mod[operand](
self[var_a][i], other[var_b][i]
)
else:
raise AttributeError(
f'No match of {var_b} in (secondary) {__class__.__name__}'
)
# This swaps the .member to a .member()
# it also adds an extra () in __getattr_of
return lambda: ret
# return ret
if attr in self.__dict__:
return self[attr]
raise AttributeError(
f'No match of {attr} in {__class__.__name__}'
)
def __getattr_of(self, other):
self.__back_ref = other
return self.__getattr__(self.__attr_ref)()
def __init__(self, **kwargs):
self.__back_ref = None
self.__attr_ref = None
#TODO: Check if data columns match in size
# if not, implement column_<name>_filler=<default>
for i in kwargs:
self.__dict__[i] = kwargs[i]
if __name__ == '__main__':
data_a = FlexibleFrame(**{
'abc': [i for i in range(10)],
'nmv': [i for i in range(10)],
'xyz': [i for i in range(10)],
})
data_b = FlexibleFrame(**{
'fee': [i + 10 for i in range(10)],
'foo': [i + 10 for i in range(10)],
})
FlexibleFrame.add_operand('set', lambda a, b: b)
var = data_a.xyz_mull_0()
var = var.abc_set_xyz()
var = var.xyz_add_fee_of(data_b)
英文:
You just have to add more complexity to the member, __getattr__ / __setattr__
gives you the string, so it can be interpreted as needed. The biggest "problem" doing this is that the return might no be consistent and piping it back to a library that expect an object to have a specific behavior can cause soft errors.
This example is not the same as yours, but it has the same concept, manipulate columns with members. To get a copy with changes a set is not needed, with a copy, modify and return, the new instance can be created with whatever needed.
For example, the __getattr__
in this line will:
- Check and interpret the string
xyz_mull_0
- Validate that the members and the operand exists
- Make a copy of
data_a
- Modify the copy and return it
var = data_a.xyz_mull_0()
This looks more complex that it actually is, with the same instance members its clear what it is doing, but the _of
modifier needs a callback, this is because the __getattr__
can only have one parameter, so it needs to save the attr
and return a callback to be called with the other instance that then will call back to the __getattr__
and complete the rest of the function.
import re
class FlexibleFrame:
operand_mod = {
'sub': lambda a, b: a - b,
'add': lambda a, b: a + b,
'div': lambda a, b: a / b,
'mod': lambda a, b: a % b,
'mull': lambda a, b: a * b,
}
@staticmethod
def add_operand(name, func):
if name not in FlexibleFrame.operand_mod.keys():
FlexibleFrame.operand_mod[name] = func
# This makes this class subscriptable
def __getitem__(self, item):
return self.__dict__[item]
# Uses:
# -> object.value
# -> object.member()
# -> object.<name>_<operand>_<name|int>()
# -> object.<name>_<operand>_<name|int>_<flow>()
def __getattr__(self, attr):
if re.match(r'^[a-zA-Z]+_[a-zA-Z]+_[a-zA-Z0-9]+(_of)?$', attr):
seg = attr.split('_')
var_a, operand, var_b = seg[0:3]
# If there is a _of: the second operand is from the other
# instance, the _of is removed and a callback is returned
if len(seg) == 4:
self.__attr_ref = '_'.join(seg[0:3])
return self.__getattr_of
# Checks if this was a _of attribute and resets it
if self.__back_ref is not None:
other = self.__back_ref
self.__back_ref = None
self.__attr_ref = None
else:
other = self
if var_a not in self.__dict__:
raise AttributeError(
f'No match of {var_a} in (primary) {__class__.__name__}'
)
if operand not in FlexibleFrame.operand_mod.keys():
raise AttributeError(
f'No match of operand {operand}'
)
# The return is a copy of self, if not the instance
# is getting modified making x = a.b() useless
ret = FlexibleFrame(**self.__dict__)
# Checks if the second operand is a int
if re.match(r'^\d+$', var_b) :
ref_b_num = int(var_b)
for i in range(len(self[var_a])):
ret[var_a][i] = FlexibleFrame.operand_mod[operand](
self[var_a][i], ref_b_num
)
elif var_b in other.__dict__:
for i in range(len(self[var_a])):
# out_index = operand[type](in_a_index, in_b_index)
ret[var_a][i] = FlexibleFrame.operand_mod[operand](
self[var_a][i], other[var_b][i]
)
else:
raise AttributeError(
f'No match of {var_b} in (secondary) {__class__.__name__}'
)
# This swaps the .member to a .member()
# it also adds and extra () in __getattr_of
return lambda: ret
# return ret
if attr in self.__dict__:
return self[attr]
raise AttributeError(
f'No match of {attr} in {__class__.__name__}'
)
def __getattr_of(self, other):
self.__back_ref = other
return self.__getattr__(self.__attr_ref)()
def __init__(self, **kwargs):
self.__back_ref = None
self.__attr_ref = None
#TODO: Check if data columns match in size
# if not, implement column_<name>_filler=<default>
for i in kwargs:
self.__dict__[i] = kwargs[i]
if __name__ == '__main__':
data_a = FlexibleFrame(**{
'abc': [i for i in range(10)],
'nmv': [i for i in range(10)],
'xyz': [i for i in range(10)],
})
data_b = FlexibleFrame(**{
'fee': [i + 10 for i in range(10)],
'foo': [i + 10 for i in range(10)],
})
FlexibleFrame.add_operand('set', lambda a, b: b)
var = data_a.xyz_mull_0()
var = var.abc_set_xyz()
var = var.xyz_add_fee_of(data_b)
As a extra thing, lambdas in python have this thing, so it can make difficult using them when self changes.
答案8
得分: 0
看起来你正在扭曲语言来做奇怪的事情。我会将其视为你的代码可能变得复杂的迹象,但我不是说永远不会有用例,所以这里是一个如何做的最小示例:
class Obj:
def _df_change(self, arg):
print('change', arg)
class DynAttributes(Obj):
def __getattr__(self, name):
return self._df_change(name)
class Something:
difference = DynAttributes()
a = Something()
b = Obj()
assert a.difference.hello == b._df_change('hello')
英文:
It seems you're bending the language to do weird things. I'd take it as a smell that your code is probably getting convoluted but I'm not saying there would never be a use-case for it so here is a minimal example of how to do it:
class Obj:
def _df_change(self, arg):
print('change', arg)
class DynAttributes(Obj):
def __getattr__(self, name):
return self._df_change(name)
class Something:
difference = DynAttributes()
a = Something()
b = Obj()
assert a.difference.hello == b._df_change('hello')
答案9
得分: -1
在调用setattr
时,使用self.__class__
而不是self
示例代码:
class A:
def __init__(self, names: List[str]):
for name in names:
setattr(self.__class__, name, property(fget=self.__create_getter(name)))
def __create_getter(self, name: str):
def inner(self):
print(f"调用 {name}")
return 10
return inner
a = A(['x', 'y'])
print(a.x + 1)
print(a.y + 2)
英文:
When calling setattr
, use self.__class__
instead of self
Code sample:
class A:
def __init__(self,names : List[str]):
for name in names:
setattr(self.__class__,name,property(fget=self.__create_getter(name)))
def __create_getter(self,name: str):
def inner(self):
print(f"invoking {name}")
return 10
return inner
a = A(['x','y'])
print(a.x + 1)
print(a.y + 2)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论