2015-06-12 5 views
1

TL; DR Python 2.7.5, используя дескрипторы в качестве декораторов, есть ли способ передать аргументы (в метод __init__)? OR Как получить доступ к атрибутам экземпляра класса с помощью декоратора метода с аргументами (as here)? - Я считаю, что это не представляется возможным, хотя, поэтому основное внимание ниже на дескрипторы ...Дескрипторы Python с аргументами?


Длинная версия

У меня есть класс объектов, с различным «типа» атрибуты. Основываясь на «типе» экземпляра, я хотел бы, чтобы метод был доступен или нет. Я знаю, что один способ состоит в создании нескольких классов, но я пытаюсь не иметь кучу if/else-операторов при создании этих объектов. Например, у меня есть два объекта A и B, которые почти идентичны, кроме объекта B. Я не хочу иметь доступный метод get_start_date(). По сути, я хочу, чтобы оба A и B были экземплярами класса MyObjects, но имеют атрибут «type», который отличается.

type(A) == type(B) 
A.genus_type != B.genus_type 

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

Я думал, что я мог бы использовать декораторы с белым списком, как:

def valid_for(whitelist): 
    def wrap(f): 
     def wrapper(*args, **kwargs): 
      return f(*args, **kwargs) 
    return wrapper 
return wrap 

class A(object): 
    @valid_for(['typeB']) 
    def do_something_cool(self): 
     print 'blah' 

Но проблема в том, что у меня не было доступа к фактическому экземпляру класса в декораторе, где я мог проверить атрибут типа экземпляра. Основываясь на this SO question, я подумал: «Я могу использовать дескрипторы!».

Так я тогда попробовал:

class valid_for(object): 
    """ descriptor to check the type of an item, to see 
    if the method is valid for that type""" 
    def __init__(self, func): 
     self.f = func 

    def __get__(self, instance, owner): 
     def wrapper(*args): 
      return self.f(instance, *args) 
     return wrapper 

Но тогда я не мог понять, как получить параметр ['typeB'] переходила в дескрипторе ... по умолчанию, Python проходит в вызываемом методе в качестве аргумента __init__. Я мог бы создавать жестко обозначенные дескрипторы для каждого типа и вставлять их, но тогда мне интересно, буду ли я работать в this problem. Предполагая, что я мог бы преодолеть вложенности вопрос, также представляется менее чистой, чтобы сделать что-то вроде:

class A(object): 
    @valid_for_type_b 
    @valid_for_type_f 
    @valid_for_type_g 
    def do_something_cool(self): 
     print 'blah' 

Делать что-то подобное только что сделал мой func равен списку ['typeB'] ...

class valid_for(object): 
    """ descriptor to check the type of an item, to see 
    if the method is valid for that type""" 
    def __init__(self, func, *args): 
     self.f = func 

    def __get__(self, instance, owner): 
     def wrapper(*args): 
      return self.f(instance, *args) 
     return wrapper 

class A(object): 
    @valid_for(['typeB']) 
    def do_something_cool(self): 
     print 'blah' 

И мой func не находится в списке *args, поэтому я не могу просто выполнить простой своп аргументов (*args пуст).

Я искал подсказки here и here, но не нашел ничего, что кажется чистым или допустимым обходным решением. Есть ли чистый способ сделать это, или мне нужно использовать несколько классов и просто смешивать различные методы? Или, прямо сейчас я склоняюсь к методам экземпляра, который проверяет, но это кажется менее чистым и многоразовыми ...

class A(object): 
    def _valid_for(self, whitelist): 
     if self.genus_type not in whitelist: 
      raise Exception 

    def do_something_cool(self): 
     self._valid_for(['foo']) 
     print 'blah' 

Я использую Python 2.7.5.


UPDATE 1

За предложением в комментариях, я попробовал:

def valid_for2(whitelist): 
    def wrap(f): 
     def wrapper(*args, **kwargs): 
      import pdb 
      pdb.set_trace() 
      print args[0].genus_type 
      return f(*args, **kwargs) 
     return wrapper 
    return wrap 

Но на данный момент, арг [0]. просто возвращает арг:

(Pdb) args[0] 
args = (<FormRecord object at 0x112f824d0>,) 
kwargs = {} 
(Pdb) args[0].genus_type 
args = (<FormRecord object at 0x112f824d0>,) 
kwargs = {} 

Однако использование functools как предложено делает работу - так что я буду награждать ответ. Кажется, есть черная магия в functools, которая позволяет аргументы в?


UPDATE 2

Так исследуя предложение jonrsharpe в более, его метод также, кажется, работает, но я должен явно использовать self вместо args[0]. Не знаю, почему поведение отличается ...

def valid_for2(whitelist): 
    def wrap(f): 
     def wrapper(self, *args, **kwargs): 
      print self.genus_type 
      return f(*args, **kwargs) 
     return wrapper 
    return wrap 

приводит тот же результат, как с functools. Спасибо!

+0

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

+0

Конечно, самая большая проблема в том, что, когда вы декорирования метод, у вас нет доступа к классу, в котором находится метод? Вы можете получить доступ к 'self' (и, следовательно, проверить' isinstance (self, ...) 'или' type (self) ') в' wrapper' (в настоящее время 'args [0]', но вы можете сделать его явным), но вы не можете включить класс, который в настоящее время определяется в 'whitelist'. Как вы разрешите '' typeB'' для фактического класса? – jonrsharpe

+0

Так что моя терминология может сбить с толку. Когда я использую «типB» в качестве примера выше, он определяется как атрибут экземпляра класса - например, B.genus_type = 'typeB'. Я надеюсь, что все может быть одним классом, поэтому type (A) == type (B), но A.genus_type! = B.genus_type. – user

ответ

2

Если я правильно понял вашу ситуацию, то вы ищете a closure - функция, которая может ссылаться на локальное пространство имен внешней функции.

Поскольку вы передаете ['typeB'] к valid_for, как в

@valid_for(['typeB']) 

мы должны сделать valid_forфункции, которая возвращает декоратор. Декоратор, в свою очередь, принимает функцию (зарождающийся метод) как входную и возвращает другую функцию (wrapper).

Ниже wrapper является закрытием, которое может получить доступ к значению typelist от в своем теле во время выполнения.


import functools 
def valid_for(typelist): 
    def decorator(func): 
     @functools.wraps(func) 
     def wrapper(self, *args): 
      if self.genus_type in typelist: 
       return func(self, *args) 
      else: 
       # handle this case 
       raise NotImplementedError(
        '{} not in {}'.format(self.genus_type, typelist)) 
     return wrapper 
    return decorator 

class A(object): 
    def __init__(self): 
     self.genus_type = 'typeA' 
    @valid_for(['typeB']) 
    def do_something_cool(self): 
     print 'blah' 

a = A() 
try: 
    a.do_something_cool() 
except NotImplementedError as err: 
    print(err) 
    # typeA not in ['typeB'] 

a.genus_type = 'typeB' 
a.do_something_cool() 
# blah 

 Смежные вопросы

  • Нет связанных вопросов^_^