Систематическая перепрограммирование функций

#python #python-3.x

#python #python-3.x

Вопрос:

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

 class someClass():
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return self.__class__( self.value.__add__(other) )

    def someMethod(self, *args, **kwargs):
        return self.__class__( self.value.someMethod(*args, **kwargs) )

    def someOtherMethod(self, *args, **kwargs):
        return self.__class__( self.value.someOtherMethod(*args, **kwargs) )
  

Конечно, не все методы внутри такие, но большинство из них.

Вместо того, чтобы реализовывать явно someMethod , someOtherMethod и __add__ , есть ли способ сделать это систематически? Возможно, с __getattr__ помощью или __getattribute__ ?


Вот почему подкласс типа значения нежизнеспособен :

 >>> class someClass(int):
    pass
>>> a = someClass(5)
>>> isinstance(a 5, someClass)
False #I need this to be True
  

Чтобы заставить эту последнюю строку вернуть True, мне пришлось бы «перепрограммировать» все операторы, как раньше: подклассы вообще не помогают

Комментарии:

1. если вы наследуете от «класса значений» вместо того, чтобы использовать его в качестве члена, вам не нужно реализовывать методы. Вы могли бы прочитать меня о наследовании здесь

2. @TheFool Большинство из этих «перепрограмм» предназначены для операторов, разве подклассы класса значения не означают, что операторы типа = или *= вернут этот класс вместо моего?

3. Возможно, я неправильно понимаю, что вы делаете. Можете ли вы попытаться лучше объяснить это с помощью кода в вашем вопросе?

4. @TheFool Я добавил конкретный пример, относящийся к вашему предлагаемому решению!

Ответ №1:

Частичный ответ, потому что вам все равно нужно перечислить методы, которые вы хотите обернуть, но следующее значительно упрощает перенос большого количества методов, потому что вы просто добавляете их в список методов, которые нужно обернуть:

 class MyClass():

    def __init__(self, value):
        self.value = value
    
    def __str__(self):
        return f'<MyClass({repr(self.value)})>'

    
def make_wrappers(cls, methods):
    for method in methods:
        def wrapper(self, *args, _method=method, **kwargs):
            return self.__class__(getattr(self.value, _method)(*args, **kwargs))
        setattr(cls, method, wrapper)    


make_wrappers(MyClass, ['__add__', '__sub__', 'upper'])

x = MyClass(3)
print(x 2, x-2)  # <MyClass(5)> <MyClass(1)>

y = MyClass('hello')
print(y.upper())  # <MyClass('HELLO')>
  

Не похоже, что вы можете использовать __getattr__ , чтобы делать полностью то, что вы хотите. Это потому, что если вы пытаетесь оценить a b , где a находится экземпляр вашего класса, то, если вы не определили __add__ метод, но определили __getattr__ , то это вызовет TypeError и проигнорирует (не вызовет) __getattr__ . Поэтому вы не можете использовать __getattr__ для переноса «магические методы», такие как __add__ (по крайней мере, в Python 3).

Обратите _method внимание, что здесь используется для обеспечения того, чтобы значение переменной method было привязано к оболочке в момент ее определения (оно добавляется в словарь по умолчанию). Если бы вместо этого вы использовали method непосредственно внутри wrapper where _method is used , то вы обнаружили бы, что каждая оболочка будет использовать значение method , унаследованное от внешней области при его вызове, что было бы не тем, что вы хотите.

Обратите также внимание, что решение основано на «обезьяньем исправлении» класса. Это будет работать. Что не будет работать в Python 3, так это исправлять экземпляры класса — например, если вы попытались использовать setattr для добавления метода непосредственно к x тому, который не существует MyClass , тогда его вызов не будет работать.

Комментарии:

1. Мне нужно очень внимательно перечитать это и протестировать, но, похоже, оно удовлетворяет моим требованиям. Будет ли это работать, если я помещу его в модуль и импортирую его?

2. @AmyLucyRose Я не понимаю, почему нет.

3. После повторного прочтения это действительно решает мою проблему. Однако есть ли способ избавиться от _method kwarg без проблем, о которых вы упомянули? Если нет, то все в порядке, но я думаю, что это делает реализацию немного более запутанной. (Надеюсь, добавленная мной строка документа позаботится об этом, но все же было бы неплохо, если бы был способ избавиться от этого) Я думаю, что могу придумать подход с помощью lambda, я также попытаюсь его реализовать и протестировать

Ответ №2:

Я бы использовал этот очень простой подход:

 class SomeClass():
    def __init__(self, value):
        self.value = value

        for method_name in ("some_method",
                            "some_other_method"):
            new_method = lambda *args, **kwargs: self.__class__(getattr(self.value, method_name)(*args, **kwargs)
            setattr(self, method_name, new_method)
  
     
  

Теперь вам нужен только способ присвоить этим методам строки документации.

Комментарии:

1. Это не работает, но причина этого довольно тонкая: для некоторых методов (в основном операторов и других dunders) они должны существовать на уровне класса, потому что __getattr__ и __getattribute__ обходятся. Кроме того, это делает каждый отдельный экземпляр намного больше в памяти, что приведет к ужасному масштабированию.