【问题标题】:Python: renaming a superclass's methods and their function-scope referencesPython:重命名超类的方法及其函数范围引用
【发布时间】:2015-02-28 01:09:41
【问题描述】:

考虑以下示例:

class Company():

    def hireEmployee():

    def fireEmployee():

    def promoteEmployee():

    etc...

class EngineeringFirm(Company):
    pass

class PaintingFirm(Company):
    pass

假设 Company 类有更多的方法。如果我想从超类中重命名这些方法,以便获得以下内容怎么办:

class EngineeringFirm(Company):

    def hireEngineer():
    ...

class PaintingFirm(Company):

    def hirePainter():
    ...

...等等。虽然在这种情况下使用“员工”确实不会有一点伤害,但这只是为了说明这个想法。我该怎么办?

我的想法是使用一个 classFactory 函数,它将员工的类型作为参数并生成一个 Company 类,而元类将通过遍历属性字典并将“员工”替换为所述类型来处理重命名。

class EngineeringFirm(companyFactory('Engineer'))
    ...

唯一的问题是:如果 Company 内部的方法通过默认的 'Employee' 名称相互调用会怎样?这就是我难过的地方。我的想法是,重命名方法所涉及的元类也可以获取每个函数的源(通过检查模块)并搜索是否在其中找到已知方法属性,如果是,则替换该部分并通过创建新函数exec 并将其分配回正确的属性键。

...但这看起来确实有点 hacky。我对替代方案持开放态度,尽管我意识到这个问题可能存在与设计相关的问题(我也愿意接受这方面的建议),但我很想知道这个问题是否有更优雅的解决方案。

谢谢!

编辑:另一种解决方案

为了争论,我暂时假设上面的代码确实是我正在使用的;我想我可以用我想到的另一种解决方案来解决 cmets 中的一些问题,我已经考虑过并放弃了,原因我将解释。

如果从 Company 继承的 Firm 类并且我希望维护一个相同的接口(在这种情况下通常会允许动态调用 hire()promote() 等),我可以实现一个 @ 987654328@ 接受 HirePainter()(通过访问原始的 Employee 方法),同时仍允许任何其他接口在必要时使用 HireEmployee()

我想知道,假设可以扩展我的问题,如果这会被认为是不好的做法,例如,我计划这样做是因为我认为 PaintingFirm 中的代码会提高可读性?再一次,我意识到这个例子很糟糕,因为这里的可读性确实似乎没有任何好处,但假设它有?

(我一开始没有提出这个想法的唯一原因是我的__getattribute__ 已经处理了很多,并且添加额外的噪音并没有那么吸引人。不过,我可以使用它,但这是我不得不问的一个问题,以防万一有更多神奇(但不是骇人听闻的)解决方案..)

【问题讨论】:

  • @Eithos 好吧,关键是,您的公司应该有一个相似的(如果不相同的话)界面,以便您可以告诉任何公司对象雇用某人,并让它决定雇用谁。所以你可以在不知道firm到底是什么的情况下做firm.hire()
  • @glglgl 有趣。这让我觉得很傻,因为在大多数其他情况下,这可能是我处理它的方式。完全有道理。我意识到问题很复杂,因为我还没有真正发布我的真实代码,而且几乎没有任何其他类可以/应该访问firm.hire() 的机会(如果有的话)。换句话说,虽然使用firm.hire 可以在事先不知道公司的情况下促进动态招聘,但这种动态处理确实没有必要。事实上,这些调用是由 UI 事件激活的……
  • ...只针对一个类;因此,他们永远不会称呼不同类型的“雇员”。我想这是我应该提到的......
  • 我认为这是一个很好的问题。我正在尝试在 Django 中创建一个 FormsetView 泛型类。它应该做 FormView 所做的所有事情,但用词 formset 代替。在这种情况下,我真的不需要FormsetView 成为FormView 的子类。我只是希望它是一个对属性名称进行细微更改的副本。

标签: python inheritance methods metaclass renaming


【解决方案1】:

为了子孙后代,我发布了我自己的解决方案,我认为这是一个不错的选择。我不建议将此作为答案,因为事实是我在我的问题中没有提到我更喜欢不添加额外的名称,或者保留将这些属性称为self.hireEngineer 而不是ClassDict['HireEngineer'] 的能力。鉴于此,我真的不能说这些答案中的任何一个都不能回答这个问题。

解决方案:

事后看来,问题比我想象的要简单得多。我想我只是为了它而迷上了元分类。如果还不是很明显,我真的只是在学习元类,有一段时间它似乎是一个尝试它们的好机会。唉。

