【问题标题】:Python: Dynamically add properties to class instance, properties return function value with inputsPython:动态地将属性添加到类实例,属性返回带有输入的函数值
【发布时间】:2023-01-30 20:59:53
【问题描述】:

我一直在浏览有关动态属性设置的所有 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)
        
    
    # 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))

它让我发疯......任何帮助表示赞赏!

我想要的结果是:

evolution = Evolution_Base(df_1, df_2)
evolution.difference.abc == evolution._df_change('abc')
evolution.difference.xyz == evolution._df_change('xyz')

编辑:这个简单的问题真的是,我如何为一个属性设置属性功能

【问题讨论】:

  • 没有理由为 Evolution_Base 的每个实例重新定义 Results_Data:只需全局定义它。
  • 显示预期的输入 (df_1, df_2) 和输出。
  • 有点旁注,但我怀疑您是否真的想将 property_list = [] 指定为 mutable default argument。也许你的意思是property_list: list
  • 我不知道你的用例,所以这可能是一个愚蠢的问题,但是你没有在 Value_Difference 上定义找零方法的原因是什么,如果这是你想要访问它的路线?如果它是您使用 Value_Difference 的主要目的,并且您不想继续键入方法名称,您甚至可以将其定义为 __call__。那么它就像evolution.difference('xyz')一样简单。
  • @keynesiancross 你能解释一下你为什么要这样做吗?在我看来,使用 python 动态设置类的属性会给代码库带来很多维护问题,如果你想这样做,我真的会考虑重新考虑。例如,您可以创建所有可能的属性(假设它是一个已定义的列表),并使它们返回 None 或其他一些无差异指示。它还将为那些与此类交互的人创建一个更清晰的 api,因此他们将来不可能获得AttributeErrors

标签: python


【解决方案1】:

正如所问

我如何为属性设置属性功能

要用作property,访问器函数需要包装为属性,然后分配为属性类的,不实例。

与此同时,该功能需要有一个未绑定参数 - 这将是该类的一个实例,但是不是必须是当前的self。它的逻辑需要使用name的当前值,但是late binding will be an issue因为希望create lambdas in a loop

解决此问题的一种清晰而简单的方法是定义一个辅助函数,接受 Value_Differences 实例和要使用的 name,然后热切地接受 bind name 值。

天真地:

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)
            ))

但是,这当然有一个问题每一个Value_Differences 的实例将设置属性在课堂上,从而修改可用的属性对于每个其他实例.此外,在有许多实例应该具有相同属性的情况下,设置工作将在每个实例创建时重复.


明显的目标

似乎真正寻求的是能够动态创建类,以便提供属性名称列表,并弹出相应的类,并为实现特定逻辑的属性填充代码。

有多种方法可以解决这个问题。

工厂 A:向实例化模板添加属性

就像函数如何相互嵌套一样,内部函数将是一个可以修改和returned(常见的when creating a decorator)的对象,一个类主体可以出现在一个函数和一个新的类对象(具有相同的名称)在每次函数运行时创建。 (OP 中的代码已经为 Results_Data 数据类执行了此操作。)

def example():
    class Template:
        pass
    return Template

>>> TemplateA, TemplateB = example(), example()
>>> TemplateA is TemplateB
False
>>> isinstance(TemplateA(), TemplateB)
False
>>> isinstance(TemplateB(), TemplateA)
False

因此,价值差异类的“工厂”可能看起来像

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

请注意,不是硬编码帮助程序,而是希望为该工厂提供实现访问逻辑的功能。该函数有两个参数:属性名称和ValueDifferences 实例。 (他们按这个顺序是因为 functools.partial 使用起来更方便。)

Factory B:直接使用type构造函数

Python 中内置的type 有两个完全独立的函数。

通过一个参数,它公开了对象的类型。 通过三个参数,它创建了一个新类型。 class 语法实际上是调用此内置函数的语法糖。论点是:

  • 一个字符串名称(将被设置为__name__属性)
  • 用作超类的类列表(将设置为__bases__
  • 将属性名称映射到它们的值的字典(包括方法和属性 - 大致会变成__dict__

在这种风格下,同一个工厂可能看起来像:

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)

使用工厂

在上述任何一种情况下,EvolutionBase 都会以相同的方式修改。

据推测,每个EvolutionBase都应该使用相同的ValueDifferences类(即专门定义abcxyz属性的类),因此EvolutionBase类可以将该类缓存为类属性,并使用它之后:

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)

请注意,缓存的 _MyValueDifferences 类不再需要构造属性名称列表。那是因为已经提供了上课的时候被创建。 _MyValueDifferences 的每个实例都不同的实际内容是 parent,因此这就是所有通过的内容。


