2015-01-01 2 views
3

Рассмотрим следующий пример:Python: переименование методов суперкласса и их функции-Scope ссылки

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(): 
    ... 

... и так далее. Хотя использование «Employee» в этом сценарии действительно не повредило бы, это действительно просто иллюстрирует идею. Как я могу это сделать?

Моя идея состояла в использовании функции classFactory, которая использовала бы тип сотрудника как аргумент и генерировала бы класс компании, в то время как метаклас обрабатывал бы переименование путем итерации через словарь атрибутов и замены «Employee» указанным типом.

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

Единственная проблема заключается в следующем: что, если методы внутри Компании совершают вызовы друг к другу по умолчанию «Employee»? Вот где я в тупике. У меня возникла идея, что метакласс, участвующий в переименовании методов, также может получить источник каждой функции (через модуль проверки) и выполнить поиск, если обнаружен известный атрибут метода внутри и, если это так, заменить эту часть и создать новую функцию через exec и присвоить его правильному ключу атрибута.

... Но это действительно похоже на хакерство. Я открыт для альтернатив, и хотя я понимаю, что могут возникнуть проблемы с дизайном с вопросом (я также открыт для предложений на этом фронте). Мне было бы интересно узнать, имеет ли эта проблема более элегантное решение.

Спасибо!

Edit: другое решение

Ради аргумента, я на минуту предположим, что приведенный выше код действительно то, что я работаю с; Я подумал, что могу затронуть некоторые проблемы в комментариях другим решением, которое я имел в виду, которое я уже рассмотрел и убрал по причинам, которые я объясню.

Если Firm классы, унаследованные от компании, и я хотел, чтобы поддерживать идентичный интерфейс (как один обычно будет в таком случае, как это разрешить динамические вызовы hire() или promote(), и т.д.) я мог бы реализовать __getattribute__, который принимает HirePainter() (по доступ к исходному методу Employee), в то же время позволяя любому другому интерфейсу использовать HireEmployee(), если это необходимо.

Интересно, полагая, что это нормально, чтобы продлить мой вопрос, если это то, что считается плохим, если, скажем, я планировал сделать это, потому что я думал, что код внутри PaintingFirm принесет пользу в читабельности? Опять же, я понимаю, что этот пример ужасен в том, что читаемость здесь действительно не приносит никакой пользы, но предположим, что это так?

(Единственная причина, по которой я не предлагал эту идею, в первую очередь состоит в том, что мой __getattribute__ уже обрабатывает совсем немного, и добавление дополнительного шума к ней не было таким привлекательным. Тем не менее, я мог бы работать, но это вопрос, который я должен был задать, если бы там были более волшебные (но не взломанные) решения ..)

+3

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

+2

@ Eithos Ну, дело в том, что ваши фирмы должны иметь похожий, если не идентичный интерфейс, чтобы вы могли указать любой твердый объект нанять кого-то, и позволить ему решать, кого он нанимает. Таким образом, вы можете сделать 'firm.hire()', не зная, что именно «фирма». – glglgl

+0

@glglgl Интересно. Это заставляет меня чувствовать себя глупо, потому что в большинстве других ситуаций это, вероятно, так, как я бы справился с этим. Делает совершенный смысл. Я понимаю, что проблема осложняется тем фактом, что я действительно не разместил свой настоящий код, и есть очень мало шансов, что любой другой класс должен/должен иметь доступ к 'firm.hire()'. Другими словами, при использовании 'firm.hire' будет способствовать динамичным вызовам на прокат, не зная заранее фирму, эта динамическая обработка действительно не понадобится. Фактически, эти вызовы активируются с помощью событий пользовательского интерфейса, которые ... – Eithos

ответ

0

Вы можете попробовать добавить словарь для каждого класса.

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

Всякий раз, когда вы хотите вызвать функцию, которую вы будете использовать

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

Это не особенно элегантно, но это может заставить вас близко к тому, что вы просите.

+0

Спасибо! У меня было другое решение, близкое к этому, но это лучше и не загрязняет пространство имен, как у меня. Я подожду немного, прежде чем я приму решение, но я рад, что ты разбился. – Eithos

0

Я схожу с мнений по этому вопросу: Я подозреваю, что то, что вы просите, добавит ненужное усложнение кода, затрудняя чтение &, чтобы поддерживать только незначительную «косметическую» особенность сомнительной выгоды ,

Однако, если вы действительно хотите сделать это, возможно, вы могли бы создать методы, которые синонимов из существующих методов, поэтому вы можете вызвать метод с исходным именем или с «заказным» именем, когда его кажется подходящим.

Вот один довольно прямой способ сделать это. Я предполагаю, что есть какой-то изящный способ сделать это с классными декораторами, но я не знаю, как их использовать. :)

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

Справедливо, но IMHO это не имеет большого значения, так как это влияет только на пространство имен класса. Новые имена не должны сталкиваться со старыми именами, поэтому я бы не классифицировал их как загрязнение пространства имен, а YMMV. Никаких дополнительных методов не создается, просто (некоторые из) существующих методов получают дополнительное имя. –

+0

Дополнительные методы: Ой, ты совершенно прав. «Это, конечно, не то, что я хотел сказать, хотя это именно то, что я написал. Хороший улов. – Eithos

1

Ради потомства, я отправляю решение моей, что я считаю, является достойной альтернативой. Я не предлагаю это как ответ, потому что, по правде говоря, я не упомянул в своем вопросе, что предпочитаю не добавлять лишние имена, или сохранить возможность называть эти атрибуты как self.hireEngineer, а не ClassDict['HireEngineer']. Учитывая это, я не могу сказать, что ни один из этих ответов не отвечает на вопрос.

Решение:

В ретроспективе, проблема была намного проще, чем я сделал это, чтобы быть. Наверное, я попался на метаклассию только ради этого. Если это еще не очевидно, я просто узнаю только о метаклассах, и на мгновение мне показалось, что это хорошая возможность попробовать их. Увы.

Я считаю, что следующее решение соответствует духу принципа Лискова (спасибо, Игнасио), давая производному классу возможность ссылаться на производные методы по-своему. Пространство имен классов остается таким же, и другие объекты могут при необходимости обращаться к этим методам с их настоящими именами.

# 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 

В следующий раз я постараюсь сделать лучшую работу, когда изложим требования. Спасибо, парни.