我相信以下解决方案尊重 Liskov 原则的精神(谢谢,Ignacio),同时赋予派生类以自己的方式引用派生方法的能力。类命名空间保持不变,如果需要,其他对象可以使用它们的真实名称调用这些方法。

# superclass...

def __getattribute__(self, attr):

    # Early exit (AttributeError) if attribute not found.
    obj = object.__getattribute__(self, attr)

    # All the extra code...

def __getattr__(self, attr):

    # Ex. self.type == 'Engineer'
    # Replacing titled-cased and lower-cased 
    # versions just to be safe (ex. self.employeeNames)

    attr = (attr
        .replace(self.type, 'Employee')
        .replace(self.type.lower(), 'employee')
    )

    if attr in self.attributes:
        return self.__getattribute__(attr)
    else:
        raise AttributeError

下次在概述需求时,我会尝试做得更好。谢谢各位。

【讨论】:

    【解决方案2】:

    我倾向于同意 cmets 在这个问题上的观点:我怀疑您所问的内容会给代码增加不必要的复杂性,从而使阅读和维护变得更加困难,只是为了实现一个具有可疑利益的次要“装饰”功能。

    但是,如果您真的想要这样做,也许您可​​以创建与现有方法同义词的方法,这样您就可以使用其原始名称调用方法或在合适的时候使用“自定义”名称。

    这是一种相当直接的方法。我想有一些使用类装饰器的时尚方法,但我不知道如何使用它们。 :)

    #! /usr/bin/env python
    
    ''' Class synonym demo
    
    From http://stackoverflow.com/q/27729681/4014959
    
    Written by PM 2Ring 2015.01.01
    '''
    
    class Foo(object):
        def __init__(self, data):
            self.foo_set(data)
    
        def foo_set(self, data):
            self.data = data
    
        def foo_add(self, n):
            self.data += n
            return self.data
    
        def foo_mul(self, n):
            self.data *= n
            return self.data
    
        def foo_mul_add(self, n, m):
            self.foo_mul(n)
            return self.foo_add(m)
    
    
    def make_synonyms(cls, old, new):
        class newclass(cls):
            pass
    
        d = cls.__dict__
        for k in d:
            if k.startswith(old):
                newname = k.replace(old, new)
                #print k, d[k], newname
                setattr(newclass, newname, d[k])
        return newclass
    
    #--------------------------------------
    
    Bar = make_synonyms(Foo, 'foo', 'bar')
    
    a = Foo(5)
    print a.data
    print a.foo_add(10)
    print a.foo_mul(4)
    print a.foo_mul_add(2, 1)
    
    print '-' * 20
    
    a = Bar(6)
    print a.data
    print a.foo_add(10)
    print a.foo_mul(4)
    print a.foo_mul_add(2, 1)
    
    print '-' * 20
    
    a.bar_set(5)
    print a.data
    print a.bar_add(10)
    print a.bar_mul(4)
    print a.bar_mul_add(2, 1)
    

    输出

    5
    15
    60
    121
    --------------------
    6
    16
    64
    129
    --------------------
    5
    15
    60
    121
    

    【讨论】:

    • 是的。这可能是我想到的第一件事。唯一的问题是命名空间被额外的方法污染了,在我的情况下这可能无关紧要;但是,出于这个原因,我一直在努力避免走这条路。
    • 很公平,但恕我直言,这没什么大不了的,因为它只会影响类的命名空间。新名称不应与旧名称冲突,因此我自己不会将其归类为命名空间污染,而是 YMMV。没有创建额外的方法,只是(某些)现有方法获得了额外的名称。
    • 额外方法:哎呀..你完全正确。 当然不是我想说的,尽管这正是我写的。很好的收获。
    • @Eithos:不用担心。我想我应该在回答中提到同义词的重点是您不必担心调用原始方法的“重命名”方法,因为新方法仍然存在。但我想你会意识到情况就是这样。 :) FWIW,我刚刚阅读了一篇关于元类的精彩问答:stackoverflow.com/q/100003/4014959
    • 是的。几周前我通读了那个。我喜欢这类问答。谁没有?
    【解决方案3】:

    您可以尝试为每个类添加字典。

    class EngineeringFirm(Company):
      ClassDict = {'HireEngineer':self.HireEmployee,
                   ...
                   };
    

    只要你想调用你会使用的函数

    <EngineeringFirmInstanc>.ClassDict['HireEngineer'](<arguments>)
    

    它不是特别优雅,但它可能会让你接近你的要求。

    【讨论】:

    • 谢谢!我想到了另一种解决方案,有点接近这个,但这更好,并且不会像我的那样污染命名空间。在决定答案之前,我会稍等片刻,但我很高兴你参与其中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    相关资源
    最近更新 更多