Přidání metody na existující instance objektu

hlasů
488

Četl jsem, že je možné přidat metodu ke stávajícímu objektu (tedy nikoli v definici třídy) v Pythonu.

Chápu, že to není vždy dobré, aby tak učinily. Ale jak mohl jeden to udělat?

Položena 04/08/2008 v 03:17
zdroj uživatelem
V jiných jazycích...                            


18 odpovědí

hlasů
736

V Pythonu, existuje rozdíl mezi funkcí a vázaných metod.

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

Vázané metody byly „vázaný“ (jak popisné) na instanci, a že například bude předán jako první argument, když se nazývá metoda.

Callables, které jsou atributy třídy (na rozdíl od například) jsou stále nevázané, i když, takže můžete změnit definici třídy, kdykoli budete chtít:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

Dříve definované případy jsou aktualizovány stejně (tak dlouho, dokud nebyly přepsány atributu sám):

>>> a.fooFighters()
fooFighters

Problém nastává, když se chcete připojit metodu jedné instance:

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

Tato funkce není automaticky vázán, když je připojen přímo k instanci:

>>> a.barFighters
<function barFighters at 0x00A98EF0>

Chcete-li ji svázat, můžeme použít funkci MethodType v modulu typy :

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

nebyly ovlivněny tentokrát jiné instance třídy:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'

Více informací lze nalézt tím, že čte o popisovačů a Metaclass programování .

Odpovězeno 06/08/2008 v 01:33
zdroj uživatelem

hlasů
80

Modul nový se nedoporučuje, protože pythonu 2,6 a odpaří se ve 3.0, použití typů

viz http://docs.python.org/library/new.html

V níže uvedeném příkladu jsem záměrně odstraněna vrácená hodnota z patch_me()funkce. Myslím, že to dává návratovou hodnotu může provést jeden věří, že náplast vrací nový objekt, který není pravdivý - modifikuje příchozí jeden. Pravděpodobně to může usnadnit více disciplinovaný využití monkeypatching.

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>
Odpovězeno 06/06/2009 v 06:31
zdroj uživatelem

hlasů
47

Přidání metody na existující instance objektu

Četl jsem, že je možné přidat metodu ke stávajícímu objektu (např není v definici třídy) v Pythonu.

Chápu, že to není vždy dobré rozhodnutí, aby tak učinily. Ale, jak by jeden to udělat?

Ano, je to možné - ale nedoporučuje

Nedoporučuji to. To není dobrý nápad. Nedělejte to.

Zde je několik důvodů:

  • Přidáte vázaný objekt každém případě to děláš. Pokud jste to udělat hodně, budete pravděpodobně odpad spoustu paměti. Vázané metody jsou typicky vytvořeny pouze na krátkou dobu svého hovoru, a oni pak přestane existovat, když automaticky uvolněna. Pokud to budete dělat ručně, budete mít název závaznou odkazování na metodu vazby - což zabrání jeho odvoz odpadu na způsobu použití.
  • Instance objektu daného typu, který má své metody na všech objektů tohoto typu. Přidáte-li metody jinde, některých případech budou mít tyto metody a jiní ne. Programátoři nebudou očekávat, že to, a riskujete poruší pravidlo nejmenšího překvapení .
  • Vzhledem k tomu, existují i ​​jiné opravdu dobré důvody, proč to udělat, budete navíc dát si špatnou pověst, pokud si to.

Takže navrhuji, abyste to udělat, pokud máte opravdu dobrý důvod. Je mnohem lépe definovat správnou metodu v definici třídy , nebo méně výhodně do opice-patch třídy přímo, například takto:

Foo.sample_method = sample_method

Vzhledem k tomu, že je to poučné, ale já vám ukážu několik způsobů, jak to udělat.

Jak to může být provedeno

Zde je několik nastavení kódu. Potřebujeme definici třídy. Mohlo by to být importována, ale je to opravdu nezáleží.

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

Vytvoření instance:

foo = Foo()

Vytvořit metodu přidat k němu:

def sample_method(self, bar, baz):
    print(bar + baz)

Metoda nula (0), - použití metody deskriptoru, __get__

Tečkované je vyhledávání pro funkce volání __get__metody funkce se například vazba objekt metody, a vytváří tak „metodu vazby.“

foo.sample_method = sample_method.__get__(foo)

a teď:

>>> foo.sample_method(1,2)
3

Metoda jeden - types.MethodType

Za prvé, druhy dovozu, z něhož budeme mít metodu konstruktoru:

import types

Nyní přidáme metodu instance. Chcete-li to provést, potřebujeme konstruktor MethodType z typesmodulu (kterou jsme dovážené výše).

