Question

J'aimerais créer un décorateur comme ci-dessous, mais je n'arrive pas à penser à une implémentation qui fonctionne. Je commence à penser que ce n'est pas possible, mais je voudrais d'abord vous demander à vous, les gars.

Je me rends compte qu’il existe différentes façons de créer des variables statiques en Python, mais je trouve ces méthodes laides. J'aimerais vraiment utiliser la syntaxe ci-dessous, si possible.

@static(x=0)
def f():
    x += 1
    print x

f() #prints 1
f() #prints 2

Je me moque de savoir si la mise en œuvre de static est longue ou de hackity, tant que cela fonctionne comme ci-dessus.

J'ai créé cette version, mais elle n'autorise qu'une <function>.<varname> syntaxe, qui devient rapidement fastidieuse avec des noms de fonction et de variable plus longs.

def static(**assignments):
    def decorate(func):
        for var, val in assignments.items():
            setattr(func, var, val)
        return func
    return decorate

Plusieurs choses auxquelles j'ai pensé mais que je n'ai pas pu me rendre au travail sont les suivantes:

  1. Transformer f (la fonction décorée) en une classe appelable, et stocker d’une manière ou d’une autre les vars statiques dans self de manière transparente.
  2. Modification des valeurs globales de f () dans le décorateur et insertion des instructions 'x globales' dans le code de f.
  3. Transformer f en un générateur où nous lions les variables à la main, puis exécutons directement le code de f.
Était-ce utile?

La solution

Voici un décorateur qui semble fonctionner. Notez que cela nécessite des sections locales de retour () à la fin de la fonction car il est impossible de définir des sections locales de l'extérieur (je n'ai pas beaucoup d'expérience en programmation, donc s'il existe un moyen, je ne le connais pas).

class Static(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __call__(self, f):
        def wrapped_f():
            try:
                new_kwargs = {}
                for key in self.kwargs:
                    i = getattr(f, key)
                    new_kwargs[key] = i
                self.kwargs = new_kwargs
            except:
                pass
            for key, value in f(**self.kwargs).items():
                setattr(f, key, value)
        return wrapped_f

@Static(x=0, y=5, z='...')
def f(x, y, z):
    x += 1
    y += 5
    print x, y, z
    return locals()

Le résultat serait:

>>> f()
1 10 ...
>>> f()
2 15 ...
>>> f()
3 20 ...

EDIT:

J'ai trouvé quelque chose sur http://code.activestate.com/recipes/410698/ et décidé d'essayer de l'ajouter à cela. Cela fonctionne sans retour maintenant.

EDIT again: Modifié en pour le rendre plus rapide de quelques secondes. Modifier 3; changé pour fonctionner à la place de la classe

def static(**kwargs):
    def wrap_f(function):
        def probeFunc(frame, event, arg):
            if event == 'call':
                frame.f_locals.update(kwargs)
                frame.f_globals.update(kwargs)
            elif event == 'return':
                for key in kwargs:
                    kwargs[key] = frame.f_locals[key]
                sys.settrace(None)
            return probeFunc
        def traced():
            sys.settrace(probeFunc)
            function()
        return traced
    return wrap_f

testé:

@static(x=1)
def f():
    x += 1

global_x = 1
def test_non_static():
    global global_x
    global_x += 1


print 'Timeit static function: %s' % timeit.timeit(f)
print 'Timeit global variable: %s' % timeit.timeit(test_non_static)

sortie:

Timeit static function: 5.10412869535
Timeit global variable: 0.242917510783

L'utilisation de settrace le ralentit considérablement.

Autres conseils

Au moment où votre décorateur obtient l'objet fonction f, il a déjà été compilé - en particulier, il a été compilé en sachant que x est local (car il est attribué avec l'attribution +=), la valeur normale optimisation (dans 2.* vous pouvez vaincre l'optimisation à un prix renversant en performances en démarrant exec '' avec f.x; dans global, vous ne pouvez pas vaincre l'optimisation). Essentiellement, pour utiliser la syntaxe dont vous rêvez, vous devez recompiler nonlocal (en récupérant ses sources, si vous savez qu'elles seront disponibles au moment de l'exécution, ou, bien plus difficile, avec des hacks bytecode) avec des sources modifiées Si vous avez décidé de suivre cette voie, la solution la plus simple consiste probablement à remplacer __builtin__ par with_static dans tout le corps de globvar.

