добавить методы в подклассы внутри конструктора суперкласса - PullRequest
2 голосов
/ 28 апреля 2010

Я хочу автоматически добавлять методы (точнее: псевдонимы методов) в подклассы Python. Если подкласс определяет метод с именем «get», я хочу добавить псевдоним метода «GET» в словарь подкласса.

Чтобы не повторяться, я бы хотел определить эту процедуру модификации в базовом классе. Но если я проверю метод базового класса __init__, такого метода не будет, поскольку он определен в подклассе. Это станет более понятным с некоторым исходным кодом:

class Base:

    def __init__(self):
        if hasattr(self, "get"):
            setattr(self, "GET", self.get)


class Sub(Base):

    def get():
        pass


print(dir(Sub))

Выход:

['__doc__', '__init__', '__module__', 'get']

Он также должен содержать 'GET'.

Есть ли способ сделать это в базовом классе?

Ответы [ 3 ]

2 голосов
/ 28 апреля 2010

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

Чем это отличается от того, что вы делаете? Ну, вы присваиваете GET экземпляр атрибута конкретного экземпляра, а не класса. Связанный метод становится частью данных экземпляра:

>>> s.__dict__
{'GET': <bound method Sub.get of <__main__.Sub object at 0xb70896cc>>}

Обратите внимание, что метод находится под ключом GET, но не под get. GET является атрибутом экземпляра, а get - нет. Это несколько отличается по ряду причин: метод не существует в объекте класса, поэтому вы не можете Sub.GET(instance) вызвать Sub метод GET, даже если вы можете сделать Sub.get(instance) , Во-вторых, если у вас есть подкласс Sub, который определяет свой собственный GET метод , но не свой собственный get метод , атрибут экземпляра скроет подкласс GET метод с связанный get метод из базового класса. В-третьих, он создает циклическую ссылку между связанным методом и экземпляром: связанный метод имеет ссылку на экземпляр, и теперь экземпляр сохраняет ссылку на связанный метод. Обычно связанные методы не сохраняются в экземпляре частично, чтобы избежать этого. Циркулярные ссылки обычно не являются большой проблемой, потому что в настоящее время у нас есть модуль cyclic-gc (gc), который заботится о них, но он не всегда может очистить циклы ссылок (например, когда ваш класс также имеет __del__ method.) И, наконец, хранение объектов связанных методов обычно делает ваши экземпляры не сериализуемыми: большинство сериализаторов (таких как pickle) не могут обрабатывать связанные методы.

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

class MethodAliasingType(type):
    def __init__(self, name, bases, attrs):
        # attrs is the dict of attributes that was used to create the
        # class 'self', modifying it has no effect on the class.
        # So use setattr() to set the attribute.
        for k, v in attrs.iteritems():
            if not hasattr(self, k.upper()):
                setattr(self, k.upper(), v)
        super(MethodAliasingType, self).__init__(name, bases, attrs)

class Base(object):
    __metaclass__ = MethodAliasingType

class Sub(Base):
    def get(self):
        pass

Теперь Sub.get и Sub.GET действительно являются псевдонимами, и переопределение одного, а не другого в подклассе работает, как и ожидалось.

>>> Sub.get
<unbound method Sub.get>
>>> Sub.GET
<unbound method Sub.get>
>>> Sub().get
<bound method Sub.get of <__main__.Sub object at 0xb708978c>>
>>> Sub().GET
<bound method Sub.get of <__main__.Sub object at 0xb7089a6c>>
>>> Sub().__dict__
{}

(Конечно, если вы не хотите, чтобы переопределял один, а другой не работал, вы можете просто сделать это ошибкой в ​​своем метаклассе.) Вы можете сделать то же самое, что и метакласс использование декораторов классов (в Python 2.6 и более поздних версиях), но это будет означать, что декораторы классов в каждом подклассе базовых классов не должны наследоваться.

2 голосов
/ 28 апреля 2010

Это потому, что класс Sub еще не был инициирован, сделайте это в своем экземпляре как

>>> s=Sub()
>>> dir(s)
['GET', '__doc__', '__init__', '__module__', 'get']
>>>
0 голосов
/ 28 апреля 2010

Создайте производный конструктор в вашем производном классе, который устанавливает атрибут.

...