Introspection Python: obtenir la liste des arguments d'un descripteur de méthode?

une illustration de code comme une introduction à mes questions:

import re, inspect, datetime

inspect.getargspec (re.findall)
# =>
# ArgSpec(args = ['pattern', 'string', 'flags'], varargs=None,
# keywords=None, defaults = (0,))

type (datetime.datetime.replace)
# => <type 'method_descriptor'>

inspect.getargspec (datetime.datetime.replace)
# => Traceback (most recent call last):
#      File "<stdin>", line 1, in <module>
#      File "/usr/lib/python2.7/inspect.py", line 816, in getargspec
#        raise TypeError('{!r} is not a Python function'.format(func))
# TypeError: <method 'replace' of 'datetime.datetime' objects> is
# not a Python function

il semble que le seul moyen pour moi de trouver la signature de datetime.datetime.replace alors que je code est de chercher dans le doc : date.replace(year, month, day) .

la seule partie d'introspection qui semble fonctionner:

datetime.datetime.replace.__doc__
# => 'Return datetime with new specified fields.'

j'ai examiné comment la fonction de Jupyter arglist tool-tip fonctionne, ils ont le même problème, i.e. pas arglist disponible pour datetime.datetime.replace .

alors voici les questions:

  1. est-il encore possible d'obtenir la liste des arguments? Peut-être que je pourrais installer les sources C pour datetime et les connecter via l'attribut __file__ ?

  2. est-il possible d'annoter un <type 'method_descriptor'> avec les informations arglist? Dans ce cas, je pourrais analyser la définition de markdown du doc lié et annoter automatiquement les fonctions du module intégré.

20
demandé sur kouty 2017-02-09 14:02:12

2 réponses

Non, vous ne pouvez pas obtenir plus d'information; installer les sources C ne vous donnerait pas un accès facile à la même. C'est parce que la plupart des méthodes définies dans le code C n'exposent pas réellement cette information; vous devez analyser un morceau plutôt cryptique de code C :

if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace",
                                  datetime_kws,
                                  &y, &m, &d, &hh, &mm, &ss, &us,
                                  &tzinfo, &fold))

la fonction re.findall() est une fonction Python pur , donc introspectable.

j'ai dit la plupart méthodes définies en C, parce qu'à partir de Python 3.4 et plus, les méthodes qui utilisent le nouveau préprocesseur de Clinique D'Argument inclura un nouvel attribut __text_signature__ , que le interne inspect._signature_fromstr() fonction peut analyser. Cela signifie que même pour de telles méthodes c-défini, vous pouvez introspecter les arguments:

>>> import io
>>> import inspect
>>> type(io.BytesIO.read)
<class 'method_descriptor'>
>>> inspect.signature(io.BytesIO.read)
<Signature (self, size=None, /)>

voir Aussi Ce sont __signature__ et __ _ _ texte_signature__ utilisées pour Python 3.4

le module datetime n'a pas encore reçu beaucoup D'Amour Clinique Argument. Nous allons devoir être patient, ou si vous vous souciez vraiment beaucoup à ce sujet, fournir des correctifs qui convertissent le module en utilisant la clinique D'Argument.

si vous voulez voir quels modules do ont déjà un support, regardez le Modules/clinic sous-répertoire qui contient le résultat clinique généré; pour le module datetime , seul le module datetime.datetime.now() est actuellement inclus. Cette méthode définit un bloc clinique :

/*[clinic input]
@classmethod
datetime.datetime.now
    tz: object = None
        Timezone object.
Returns new datetime object representing current time local to tz.
If no tz is specified, uses local timezone.
[clinic start generated code]*/

static PyObject *
datetime_datetime_now_impl(PyTypeObject *type, PyObject *tz)
/*[clinic end generated code: output=b3386e5345e2b47a input=80d09869c5267d00]*/

ce qui rend la méthode introspectable:

>>> import datetime
>>> inspect.signature(datetime.datetime.now)
<Signature (tz=None)>

il n'y a aucun moyen de rattacher directement des informations aux fonctions et méthodes C qui ne sont pas introspectables; elles ne supportent pas non plus les attributs.