Personnellement, si et quand je me force à lutter si fort contre le langage (ou une autre technologie) que je tente de me plier à ma volonté pour imposer mes désirs, je reconnais que j’utilise le mauvais langage (ou autre technologie), si ces désirs sont absolument cruciaux, alors la solution doit être de changer de technologie; ou, si ces désirs ne sont pas cruciaux, abandonnez-les.

De toute façon, je renonce à essayer de déformer le langage trop loin de ses intentions évidentes en matière de conception: même si je trouvais un kludge hacky et fragile, il serait sans doute impossible de le maintenir. Dans ce cas, les intentions de Python sont très claires: les noms de bar qui sont liés à nouveau dans une fonction sont les paramètres locaux de cette fonction, sauf s'ils sont explicitement désignés comme globals - point. Donc, votre tentative de faire des noms de bar (qui sont re-liés dans une fonction) signifie quelque chose de complètement différent de & "; Les sections locales &"; est exactement ce genre de combat.

Modifier : si vous êtes prêt à renoncer à l'utilisation de nom de fichier pour votre "ique & "; puis soudainement, ne vous battez plus contre Python, mais plutôt & "aller avec le grain &"; de la langue (malgré le problème de conception de static [et g], mais c'est un discours séparé ;-). Ainsi, par exemple:

class _StaticStuff(object):
  _static_stack = []
  def push(self, d):
    self._static_stack.append(d)
  def pop(self):
    self._static_stack.pop()
  def __getattr__(self, n):
    return self._static_stack[-1][n]
  def __setattr__(self, n, v):
    self._static_stack[-1][n] = v
import __builtin__
__builtin__.static = _StaticStuff()

def with_static(**variables):
  def dowrap(f):
    def wrapper(*a, **k):
      static.push(variables)
      try: return f(*a, **k)
      finally: static.pop()
    return wrapper
  return dowrap

@with_static(x=0)
def f():
    static.x += 1
    print static.x

f()
f()

Cela fonctionne comme vous le souhaitez, en imprimant 1 puis 2. (J'utilise <=> pour simplifier l'utilisation de <=> pour décorer des fonctions de n'importe quel module, bien sûr). Vous pouvez avoir plusieurs implémentations différentes, mais le point clé de toute bonne est que & "Variables statiques &"; seront qualifiés , pas des noms de référence, ce qui permettra de préciser qu’il ne s’agit pas de variables locales, de jouer avec le grain de la langue, etc. (Des conteneurs intégrés similaires, et des noms qualifiés basés sur eux, devraient avoir été utilisés dans la conception de Python, au lieu des problèmes de conception <=> et <=>, pour indiquer d'autres types de variables qui ne sont pas Vous ne devez pas utiliser les noms de fichiers locaux, par exemple ... ah bon, vous pouvez mettre en place vous-même un <=> conteneur spécial dans le même sens que les précédents <=>, sans même avoir besoin de décoration, bien que je ne sois pas Cela est tout à fait faisable pour le <=> cas [peut-être avec des décorations et une infime quantité de magie noire ...; =))).

Modifier : un commentaire indique que le code fourni ne fonctionne pas lorsque vous décorez uniquement une fonction qui renvoie une fermeture (au lieu de décorer la fermeture elle-même). C’est vrai: vous devez bien sûr décorer la fonction spécifique qui utilise les <=> (et il ne peut y en avoir qu’une, par définition de fonction - <=> variables!), Pas une fonction aléatoire qui n’utilise pas réellement le <=> mais plutôt se trouve simplement être dans une connexion lexicale avec celui que fait . Par exemple:

def f():
  @with_static(x=0)
  def g():
    static.x += 1
    print static.x
  return g

x = f()
x()
x()

Cela fonctionne, tout en déplaçant le décorateur vers <=> au lieu de <=> ne le fait pas (et ne pourrait pas éventuellement).

Si les desiderata actuels ne concernent pas des variables statiques (visibles et utilisables uniquement dans une seule fonction), il s'agit plutôt d'un élément hybride utilisable dans une certaine zone particulière.Un ensemble de fonctions qui doivent être spécifiées très précisément (et sans doute mises en œuvre très différemment, en fonction des spécifications réelles sont ) - et idéalement, cela doit se produire dans une nouvelle et distincte question SO, parce que celle-ci (qui porte spécifiquement sur statique ) et la réponse à cette question spécifique sont déjà suffisamment volumineuses.

Voici une solution très simple qui fonctionne comme les variables statiques python normales.

def static(**kwargs):
  def wrap(f):
    for key, value in kwargs.items():
      setattr(f, key, value)
    return f
  return wrap

Exemple d'utilisation:

@static(a=0)
def foo(x):
  foo.a += 1
  return x+foo.a

foo(1)  # => 2
foo(2)  # => 4
foo(14) # => 17

Ceci correspond plus étroitement à la manière habituelle de faire des variables statiques python

def foo(x):
  foo.a += 1
  return x+foo.a
foo.a = 10

Vous pouvez faire quelque chose comme ça (mais je ne l'ai pas testé de manière approfondie; utilisé avec CPython 2.6):

import types

def static(**dict):
    def doWrap(func):
        scope = func.func_globals
        scope.update(dict)
        return types.FunctionType(func.func_code, scope)
    return doWrap

# if foo() prints 43, then it's wrong
x = 42

@static(x = 0)
def foo():
   global x
   x += 1
   print(x)

foo() # => 1
foo() # => 2

Il faut déclarer ces variables en tant que variables globales globales et ombrées au niveau supérieur, mais cela devrait fonctionner autrement. Pas sûr de la performance, cependant.

Pourquoi pas ça, sans décorateurs?

class State(dict):
    """Object interface to dict."""
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError, name

def f(d=State(x=0)):
    d.x += 1
    return d.x

Et le voici en action:

>>> f()
1
>>> f()
2
>>> f()
3

Voici quelque chose qui pourrait être beaucoup plus clair. Cela n'implique aucun décorateur ni piratage.

class F( object ):
    def __init__( self ):
        self.x= 0
    def __call__( self ):
        self.x += 1
        print self.x

f= F()

Vous avez maintenant votre fonction f avec une variable statique.

f() #prints 1
f() #prints 2

Lorsque vous avez besoin de sauvegarder l’état entre les invocations d’une fonction, il est presque toujours préférable d’utiliser un générateur / coroutine ou un objet. Puisque vous voulez utiliser & "Nue &"; les noms de variables, alors vous voudrez la version coroutine.

# the coroutine holds the state and yields rather than returns values
def totalgen(x=0, y=0, z=0):
    while True:
       a, b, c = (yield x, y, z)
       x += a
       y += b
       z += c

# the function provides the expected interface to callers
def runningtotal(a, b, c, totalgen=totalgen()):
    try:
        return totalgen.send((a, b, c))    # conveniently gives TypeError 1st time
    except TypeError:
        totalgen.next()                    # initialize, discard results
        return totalgen.send((a, b, c))

Le résultat est une fonction qui accumule les totaux des trois valeurs qui lui sont transmises, exactement comme si elle avait des variables statiques, mais les accumulateurs sont de vieilles variables locales dans ce qui est essentiellement un générateur infini.

Une légère modification de autre réponse :

def static(**kwargs):
    def decorator(func):
        return type(func)(func.func_code, dict(func.func_globals, **kwargs))
    return decorator

message = "goodbye, world!"
@static(message="hello, world!")
def hello(): print message

hello()

J'ai trouvé qu'il était difficile de remplacer un nom intégré par un nom d'argument de fonction. J'ai donc remplacé **dict par le plus canonique **kwargs. J'ai également enregistré quelques lignes et IMO a rendu le code plus propre en construisant un nouveau dict avec dict(the_original, **the_updates). Enfin, j’ai sauvé quelques lignes en accédant au constructeur de la fonction via type(func) plutôt que par une importation - les objets type et classe étant des méthodes fabriques, utilisez-les!

J'ai également supprimé une global déclaration. Cela fonctionne tant que vous ne réassociez pas la variable, c’est-à-dire que la suppression de let rend le pointeur (mais pas l’objet) en lecture seule. Si vous l’utilisez de cette façon, static est peut-être un meilleur nom que <=> pour la liaison ainsi introduite.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top