2012-01-21 2 views
6

У меня есть то, что, по существу, следующий в Python:Частично Оценка Python Classmethod основы Где Он Accessed От

class X(object): pass 

class Y(object): 
    @classmethod 
    def method(cls, x_inst, *args, **kwargs): 
    #do some work here that requires an instance of x 
    pass 

То, что я хотел бы сделать, это добавить динамическое свойство ко всем экземплярам X позволяя доступ к Y что неявно заполняет первый параметр method с данным экземпляром. Например, я хотел бы следующий код работать одинаково:

# current 
x = X() 
result = Y.method(x, 1, 2, 3) 

# desired 
x = X() 
x.Y.method(1, 2, 3) 

Есть несколько методов на несколько подклассов, которые я хотел бы реализовать это поведение для. В настоящее время я создал класс YProxy, который фактически возвращает X, а затем поместите в него часть кода. Это кажется довольно безвкусный и трудно поддерживать, однако:

class X(object): 
    @property 
    def Y(self): 
    return YProxy(self) 

class Y(object): 
    @classmethod 
    def method(cls, x_inst, *args, **kwargs): 
    #do some work here that requires an instance of x 
    pass 

class YProxy(object): 
    def __init__(self, x_inst): 
    self.x_inst = x_inst 

    def method(self, *args, **kwargs): 
    return Y.method(self.x_inst, *args, **kwargs) 

Есть ли способ условно частично оценить на методы класса объекта?

+0

В 'х(). Y.foo', будет' Foo' всегда класс-метод 'Y', который должен иметь свой первый аргумент nt заполнено? Или вы хотите иметь доступ к произвольным атрибутам 'Y' из экземпляров' X'? –

+0

@benw будут методы на Y, которые не должны иметь x_inst как есть первый параметр, хотя в настоящее время это все методы экземпляра, а не методы класса. –

ответ

2

Это может быть сделано с помощью описателя объекта + класса обертка объявления явно объявляя классы, которые нужно обернуть на этом пути на целевом классе.

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

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

Переводя все это в Python, мы имеем:

import types 
from functools import partial 

class APIWrapper(object): 
    def __init__(self, apicls, instance): 
     self._apicls = apicls 
     self._instance = instance 
    def __getattribute__(self, attr): 
     apicls = object.__getattribute__(self, "_apicls") 
     instance = object.__getattribute__(self,"_instance") 
     obj = getattr(apicls, attr) 
     if isinstance(obj, types.MethodType): 
      return partial(obj,instance) 
     return obj 


class APIProperty(object): 
    def __init__(self, cls): 
     self.cls = cls 
    def __get__(self, instance, cls): 
     return APIWrapper(self.cls, instance) 

class Y(object): 
    @classmethod 
    def method(cls, x, *args): 
     print cls, x, args 

class X(object): 
    Y = APIProperty(Y) 

#Example usage: 
x = X() 
x.Y.method(1,2,3) 

(печатает <class '__main__.Y'> <__main__.X object at 0x18ad090> (1, 2, 3) при запуске)

Но я полагаю, вы не хотите, чтобы нужно написать

Y = APIWrapper(Y) 

для каждый из классов, которые вы хотите обернуть таким образом. (И имеют эти классы, определенные после обернутого класса, так что Y уже был проанализирован при анализе тела X).

Это можно сделать с помощью метаклассов, декораторов классов, которые должны быть определены для каждого класса, который вы хотите применить методы, вместо этого я создал функцию, которая должна быть вызвана в конце определения модуля , где вы определяете свой класс «X» - эта функция добавит нужные классы в качестве атрибутов для каждого определенного класса (в моем примере, я хочу, чтобы класс был помечен атрибутом «auto_api», но вам нужно подобрать себя) - Таким образом, функция «auto_api», определение классов х и Y становится, как это (с использованием той же APIProperty и APIWrapper, как указано выше)

def auto_api(api_classes, glob_dict): 
    for key, value in glob_dict.items(): 
     if isinstance(value, type) and hasattr(value, "auto_api"): 
      for api_class in api_classes: 
       setattr(value, api_class.__name__, APIProperty(api_class)) 


class X(object): 
    auto_api = True 

class Y(object): 
    @classmethod 
    def method(cls, x, *args): 
     print cls, x, args 

auto_api((Y,), globals()) 

#Example 

x = X() 
x.Y.method(1,2,3) 
+0

Метод '__get__'! Блестяще, я забыл об этом. Это сделало это для меня. объявите все потенциальные свойства класса впереди.Есть предопределенный набор из них и их немного. Я полагаю, что если бы я действительно хотел, я мог бы сделать некоторую интроспекцию моего модуля с помощью метакласса, но для этого потребуется больше кода, чем я, db будет экономить и выглядел бы ужасно волшебным. –

+0

Чтение других ответов теперь, решение @ ben-w очень похоже на это, но здесь я делаю шаг дальше i n, который я разрешаю вам передавать классы, которые вы хотите обернуть в качестве параметров, вместо того, чтобы их жестко кодировать. – jsbueno

