Как я могу динамически создавать методы класса для класса в Python
Если я определю небольшую программу на Python как
class a():
def _func(self):
return "asdf"
# Not sure what to resplace __init__ with so that a.func will return asdf
def __init__(self, *args, **kwargs):
setattr(self, 'func', classmethod(self._func))
if __name__ == "__main__":
a.func
Я получаю ошибку трассировки
Traceback (most recent call last):
File "setattr_static.py", line 9, in <module>
a.func
AttributeError: class a has no attribute 'func'
Я пытаюсь понять, как я могу динамически установить метод класса в класс без создания объекта?
Редактировать:
Ответ на эту проблему
class a():
pass
def func(cls, some_other_argument):
return some_other_argument
setattr(a, 'func', classmethod(func))
if __name__ == "__main__":
print(a.func)
print(a.func("asdf"))
возвращает следующий вывод
<bound method type.func of <class '__main__.a'>>
asdf
6 ответов
Вы можете динамически добавить метод класса к классу простым присваиванием объекту класса или setattr для объекта класса. Здесь я использую соглашение Python, что классы начинаются с заглавных букв, чтобы уменьшить путаницу:
# define a class object (your class may be more complicated than this...)
class A(object):
pass
# a class method takes the class object as its first variable
def func(cls):
print 'I am a class method'
# you can just add it to the class if you already know the name you want to use
A.func = classmethod(func)
# or you can auto-generate the name and set it this way
the_name = 'other_func'
setattr(A, the_name, classmethod(func))
Здесь есть пара проблем:
__init__
запускается только при создании экземпляра, напримерobj = a()
, Это означает, что когда вы делаетеa.func
,setattr()
звонок не состоялся- Вы не можете получить доступ к атрибутам класса напрямую из методов этого класса, поэтому вместо использования просто
_func
Внутри__init__
вам нужно будет использоватьself._func
или жеself.__class__._func
self
будет примеромa
Если вы установите атрибут для экземпляра, он будет доступен только для этого экземпляра, но не для класса. Так что даже после звонкаsetattr(self, 'func', self._func)
,a.func
вызовет AttributeError- С помощью
staticmethod
так, как ты, ничего не сделаешь,staticmethod
вернет результирующую функцию, она не изменяет аргумент. Так что вместо этого вы хотели бы что-то вродеsetattr(self, 'func', staticmethod(self._func))
(но принимая во внимание вышеупомянутые комментарии, это все еще не будет работать)
Итак, теперь вопрос в том, что вы на самом деле пытаетесь сделать? Если вы действительно хотите добавить атрибут к классу при инициализации экземпляра, вы можете сделать что-то вроде следующего:
class a():
def _func(self):
return "asdf"
def __init__(self, *args, **kwargs):
setattr(self.__class__, 'func', staticmethod(self._func))
if __name__ == '__main__':
obj = a()
a.func
a.func()
Тем не менее, это все еще немного странно. Теперь вы можете получить доступ a.func
и позвонить без проблем, но self
аргумент a.func
всегда будет самым последним созданным экземпляром a
, Я не могу придумать какой-либо вменяемый способ превратить метод экземпляра, как _func()
в статический метод или метод класса класса.
Поскольку вы пытаетесь динамически добавить функцию в класс, возможно, что-то вроде следующего ближе к тому, что вы на самом деле пытаетесь сделать?
class a():
pass
def _func():
return "asdf"
a.func = staticmethod(_func) # or setattr(a, 'func', staticmethod(_func))
if __name__ == '__main__':
a.func
a.func()
Вам нужно setattr(self, 'func', staticmethod(self._func))
Вам нужно инициализировать класс variable=a()
звонить __init__
В статическом классе нет инициализации
Вы можете сделать это таким образом
class a():
def _func(self):
return "asdf"
setattr(a, 'func', staticmethod(a._func))
if __name__ == "__main__":
a.func()
1. Основная идея: использовать дополнительный класс для хранения методов
Я нашел осмысленный способ сделать работу:
Сначала мы определим такой BaseClass:
class MethodPatcher:
@classmethod
def patch(cls, target):
for k in cls.__dict__:
obj = getattr(cls, k)
if not k.startswith('_') and callable(obj):
setattr(target, k, obj)
Теперь, когда у нас есть оригинальный класс:
class MyClass(object):
def a(self):
print('a')
Затем мы определяем новый метод, который мы хотим добавить в новый Patcher
учебный класс:
(Не делайте имя метода начинается с _
в этом случае)
class MyPatcher(MethodPatcher):
def b(self):
print('b')
Затем позвоните:
MyPatcher.patch(MyClass)
Итак, вы найдете новый метод b(self)
добавлен в оригинал MyClass
:
obj = MyClass()
obj.a() # which prints an 'a'
obj.b() # which prints a 'b'
2. Сделать синтаксис менее многословным, мы используем декоратор класса
Теперь, если у нас есть MethodPatcher
нам нужно сделать две вещи:
- определить дочерний класс
ChildClass
изModelPatcher
который содержит дополнительные методы для добавления - вызов
ChildClass.patch(TargetClass)
Вскоре мы обнаружили, что второй шаг можно упростить с помощью декоратора:
Мы определяем декоратор:
def patch_methods(model_class):
def do_patch(cls):
cls.patch(model_class)
return do_patch
И мы можем использовать это как:
@patch_methods(MyClass)
class MyClassPatcher(MethodPatcher):
def extra_method_a(self):
print('a', self)
@classmethod
def extra_class_method_b(cls):
print('c', cls)
# !!ATTENTION!! the effect on declaring staticmethod here may not work as expected:
# calling this method on an instance will take the self into the first argument.
# @staticmethod
# def extra_static_method_c():
# print('c')
3. Завернуть вместе
Итак, теперь мы можем поставить определение MethodPatcher
а также patch_method
в единый модуль:
# method_patcher.py
class MethodPatcher:
@classmethod
def patch(cls, target):
for k in cls.__dict__:
obj = getattr(cls, k)
if not k.startswith('_') and callable(obj):
setattr(target, k, obj)
def patch_methods(model_class):
def do_patch(cls):
cls.patch(model_class)
return do_patch
Так что мы можем использовать его свободно:
from method_patcher import ModelPatcher, patch_model
4. Окончательное решение: более простая декларация
Вскоре я обнаружил, что MethodPatcher
класс не является обязательным, в то время как @patch_method
Декоратор может сделать работу, поэтому, наконец, нам нужно только patch_method
:
def patch_methods(model_class):
def do_patch(cls):
for k in cls.__dict__:
obj = getattr(cls, k)
if not k.startswith('_') and callable(obj):
setattr(model_class, k, obj)
return do_patch
И использование становится:
@patch_methods(MyClass)
class MyClassPatcher:
def extra_method_a(self):
print('a', self)
@classmethod
def extra_class_method_b(cls):
print('c', cls)
# !!ATTENTION!! the effect on declaring staticmethod here may not work as expected:
# calling this method on an instance will take the self into the first argument.
# @staticmethod
# def extra_static_method_c():
# print('c')
Я использую Python 2.7.5, и я не смог заставить вышеуказанные решения работать на меня. Вот чем я закончил:
# define a class object (your class may be more complicated than this...)
class A(object):
pass
def func(self):
print 'I am class {}'.format(self.name)
A.func = func
# using classmethod() here failed with:
# AttributeError: type object '...' has no attribute 'name'