Que sont les métaclasses en Python?

que sont les métaclasses et à quoi servent-elles?

4723
demandé sur Veltzer Doron 2008-09-19 10:10:46

16 réponses

Une métaclasse est la classe d'une classe. Comme une classe définit le comportement d'une instance de la classe, une métaclasse définit le comportement d'une classe. Une classe est une instance d'une métaclasse.

alors qu'en Python vous pouvez utiliser des callables arbitraires pour les métaclasses (comme Jerub shows), l'approche la plus utile est en fait d'en faire une classe elle-même. type est la métaclasse habituelle en Python. Au cas où vous vous demandiez, Oui, type est lui-même une classe, et c'est son propre type. Vous ne pourrez pas recréer quelque chose comme type purement en Python, mais Python triche un peu. Pour créer votre propre metaclass en Python vous voulez vraiment juste sous-classe type .

Une métaclasse est le plus couramment utilisé comme une fabrique de classe. Comme vous créez une instance de la classe en appelant la classe, Python crée une nouvelle classe (quand il exécute la déclaration 'class') en appelant le metaclass. Combiné avec le les méthodes normales __init__ et __new__ , les métaclasses vous permettent donc de faire des 'choses supplémentaires' lors de la création d'une classe, comme Enregistrer la nouvelle classe avec un certain registre, ou même remplacer la classe par quelque chose d'autre entièrement.

lorsque l'instruction class est exécutée, Python exécute d'abord le corps de l'instruction class comme un bloc normal de code. L'résultant de l'espace de noms (dict) détient les attributs de la classe. La métaclasse est déterminée en regardant le baseclasses de la classe (metaclasses sont hérités), à la __metaclass__ attribut de la classe (le cas échéant) ou le __metaclass__ variable globale. Le metaclass est alors appelé avec le nom, les bases et les attributs de la classe pour l'instancier.

cependant, metaclasses en fait définir le type d'une classe, pas seulement une usine pour elle, de sorte que vous pouvez faire beaucoup plus avec eux. Vous pouvez, par exemple, définir des méthodes sur la métaclasse. Ces méthodes métaclass sont comme des méthodes de classemethodes, en ce qu'elles peuvent être appelées sur la classe sans instance, mais elles ne sont pas non plus comme des méthodes de classemethodes en ce qu'elles ne peuvent pas être appelées sur une instance de la classe. type.__subclasses__() est un exemple de méthode sur le type metaclass. Vous pouvez également définir les méthodes "magiques" normales, comme __add__ , __iter__ et __getattr__ , pour implémenter ou changer le comportement de la classe.

voici un agrégé exemple des bits et pièces:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
2178
répondu Thomas Wouters 2018-09-20 14:01:08

Classes comme objets

avant de comprendre les métaclasses, vous devez maîtriser les cours de Python. Et Python a une idée très particulière de ce que sont les classes, empruntée au langage Smalltalk.

dans la plupart des langues, les classes ne sont que des morceaux de code qui décrivent comment produire un objet. C'est un peu vrai en Python aussi:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

mais les classes sont plus que cela en Python. Les Classes sont des objets trop.

Oui, objets.

dès que vous utilisez le mot-clé class , Python l'exécute et crée objet. L'instruction

>>> class ObjectCreator(object):
...       pass
...

crée en mémoire un objet avec le nom "ObjectCreator".

cet objet (la classe) est lui-même capable de créer des objets (les instances), et c'est pourquoi c'est une classe .

But pourtant, c'est un objet, et donc:

  • , vous pouvez l'affecter à une variable
  • vous pouvez le copier
  • vous pouvez y ajouter des attributs
  • vous pouvez le passer comme paramètre de fonction

p.ex.:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

création dynamique de classes

puisque les classes sont des objets, vous pouvez les créer sur la mouche, comme n'importe quel objet.

tout d'abord, vous pouvez créer une classe dans une fonction en utilisant class :

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

mais ce n'est pas si dynamique, puisque vous devez encore écrire toute la classe vous-même.

puisque les classes sont des objets, elles doivent être générées par quelque chose.

lorsque vous utilisez le mot-clé class , Python crée cet objet automatiquement. Mais comme avec la plupart des choses en Python, il vous donne un moyen de le faire manuellement.

vous vous souvenez de la fonction type ? La bonne vieille fonction qui vous permet de savoir ce type un objet est:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

puits, type a une capacité complètement différente, il peut également créer des classes à la volée. type peut prendre la description d'une classe comme paramètres, et le retour d'une classe.

