的Python:重命名一个父类的方法和他们的功能范围引用

问题描述:

请看下面的例子:的Python:重命名一个父类的方法和他们的功能范围引用

class Company(): 

    def hireEmployee(): 

    def fireEmployee(): 

    def promoteEmployee(): 

    etc... 

class EngineeringFirm(Company): 
    pass 

class PaintingFirm(Company): 
    pass 

假设公司类有很多方法。如果我想从超类中重命名这些方法,那么我可以得到以下内容:

class EngineeringFirm(Company): 

    def hireEngineer(): 
    ... 

class PaintingFirm(Company): 

    def hirePainter(): 
    ... 

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

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

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

唯一的问题是:如果公司内部的方法通过默认的'员工'名称互相打电话怎么办?这是我难倒的地方。我有一个想法,即涉及重命名方法的元类也可以获取每个函数的源代码(通过检查模块),并搜索是否在内部找到已知的方法属性,如果是,则替换该部分并通过通过创建新函数exec并将其分配回正确的属性键。

...但这真的看起来有点哈克。我愿意接受替代品,尽管我意识到这个问题可能存在与设计相关的问题(我也愿意接受有关该方面的建议),但我很想知道这个问题是否具有更优雅的解决方案。

谢谢!

编辑:另一种解决方案

为了讨论的方便,我假设了一下,上面的代码是真的是我的工作;我想我可以用我想到的另一个解决方案来解决评论中的一些问题,其中一个我已经考虑过,并且因为我将解释的原因而放弃。

如果Firm类从公司继承和我希望保持一个相同的接口(如一个通常会在这样的情况下,以允许动态调用hire()promote()等)我可以实现一个__getattribute__接受HirePainter()(由访问最初的Employee方法),同时仍然允许任何其他接口在必要时使用HireEmployee()

我想知道,假如可以延长我的问题,如果我认为PaintingFirm中的代码会使可读性受益,那么这会被认为是不好的做法。再一次,我意识到这个例子很可怕,因为这里的可读性似乎并没有任何好处,但假设它是这样做的?

(我首先没有提出这个想法的唯一原因是我的__getattribute__已经处理了很多,并且增加了额外的噪音并没有那么吸引人,但我仍然可以使用它,但这是一个问题,如果有更多神奇的(但不是哈希)解决方案,我不得不提问。)

+3

[EEEEEEEEEEWWWWWWWWWWWWWWW](http://en.wikipedia.org/wiki/Liskov_substitution_principle) –

+2

@Eithos问题是,您的公司应该有一个类似的界面(如果不是相同的话),以便您可以告诉任何公司对象聘请一个人,让它决定雇用谁。所以你可以在不知道“公司”究竟是什么的情况下做firm.hire()。 – glglgl

+0

@glglgl有趣。这让我感到很无聊,因为在大多数其他情况下,这可能是我处理它的方式。非常有意义。我意识到这个问题很复杂,因为我没有真正发布我的真实代码,并且如果有任何其他类可以/应该有权访问'firm.hire()',很少有机会。换句话说,虽然使用“firm.hire”可以促进动态调用,而不必事先了解公司情况,但这种动态处理确实不是必要的。事实上,这些调用是由UI Events激活的...... – Eithos

您可以尝试在每个类的字典中添加。

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

每当你想调用的函数,你会使用

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

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

+0

谢谢!我有另一种解决方案,在某种程度上接近于此,但这是更好的,不污染像我的名字空间。在我决定回答之前,我会等一会儿,但我很高兴你投入。 – Eithos

我倾向于同意关于这个问题的评论:我怀疑你所要求的会给代码增加不必要的复杂性,使其更难以阅读&仅仅为了实现可疑利益的小“整容”特征。

不过,如果你真的要做到这一点,也许你可以创建出同义词现有的方法方法,这样你就可以调用的方法,其原有的名称或用“定制”的名字,当似乎是合适的

这是一个相当直接的方法来做到这一点。我想有一些时髦的方式来与类装饰器,但我不知道如何使用这些。 :)

#! /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 
+0

是的..这可能是第一件发生在我身上的事情。唯一的问题是命名空间被额外的方法所污染,这在我的情况下可能无关紧要;然而,我努力避免这样做是因为这个原因。 – Eithos

+0

够公平的,但恕我直言,这不是什么大事,因为它只影响类的命名空间。新名称不应与旧名称冲突,所以我不会将它命名为命名空间污染,而是YMMV。没有额外的方法正在创建,只是(一些)现有的方法获得额外的名称。 –

+0

额外的方法:糟糕..你是完全正确的。这当然不是我想说的,尽管这正是我写的。接得好。 – Eithos

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

解决方案:

事后看来,这个问题是简单了很多,比我做出来的人。我想我只是为了它而迷上了这个metaclassery。如果它还不是很明显,那么我真的只是在学习元类,并且一会儿这似乎是一个尝试它们的好机会。唉。

我相信以下解决方案尊重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 

我会在下次概述要求时尝试做得更好。多谢你们。