Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

vos types customisés utiles

0 votes

relisant pour la n-ième fois le très bon tuto sur la POO:

quels sont vos types customisés très utiles ?
ex:

import os

class path(str): # hé oui, on peut hériter des types de base

    def __div__(self, other): # override le comportement face à l'opérateur '/'

        return path(os.path.join(self, other))

... p = path('/home/sam')
... print p / 'blog'
/home/sam/blog

http://sametmax.com/le-guide-ultime-et-definitif-sur-la-programmation-orientee-objet-en-python-a-lusage-des-debutants-qui-sont-rassures-par-les-textes-detailles-qui-prennent-le-temps-de-tout-expliquer-partie-4/

demandé 13-Aou-2016 par buffalo974 (2,886 points)

path.py implémente cela, ainsi que plein d'autres choses encore.

2 Réponses

+3 votes
 
Meilleure réponse

Perso j'ai un type g qui ressemble à ça:

class g:

    def __init__(self, iterable: Iterable, *args: Iterable):


        if args:
            iterable = chain(iterable, *args)
        self.iterator = iter(iterable)
        self._tee_called = False

    def __iter__(self):

        if self._tee_called:
            raise RuntimeError("You can't iterate on a g object after g.tee "
                               "has been called on it.")
        return self.iterator

    def next(self, default: Any=None):

        return next(self.iterator, default)

    __next__ = next

    def __add__(self, other: Iterable):

        return g(chain(self.iterator, other))

    def __radd__(self, other: Iterable):

        return g(chain(other, self.iterator))

    # TODO: allow non iterable
    def __sub__(self, other: Iterable):

        filter_from = set(ensure_tuple(other))
        return g(x for x in self if x not in filter_from)

    def __rsub__(self, other: Iterable):

        filter_from = set(self.iterator)
        return g(x for x in other if x not in filter_from)

    def __mul__(self, num: int):

        return g(chain(*tee(self.iterator, num)))

    __rmul__ = __mul__

    def tee(self, num: int=2):

        gen = g(g(x) for x in tee(self, num))
        self._tee_called = True
        return gen

    # TODO: allow negative end boundary
    def __getitem__(self, index: Union[int, slice]):


        if isinstance(index, int):
            return at_index(self.iterator, index)

        if callable(index):
            return first_true(self.iterator, index)

        try:
            start = index.start or 0
            step = index.step or 1
            stop = index.stop
        except AttributeError:
            raise ValueError('Indexing works only with integers or callables')

        return g(iterslice(self.iterator, start, stop, step))

    def map(self, func: Callable):

        return g(imap(func, self.iterator))

    def zip(self, *others: Iterable):

        return g(izip(self.iterator, *others))

    def cycle(self):
        return g(cycle(self.iterator))

    def sorted(self, keyfunc=None, reverse=False):
        return g(sorted(self.iterator, key=reverse))

    def groupby(self, keyfunc=None, reverse=False, cast=tuple):
        return g(groupby(self, keyfunc, reverse, cast))

    def enumerate(self, start):
        return g(enumerate(self.iterator, start))

    def count(self):
        try:
            return len(self.iterator)
        except TypeError:
            for i, _ in enumerate(self.iterator):
                pass
            return i

    def copy(self):
        self.iterator, new = tee(self.iterator)
        return g(new)

    def list(self):
        return list(self.iterator)

    def tuple(self):
        return tuple(self.iterator)

    def set(self):
        return set(self.iterator)

    def join(self, separator="", cast=str):
        return separator.join(cast(x) for x in self.iterator)

    def __repr__(self):
        return "<g generator>"

    def chunks(self, chunksize, cast=__builtins__['tuple']):

        return g(chunks(self.iterator, chunksize, cast))

    def window(self, size=2, cast=__builtins__['tuple']):

        return g(window(self.iterator, size, cast))

    def first(self, items=1, default=None):
        return g(first(self.iterator, items, default))

    def last(self, items=1, default=None):
        return g(last(self.iterator, items, default))

    def skip_duplicates(self, key=lambda x: x,  fingerprints=None):
        return g(skip_duplicates(self.iterator, key, fingerprints))

Bon y a plus de code mais en gros ça permet de faire:

>>> g('fdjsqkmdsqjmdljlqdksfjlkmfjdlqkz').skip_duplicates()[lambda x: x == 'k':10].list()
['k', 'm', 'l']
>>> ("fjl" + g([None, True]) + range(3)).window(3).tuple()
(('f', 'j', 'l'), ('j', 'l', None), ('l', None, True), (None, True, 0), (True, 0, 1), (0, 1, 2))

Ca permet d'unifier les traitements des itérables, donne tous les goodies d'itertools avec une jolie syntaxe, de la chainabilité, etc.

J'ai la même chose pour les strings:

>>> s(',').join(range(3))
'0,1,2'

Les bytes, les dicos, les tuples, les regex, etc. C'est une lib que j'appelles "wonderful wrappers", abbrégées "ww" que je n'ai jamais vraiment finie ou publiée. Faudra peut être que je le fasse.

répondu 30-Aou-2016 par Sam (4,984 points)
sélectionné 31-Aou-2016 par buffalo974

ben il ne me reste plus qu' à méditer là-dessus ;-)

J'aime beaucoup, je pense que je vais me faire un truc dans le genre.
C'est le problème avec les libs qui proposent plein de petites fonctions super utiles… C'est un appel insidieux à utiliser from <> import *.

Quelle est la raison derrière la déclaration de la méthode next et son affectation à __next__ après coups ?

Support python 2 et 3

0 votes

Ton exemple est le type même de fausse bonne idée, le truc qu'il ne faut surtout pas faire quoi...

Si un path peut se représenter par un string, ce sont deux concepts qui n'ont sémantiquement rien a voir, et sous-typer str pour ça est une très mauvaise idée en soi.

Une des bases de la POO est de bien comprendre le principe de substitution de Liskov. J'ajouterais qu'un bon réflexe de débutant en POO est de se demander systématiquement si la composition ne serait pas préférable a l’héritage (c'est très souvent le cas).

répondu 17-Aou-2016 par yoch (2,514 points)

Voilà une présentation sur le sujet.

Concernant le subclassing de string, UserString est justement là pour rendre les choses plus propres.

J' avoue ne rien comprendre au wikipedia sur Liskov.
Si tu l' expliquais à un gamin surdoué de 3 ans ?

Vous mijotez jamais des types dérivés ?

J'avoue ne pas avoir vérifié la qualité de la page en question. Ce lien est peut-être un peu plus didactique.

Pour les types dérivés, bien sur qu'on s'en sert, mais c'est beaucoup moins courant d'en avoir besoin sur les types natifs. Le seul type natif qu'il m'arrive de sous-classer, c'est dict pour lui ajouter des features bonus comme l'auto-vivification par exemple.

tu veux bien déposer un bout de code pour voir la tête que ça a ?

J'ai déjà posté ce trick ici.

Un chemin et une strings ne sont pas théoriquement la même chose, mais en pratique dans le code, c'est ce qu'on utilise 99% du temps. D'ailleurs Python n'avait rien pour les représenter autrement pendant de nombreuses versions. Il faut être pragmatique, et se faciliter la vie.

Un path étant comme tu dis à 99% un string, ça peut se défendre sémantiquement.
Si je dis pas de conneries, le principe de substitution de Liskov implique dans ce cas que tous les comportements de string doivent avoir du sens pour la classe path ; ici ça semble être le cas. Sinon il aurait mieux valu une compo, la class path possédant alors un attribut de type string.

En même temps je rassure tout le monde, meme sur la mailling list Python ils sont pas d'accord là dessus.

...