(je sais, c'est idiot, que le même la fonction peut avoir deux usages différents selon les paramètres que vous lui transmettez. C'est un problème dû à l'arrière compatibilité en Python)

type fonctionne de cette façon:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

p.ex.:

>>> class MyShinyClass(object):
...       pass

peut être créé manuellement de cette façon:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Vous remarquerez que nous utilisons "MyShinyClass" comme le nom de la classe et comme variable pour tenir la classe référence. Ils peuvent être différents, mais il n'y a aucune raison de compliquer les choses.

type accepte un dictionnaire pour définir les attributs de la classe. So:

>>> class Foo(object):
...       bar = True

peut être traduit en:

>>> Foo = type('Foo', (), {'bar':True})

et utilisée comme classe normale:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

et bien sûr, vous pouvez en hériter, donc:

>>>   class FooChild(Foo):
...         pass

serait:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

éventuellement, vous voudrez ajouter des méthodes à votre classe. Il suffit de définir une fonction avec la signature et le définir comme un attribut.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

et vous pouvez ajouter encore plus de méthodes après avoir créé dynamiquement la classe, tout comme ajouter des méthodes à un objet de classe normalement créé.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Vous voyez où nous allons: en Python, les classes sont des objets, et vous pouvez créer une classe à la volée, dynamiquement.

c'est Ce que Python n'lorsque vous utilisez le mot-clé class , et il le fait en utilisant une métaclasse.

Que sont les metaclasses (enfin)

les métaclasses sont le "truc" qui crée les classes.

vous définissez des classes pour créer des objets, non?

mais nous avons appris que les classes Python sont des objets.

puits, les métaclasses sont ce qui crée ces objets. Ce sont les classes des classes, vous pouvez les imaginer de cette façon:

MyClass = MetaClass()
my_object = MyClass()

vous avez vu que type vous permet de faire quelque chose comme ceci:

MyClass = type('MyClass', (), {})

C'est parce que la fonction type est en fait une métaclasse. type est le métaclasse Python utilise pour créer toutes les classes en coulisses.

Maintenant vous vous demandez pourquoi diable est-il écrit en en minuscules, et pas Type ?

Eh bien, je suppose que c'est une question de cohérence avec str , la classe qui crée strings objects, et int la classe qui crée des objets entiers. type est juste la classe qui crée des objets de classe.

vous voyez cela en cochant l'attribut __class__ .

tout, et je veux dire tout, est un objet en Python. Ce qui inclut les ints, chaîne, fonctions et classes. Tous d'entre eux sont des objets. Et tous ont été créé à partir d'une classe:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

maintenant, qu'est-ce que le __class__ de n'importe quel __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

ainsi, une métaclasse est juste la chose qui crée des objets de classe.

vous pouvez l'appeler une "usine de classe" si vous le souhaitez.

type est l'utilisation intégrée de metaclass Python, mais bien sûr, vous pouvez créer votre propre métaclasse.

Le __metaclass__ attribut

en Python 2, Vous pouvez ajouter un attribut __metaclass__ lorsque vous écrivez une classe (voir la section suivante pour la syntaxe Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

si vous le faites, Python utilisera le metaclass pour créer la classe Foo .

attention, c'est délicat.

vous écrivez class Foo(object) tout d'abord, mais l'objet de classe Foo n'est pas créé en souvenir encore.

Python recherchera __metaclass__ dans la définition de la classe. S'il le trouve, il va l'utiliser pour créer la classe d'objet Foo . Si ça ne marche pas, il va utiliser type pour créer la classe.

lu plusieurs fois.

quand vous faites:

class Foo(Bar):
    pass

Python fait ce qui suit:

y a-t-il un attribut __metaclass__ dans Foo ?

si oui, créez en mémoire un objet de classe (j'ai dit un objet de classe, restez avec moi ici), avec le nom Foo en utilisant ce qui est dans __metaclass__ .

si Python ne trouve pas __metaclass__ , il cherchera un __metaclass__ au niveau du MODULE, et essaiera de faire la même chose (mais seulement pour les classes qui n'héritent de rien, essentiellement des classes à l'ancienne).

alors s'il ne peut trouver aucune __metaclass__ du tout, il utilisera le Bar 's (le premier parent) propre metaclass (qui pourrait être la valeur par défaut type ) pour créer l'objet de classe.

faites attention ici que l'attribut __metaclass__ ne sera pas hérité, la métaclasse du parent ( Bar.__class__ ) le sera. Si Bar utilisait un attribut __metaclass__ qui créait Bar avec type() (et non type.__new__() ), les sous-classes n'hériteront pas ce comportement.

maintenant la grande question Est, Que pouvez-vous mettre dans __metaclass__ ?

, La réponse est: quelque chose qui peut créer une classe.

et qu'est-ce qui peut créer une classe? type , ou quoi que ce soit qui le sous-classe ou l'utilise.

métaclasses en Python 3

la syntaxe pour définir le metaclass a été changée en Python 3:

class Foo(object, metaclass=something):
    [...]

c'est-à-dire que l'attribut __metaclass__ n'est plus utilisé, en faveur d'un argument de mot-clé dans la liste des classes de base.

Le comportement des metaclasses cependant séjours essentiellement la même .

Metaclasses sur mesure

Le but principal d'une métaclasse est de changer automatiquement la classe, quand il est créé.

vous faites habituellement ceci pour APIs, où vous voulez créer des classes contexte actuel.

Imaginez un exemple stupide, où vous décidez que toutes les classes de votre module devrait avoir leurs attributs écrits en majuscules. Il existe plusieurs façons de faites ceci, mais une façon est de définir __metaclass__ au niveau du module.

de cette façon, toutes les classes de ce module seront créées en utilisant ce metaclass, et nous devons juste dire au metaclass de tourner tous les attributs à majuscule.

heureusement, __metaclass__ peut en fait être n'importe quel appelant, il n'a pas besoin d'être un classe formelle (je sais, quelque chose avec "classe" dans son nom n'a pas besoin d'être une classe, allez comprendre... mais il est utile).

nous allons donc commencer par un exemple simple, en utilisant une fonction.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

maintenant, faisons exactement la même chose, mais en utilisant une vraie classe pour une métaclasse:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

But ce n'est pas vraiment de la programmation orientée objet. Nous appelons type directement et nous n'outrepassons pas ou appelez le parent __new__ . Faisons-le:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

vous avez peut-être remarqué l'argument supplémentaire upperattr_metaclass . Il est rien de spécial: __new__ reçoit toujours la classe dans laquelle il est défini, comme premier paramètre. Tout comme vous avez self pour les méthodes ordinaires qui reçoivent l'instance comme premier paramètre, ou la classe de définition pour les méthodes de classe.

bien sûr, les noms que j'ai utilisés ici sont longs par souci de clarté, mais comme pour self , tous les arguments ont des noms conventionnels. Donc une vraie production métaclasse ressemblerait à ceci:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

nous pouvons le rendre encore plus propre en utilisant super , ce qui facilitera l'héritage (parce que oui, vous pouvez avoir des métaclasses, hériter de métaclasses, hériter de type):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

C'est ça. Il est vraiment rien de plus sur les metaclasses.

la raison derrière la complexité du code en utilisant des métaclasses n'est pas parce que des métaclasses, c'est parce que vous utilisez habituellement des métaclasses pour faire des trucs tordus s'appuyant sur l'introspection, la manipulation de l'héritage, vars tels que __dict__ , etc.

en effet, les métaclasses sont particulièrement utiles pour faire de la magie noire, et donc des trucs compliqués. Mais par eux-mêmes, ils sont simples:

  • l'interception d'une création de classes
  • modifier la classe
  • retourner la classe modifiée

Pourquoi utiliser les classes metaclasses au lieu des fonctions?

puisque __metaclass__ peut accepter n'importe quel appel, pourquoi utiliser une classe car il est évidemment plus compliqué?

Il y a plusieurs raisons de le faire:

  • L'intention est claire. Quand vous lisez UpperAttrMetaclass(type) , vous savez ce qui va suivre
  • vous pouvez utiliser OOP. Metaclass peut hériter de metaclass, outrepasser les méthodes des parents. Les métaclasses peuvent même utiliser des métaclasses.
  • sous-classes d'une classe seront des instances de sa métaclasse si vous avez spécifié une métaclasse classe, mais pas avec une métaclasse-fonction.
  • vous pouvez mieux structurer votre code. Vous n'avez jamais utilisez des métaclasses pour quelque chose comme trivial comme l'exemple ci-dessus. C'est quelque chose de compliqué. Avoir l' possibilité de créer plusieurs méthodes et de les regrouper dans une classe est très utile pour rendre le code plus facile à lire.
  • vous pouvez accrocher __new__ , __init__ et __call__ . Qui permettra tu fais des choses différentes. Même si d'habitude vous pouvez tout faire dans __new__ , certaines personnes sont tout simplement plus à l'aise en utilisant __init__ .
  • ça s'appelle des métaclasses, Merde! Il doit vouloir dire quelque chose!

Pourquoi utiliser des métaclasses?

maintenant la grande question. Pourquoi utiliser une fonction obscure sujette aux erreurs?

Eh bien, habituellement vous ne le faites pas:

les métaclasses sont une magie plus profonde que 99% des utilisateurs ne devraient jamais s'inquiéter. Si vous vous demandez si vous en avez besoin, vous n'avez pas (le les personnes qui en fait besoin de savoir avec certitude que ils ont besoin d'eux, et n'ont pas besoin d'un explication sur le pourquoi).

Python Guru Tim Peters

le cas d'utilisation principal d'une métaclasse est la création d'une API. L'ORM Django en est un exemple typique.

il vous permet de définir quelque chose comme ceci:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

mais si vous faites ceci:

guy = Person(name='bob', age='35')
print(guy.age)

il ne retournera pas un objet IntegerField . Il retournera un int , et pourra même le prendre directement à partir de la base de données.

c'est possible parce que models.Model définit __metaclass__ et il utilise un peu de magie qui va tourner le Person que vous venez de définir avec des déclarations simples dans un complexe crochet à un champ de base de données.

Django rend quelque chose de complexe simple en exposant un simple API et en utilisant des métaclasses, recréer du code à partir de cette API pour faire le vrai travail derrière les coulisses.

le dernier mot

tout d'abord, vous savez que les classes sont des objets qui peuvent créer des instances.

en fait, les classes sont elles-mêmes des instances. Des metaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

tout est un objet en Python, et ils sont tous deux des instances de classes ou des instances de metaclasses.

sauf pour type .

type est en fait sa propre métaclasse. Ce n'est pas quelque chose que vous pourriez reproduire en Python pur, et se fait en trichant un peu à l'implémentation niveau.

Deuxièmement, les métaclasses sont compliquées. Vous ne voulez pas les utiliser pour des changements de classe très simples. Vous pouvez changer de classe en utilisant deux techniques différentes:

99% du temps où vous avez besoin d'un changement de catégorie, il est préférable de les utiliser.

mais 98% du temps, vous n'avez pas besoin d'un changement de catégorie.

5873
répondu e-satis 2018-09-14 00:23:09

Note, Cette réponse est pour Python 2.x comme il a été écrit en 2008, metaclasses sont légèrement différents dans 3.x, voir les commentaires.

les métaclasses sont la sauce secrète qui fait le travail de "classe". La métaclasse par défaut pour un nouvel objet style s'appelle 'type'.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

les métaclasses prennent 3 args. ' nom ', ' bases " et " dict '

C'est ici que commence le secret. Cherchez d'où viennent le nom, les bases et le dict dans cet exemple de définition de classe.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

permet de définir une métaclasse qui démontrera comment" classe: " l'appelle.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

et maintenant, un exemple qui signifie quelque chose, cela fera automatiquement que les variables de la liste" Attributs " seront définies sur la classe, et définies à None.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

notez que le comportement magique qui 'Initalisé' gagne en ayant la métaclasse init_attributes n'est pas transmis à une sous-classe de Initalisé.

voici un exemple encore plus concret, montrant comment vous pouvez sous-classe "type" pour faire une métaclasse qui effectue une action lorsque la classe est créée. C'est assez délicat:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b
321
répondu Jerub 2017-04-07 16:21:39

l'une des utilisations des métaclasses est d'ajouter automatiquement de nouvelles propriétés et méthodes à une instance.

par exemple, si vous regardez Django models , leur définition semble un peu confuse. Il semble que vous ne définissiez que les propriétés de classe:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

cependant, à l'exécution les objets de personne sont remplis de toutes sortes de méthodes utiles. Voir la source pour quelques métaclasserie étonnante.

131
répondu Antti Rasinen 2008-09-19 06:45:40

D'autres ont expliqué comment fonctionnent les métaclasses et comment elles s'insèrent dans le système de type Python. Voici un exemple de ce qu'ils peuvent être utilisés pour. Dans un cadre de test que j'ai écrit, j'ai voulu garder trace de l'ordre dans lequel les classes étaient définies, pour que je puisse plus tard les instancier dans cet ordre. J'ai trouvé ça plus facile de faire ça en utilisant une métaclasse.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Tout ce qui est une sous-classe de MyType reçoit alors un attribut de classe _order qui enregistre l'ordre dans lequel les classes ont été définies.

124
répondu kindall 2016-11-28 18:04:41

je pense que l'introduction ONLamp à la programmation metaclass est bien écrite et donne une très bonne introduction au sujet malgré le fait qu'elle ait déjà plusieurs années.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (archivée à https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html )

En bref: Une classe est un projet pour la création d'une instance d'une métaclasse est un projet pour la création d'une classe. Il est facile de voir que dans les classes Python doivent être des objets de première classe aussi pour permettre ce comportement.

Je n'en ai jamais écrit moi-même, mais je pense que l'une des plus belles utilisations des métaclasses peut être vue dans le Django framework . Les classes de modèles utilisent une approche métaclasse pour permettre un style déclaratif d'écrire de nouveaux modèles ou des classes de formes. Alors que le metaclass crée la classe, tous les membres ont la possibilité de personnaliser la classe elle-même.

la chose qui reste à dire est: si vous ne savez pas ce que sont les métaclasses, la probabilité que vous n'aura pas besoin d'eux est de 99%.

92
répondu Matthias Kestenholz 2018-08-13 04:53:15

que sont les métaclasses? Que voulez-vous utiliser?

TLDR: un metaclass instantiate et définit le comportement pour une classe comme une classe instantiates et définit le comportement pour une instance.

Pseudo:

>>> Class(...)
instance

ce qui précède doit sembler familier. D'où vient Class ? C'est une instance d'un metaclass (aussi pseudocode):

>>> Metaclass(...)
Class

en code réel, nous pouvons passer le metaclass par défaut, type , tout ce dont nous avons besoin pour instancier une classe et nous obtenons une classe:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Mettre les choses différemment

  • Une classe est une instance d'une métaclasse est d'une classe.

    lorsque nous instancions un objet, nous obtenons une instance:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    de Même, lorsque nous définissons une classe explicitement avec le metaclass par défaut, type , nous l'instancions:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • autrement dit, une classe est une instance d'une métaclasse:

    >>> isinstance(object, type)
    True
    
  • en un mot, une métaclasse est la classe d'une classe.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

lorsque vous écrivez une définition de classe et que Python l'exécute, il utilise un metaclass pour instancier l'objet de classe (qui, à son tour, être utilisé pour instancier les instances de cette classe).

tout comme nous pouvons utiliser les définitions de classe pour changer le comportement des instances d'objets personnalisés, nous pouvons utiliser une définition de classe metaclass pour changer le comportement d'un objet de classe.

A quoi peuvent-ils servir? De la docs :

les utilisations potentielles des métaclasses sont illimitées. Quelques idées qui ont été explorées exploitation forestière, vérification de l'interface, délégation automatique, création automatique de propriétés, procurations, cadres, et verrouillage/synchronisation automatique des ressources.

néanmoins, il est généralement conseillé aux utilisateurs de ne pas utiliser les métaclasses sauf en cas d'absolue nécessité.

vous utilisez un metaclass chaque fois que vous créez une classe:

Quand vous écrivez une définition de classe, par exemple, comme ceci,

class Foo(object): 
    'demo'

vous instanciez un objet de classe.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

c'est la même chose que d'appeler fonctionnellement type avec les arguments appropriés et d'attribuer le résultat à une variable de ce nom:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Note, certaines choses sont automatiquement ajoutées au __dict__ , i.e., l'espace de noms:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

Le métaclasse de l'objet que nous avons créé, dans les deux cas, est type .

(une note latérale sur le contenu de la classe __dict__ : __module__ est là parce que les classes doivent savoir où elles sont définies, et __dict__ et __dict__ sont là parce que nous ne définissons pas __slots__ -si nous définir __slots__ nous économiserons un peu d'espace dans les cas, comme nous pouvons rejeter __dict__ et __weakref__ en les excluant. Par exemple:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... mais je m'égare.)

nous pouvons étendre type comme toute autre définition de classe:

Voici la valeur par défaut __repr__ des classes:

>>> Foo
<class '__main__.Foo'>

une des choses les plus précieuses que nous pouvons faire par défaut en écrivant un objet Python est de lui fournir un bon __repr__ . Lorsque nous appelons help(repr) , nous apprenons qu'il y a un bon test pour un __repr__ qui nécessite également un test d'égalité - obj == eval(repr(obj)) . La mise en œuvre simple suivante de __repr__ et __eq__ pour les instances de classe de notre classe de type nous fournit une démonstration qui peut améliorer sur la valeur par défaut __repr__ des classes:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

donc maintenant quand nous créons un objet avec cette métaclasse ,le __repr__ fait écho sur la ligne de commande fournit un spectacle beaucoup moins laid que le défaut:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

avec un __repr__ défini pour la classe par exemple, nous avons une capacité plus forte pour déboguer notre code. Cependant, une vérification beaucoup plus poussée avec eval(repr(Class)) est peu probable (car les fonctions seraient plutôt impossibles à évaluer de leur défaut __repr__ 's).

Une utilisation attendue: __prepare__ un espace de noms

si, par exemple, nous voulons savoir dans quel ordre les méthodes d'une classe sont créées, nous pouvons fournir un dict ordonné comme espace de noms de la classe. Nous le ferions avec __prepare__ qui retourne l'espace de noms dict pour la classe si elle est implémentée en Python 3 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

et usage:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

et maintenant nous avons un enregistrement de l'ordre dans lequel ces méthodes (et d'autres attributs de classe) ont été créées:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Note, Cet exemple a été adapté de la documentation - le nouveau enum dans la bibliothèque standard fait ceci.

donc ce que nous avons fait était instanciate un metaclass en créant une classe. Nous pouvons aussi traiter le metaclass comme n'importe quelle autre classe. Il a un ordre de résolution de méthode:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

et il a approximativement le correct repr (que nous ne pouvons plus évaluer à moins que nous puissions trouver un moyen de représenter nos fonctions.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
79
répondu Aaron Hall 2017-08-30 03:19:14

Python 3 mise à jour

il y a (à ce point) deux méthodes clés dans une métaclasse:

  • __prepare__ , et
  • __new__

__prepare__ vous permet de fournir un mapping personnalisé (tel qu'un OrderedDict ) pour être utilisé comme espace de nom pendant que la classe est créée. Vous devez retourner une instance de n'importe quel namespace que vous choisissez. Si vous ne mettez pas en œuvre __prepare__ un normal dict est utilisé.

__new__ est responsable de la création/modification de la classe finale.

Un bare-bones, ne-rien-extra métaclasse tiens:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

un exemple simple:

dit que vous voulez qu'un code de validation simple s'exécute sur vos attributs -- comme il doit toujours être un int ou un str . Sans métaclasse, votre classe ressemblerait à quelque chose comme:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

comme vous pouvez le voir, vous devez répéter le nom de l'attribut deux fois. Cela rend les fautes de frappe possibles avec des bogues irritants.

Un simple métaclasse peut remédier à ce problème:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

voici à quoi ressemblerait la métaclasse (ne pas utiliser __prepare__ car elle n'est pas nécessaire):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Un exemple d'exécution de:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

produit:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Note : cet exemple est assez simple, il aurait pu être réalisé avec un décorateur de classe, mais probablement une métaclasse réelle serait faire beaucoup plus.

la classe "ValidateType" pour référence:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value
58
répondu Ethan Furman 2016-03-01 19:48:34

le Rôle d'une métaclasse' __call__() méthode lors de la création d'une instance de classe

si vous avez fait de la programmation Python pendant plus de quelques mois, vous finirez par tomber sur du code qui ressemble à ceci:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

ce dernier est possible lorsque vous implémentez la méthode magique __call__() sur la classe.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

la méthode __call__() est invoquée lorsqu'une instance d'une classe est utilisée comme appelant. Mais comme nous l'avons vu dans les réponses précédentes, une classe elle-même est une instance d'une métaclasse, donc quand nous utilisons la classe comme une callable (c'est-à-dire quand nous en créons une instance) nous appelons en fait sa méthode metaclass' __call__() . À ce stade, la plupart des programmeurs Python sont un peu confus parce qu'on leur a dit que lors de la création d'une instance comme celle-ci instance = SomeClass() vous appelez sa méthode __init__() . Certains qui ont creusé un peu plus profondément savent qu'avant __init__() il y a __new__() . Bien, aujourd'hui une autre couche de vérité est révélée, avant __new__() il y a le metaclass' __call__() .

étudions la chaîne d'appel de méthode à partir spécifiquement de la perspective de créer une instance d'une classe.

c'est une métaclasse qui enregistre exactement le moment avant qu'une instance ne soit créée et le moment où elle est sur le point de la retourner.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

il s'agit d'une catégorie qui utilise cette métaclasse

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

et maintenant créons une instance de Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Observe que le code ci-dessus ne fait rien de plus que de journaliser les tâches. Chaque méthode délègue le travail réel à l'implémentation de son parent, gardant ainsi le comportement par défaut. Puisque type est la classe de parent de Meta_1 ( type étant la métaclasse de parent par défaut) et considérant la séquence d'ordre de la sortie ci-dessus, nous avons maintenant un indice quant à la pseudo mise en œuvre de type.__call__() :

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

nous pouvons voir que la méthode metaclass' __call__() est celle qui s'appelle en premier. Il délègue ensuite la création de l'instance à la méthode __new__() de la classe et l'initialisation à la méthode __init__() de l'instance . C'est aussi celui qui renvoie finalement l'instance.

de ce qui précède il découle que le metaclass' __call__() est également donné la possibilité pour décider si oui ou non un appel à Class_1.__new__() ou Class_1.__init__() sera éventuellement. Au cours de son exécution, elle peut renvoyer un objet qui n'a pas été touché par l'une de ces méthodes. Prenez par exemple cette approche du modèle singleton:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

observons ce qui se passe en essayant à plusieurs reprises de créer un objet de type Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True
45
répondu Michael Ekoka 2018-08-27 17:21:01

une métaclasse est une classe qui indique comment une autre classe doit être créée.

C'est un cas où j'ai vu métaclasse comme une solution à mon problème: J'avais un problème très compliqué, qui aurait probablement pu être résolu différemment, mais j'ai choisi de le résoudre en utilisant une métaclasse. En raison de la complexité, c'est l'un des rares modules que j'ai écrit où les commentaires dans le module dépassent la quantité de code qui a été écrit. Elle est ici...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()
43
répondu Craig 2016-01-25 20:08:37

type est en fait une metaclass -- une classe qui crée une autre classe. La plupart des metaclass sont les sous-classes de type . Le metaclass reçoit la classe new comme premier argument et fournit l'accès à l'objet de classe avec les détails mentionnés ci-dessous:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

noter que la classe n'a pas été instanciée à tout moment; le simple fait de créer la classe a déclenché l'exécution de la metaclass .

29
répondu Mushahid Khan 2017-08-29 05:23:15

the tl;dr version

la fonction type(obj) vous donne le type d'un objet.

Le type() d'une classe est son métaclasse .

Pour utiliser une métaclasse:

class Foo(object):
    __metaclass__ = MyMetaClass
25
répondu noɥʇʎԀʎzɐɹƆ 2016-12-30 17:28:40

les classes Python sont elles - mêmes des objets - comme par exemple-de leur méta-classe.

la métaclasse par défaut, qui est appliquée lorsque vous déterminez les classes comme:

class foo:
    ...

méta-classe sont utilisés pour appliquer une règle à un ensemble de classes. Par exemple, supposons que vous construisez un ORM pour accéder à une base de données, et que vous voulez que les enregistrements de chaque table soient d'une classe mappée à cette table (basée sur les champs, les règles d'affaires, etc..,), un l'utilisation possible de metaclass est par exemple, la logique du pool de connexion, qui est partagée par toutes les classes d'enregistrement de toutes les tables. Une autre utilisation est la logique pour supporter des clés étrangères, ce qui implique plusieurs classes d'enregistrements.

lorsque vous définissez metaclass, vous tapez la sous-classe, et pouvez outrepasser les méthodes magiques suivantes pour insérer votre logique.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

quoi qu'il en soit, ces deux-là sont les crochets les plus couramment utilisés. métaclazing est puissant, et au-dessus est pas de liste exhaustive des utilisations du métaclazage.

15
répondu Xingzhou Liu 2017-07-13 08:18:22

deux phrases pour maîtriser la connaissance la plus difficile de Python point: Metaclass

source originale: segmentfault.com/a/1190000011447445

traduit et corrigé par moi.

l'auteur original de cet article conserve tous les droits, mais les travaux de traduction ne peuvent toujours pas être ignorés

S'il y a des erreurs ou un format contre PEP8, Aidez-moi à le corriger. Merci!!!

au début, il y a quelques exemples de la culture traditionnelle chinoise(Je ne suis pas chinois, mais j'ai une certaine connaissance à ce sujet. Si vous le souhaitez, il est bon. Et si vous ne le faites pas, ignorez-le. La compréhension de la métaclasse est le plus important)

c'est une brève introduction de Metaclass en Python avec pratique et utile exemple. Souhaitez vous l'aimez.

ne soyez pas effrayés par une rhétorique telle que la soi-disant "caractéristique que le metaclass n'est pas utilisé par 99% des programmeurs Python."Parce que chaque personne est un utilisateur naturelles.

pour comprendre les métaclasses, il suffit de connaître deux phrases:

phrase 1: Un est venu de la vérité, deux sont venus d'un, trois sont venus de deux, toutes les choses sont venues de trois

phrase 2: Qui Suis-je? D'où viens-je? Où dois-je aller?

Dans le python du monde, il y a une vérité éternelle, qui est, "type", s'il vous plaît rappelez à l'esprit est la vérité. L'écosystème python qui est si vaste est produit par type.

l'un est venu de la vérité, deux sont venus d'un, trois sont venus de deux, toutes les choses sont venues de trois:

la vérité est le type

est la métaclasse (métaclasse, ou générateur de classe)

Second est la classe (classe, ou générateur d'instance)

trois est une instance (exemple)

Tout est divers attributs et méthodes d'une instance. Quand on utilise Python, on les appelle.

la vérité et L'Une sont les propositions dont nous discutons aujourd'hui. Les deuxième, troisième, et toutes les choses sont les classes, les instances, les attributs et les méthodes que nous utilisons souvent. Nous utilisons hello world comme exemple:

# Create a Hello class that has the attributes say_hello ---- Second Origin

class Hello () :
     def say_hello(self ,name= 'world'):
         print('Hello, %s.' % name)



# Create an instance hello from the Hello class ---- Two students three

Hello = Hello()


# Use hello to call the method say_hello ---- all three things

Hello.say_hello() 

effet de sortie:

Hello, world. 

C'est un standard "trois provenaient de deux, toutes les choses sont venus de trois". De la classe aux méthodes que nous pouvons appeler, ces deux étapes sont utilisées.

alors nous ne pouvons pas aider de la question principale, d'où vient la classe? Retournez à la première ligne du code.

La classe Hello est en fait une "abréviation sémantique" d'une fonction, juste pour rendre le code plus facile à comprendre. Une autre façon de l'écrire est:

def  fn(self ,name='world') :  # If we have a function called fn
     print ('Hello, %s.' % name)


Hello = type ('Hello',(object,), dict(say_hello=fn))
# Create Hello class by type ---- Mysterious "Truth", you can change everything, this time we directly from the "Truth" gave birth to "2" 

ce type d'écriture est exactement le même que le cours précédent Hello writing. Vous pouvez essayer de créer une instance et d'appel.

# Create an instance of hello from the Hello class.

hello = Hello()


# Use hello call method say_hello ---- all three things, exactly the same

Hello.say_hello() 

effet de sortie:

Hello, world. ---- The result of the call is exactly the same. 

nous avons regardé en arrière à l'endroit le plus excitant. La route a donné naissance directement à deux:

Hello = type('Hello', (object,), dict(say_hello=fn)) 

C'est la" vérité", l'origine du monde python. Vous pouvez admirer cette.

faites attention à ses trois paramètres! Trois éternelle propositions qui coïncident avec l'humanité: Qui suis-je, d'où viens-je, où vais-je?

The first parameter: who I am. Here, I need a name that distinguishes everything else. The above example names me "Hello."

The second parameter: where do I come from. Here, I need to know where I come from, which is my "parent". In my example above, my parent is "object" - a very primitive class in Python.

The third parameter: Where do I go? Here, we include the methods and properties that we need to call into a dictionary and pass them as parameters. In the above example, we have a say_hello method packed into a dictionary. 

il est intéressant de noter que les trois principales propositions éternelles sont toutes les classes, toutes les instances, et même toutes les propriétés et méthodes des instances. Comme il se doit, leur les "créateurs", Truth et One, à savoir type et metaclass, ont aussi ces trois paramètres. Mais habituellement, les trois propositions éternelles de la classe ne sont pas passées comme paramètres, mais sont passées comme suit

class Hello(object):{

After class # statement "Who Am I?"

# In the parentheses declare "where do I come from"

# In curly brackets declare "Where do I go?"

     def say_hello ():{



     }

} 


The Creator can create a single person directly, but this is a hard labor. The Creator will first create the species "human" and then create a specific individual in batches. And pass on the three eternal propositions.

"Truth" can produce "2" directly, but it will produce "1" and then make "2" in batches.

Type can directly generate a class, but it can also be a metaclass and then use a metaclass to customize a class. 

Métaclasse - On est venu de la Vérité, à deux venus de.

en général, les métaclasses sont nommées suffixe Metaclass. Imaginez que nous ayons besoin d'une métaclasse qui puisse automatiquement dire bonjour. Les méthodes de classe dans lui, parfois besoin say_Hello, parfois say_Hi, parfois say_Sayolala, et parfois say_Nihao.

si chaque say_xxx intégré doit être déclaré une fois dans une classe, quel travail ce sera! Il est préférable d'utiliser des métaclasses pour résoudre le problème.

ce qui suit est un code de meta class pour créer un "salut" spécial:

class SayMetaClass(type):



     def __new__ (cls, Name ,Bases ,Attrs) :

         attrs[ 'say_' + name ] = lambda   self, value , saying = name : print ( saying + ',' + value + '!' )

         Return   Type . __new__ ( cls ,name, bases ,   attrs) 

rappelez-vous deux choses:

Metaclasses are derived from "type", so the parent class needs to pass in the type. [Taosheng 1, so one must include Tao]

Metaclass operations are done in __new__. The first parameter is the class that will be created. The following parameters are the three eternal propositions: Who am I, where do I come from, and where do I go. The objects it returns are also the three eternal propositions. Next, these three parameters will always be with us.

Dans nouvelle , je ne a effectué une opération.

Attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!') 

Il crée une méthode de classe avec le nom de la classe. Par exemple, la classe que nous avons créée à partir de metaclass s'appelle "Hello". Quand il a été créé, il aurait automatiquement une méthode de classe appelée "say_Hello". Ensuite, il utiliserait le nom de classe "Hello" comme paramètre par défaut pour dire, et l'aurait passé à la méthode. Puis passez l'appel de méthode hello comme valeur, et finalement imprimez-le.

alors comment un métaclasse aller de la création à l'invocation?

venez! Avec les principes de Daosheng, Yishengyou, Bishengsan, Sanshengwu, entrer dans le cycle de vie de la classe Yuan!

# Tao Shengyi: incoming type

class SayMetaClass(type):



     # Incoming three eternal propositions: class name, parent class, attribute

     def __new__(cls ,name ,bases ,attrs):

         # Create "talent"

         attrs[ 'say_' + name ] = lambda   self, value , saying = name : print( saying + ',' + value + '!' )

         # Three eternal propositions: class name, parent class, attribute

         return type . __new__ ( cls ,name ,bases ,attrs )



# Lifetime 2: Create class

class Hello ( object ,metaclass = SayMetaClass):
     pass



# two students three: create a real column

Hello = Hello ()



# Three things: call the instance method

hello.say_Hello('world!') 

la sortie est

Hello, world! 

Note: la classe créée par le metaclass, le premier paramètre est la classe parent, le second paramètre est le metaclass

les gens ordinaires ne pourront pas parler à naissance, mais certaines personnes diront bonjour, "bonjour" et "sayolala" quand ils sont nés. C'est le pouvoir du talent. Il nous donnera la programmation orientée objet pour sauver d'innombrables ennuis.

maintenant, en gardant la métaclasse inchangée, nous pouvons continuer à créer la Sayolala, Nihao classe, comme suit:

# Two came from one: Create class

class Sayolala ( object ,metaclass = SayMetaClass ) :
    pass



# three came from two: create a real column

s = Sayolala ()



# all things came from two: call the instance method

s.say_Sayolala ( 'japan!' ) 

sortie

Sayolala, japan! 

peut aussi parler chinois

# Two came from one: Create class

class Nihao(object ,metaclass = SayMetaClass ) :
    pass



# two students three: create a real column

n = Nihao()



# Three things: call the instance method

n.say_Nihao ( '中 中华!' ) 

sortie

Nihao, China! 

un autre petit exemple:

# one came from truth.

class ListMetaclass (type) :

     def   __new__ ( cls ,name, bases ,   attrs) :

         # Talent: Bind values ​​by the add method

         attrs[ 'add' ] = lambda   self, value: self.append(value)

         return type . __new__ ( cls ,name ,bases ,attrs )



# One lifetime

class MyList ( list ,   Metaclass = ListMetaclass ) :
    pass



# Two students three

L = MyList ()



# Three things

L.add( 1 ) 

maintenant nous imprimons l

print(L)



>>> [ 1 ] 

la liste ordinaire ne comporte pas de méthode add ()

L2 = list ()

L2 . add ( 1 )



>>> AttributeError : 'list'   Object   Has no attribute   'add' 

génial! Appris ici, avez-vous connu la joie du Créateur?

Everything in the python world is at your fingertips. 

jeune créateur, s'il vous plaît suivez-moi pour créer un nouveau monde.

nous choisissons deux domaines, l'un est le idée centrale de Django," cartographie relationnelle D'objet", cartographie relationnelle d'objet, appelée ORM.

c'est une difficulté majeure de Django, mais après avoir appris la métaclasse, tout devient clair. Votre compréhension de Django sera encore mieux!

un autre domaine est les reptiles (hackers), une recherche automatique des agents disponibles sur le réseau, et ensuite changer IP pour briser les restrictions anti-crawler des autres personnes.

Ces deux compétences sont très utiles et très amusant!

Défi 1: Créer ORM par la Métaclasse

préparez-vous à créer une classe de champ

class Field ( object ) :
     def __init__ ( self, name, column_type ) :

         Self.name = name

         Self.column_type = column_type



     def   __str__ ( self ) :

         return   '<%s:%s>' % ( self . __class__ . __name__ ,   self. name ) 

son rôle est

lorsque la classe de champ est instanciée, elle obtient deux paramètres, nom et column_type. Ils seront liés à la propriété privée de Field. Si vous voulez convertir le champ en chaîne de caractères, vous obtiendrez "Field:XXX". XXX est passé. Nom nom.

Préparation: Créer StringField et IntergerField

class StringField ( Field ) :



    def   __init__ ( self ,   name ) :

         super( StringField ,   self). __init__ ( name ,   'varchar(100)' )



class IntegerField ( Field ) :
     def   __init__ ( self ,name) :

         super( IntegerField ,   self). __init__ ( name ,   'bigint' ) 

son rôle est

lorsque L'instance StringField, IntegerField est initialisée, la méthode d'initialisation du parent est automatiquement appelée.

on est venu de la vérité

class ModelMetaclass ( type ) :



     def __new__ ( cls ,name, bases ,   attrs) :

         Ifname== 'Model' :

             Return   Type . __new__ ( cls ,name, bases ,   attrs)

         print( 'Found model: %s' % name )

         Mappings = dict ()

         for k ,   v   In   attrs. items () :

             If   Isinstance ( v ,   Field ) :

                 print( 'Found mapping: %s ==> %s' % ( k ,   v ))

                 Mappings [ k ] = v

         for k   In   Mappings . keys () :

             attrs. pop ( k )

         attrs[ '__mappings__' ] = mappings   # Save the mapping between attributes and columns

         attrs[ '__table__' ] = name   # Assume that the table name and class name are the same

         Return   Type . __new__ ( cls ,name, bases ,   attrs) 

il fait les choses suivantes

Create a new dictionary mapping

Each property of the class is traversed through its .items() key-value pair. If the value is a Field class, the key is printed and the key is bound to the mapping dictionary.

Delete the property that was just passed in as the Field class.

Create a special __mappings__ attribute and save the dictionary mapping.

Create a special __table__ attribute and save the name of the passed in class. 

deux provenaient d'un

class Model ( dict ,   Metaclass = ModelMetaclass ) :



     def __init__ ( self , ** kwarg ) :

         super(model ,   self). __init__ ( ** kwarg )



     def __getattr__ ( self ,   Key ) :

         Try :

             Return   self[ key ]

         except KeyError :

             Raise   AttributeError ( "'Model' object has no attribute '%s'" % key )



     def __setattr__ ( self ,   Key ,   Value ) :

         self[ key ] = value



     # Simulate table creation operation

     def save( self ) :

         Fields = []

         Args = []

         for k ,   v   In   self. __mappings__ . items () :

             Fields . append ( v . name )

             Args . append ( getattr ( self ,   k ,   None ))

         Sql = 'insert into %s (%s) values ​​(%s)' % ( self . __table__ ,   ',' . join ( fields ),   ',' . join ([ str ( i )   for i   In   Args ]))

         print( 'SQL: %s' % sql )

         print( 'ARGS: %s' % str ( args )) 

si vous créez un utilisateur de la sous-classe à partir du modèle:

class User (model ) :

     # Define the mapping of attributes's attributes to columns:

     Id = IntegerField ( 'id' )

  name= StringField ( 'username' )

     Email = StringField ( 'email' )

     Password = StringField ( 'password' ) 

en ce moment

Id= IntegerField('id') résout automatiquement à:

Modèle

. setattr (self, 'id', IntegerField ('id'))

parce que IntergerField ('id') est une instance de la sous-classe de Field, le nouveau de metaclass est automatiquement déclenché, de sorte que le IntergerField ('id') est stocké dans mappings et la paire clé-valeur est supprimée.

deux étudiants, trois étudiants, toutes choses

quand vous initialisez une instance et appelez la méthode save ()

u = User ( id = 12345 ,name= 'Batman' ,   Email = 'batman@nasa.org' ,   Password = 'iamback' )

u . save () 

à cette époque, le processus de deux élèves est terminé en premier:

First call Model.__setattr__ to load key values ​​into private objects

Then call the "genius" of the metaclass, ModelMetaclass.__new__, and private objects in the Model, as long as they are instances of Field, are automatically stored in u.__mappings__. 

la prochaine étape est de compléter les trois choses:

Simulation des opérations d'inventaire des données par U. save(). Ici, nous faisons juste un peu de traversée mappings opération, sql virtuel et impression, en réalité, en entrant la déclaration sql et la base de données à exécuter.

la sortie est

Found model : User

Found mapping : name ==> < StringField : username >

Found mapping : password ==> < StringField : password >

Found mapping : id ==> < IntegerField : id >

Found mapping : email ==> < StringField : email >

SQL : insert into User   ( username , password , id , email )   Values   ( Batman , iamback , 12345 , batman @ nasa . org )

ARGS : [ 'Batman' ,   'iamback'   12345 ,   'batman@nasa.org' ] 


    Young Creator, you have experienced with me the great course of evolution of Everything from the Tao, which is also the core principle of the Model section in Django.

    Next, join me in a more fun reptile battle (well, you are now a junior hacker): crawling web agents! 

Challenge II: l'analyse de Réseau des Agents

préparez-vous à grimper une page pour jouer

s'il Vous Plaît assurez-vous que les deux paquets, requêtes et pyquery, sont installés.

# File: get_page.py

import Requests



Base_headers = {

     'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' ,

     'Accept-Encoding' : 'gzip, deflate, sdch' ,

     'Accept-Language' : 'zh-CN,zh;q=0.8'

}





def Get_page ( url ) :

     Headers = dict ( base_headers )

     print( 'Getting' ,   Url )

     Try :

         r = requests . get ( url ,   Headers = headers )

         print( 'Getting result' ,   Url ,   r . status_code )

         If   r . status_code == 200 :

             Return   r .

     exceptConnectionError :

         print( 'Crawling Failed' ,   Url )

         Return   None 

ici, nous utilisons le paquet de requête pour sortir du code source de Baidu.

Essayer à essayer Baidu

coller ce paragraphe derrière get_page.py et essayez de supprimer

If ( __name__ == '__main__' ) :

     Rs = get_page ( 'https://www.baidu.com' )

     print( 'result: ' ,   Rs ) 

Essayez d'attraper les agents

coller ce paragraphe derrière get_page.py et essayez de supprimer

If ( __name__ == '__main__' ) :

     from Pyquery import   PyQuery as   Pq

     Start_url = 'http://www.proxy360.cn/Region/China'

     print( 'Crawling' ,   Start_url )

     Html = get_page ( start_url )

     If   Html :

         Doc = pq ( html )

         Lines = doc ( 'div[name="list_proxy_ip"]' ). items ()

         for Line in   Lines :

             Ip = line . find ( '.tbBottomLine:nth-child(1)' ). text ()

             Port = line . find ( '.tbBottomLine:nth-child(2)' ). text ()

             print( ip + ':' + port ) 


Next, go to the topic: Use the metaclass batch fetch proxy 

traitement par lots agent crawling

from Getpage import   Get_page

from Pyquery import   PyQuery as   Pq





# one came from truth: Create metaclass of extraction agent

class ProxyMetaclass ( type ) :

     """

Metaclass, added in the FreeProxyGetter class

__CrawlFunc__ and __CrawlFuncCount__

Two parameters, which represent the crawler function and the number of crawler functions, respectively.

"""

     def __new__ ( cls ,name, bases ,   attrs) :

         Count = 0

         attrs[ '__CrawlFunc__' ] = []

         attrs[ '__CrawlName__' ] = []

         for k ,   v   In   attrs. items () :

             If   'crawl_'   In   k :

                 attrs[ '__CrawlName__' ]. append ( k )

                 attrs[ '__CrawlFunc__' ]. append ( v )

                 Count += 1

         for k   In   attrs[ '__CrawlName__' ] :

             attrs. pop ( k )

         attrs[ '__CrawlFuncCount__' ] = count

         Return   Type . __new__ ( cls ,name, bases ,   attrs)





# two came from one: Create an agent to get the class



class ProxyGetter ( object ,   Metaclass = ProxyMetaclass ) :

     def Get_raw_proxies ( self ,   Site ) :

         Proxies = []

         print( 'Site' ,   Site )

         for Func in   self. __CrawlFunc__ :

             If   Func . __name__ == site :

                 This_page_proxies = func ( self )

                 for Proxy in   This_page_proxies :

                     print( 'Getting' ,   Proxy ,   'from' ,   Site )

                     Proxies . append ( proxy )

         Return   Proxies





     def Crawl_daili66 ( self ,   Page_count = 4 ) :

         Start_url = 'http://www.66ip.cn/{}.html'

         Urls = [ start_url . format ( page )   for Page in   Range ( 1 ,   Page_count + 1 )]

         for Url in   Urls :

             print( 'Crawling' ,   Url )

             Html = get_page ( url )

             If   Html :

                 Doc = pq ( html )

                 Trs = doc ( '.containerbox table tr:gt(0)' ). items ()

                 for Tr in   Trs :

                     Ip = tr . find ( 'td:nth-child(1)' ). text ()

                     Port = tr . find ( 'td:nth-child(2)' ). text ()

                     Yield   ':' . join ([ ip ,   Port ])



     def Crawl_proxy360 ( self ) :

         Start_url = 'http://www.proxy360.cn/Region/China'

         print( 'Crawling' ,   Start_url )

         Html = get_page ( start_url )

         If   Html :

             Doc = pq ( html )

             Lines = doc ( 'div[name="list_proxy_ip"]' ). items ()

             for Line in   Lines :

                 Ip = line . find ( '.tbBottomLine:nth-child(1)' ). text ()

                 Port = line . find ( '.tbBottomLine:nth-child(2)' ). text ()

                 Yield   ':' . join ([ ip ,   Port ])



     def Crawl_goubanjia ( self ) :

         Start_url = 'http://www.goubanjia.com/free/gngn/index.shtml'

         Html = get_page ( start_url )

         If   Html :

             Doc = pq ( html )

             Tds = doc ( 'td.ip' ). items ()

             for Td in   Tds :

                 Td . find ( 'p' ). remove ()

                 Yield   Td . text (). replace ( ' ' ,   '' )





If   __name__ == '__main__' :

     # Two students three: Instantiate ProxyGetter

     Crawler = ProxyGetter ()

     print(crawler . __CrawlName__ )

     # Three things

     for Site_label in   Range ( crawler . __CrawlFuncCount__ ) :

         Site = crawler . __CrawlName__ [ site_label ]

         myProxies = crawler . get_raw_proxies ( site ) 

un est venu de la vérité: dans le metaclass nouveau , il a fait quatre choses:

Push the name of the class method that starts with "crawl_" into ProxyGetter.__CrawlName__

Push the class method that starts with "crawl_" itself into ProxyGetter.__CrawlFunc__

Calculate the number of class methods that match "crawl_"

Delete all class methods that match "crawl_" 


how about it? Is it very similar to the __mappings__ process used to create an ORM? 

deux proviennent d'un seul: la classe définit la méthode d'utilisation de pyquery pour saisir les éléments de page

chacun des agents montrés sur la page a été rampé à partir de trois sites d'agents libres.

si vous n'êtes pas familier avec l'utilisation de yield, consultez: tutoriel de Python de Liao Xuefeng: generator

trois viennent de deux: créer une instance objet crawler

légèrement

trois choses: traverser chaque CrawlFunc

Above ProxyGetter.__CrawlName__, get the URL name that can be crawled.

Trigger class method ProxyGetter.get_raw_proxies(site)

Traverse ProxyGetter.__CrawlFunc__, if the method name and URL are the same, then execute this method

Integrate the proxy obtained from each URL into an array output. 


So. . . How to use bulk agents, impact other people's websites, capture other people's passwords, frantically advertise water stickers, and regularly harass customers? Uh! Think it! These self-realization! If you do not realize it, please listen to the next decomposition! 

le jeune créateur, l'outil pour créer le monde, est déjà entre vos mains. Veuillez utiliser son pouvoir au maximum!

Rappelez-vous le contrôle de l'outil à la bouche:

One came from truth, two came from one, three came from two, all the thing came from three.

Who am I, where do I come from, where do I go 
14
répondu pah8J 2018-10-01 07:48:51

la fonction type () peut retourner le type d'un objet ou créer un nouveau type,

par exemple, nous pouvons créer une classe Hi avec la fonction type() et nous n'avons pas besoin de l'utiliser de cette façon avec la classe Hi(object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

en plus d'utiliser type() pour créer des classes dynamiquement, vous pouvez contrôler le comportement de création de classe et utiliser metaclass.

selon le modèle d'objet Python, la classe est l'objet, donc le la classe doit être une instance d'une autre classe. Par défaut, une classe Python est une instance de la classe type. Qui est, est de type métaclasse de la plupart des classes intégrées et la métaclasse de classes définies par l'utilisateur.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Magic entrera en vigueur lorsque nous passerons des arguments de mots-clés dans metaclass, il indique l'interpréteur Python pour créer la liste personnalisée via ListMetaclass. nouvelle (), à ce stade, nous pouvons modifier la définition de la classe, pour exemple, et ajouter une nouvelle méthode, puis retourner la définition révisée.

9
répondu binbjz 2018-01-12 09:30:20

en plus des réponses publiées, je peux dire qu'un metaclass définit le comportement d'une classe. Ainsi, vous pouvez définir explicitement votre métaclasse. Chaque fois que Python obtient un mot-clé class , alors il commence à chercher le metaclass . Si elle n'est pas trouvée – le type de métaclass par défaut est utilisé pour créer l'objet de la classe. En utilisant l'attribut __metaclass__ , vous pouvez définir metaclass de votre classe:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

il produira la sortie comme ceci:

class 'type'

et, bien sûr, vous pouvez créer votre propre metaclass pour définir le comportement de toute classe qui sont créés en utilisant votre classe.

pour faire cela, votre classe de type par défaut metaclass doit être héritée car c'est la principale metaclass :

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

la sortie sera:

class '__main__.MyMetaClass'
class 'type'
1
répondu Gigantic 2018-09-15 13:17:29