Argument podpis types.MethodType je (function, instance, class):

foo.sample_method = types.MethodType(sample_method, foo, Foo)

a použití:

>>> foo.sample_method(1,2)
3

Metoda dvě: lexikální vazba

Nejprve vytvoříme funkci, obálky, která se váže na metodu na instanci:

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

používání:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

Metoda třetí: functools.partial

Částečný funkce se vztahuje na první argument (y) na funkci (a případně klíčových argumentů), a může být později nazývá se zbývajícími argumenty (a naléhavých klíčových argumentů). Tím pádem:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

To dává smysl, když si uvědomíte, že vázané metody jsou dílčí funkce instance.

Nevázaný funkce jako atribut objektu - proč to nefunguje:

Pokusíme-li se přidat sample_method stejným způsobem, jako se nám to mohlo přidat do třídy, je nevázaný z instance, a nebere implicitní já jako první argument.

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

Můžeme učinit nevázané funkci práci výslovně předáním instance (nebo cokoliv, protože tato metoda není ve skutečnosti používat selfargumentů proměnné), ale to by nebylo v souladu s očekávaným podpisem jiných případech (když jsme opice-záplatování tento příklad):

>>> foo.sample_method(foo, 1, 2)
3

Závěr

Nyní víte, několik způsobů, jak byste mohli to udělat, ale ve vší vážnosti - nedělejte to.

Odpovězeno 21/01/2015 v 05:31
zdroj uživatelem

hlasů
30

Myslím si, že výše uvedené odpovědi minul klíčový bod.

Mějme třídu s metodou:

class A(object):
    def m(self):
        pass

Nyní se pojďme hrát s ním v ipython:

In [2]: A.m
Out[2]: <unbound method A.m>

Ok, m (), nějakým způsobem se stává nevázané způsob . Ale je to opravdu tak?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

Ukazuje se, že m () je jen funkce, s jejichž pomocí se přidává do A třídy slovníku - je tam žádné kouzlo. Tak proč Am nám dává nevázané metoda? Je to proto, že tečka není přeložen do jednoduchého slovníku vyhledávání. Je to de facto základě výzvy A .__ class __.__ getAttribute __ (A, 'm'):

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

Teď jsem si jistý, z vrcholu mé hlavy, proč se poslední řádek tištěný dvakrát, ale je jasné, že to, co se tam děje.

A teď, co je výchozí __getattribute__ dělá, je, že se kontroluje, zda je atribut takzvaný deskriptor nebo ne, tj pokud se realizuje speciální metodu __get__. Pokud implementuje tuto metodu, pak to, co je vrácena je výsledek volání této metody __get__. Vraťme se zpět k první verzi našeho A třídy, to je to, co máme:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

A protože Python funkce implementace protokolu popisovač, pokud se nazývají jménem objektu, se zavazují k tomuto objektu v jejich způsobu __get__.

Ok, tak jak se přidat metodu k existujícímu objektu? Za předpokladu, že vám nevadí, že záplatování třídu, je to tak jednoduché, jak:

B.m = m

Pak Bm „se stane“ nevázané metoda, díky popisovače magie.

A pokud chcete přidat metodu jen jeden objekt, pak budete muset napodobovat strojním zařízením sami pomocí types.MethodType:

b.m = types.MethodType(m, b)

Mimochodem:

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>
Odpovězeno 22/01/2012 v 15:20
zdroj uživatelem

hlasů
16

V Pythonu opice záplatování obecně funguje přepsáním třídní nebo funkce podpis sami. Níže je uveden příklad z Zope Wiki :

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak

Tento kód přepíše / vytvořit metodu zvanou mluvit na třídě. Jeff Atwood v nedávném příspěvku na opičí záplatování . Ten ukazuje příklad v C # 3.0, která je aktuální jazyk používám k práci.

Odpovězeno 04/08/2008 v 03:31
zdroj uživatelem

hlasů
9

Existují nejméně dva způsoby připojení metodu instance, aniž by types.MethodType:

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

Užitečné odkazy:
Datový model - vyvolání popisovače
Descriptor HowTo Guide - vyvolání popisovače

Odpovězeno 26/04/2013 v 16:47
zdroj uživatelem

hlasů
7

Můžete použít lambda vázat metodu instanci:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

To je instance string

Proces skončil s kódem ukončení 0

Odpovězeno 21/07/2014 v 13:55
zdroj uživatelem

hlasů
6

Vzhledem k této otázce požádán o non-Python verze, tady je JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
Odpovězeno 09/03/2012 v 16:07
zdroj uživatelem