la Plupart de saisie semi-automatique les solutions qui veulent prendre en charge de tels objets utilisent des structures de données séparées où l'information est maintenue indépendamment (avec tous les risques inhérents à la synchronisation des données). Certains d'entre eux sont disponibles pour vos propres besoins:

  • la bibliothèque D'intelligence de code Komodo IDE (open source, utilisé d'autres éditeurs aussi) utilise le format CIX pour coder ces données; vous pouvez télécharger le catalogue Python 3 . Malheureusement pour votre exemple spécifique, la signature de fonction datetime.replace() n'a pas été étoffée soit :

    <scope doc="Return datetime with new specified fields." ilk="function" name="replace" />
    
  • la nouvelle syntaxe Python 3.5 Type hinting doit également savoir quels types d'arguments les objets attendent, et à cette fin les fichiers de raccourcis doivent être fournis pour les objets qui ne peuvent pas être introspectés. Le Python typeshed project fournit ceux-ci. Cela comprend tous les noms d'arguments pour le datetime module :

    class datetime:
        # ...
        def replace(self, year: int = ..., month: int = ..., day: int = ..., hour: int = ...,
            minute: int = ..., second: int = ..., microsecond: int = ..., tzinfo:
            Optional[_tzinfo] = None) -> datetime: ...
    

    vous devez analyser un tel fichier vous-même; ils ne peuvent pas toujours être importés comme les types de référence stubs pas encore définis, plutôt que d'utiliser forward references :

    >>> import importlib.machinery
    >>> path = 'stdlib/3/datetime.pyi'
    >>> loader = importlib.machinery.SourceFileLoader('datetime', path)
    >>> loader.load_module()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<frozen importlib._bootstrap_external>", line 399, in _check_name_wrapper
      File "<frozen importlib._bootstrap_external>", line 823, in load_module
      File "<frozen importlib._bootstrap_external>", line 682, in load_module
      File "<frozen importlib._bootstrap>", line 251, in _load_module_shim
      File "<frozen importlib._bootstrap>", line 675, in _load
      File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
      File "<frozen importlib._bootstrap_external>", line 678, in exec_module
      File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed
      File "stdlib/3/datetime.pyi", line 12, in <module>
        class tzinfo:
      File "stdlib/3/datetime.pyi", line 13, in tzinfo
        def tzname(self, dt: Optional[datetime]) -> str: ...
    NameError: name 'datetime' is not defined
    

    vous pouvez être en mesure de travailler autour de cela en utilisant un objet de module prédéfini et globals, puis itérer sur les erreurs de nom jusqu'à ce qu'il importe cependant. Je vais laisser cela comme un exercice pour le lecteur. Mypy et les autres vérificateurs de type n'essaient pas d'exécuter le code, ils construisent simplement un AST.

7
répondu Martijn Pieters 2017-05-23 11:46:11

le problème que vous avez est causé par le fait que les fonctions codées C n'exposent pas leur signature. Vous trouverez plus d'informations sur cette réponse à "Comment trouver l'arité d'une méthode en Python" .

dans votre cas, re.findall est défini en Python (voir def findall(pattern, string, flags=0): ) alors que datetime.datetime.replace est écrit en C (voir datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) ).

Vous pouvez voir en utilisant les différents attributs disponibles (et l'attribut __code__ en particulier) sur la fonction avec le dir builtin:

>>> dir(datetime.datetime.replace)
['__call__', '__class__', '__delattr__', '__doc__', '__format__', '__get__', '__getattribute__', '__hash__', '__init__', '__name__', '__new__', '__objclass__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> dir(re.findall)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> datetime.datetime.replace.__code__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'method_descriptor' object has no attribute '__code__'
>>> re.findall.__code__
<code object findall at 0x7fe7234e74b0, file "/usr/lib/python2.7/re.py", line 173>

habituellement, le help vous donne ce dont vous avez besoin (basé sur l'attribut __doc__ ) mais dans votre cas, il ne semble pas aider beaucoup:

>>> help(datetime.datetime.replace)
Help on method_descriptor:

replace(...)
    Return datetime with new specified fields.

aussi, une idée pourrait être d'essayer de mettre l'attribut __code__ à quelque chose correspondant à vos besoins mais vous ne pouvez pas tweak beaucoup sur builtin types sans sous-classement .

3
répondu Josay 2017-05-23 12:17:05