+0

AFAICT это сделает неправильную вещь для методов, которые либо (а) не являются методами класса, либо (б) не ожидают, что экземпляр X в качестве своего первого аргумента --- необходим какой-либо осмотр, за исключением того, что атрибут Y это метод, чтобы убедиться, что это правильно. (Если все методы * Y не являются методами класса, ожидающими экземпляр X.) (Но я признаю, что джаз 'auto_api' отличный.) –

1
import inspect 

def callthing(): 
    my_caller = inspect.stack()[1] 
    args, varargs, keywords, locals = inspect.getargvalues(my_caller) 
    print args[0] 

Когда выше callthing вызывается, он получит аргументы его Звонящего ([1] быть «кадр стека выше текущей»), печатая первый. Вы даже можете использовать именованные аргументы для «получения значения аргумента с именем x_inst». Возможно, @classmethod должен вернуть функцию, завернутую в вызов, например, callthing?

Вы также могли бы сделать разветвление происходит в декоратор сам, используя inspect.getargspec(my_function), имея в виду, что фактическое значение аргумент приведен не доступен в этой точке, так как функция строится, а не называется.

+1

В python 2 лучше использовать 'inspect.currentframe (1)' вместо 'inspect.stack() [1]' - более поздняя версия требует времени для обработки, до такой степени, что она обычно непригодна для использования в производственный код. – jsbueno

+0

Я не думаю, что это может сработать - экземпляр «x» фактически не используется как параметр в чем-либо в выражении типа «xYmethod()» - «Y» извлекается как атрибут экземпляра x, но поскольку Y не является методом, аргумент «я» никогда не передается ему. – jsbueno

0

Почему вы связываете классы следующим образом? Безусловно

>>> class X(object): 
...  def __init__(self, y): 
...    self.y = y 
...  
...  def method(self, *args, **kwargs): 
...    self.y.method(self, *args, **kwargs) 

>>> class Y(object): 
...  @classmethod 
...  def method(cls, x, *args): 
...    print cls, x, args 
... 
>>> y = Y() 
>>> x = X(y) 
>>> x.method(Ellipsis) 
<class '__main__.Y'> <__main__.X object at 0xb727612c> (Ellipsis,) 

будет работать просто отлично?


То, о чем вы просили, несколько противоречиво. Если x.Y действительно просто объект класса Y, то, безусловно, он не имеет этого автоматического свойства привязки аргументов, которое вы хотите. Таким образом, x.Y должен быть своего рода прокси-объектом, который связывает вас.

Но Python уже делает именно это, например, методы классов! Если у вас есть экземпляр X, и вы вызываете метод на нем, этот метод получит self в качестве первого аргумента; ты знаешь что. Так что, конечно, имеет смысл использовать это для привязки аргументов?

+0

Y - это только один из классов, к которому я хочу получить доступ. Существует также A, B, C и т. Д. По сути, X является классом API, а все остальные классы представляют собой объектные представления ресурсов, возвращаемые api. Я хотел бы сказать «api.ResourceA.retrieve (id_)» или «api.ResourceB».all() " –

+0

И сами эти ресурсы нуждаются в экземпляре класса API? – katrielalex

+0

Доступ к ресурсам должен осуществляться через объект api (X), который содержит данные аутентификации и другие общие методы для связи с удаленным сервером. быть подклассом чего-то другого (APIObject, например), но поддерживать ссылку на экземпляр api, из которого они пришли. –

2

Не уверен, если это то, что вы хотите, но некоторые самоанализа/VarName дисциплина вы можете получить где-то:

import functools 

class X(object): 
    @property 
    def Y(self): 
     return YProxy(self) 

class Y(object): 
    @classmethod 
    def method(cls, x_inst, *a): 
     print x_inst, a 

instancemethod = type(Y.method) 

class YProxy(object): 
    def __init__(self, x_inst): 
     self.x_inst = x_inst 
    def __getattr__(self, k): 
     obj = getattr(Y, k) 
     if isinstance(obj, instancemethod) and obj.func_code.co_varnames[0] == 'cls': 
      return functools.partial(obj, self.x_inst) 
     return obj 

Использование cls в качестве имени первого арг к методу класса, как правило, принимается стиль, я думать. self на самом деле не подходит.

ETA: вы также можете проверить имя переменной «x_inst» или что-то в этом роде, если существуют другие методы класса, или использовать декоратор в соответствующих классах, чтобы установить свойство для методов, & c. Дело в том, что вы можете делать то, что вам нужно, в __getattr__, если у вас есть программный способ отличить, какие методы должны поставляться с экземпляром, а какие нет.

0

Можно ли сделать что-то подобное вместо этого?

class X(object): 
def __init__(self): 
    self.Y = Y(self) 

class Y(object): 
def __init__(self, x): 
    self.x = x 

def method1(self, *args, **kwargs): 
    pass 

x = X() 
x.Y.method1() 
Смежные вопросы