hlasů
6

Co hledáte, je setattrmi věřit. Pomocí této funkce lze nastavit atribut na objekt.

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>
Odpovězeno 07/08/2008 v 12:30
zdroj uživatelem

hlasů
5

To je vlastně doplněk k odpovědí „Jason Pratt“

Ačkoli Jasons odpovědět funguje, to funguje pouze tehdy, pokud někdo chce přidat funkci do třídy. Nefungovalo to pro mě, když jsem se snažil načíst již existující metody ze souboru .py zdrojového kódu.

Trvalo mi věky najít řešení, ale trik vypadá jednoduše ... 1.st importovat kód ze souboru zdrojového kódu 2.nd vynutit použití Obnovit 3.rd types.FunctionType (...) převést dováženy a vázané metoda k funkci můžete také předat na současných globálních proměnných, jako reloaded metoda by být v jiném oboru názvů 4.th nyní můžete pokračovat, jak navrhuje „Jason Pratt“ pomocí types.MethodType (... )

Příklad:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code
Odpovězeno 18/08/2015 v 15:32
zdroj uživatelem

hlasů
5

Měli byste opravdu podívat na zakázané ovoce , je to python knihovna, která poskytuje podporu pro opice záplatování ŽÁDNOU python třídu, a to i řetězce.

Odpovězeno 25/08/2013 v 22:56
zdroj uživatelem

hlasů
5

Konsolidace Jason Pratt a komunita wiki odpovědi, s pohledem na základě výsledků různých způsobů vázání:

Zejména na vědomí, jak přidání funkci vazby jako metoda třídy funguje , ale odkazuje rozsah je nesprávná.

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

Osobně dávám přednost externí funkce ADDMETHOD cestu, protože mi umožňuje dynamicky přiřadit nové názvy metod v iterátor stejně.

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
Odpovězeno 28/01/2012 v 01:12
zdroj uživatelem

hlasů
4

Co Jason Pratt posta je správná.

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

Jak můžete vidět, Python nepovažuje b () nic jiného než (). V Pythonu všechny metody jsou pouze proměnné, které shodou okolností funkce.

Odpovězeno 22/08/2008 v 15:40
zdroj uživatelem

hlasů
3

Jestli to může být jakékoliv pomoci, Nedávno jsem vydal knihovny Python s názvem Gorila, aby byl proces opice záplatování pohodlnější.

Pomocí funkce needle()opravit modul pojmenovaný guineapigjde takto:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

Ale také pečuje o případech zajímavějších použití, jak je uvedeno ve FAQ z dokumentace .

Kód je k dispozici na GitHub .

Odpovězeno 15/07/2014 v 03:12
zdroj uživatelem

hlasů
2

Zjistil jsem, že zvláštní, že nikdo je uvedeno, že všechny z výše uvedených metod se vytvoří referenční cyklus mezi přidaným způsobu a instance, což má být objekt perzistentní do úklid. Tam byl starý trik přidáním deskriptor rozšířením třídy objektu:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
Odpovězeno 30/04/2017 v 04:57
zdroj uživatelem

hlasů
2

Tato otázka byla otevřena před několika lety, ale hej, tam je jednoduchý způsob, jak simulovat vazby funkce na instanci třídy pomocí malíři:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

Tam, při předání funkce a instance do dekoratér pojiva, bude to vytvořit novou funkci, se stejným kódem objektu jako první. Poté daná instance třídy je uložen v atributu nově vytvořené funkci. Dekoratér vrátit (třetí) funkce volání automaticky zkopírovaný funkci, což instanci jako první parametr.

Na závěr dostanete funkce simulující to navázání na instanci třídy. Nechat původní funkce beze změny.

Odpovězeno 21/12/2015 v 21:39
zdroj uživatelem

hlasů
1
from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

S tímto, můžete použít vlastní ukazatel

Odpovězeno 27/07/2017 v 04:21
zdroj uživatelem

hlasů
-8

Nevím syntaxe jazyka Python, ale vím, že Ruby může udělat, a to je poměrně triviální. Řekněme, že chcete přidat metodu Array, která tiskne délku na standardní výstup:

class Array
  def print_length
    puts length
  end
end

Pokud si nechcete měnit celou třídu, stačí přidat metodu, která má jednu instanci pole a žádné další pole bude mít metodu:

array = [1, 2, 3]
def array.print_length
  puts length
end

Stačí být vědomi problematiky v této funkci. Jeff Atwood vlastně psal o tom není to tak dávno.

Odpovězeno 04/08/2008 v 03:36
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more