IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Typage statique et constructeur privé


Sujet :

Python

  1. #21
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    Mais faire ça sans exécution, je ne vois pas...
    Certains outils, dont mypy, analysent le code existant et détectent les erreurs de type. Par exemple, si on écrit pow('foo', 'bar') dans un code Python et qu'on lance mypy dessus alors, sans exécuter ce code Python, mypy détecte quand même qu'il y a un problème et affiche un message d'erreur de la forme « {nomFichier}:{numéroLigne}: error: No overload variant of "pow" matches argument types "str", "str" ».

    Dans les codes de mes précédents messages, toutes les vérifications reposent sur ce genre d'outil, qui s'appuie sur les annotations de type pour détecter les erreurs de type.

    Quand le typage statique est actif dans tout le code, vérifier les types au runtime ne sert généralement à rien, puisque toutes les erreurs possibles sur les types ont déjà été vérifiées par un outil qui a lu le code.

    Par contre, ces vérifications se limitent aux types. Elles ne permettent pas de vérifier des préconditions du genre N>1.

    Citation Envoyé par fred1599 Voir le message
    @Pyramidev,

    Regarde du côté de ce module, je ne le connais pas...
    J'avais jeté un coup d'œil à une époque, mais je crois que les vérifications se font uniquement au runtime.
    À part ça, la dernière version date de 2017. Je ne sais pas si ce projet est vraiment maintenu.

  2. #22
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 461
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 461
    Points : 9 248
    Points
    9 248
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Dans les codes de mes précédents messages, toutes les vérifications reposent sur ce genre d'outil, qui s'appuie sur les annotations de type pour détecter les erreurs de type.
    Merci, je comprends mieux ta recherche.

    Sans exécution, je n'utilise que PyLint, mais je ne lui demande que des infos sur la syntaxe.

    Je n'ai donc pas de solution, mais j'en vois tout l’intérêt, et ça commence à m’intéresser!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  3. #23
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 101
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 101
    Points : 4 446
    Points
    4 446
    Par défaut
    après install de mypy, j'ai vérifié mon code ... mypy n’interprète pas __new__() ! du coup pas si génial si il ne couvre qu'une partie du code

    De plus j'ai essayé de reprendre ton exemple et je n'arrive pas à le faire passer à mypy ???

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def test(c: ClientCode):
        print(c)
     
    def mockup(c: bool)-> Optional[ClientCode]:
        if c:
            return ClientCode("test")
        else:
            return None
     
    code = mockup(True)
    test(code)
    mypy error: Argument 1 to "test" has incompatible type "Optional[ClientCode]"; expected "ClientCode"
    $moi= ( !== ) ? : ;

  4. #24
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par papajoker Voir le message
    De plus j'ai essayé de reprendre ton exemple et je n'arrive pas à le faire passer à mypy ???

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def test(c: ClientCode):
        print(c)
     
    def mockup(c: bool)-> Optional[ClientCode]:
        if c:
            return ClientCode("test")
        else:
            return None
     
    code = mockup(True)
    test(code)
    mypy error: Argument 1 to "test" has incompatible type "Optional[ClientCode]"; expected "ClientCode"
    Quand mypy lit def mockup(c: bool)-> Optional[ClientCode], il considère que le type de retour est toujours Optional[ClientCode], indépendamment de la valeur en entrée. En gros, que tu écrives mockup(True), mockup(False) ou mockup(mon_booleen_inconnu), dans tous les cas, il va considérer que le type de retour est Optional[ClientCode]. Il ne va pas se dire « ah, dans le cas de mockup(True), le type de retour est ClientCode ».

    Donc il faut écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    code = mockup(True)
    if code is not None:
        test(code)
    L'intérêt est que tu es libre de changer l'implémentation du code de mockup. Par exemple, tu peux écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    def mockup(c: bool) -> Optional[ClientCode]:
        if c:
            return None
        else:
            return ClientCode("test")
    Autrement dit, l'erreur venait de la manière qu'à mypy de séparer l'interface et l'implémentation : il considère que les annotations de type des paramètres et du retour de la fonction font partie de l'interface qui doit être respectée par le code appelant, tandis que le contenu de la fonction est une implémentation qui peut changer.

    Le cas le plus fréquent dans lequel ce fonctionnement m'arrange est quand j'ai une fonction qui retourne un générateur et que je veux pouvoir à tout moment pouvoir changer l'implémentation et retourner une liste, ou le contraire. Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    def get_direct_children_file_names(dirPath: str) -> Iterable[str]:
    	for entry in os.scandir(dirPath):
    		if entry.is_file():
    			yield entry.name
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    def get_direct_children_file_names(dirPath: str) -> Iterable[str]:
    	result: List[str] = list()
    	for entry in os.scandir(dirPath):
    		if entry.is_file():
    			result.append(entry.name)
    	return result
    Pour avoir la liberté de passer de l'un à l'autre, je veux que le code appelant ne se repose que sur l'hypothèse que le type de retour est Iterable[str]. Je ne veux pas que le mypy se dise, par exemple « là, je sais que l'implémentation retourne une liste, donc je laisse le code appelant appeler les méthodes spécifiques aux listes », car je n'aurais plus la garantie de pouvoir changer l'implémentation en utilisant un autre type d'itérable sans craindre de casser le code appelant.

  5. #25
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 101
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 101
    Points : 4 446
    Points
    4 446
    Par défaut
    merci, mais en fait ce que je n'avais pas compris c'est que mypy m'oblige à écrire if not None avec un type Optional[...]

    mais au moins maintenant j'ai pu testé ma classe (sans __new__ puisque mypy ne suit pas ce code)
    types valide par mypy et l'init n'a même pas a être obligatoirement privé (c'est surtout que tu viens du C++)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    from typing import Optional
     
    class ClientCode:
        pass
     
    class ClientCode():
        def __init__(self, value: str, error = None)-> None:
            self.data = ''
            import inspect, sys
            if inspect.stack(context=1)[1].function != "create":
                raise Exception('oops init privé')
            self.data = value
     
        @classmethod
        def create(cls, value: str, error = None)-> Optional[ClientCode]:
            if cls.tester(value, error):
                return ClientCode(value, error)
            return None
     
        @staticmethod
        def tester(value: str, error = "raise")-> bool:
            if 'b' in value:
                if error == "raise":
                    raise Exception('oops')
                else:
                    return False
            return True
     
        def __str__(self):
            return self.data
     
        def __bool__(self):
            return self.data is not None
     
    # ---------------------------
     
    def test(c: ClientCode):
        print(c)
     
    code = ClientCode.create("aaa")
    if code is not None:
        test(code)
    code = ClientCode.create('1b1')  # NoneType
    if code is not None:
        test(code)
    else:
        test(code) # erreur mypy
     
    code = ClientCode.create("aba", "raise")   # exception
    if code is not None:
        test(code)
    ------

    Par contre, je me demande comment mypy gère le polymorphisme ... car ici il serait plus logique d'avoir une class Validator plus une ClientCode(Validator) ... et si j'ai une liste de Validator(Fournisseurs ... Créanciers)
    $moi= ( !== ) ? : ;

  6. #26
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par papajoker Voir le message
    Par contre, je me demande comment mypy gère le polymorphisme ... car ici il serait plus logique d'avoir une class Validator plus une ClientCode(Validator) ... et si j'ai une liste de Validator(Fournisseurs ... Créanciers)
    De ce que je comprends, tu veux factoriser du code de ClientCode vers une classe de base Validator. Du coup, il faut pouvoir gérer le type de retour de Validator.create qui dépend du type de la classe dérivée (Optional[ClientCode], Optional[SupplierCode]... Optional[CreditorCode]).
    Il est possible de le faire en mypy, mais il faut faire intervenir de la programmation générique.
    Exemples extraits de la documentation de mypy :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from typing import TypeVar
     
    T = TypeVar('T', bound='Shape')
     
    class Shape:
        def set_scale(self: T, scale: float) -> T:
            self.scale = scale
            return self
     
    class Circle(Shape):
        def set_radius(self, r: float) -> 'Circle':
            self.radius = r
            return self
     
    class Square(Shape):
        def set_width(self, w: float) -> 'Square':
            self.width = w
            return self
     
    circle = Circle().set_scale(0.5).set_radius(2.7)  # type: Circle
    square = Square().set_scale(0.5).set_width(3.2)  # type: Square
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from typing import TypeVar, Tuple, Type
     
    T = TypeVar('T', bound='Friend')
     
    class Friend:
        other = None  # type: Friend
     
        @classmethod
        def make_pair(cls: Type[T]) -> Tuple[T, T]:
            a, b = cls(), cls()
            a.other = b
            b.other = a
            return a, b
     
    class SuperFriend(Friend):
        pass
     
    a, b = SuperFriend.make_pair()
    Dans le deuxième exemple, le type de SuperFriend.make_pair() est Tuple[SuperFriend, SuperFriend].
    Tu pourrais utiliser la même technique pour Validator.create.

  7. #27
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 101
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 101
    Points : 4 446
    Points
    4 446
    Par défaut
    en déplaçant le code de __new__() dans create() un moyen (élégant?) de rendre privé init() et compatible mypy

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    class ClientCode():
        def __init__(self, value=None):
            raise Exception('init oops privé')
     
        @classmethod
        def create(cls, value: str, use_exception:bool = False)-> Optional['ClientCode']:
            if cls.tester(value, use_exception):
                obj = cls.__new__(cls)
                obj.data = value
                return obj
            return None
     
        @staticmethod
        def tester(value: str, use_exception:bool = True)-> bool:
            if 'b' in value:
                if use_exception:
                    raise Exception('oops bad client ID')
                else:
                    return False
            return True
     
        def __str__(self):
            return self.data
     
    # tests
     
    def test(c: ClientCode):
        print('test: ', c)
     
    # exceptions
    code = ClientCode("aba") # oops init
    code = ClientCode.create("aba", True) # oops ID
    # ok
    code = ClientCode.create("ccc")
    if code is not None:
        test(code)
    # erreur type mypy
    code = ClientCode.create("aba")
    if code is not None:
        test(code)
    else:
        test(code)
    $moi= ( !== ) ? : ;

  8. #28
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Merci pour le tuyau.

    Si j'adapte le code à ma sauce, cela donne :

    clientcode.py :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
     
    from __future__ import annotations
     
    __all__ = [
    	'ClientCode',
    	'InvalidClientCodeFormatException',
    ]
     
    __author__ = 'Pyramidev'
     
    from typing import Optional
     
     
    class ClientCode:
     
    	__value: str
     
    	def __init__(self) -> None:
    		raise TypeError('Forbidden operation.')
     
            @classmethod
    	def create_or_raise(cls, value: str) -> ClientCode:
    		result = cls.create_or_None(value)
    		if result is not None:
    			return result
    		raise InvalidClientCodeFormatException(f'"{value}" does not have a valid client code format.')
     
            @classmethod
    	def create_or_None(cls, value: str) -> Optional[ClientCode]:
    		if cls.valid_format(value):
    			result: ClientCode = cls.__new__(cls)
    			result.__value = value
    			return result
    		return None
     
            @classmethod
    	def valid_format(cls, value: str) -> bool:
    		return bool(value) and all(map(lambda char: char >= 'A' and char <= 'Z', value))
     
    	def __str__(self) -> str:
    		return self.__value
     
     
    class InvalidClientCodeFormatException(Exception):
    	pass
    Code utilisateur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    __author__ = 'Pyramidev'
     
    import os
    import sys
     
    from clientcode import ClientCode, InvalidClientCodeFormatException
     
    def print_something_about_the_client_code(client_code: ClientCode) -> None:
    	print(f'"{client_code}" has a valid client code format.')
     
    def mainV1() -> None:
    	first_arg = sys.argv[1]
    	client_code = ClientCode.create_or_None(first_arg)
    	if client_code is not None:
    		print_something_about_the_client_code(client_code)
    	else:
    		print('There is nothing to say.')
    	os.system('pause')
     
    def mainV2() -> None:
    	first_arg = sys.argv[1]
    	try:
    		client_code = ClientCode.create_or_raise(first_arg)
    		print_something_about_the_client_code(client_code)
    	except InvalidClientCodeFormatException:
    		print('There is nothing to say.')
    	os.system('pause')
     
    def mainV3() -> None:
    	first_arg = sys.argv[1]
    	client_code = ClientCode.create_or_None(first_arg)
    	if client_code is not None:
    		print_something_about_the_client_code(client_code)
    	else:
    		print('There is nothing to say.')
    		print_something_about_the_client_code(client_code) # Erreur détectée par mypy.
    	print_something_about_the_client_code(client_code) # Erreur détectée par mypy.
    	os.system('pause')
     
    if __name__ == '__main__':
    	mainV1()
    Et si je veux déplacer une partie du code de ClientCode vers une classe de base StringWithFormat, "clientcode.py" est découpé en deux fichiers :

    stringwithformat.py :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     
    __all__ = [
    	'StringWithFormat',
    ]
     
    __author__ = 'Pyramidev'
     
    import abc
    from typing import NoReturn, Optional, Type, TypeVar
     
     
    T = TypeVar('T', bound='StringWithFormat')
     
    class StringWithFormat:
     
    	_value: str
     
            @classmethod
    	def create_or_raise(cls: Type[T], value: str) -> T:
    		result = cls.create_or_None(value)
    		if result is not None:
    			return result
    		cls._raise_exception(value)
     
            @classmethod
    	def create_or_None(cls: Type[T], value: str) -> Optional[T]:
    		if cls.valid_format(value):
    			result: T = cls.__new__(cls)
    			result._value = value
    			return result
    		return None
     
            @classmethod
            @abc.abstractmethod
    	def valid_format(cls, value: str) -> bool:
    		pass
     
            @classmethod
            @abc.abstractmethod
    	def _raise_exception(cls, value: str) -> NoReturn:
    		pass
     
    	def __str__(self) -> str:
    		return self._value
    clientcode.py (nouveau) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    __all__ = [
    	'ClientCode',
    	'InvalidClientCodeFormatException',
    ]
     
    __author__ = 'Pyramidev'
     
    from stringwithformat import StringWithFormat
    from typing import NoReturn
     
     
    class ClientCode(StringWithFormat):
     
    	def __init__(self) -> None:
    		"""
                    To construct a ClientCode, you should call
                    ClientCode.create_or_raise or ClientCode.create_or_None.
                    """
    		raise TypeError('Forbidden operation.')
     
            @classmethod
    	def valid_format(cls, value: str) -> bool:
    		return bool(value) and all(map(lambda char: char >= 'A' and char <= 'Z', value))
     
            @classmethod
    	def _raise_exception(cls, value: str) -> NoReturn:
    		raise InvalidClientCodeFormatException(f'"{value}" does not have a valid client code format.')
     
     
    class InvalidClientCodeFormatException(Exception):
    	pass
    Edit 12h48 : utilisation de typing.NoReturn.
    Edit 12h53 : changement cosmétique dans le code.
    Edit 13h03 : ajout d'un "_" au début de raise_exception, car on n'est pas supposé utiliser cette méthode en dehors de la hiérarchie de classes.

  9. #29
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Améliorations du code. Entre autres :
    • renommage de StringWithFormat en MixinStringWithFormat ;
    • ajout du support de == et de hash.


    mixinstringwithformat.py :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
     
    __all__ = [
    	'MixinStringWithFormat',
    ]
     
    __author__ = 'Pyramidev'
     
    import abc
    from typing import NoReturn, Optional, Type, TypeVar
     
     
    T = TypeVar('T', bound='MixinStringWithFormat')
     
    class MixinStringWithFormat:
     
    	__value: str
     
    	# pylint: disable=inconsistent-return-statements
            @classmethod
    	def create_or_raise(cls: Type[T], value: str) -> T:
    		result = cls.create_or_None(value)
    		if result is not None:
    			return result
    		cls._raise_exception(value)
    	# pylint: enable=inconsistent-return-statements
     
            @classmethod
    	def create_or_None(cls: Type[T], value: str) -> Optional[T]:
    		if cls.valid_format(value):
    			result: T = cls.__new__(cls)
    			result.__value = value # pylint: disable=protected-access
    			return result
    		return None
     
            @classmethod
            @abc.abstractmethod
    	def valid_format(cls, value: str) -> bool:
    		pass
     
            @classmethod
            @abc.abstractmethod
    	def _raise_exception(cls, value: str) -> NoReturn:
    		pass
     
    	def __str__(self) -> str:
    		return self.__value
     
    	def __eq__(self, other: object) -> bool:
    		if type(self) != type(other): # pylint: disable=unidiomatic-typecheck
    			return False
    		return str(self) == str(other)
     
    	def __hash__(self) -> int:
    		return hash(self.__value)
    clientcode.py :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    __all__ = [
    	'ClientCode',
    	'InvalidClientCodeFormatException',
    ]
     
    __author__ = 'Pyramidev'
     
    from typing import NoReturn
    from mixinstringwithformat import MixinStringWithFormat
     
     
    class ClientCode(MixinStringWithFormat):
     
    	def __init__(self) -> None:
    		"""
                    To construct a ClientCode, you should call
                    ClientCode.create_or_raise or ClientCode.create_or_None.
                    """
    		raise TypeError('Forbidden operation.')
     
            @classmethod
    	def valid_format(cls, value: str) -> bool:
    		return bool(value) and all(map(lambda char: 'A' <= char <= 'Z', value))
     
            @classmethod
    	def _raise_exception(cls, value: str) -> NoReturn:
    		raise InvalidClientCodeFormatException(f'"{value}" does not have a valid client code format.')
     
     
    class InvalidClientCodeFormatException(Exception):
    	pass
    Code utilisateur (inchangé) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    __author__ = 'Pyramidev'
     
    import os
    import sys
     
    from clientcode import ClientCode, InvalidClientCodeFormatException
     
    def print_something_about_the_client_code(client_code: ClientCode) -> None:
    	print(f'"{client_code}" has a valid client code format.')
     
    def mainV1() -> None:
    	first_arg = sys.argv[1]
    	client_code = ClientCode.create_or_None(first_arg)
    	if client_code is not None:
    		print_something_about_the_client_code(client_code)
    	else:
    		print('There is nothing to say.')
    	os.system('pause')
     
    def mainV2() -> None:
    	first_arg = sys.argv[1]
    	try:
    		client_code = ClientCode.create_or_raise(first_arg)
    		print_something_about_the_client_code(client_code)
    	except InvalidClientCodeFormatException:
    		print('There is nothing to say.')
    	os.system('pause')
     
    def mainV3() -> None:
    	first_arg = sys.argv[1]
    	client_code = ClientCode.create_or_None(first_arg)
    	if client_code is not None:
    		print_something_about_the_client_code(client_code)
    	else:
    		print('There is nothing to say.')
    		print_something_about_the_client_code(client_code) # Erreur détectée par mypy.
    	print_something_about_the_client_code(client_code) # Erreur détectée par mypy.
    	os.system('pause')
     
    if __name__ == '__main__':
    	mainV1()
    Ce que je retiens du fil :
    • À la place de ma bidouille avec les arguments nommés This_constructor_is_private et Do_not_call_it_outside_this_module, je peux utiliser l'astuce de papajoker avec __new__.
    • À la place de fonctions globales as_ClientCode_or_raise et as_ClientCode_or_None, il vaut mieux définir des méthodes statiques ou méthodes de classe ClientCode.create_or_raise et ClientCode.create_or_None, car on pourra plus facilement les déplacer vers une classe de base plus tard.

  10. #30
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par tyrtamos Voir le message
    J'ai donc fabriqué un décorateur sous forme d'une classe avec arguments, et dont les arguments sont les conditions et les obligations de type sur les arguments de la fonction décorée.
    J'aime beaucoup !!!

    Citation Envoyé par tyrtamos Voir le message
    Si ça vous intéresse dans le cadre de ce fil, je peux vous donner le code du décorateur (Python 3.7) et vous expliquer comment il fonctionne.
    Pas besoin. Un minimum de recherche sur ta page et on trouve de suite

    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  11. #31
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 461
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 461
    Points : 9 248
    Points
    9 248
    Billets dans le blog
    6
    Par défaut
    Bonjour Sve@r

    Citation Envoyé par Sve@r Voir le message
    Pas besoin. Un minimum de recherche sur ta page et on trouve de suite
    C'est vrai, mais j'en suis encore avec Python 2 sur le site (j'ai beaucoup de retard...), et le passage à Python 3 (3.7 actuellement pour moi) a provoqué pas mal de modifs du décorateur.

    Comme ça fait plusieurs fils du forum qui parlent du sujet, je vais mettre la mise à jour du décorateur, réduit à la vérification de types, dans le sous-forum "contribuez", et j'en indiquerai l'adresse. Et je moderniserai mon site un de ces jours...
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  12. #32
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Il y a juste une petite erreur aux environs de la ligne 58
    Il est écrit
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    args_def = fonc.func_defaults
    if argsfn_def == None: argsfn_def = ()
    or "argsfn_def" n'existe pas. J'ai présumé que le code exact était en fait
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    args_def = fonc.func_defaults
    if args_def == None: args_def = ()
    et pour l'instant ça marche (toutefois certains préconisent de tester "is None" plutôt que "== None")

    Et finalement peut-être que args_def = fonc.func_defaults or () peut le faire aussi ...

    [EDIT]Euh en fait non, gros souci

    Voici ce que j'ai tenté
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    @verifargs("b > 0", a=(int, long))
    def fct(a, b=0): return a*b
    print fct(5, 6)
    Or ça me renvoie un échec car il teste "b > 0" avec b pris par défaut à 0 alors que j'ai placé 6 dans b.

    Je pense que la cause se trouve dans cette partie de code
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    dicvars=dict(zip(vars[:len(args)],args))
    dicvars.update(kwargs)
    for v in dvars_def:
    	if v not in kwargs:
    		dicvars[v]=dvars_def[v]
    Je comprends l'idée. Si une variable n'est pas demandée lors de l'appel, elle ne se troue pas dans kwargs et le code récupère la variable par défaut qui y est associée.
    Le souci, c'est que si on passe une variable par position, elle ne se trouve pas dans "kwargs" et le code continue à récupérer la variable par défaut.

    Accessoirement, ceci print fct(5, b=6) fonctionne tandis que dans cet essai inverse du précédent...
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    @verifargs("b >= 0", a=(int, long))
    def fct(a, b=0): return a*b
     
    print fct(5, -1)
    ... bien évidemment la fonction produit un résultat sans détecter le cas interdit.

    Je pense que le test devrait être en réalité if v not in kwargs.keys() + dicvars.keys().
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  13. #33
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 461
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 461
    Points : 9 248
    Points
    9 248
    Billets dans le blog
    6
    Par défaut
    @=> Sve@r

    Un grand merci pour tes remarques! Comme il y a longtemps que je n'utilise plus Python 2, j'aurai du mal à mettre cette version au point. Si tu utilises encore Python 2, je peux m'y replonger, mais il faudra me laisser un peu de temps.


    En ce qui concerne Python 3, j'ai fait de que j'ai dit ce matin: une contribution ici: https://www.developpez.net/forums/d1...de-decorateur/.

    N'hésite pas à me donner tes remarques!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

Discussions similaires

  1. Tableau de template avec constructeur privé
    Par polonain2 dans le forum Langage
    Réponses: 3
    Dernier message: 03/06/2010, 13h24
  2. Extend d'une classe avec un constructeur privé
    Par aelmalki dans le forum Langage
    Réponses: 5
    Dernier message: 13/03/2010, 11h09
  3. Héritage d'une classe avec constructeur privé
    Par Braillane dans le forum Langage
    Réponses: 13
    Dernier message: 02/09/2009, 11h59
  4. [JUnit] [Test][Débutant] Constructeur privé
    Par Shabata dans le forum Tests et Performance
    Réponses: 2
    Dernier message: 12/01/2006, 15h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo