Копирование объекта класса в Python

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

Очевидное решение — создать новый класс, скопировав код исходного и удалив __call__. Но есть ли способ сделать это программно?

Я пошел из:

import copy

class A:
    def __call__(self):
       print(self, "CALLED")

B = copy.deepcopy(A) # for some reason it is not a real copy...
del B.__call__ # seems to work, but also removes `__call__` from A !!!

a=A()
a() # TypeError: 'A' is not callable

# this was the more surprising to me...

к:

class B(A):
    def __getattribute__(self, attr):
        if attr == "__call__":
            raise AttributeError(attr)
        return super().__getattribute__(attr)

b = B()
b.__call__ # raises attribute error, nice
b() # still calls A.__call__... WTF!

пройдя через множество попыток.

Есть ли способ? Как получить настоящую копию объекта класса (а не экземпляра) в Python?

Я проверил существующие вопросы и ответы, но, похоже, ни один из них не касается этой конкретной проблемы. Я также подумал об извлечении исходного кода, удалении метода __call__ из строки и его повторной оценке.

Почему бы не использовать наследование?

Maurice Meyer 21.08.2024 12:34

Мне нужно, чтобы у дочернего класса не было __call__ — то есть удаление родительского метода (в случае наследования). Вы не можете. Вот я и подумал, давай скопируем класс, удалим его __call__ и сделаем из него объект... Но наткнулся на неожиданное

mguijarr 21.08.2024 12:35

«Почему-то» — по какой причине? Очень сложно предложить возможные решения, не понимая контекста и ограничений.

jonrsharpe 21.08.2024 12:38

Хорошо, я удалил "по какой-то причине", отредактировал сообщение

mguijarr 21.08.2024 12:40

Не могли бы вы пойти наоборот? Определите B как базовый класс, определите A(B) (A наследуется от B) и реализуйте его __call__

Tomerikoo 21.08.2024 12:45

Просто удалить «по какой-то причине» на самом деле не то же самое, что указать причину.

jonrsharpe 21.08.2024 12:52

Возможно, этот пост поможет stackoverflow.com/questions/9541025/how-to-copy-a-class

maxbalrog 21.08.2024 12:52
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
7
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете использовать type() для динамического создания нового класса без указанного метода:

def remove_method_from_class(cls, new_class_name: str, method_name: str):
    attrs = {k: v for k, v in cls.__dict__.items() if k != method_name}
    return type(new_class_name, cls.__bases__, attrs)

class A:
    def __call__(self):
        print(self, "CALLED")
    
    def other_method(self):
        print("Other method")

B = remove_method_from_class(A, "B", "__call__")

a = A()
a()  # This should work

b = B()
# b()  # This raises a TypeError because B is not callable
print(hasattr(b, '__call__'))  # Prints False
print(hasattr(a, '__call__'))  # Prints True
b.other_method()  # This should still work

Вы можете использовать метакласс, который позволяет вам контролировать создание класса:

class Meta(type):
    def __new__(cls, clsname, bases, dct):
        if not clsname.startswith('A'):
            dct.pop('__call__', None)
        return super(Meta, cls).__new__(cls, clsname, bases, dct)

class A(metaclass=Meta):
    def __call__(self):
        print(f"__called__: {self.__class__.__name__}")

class B(metaclass=Meta):
    pass

class C(metaclass=Meta):
    pass


for _cls in [A, B, C]:
    try:
        _cls()()
    except:
        print(f"{_cls} has no __call__ method")

Вне:

__called__: A
<class '__main__.B'> has no __call__ method
<class '__main__.C'> has no __call__ method

Другие вопросы по теме