2010-07-30 3 views
5

Я хочу, чтобы задать способ класса __init__, каковы его параметры. Простой подход заключается в следующем:Поиск параметров функции в Python

cls.__init__.__func__.__code__.co_varnames[:code.co_argcount] 

Однако, это не будет работать, если класс имеет какие-либо декораторов. Он предоставит список параметров для функции, возвращаемой декоратором. Я хочу перейти к исходному методу __init__ и получить эти исходные параметры. В случае декоратор, функция декоратора будет найдена в замыкании функции возвращенного декоратор:

cls.__init__.__func__.__closure__[0] 

Тем не менее, является более сложным, если есть и другие вещи, в замыкании, который декораторы может сделать время от времени:

def Something(test): 
    def decorator(func): 
     def newfunc(self): 
      stuff = test 
      return func(self) 
     return newfunc 
    return decorator 

def test(): 
    class Test(object): 
     @Something(4) 
     def something(self): 
      print Test 
    return Test 

test().something.__func__.__closure__ 
(<cell at 0xb7ce7584: int object at 0x81b208c>, <cell at 0xb7ce7614: function object at 0xb7ce6994>) 

И тогда я должен решить, хочу ли я с параметрами от декоратора или параметров из исходной функции. Функция, возвращаемая декоратором, может иметь *args и **kwargs по своим параметрам. Что делать, если есть несколько декораторов, и я должен решить, что именно меня волнует?

Итак, каков наилучший способ найти параметры функции, даже если функция может быть украшена? Кроме того, как лучше всего пойти вниз по цепочке декораторов обратно к украшенной функции?

Update:

Вот эффективно, как я делаю это прямо сейчас (имена изменены, чтобы защитить личность обвиняемого):

import abc 
import collections 

IGNORED_PARAMS = ("self",) 
DEFAULT_PARAM_MAPPING = {} 
DEFAULT_DEFAULT_PARAMS = {} 

class DICT_MAPPING_Placeholder(object): 
    def __get__(self, obj, type): 
     DICT_MAPPING = {} 
     for key in type.PARAMS: 
      DICT_MAPPING[key] = None 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.DICT_MAPPING = DICT_MAPPING 
       break 
     return DICT_MAPPING 

class PARAM_MAPPING_Placeholder(object): 
    def __get__(self, obj, type): 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.PARAM_MAPPING = DEFAULT_PARAM_MAPPING 
       break 
     return DEFAULT_PARAM_MAPPING 

class DEFAULT_PARAMS_Placeholder(object): 
    def __get__(self, obj, type): 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.DEFAULT_PARAMS = DEFAULT_DEFAULT_PARAMS 
       break 
     return DEFAULT_DEFAULT_PARAMS 

class PARAMS_Placeholder(object): 
    def __get__(self, obj, type): 
     func = type.__init__.__func__ 
     # unwrap decorators here 
     code = func.__code__ 
     keys = list(code.co_varnames[:code.co_argcount]) 
     for name in IGNORED_PARAMS: 
      try: keys.remove(name) 
      except ValueError: pass 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.PARAMS = tuple(keys) 
       break 
     return tuple(keys) 

class BaseMeta(abc.ABCMeta): 
    def __init__(self, name, bases, dict): 
     super(BaseMeta, self).__init__(name, bases, dict) 
     if "__init__" not in dict: 
      return 
     if "PARAMS" not in dict: 
      self.PARAMS = PARAMS_Placeholder() 
     if "DEFAULT_PARAMS" not in dict: 
      self.DEFAULT_PARAMS = DEFAULT_PARAMS_Placeholder() 
     if "PARAM_MAPPING" not in dict: 
      self.PARAM_MAPPING = PARAM_MAPPING_Placeholder() 
     if "DICT_MAPPING" not in dict: 
      self.DICT_MAPPING = DICT_MAPPING_Placeholder() 


class Base(collections.Mapping): 
    __metaclass__ = BaseMeta 
    """ 
    Dict-like class that uses its __init__ params for default keys. 

    Override PARAMS, DEFAULT_PARAMS, PARAM_MAPPING, and DICT_MAPPING 
    in the subclass definition to give non-default behavior. 

    """ 
    def __init__(self): 
     pass 
    def __nonzero__(self): 
     """Handle bool casting instead of __len__.""" 
     return True 
    def __getitem__(self, key): 
     action = self.DICT_MAPPING[key] 
     if action is None: 
      return getattr(self, key) 
     try: 
      return action(self) 
     except AttributeError: 
      return getattr(self, action) 
    def __iter__(self): 
     return iter(self.DICT_MAPPING) 
    def __len__(self): 
     return len(self.DICT_MAPPING) 

print Base.PARAMS 
#() 
print dict(Base()) 
# {} 

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

class Sub1(Base): 
    def __init__(self, one, two): 
     super(Sub1, self).__init__() 
     self.one = one 
     self.two = two 

Sub1.PARAMS 
# ("one", "two") 
dict(Sub1(1,2)) 
# {"one": 1, "two": 2} 

class Sub2(Base): 
    PARAMS = ("first", "second") 
    def __init__(self, one, two): 
     super(Sub2, self).__init__() 
     self.first = one 
     self.second = two 

Sub2.PARAMS 
# ("first", "second") 
dict(Sub2(1,2)) 
# {"first": 1, "second": 2} 
+1

Зачем вам это нужно? – delnan

+4

Тот факт, что это так сложно, должно указывать на то, что это не так. – katrielalex

+0

Я хочу, чтобы иметь возможность использовать объекты класса как dicts и иметь явные элементы управления, какие ключи отображаются через '__getitem__' и' __iter__'. Параметры '__init__' делают отличные ключи по умолчанию, поэтому я их программно перетаскиваю. Я просто пытаюсь обратиться к угловым делам, например, когда задействованы дескрипторы. –

ответ

3

Рассмотрим декоратора:

def rickroll(old_function): 
    return lambda junk, junk1, junk2: "Never Going To Give You Up" 

class Foo(object): 
    @rickroll 
    def bar(self, p1, p2): 
     return p1 * p2 

print Foo().bar(1, 2) 

В этом, rickroll декоратор берет планку метод, отбрасывает его, заменяет его новой функцией, которая игнорирует свои именованные (и, возможно, пронумерованные!) параметры и вместо этого возвращает строку из классической песни.

Нет дополнительных ссылок на исходную функцию, и сборщик мусора может приходить и удалять его в любое время.

В таком случае я не вижу, как вы можете найти имена параметров p1 и p2. По моему мнению, даже интерпретатор Python сам не знает, что они называли.

+0

LOL Я натолкнулся на вас только за то, что этот декоратор. Теперь читаем ваш ответ ... –

+0

Это отличный момент. Я определенно не считал декоратора, который возвращает совершенно другую, несвязанную функцию. Вы правы в отсутствии ссылок. Это имеет смысл, но в то же время кажется неинтуитивным, что функция, определенная в классе, исчезнет. Вы мне что-то задумали. Благодаря! –

+0

Я преследовал эту тему более конкретно в http://stackoverflow.com/questions/3481872/decorated-for-python-decorators –

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