更简单的方法

目标似乎是拥有一个类,其实例与 Evolution_Base 的实例紧密关联,提供专门命名为 abcxyz 的属性,这些属性是使用 Evolution_Base 的数据计算的。

这可能只是硬编码为嵌套类:

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

更简单的,直接在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

如果有很多这样的属性,可以使用简单多了动态方法(对于定义 _get_df_change 的其他类仍然可以重用):

def add_df_change_property(name, cls):
    setattr(
        cls, f'{name}_difference',
        property(fget = lambda instance: instance._get_df_change(name))
    )

也可以用作装饰器:

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

【讨论】:

    【解决方案2】:

    这真是个兔子洞。不可能是一个大问题,但我会这样说:他们不打算让你这样做。实现示例用例的“Pythonic”方法是 __getattr__ 方法。您还可以覆盖 __dir__ 方法以插入自定义属性以实现可发现性。

    这是为此的代码:

    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)
    

    但是那个这不是问题,并尊重一个非常非常有趣的问题。这是您看到并说“嗯,应该是可能的”并且几乎​​可以找到解决方案的事情之一。我最初以为你要求的是技术上可能,只是很难实现。但事实证明,如果可能的话,这将是非常非常奇怪的黑客行为。

    从两个小基础开始:

    1. 提醒我们自己在定义和实例化类时运行时使用的 Python 对象的层次结构:
      • 元类(默认为type),用于构建类。我将把它称为元类类型对象 (MTO)。
      • 类定义,用于构建对象。我将把它称为类类型对象 (CTO)。
      • 以及类实例或对象,我将其称为类实例对象 (CIO)。

      MTO 是 type 的子类。 CTO 是 object 的子类。 CIO 是 CTO 的实例,但由 MTO 实例化。

      1. Python 在类定义中运行代码,就好像它在运行函数一样:
      class Class1:
        print("1")
        def __init__(self, v1):
          print("4")
        print("2")
      print("3")
      c1 = Class1("x")
      print("5")
      

      给出 1、2、3、4、5

      将这两件事放在一起:

      class Class1:
        def attr1_get(self):
          return 'attr1 value'
        attr1 = property(attr1_get)
      

      我们将函数 attr1_get 定义为类定义的一部分。然后我们运行一段内联代码,创建一个property 类型的对象。请注意,这只是对象类型的名称——它不是您描述的属性。只是一个具有某些属性的对象,是对各种功能的引用。然后我们将该对象分配给我们正在定义的类中的一个属性。

      在我上面使用的术语中,一旦该代码运行,我们就会将 CTO 实例化为内存中的一个对象,该对象包含 property 类型的属性 attr1(一个对象子类,本身包含一堆属性 - 其中一个是对函数attr1_get 的引用)。

      即可以用来实例化一个对象,CIO。

      这就是 MTO 的用武之地。您在定义 CTO 时实例化属性对象,以便在运行时应用 MTO 从 CTO 创建 CIO 时,CIO 上的属性将使用该属性的自定义 getter 函数形成,而不是比运行时将使用的“标准”getter 函数。 property 对象在构建新的object 时对type 对象意味着某些东西。

      所以当我们运行时:

      c1 = Class1()
      

      我们没有得到一个 CIO c1,其属性 attr1property 类型的对象。 type 类型的元类形成了一组对属性内部状态的引用,指向我们存储在property 对象中的所有函数。请注意,这是在运行时内部发生的,您不能直接从您的代码中调用它 - 您只需告诉 type 元类通过使用 property 包装器对象来完成它。

      因此,如果您直接将 property() 结果分配给 CIO 的属性,您将分配一个引用某些函数的 Pythonic 对象,但未设置运行时用于引用 getter、setter 等的内部状态.包含属性对象的属性的 getter 是标准的 getter,因此返回对象实例,而不是它包装的函数的结果,

      下一段代码演示了它是如何流动的:

      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)
      

      我已将所有这些打印语句放在那里,以非常清楚地展示事情发生的顺序。

      在中间,您会看到:

      g1 of Class1 is defined
      Class1.attr1 =  <property object at 0x105115c10>
      attr1 of Class1 is defined
      

      我们创建了一个类型为property 的对象,并将其分配给一个类属性。

      继续:

      addProperty of Class1 is defined
      Metaclass1 __new__(<class '__main__.MetaClass1'>)
      Metaclass1 __init__(<class '__main__.Class1'>)
      Class is defined
      

      元类被实例化,首先被传递给它自己 (__new__),然后是它将工作的类 (__init__)。当我们走出类定义时,这就发生了。我只包含元类来显示默认情况下 type 元类会发生什么。

      然后:

      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 被实例化,首先将其类型提供给__new__,然后将其实例提供给__init__

      我们看到 attr1 被正确实例化,但 attr2 没有。这是因为一旦构造了类实例,就会调用 setattr,只是说 attr2 是类 property 的实例,而不是将 attr2 定义为属性的实际运行时构造。

      如果我们运行:

      print(c1.attr2.fget(c1))
      print(c1.attr1.fget(c1))
      

      attr2(属性对象)不知道包含属性的父级的类或实例。它包装的函数仍然需要被赋予实例来处理。

      attr1 不知道该怎么做,因为就它而言,它是一个字符串对象,并且不知道运行时如何映射它的 getter。

    【讨论】:

    • 感谢您试一试!所以你认为这不可能吗?
    • 不可能是一个大问题。自从上面回答以来,我花了很多时间阅读 Python 运行时环境的源代码。属性很吸引人。要实现你想要的(动态创建一个类属性,它是一个包装的 getter 函数,直接在现有类实例上实例化)需要两件看起来相互排斥的事情。我会编辑我的答案来解释,但这需要一些打字!
    • 天啊。 . .在这里,我认为这将是一些非常具体的 lambda 设置能够做到这一点的情况。 . .我不认为我会涉及源代码!
    • 所有这一切仅仅是因为我在使用属性时(每个类实例的结果是静态的)与我代码中的所有这些函数调用相比看起来有多“干净”
    【解决方案3】:

    您尝试的方法不起作用的根本原因是属性(描述符的用例)在设计上必须存储为类变量,而不是实例属性。

    摘自documentation of descriptor

    要使用描述符,它必须存储为类变量在 另一类:

    要创建具有可访问父类的动态命名属性的类,一种优雅的方法是在主类的方法中创建类,并使用 setattr 创建具有动态名称和属性对象的类属性。在方法闭包中创建的类自动访问父实例的 self 对象,避免像您在尝试时那样管理笨重的 _parent 属性:

    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
    

    以便:

    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

    【讨论】:

    • 您是否认为有一种方法可以做到这一点,而无需重新创建 Value_Differences 类并在每次调用 .difference 时为 property_list 中的每个名称重建一个函数?
    • 的确。您可以在实例属性中缓存 Value_Differences 的实例。那时我已经相应地更新了我的答案。
    【解决方案4】:

    直接回答你的问题,你可以创建一个类:

    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 的疯狂世界中没有严格保证这一切——他们传达了错误的信息:访问很便宜并且不涉及计算,也许您可​​以尝试写入它。

    【讨论】:

      【解决方案5】:

      我认为当您在循环中定义函数 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)))
      

      这对你有帮助吗?

      【讨论】:

      • 让我试一试然后再回来找你
      【解决方案6】:

      问题很简单,如何为属性函数设置属性?

      在 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) # prints 1
      print(dync.b) # prints 1 
      
      
      

      如果我错了,请纠正我,但是通过查看您的代码,您想创建一个动态属性,然后将它们的值设置为同一类中的特定函数调用,其中传入的数据在构造函数中传入属性“在里面“这是可以实现的,举个例子:

      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】:

        您只需为成员添加更多复杂性,__getattr__ / __setattr__ 为您提供字符串,因此可以根据需要对其进行解释。这样做的最大“问题”是返回可能不一致,并且将其管道返回到期望对象具有特定行为的库可能会导致软错误。

        这个例子和你的不一样,但是概念是一样的,用成员操作列。要获得具有更改的副本不需要,通过复制、修改和返回,可以根据需要创建新实例。

        例如,此行中的 __getattr__ 将:

        1. 检查并解释字符串xyz_mull_0
        2. 验证成员和操作数是否存在
        3. 复制data_a
        4. 修改副本并返回
          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 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)
          

          另外,python 中的 lambdas 有 this 东西,所以当自我改变时使用它们会很困难。

        【讨论】:

          【解决方案8】:

          看起来你正在扭曲语言来做奇怪的事情。我认为您的代码可能变得令人费解,但我并不是说它永远不会有用例,所以这里是一个如何做到这一点的最小示例:

          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】:

            调用 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"invoking {name}")
                        return 10
                    return inner
            
            a = A(['x','y'])
            
            print(a.x + 1)
            print(a.y + 2)
            

            【讨论】:

            • 这有错误的缩进,但很清楚应该如何缩进,所以我修复了它。这个想法很好而且是正确的,但是这里的解释很稀疏。
            猜你喜欢
            • 2021-07-26
            • 2020-03-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-15
            • 1970-01-01
            • 2019-04-28
            相关资源
            最近更新 更多