美文网首页
Python学习手册

Python学习手册

作者: plutoese | 来源:发表于2017-06-10 14:26 被阅读21次

    Managed Attributes

    (1)Properties

    A property is created by assigning the result of a built-in function to a class attribute:

    attribute = property(fget, fset, fdel, doc)
    
    class Person:                       # Add (object) in 2.X
        def __init__(self, name):
            self._name = name
        def getName(self):
            print('fetch...')
            return self._name
        def setName(self, value):
            print('change...')
            self._name = value
        def delName(self):
            print('remove...')
            del self._name
        name = property(getName, setName, delName, "name property docs")
    
    bob = Person('Bob Smith')           # bob has a managed attribute
    print(bob.name)                     # Runs getName
    bob.name = 'Robert Smith'           # Runs setName
    print(bob.name)
    del bob.name                        # Runs delName
    
    print('-'*20)
    sue = Person('Sue Jones')           # sue inherits property too
    print(sue.name)
    print(Person.name.__doc__)          # Or help(Person.name)
    

    Coding Properties with Decorators

    class Person:
        def __init__(self, name):
            self._name = name
    
        @property
        def name(self):                 # name = property(name)
            "name property docs"
            print('fetch...')
            return self._name
    
        @name.setter
        def name(self, value):          # name = name.setter(name)
            print('change...')
            self._name = value
    
        @name.deleter
        def name(self):                 # name = name.deleter(name)
            print('remove...')
            del self._name
    
    bob = Person('Bob Smith')           # bob has a managed attribute
    print(bob.name)                     # Runs name getter (name 1)
    bob.name = 'Robert Smith'           # Runs name setter (name 2)
    print(bob.name)
    del bob.name                        # Runs name deleter (name 3)
    
    print('-'*20)
    sue = Person('Sue Jones')           # sue inherits property too
    print(sue.name)
    print(Person.name.__doc__)          # Or help(Person.name)
    

    (2)Descriptors

    Descriptors provide an alternative way to intercept attribute access.

    Descriptors are coded as separate classes and provide specially named accessor methods for the attribute access operations they wish to intercept —get, set, and deletion methods in the descriptor class are automatically run when the attribute assigned to the descriptor class instance is accessed in the corresponding way.

    class Descriptor:
        "docstring goes here"
        def __get__(self, instance, owner): ... # Return attr value
        def __set__(self, instance, value): ... # Return nothing (None)
        def __delete__(self, instance): ... # Return nothing (None)
    

    Classes with any of these methods are considered descriptors, and their methods are special when one of their instances is assigned to another class’s attribute—when the attribute is accessed, they are automatically invoked.

    Unlike properties, simply omitting the __set__ method in a descriptor isn’t enough to make an attribute read-only, because the descriptor name can be assigned to an instance. In the following, the attribute assignment to X.a stores a in the instance object X, thereby hiding the descriptor stored in class C.

    >>> class D:
              def __get__(*args): print('get')
    >>> class C:
              a = D() # Attribute a is a descriptor instance
    >>> X = C()
    >>> X.a # Runs inherited descriptor __get__
    get
    >>> C.a
    get
    >>> X.a = 99 # Stored on X, hiding C.a!
    >>> X.a
    99
    >>> list(X.__dict__.keys())
    ['a']
    >>> Y = C()
    >>> Y.a # Y still inherits descriptor
    get
    >>> C.a
    get
    

    To make a descriptor-based attribute read-only, catch the assignment in the descriptor class and raise an exception to prevent attribute assignment.

    >>> class D:
              def __get__(*args): print('get')
              def __set__(*args): raise AttributeError('cannot set')
    >>> class C:
              a = D()
    >>> X = C()
    >>> X.a # Routed to C.a.__get__
    get
    >>> X.a = 99 # Routed to C.a.__set__
    AttributeError: cannot set
    

    A First Example

    class Name:                             # Use (object) in 2.X
        "name descriptor docs"
        def __get__(self, instance, owner):
            print('fetch...')
            return instance._name
        def __set__(self, instance, value):
            print('change...')
            instance._name = value
        def __delete__(self, instance):
            print('remove...')
            del instance._name
    
    class Person:                           # Use (object) in 2.X
        def __init__(self, name):
            self._name = name
        name = Name()                       # Assign descriptor to attr
    
    bob = Person('Bob Smith')               # bob has a managed attribute
    print(bob.name)                         # Runs Name.__get__
    bob.name = 'Robert Smith'               # Runs Name.__set__
    print(bob.name)
    del bob.name                            # Runs Name.__delete__
    
    print('-'*20)
    sue = Person('Sue Jones')               # sue inherits descriptor too
    print(sue.name)
    print(Name.__doc__)                     # Or help(Name)
    
    c:\code> py −3 desc-person.py
    fetch...
    Bob Smith
    change...
    fetch...
    Robert Smith
    remove...
    --------------------
    fetch...
    Sue Jones
    name descriptor docs
    

    Using State Information in Descriptors

    class DescState:                           # Use descriptor state, (object) in 2.X
        def __init__(self, value):
            self.value = value
        def __get__(self, instance, owner):    # On attr fetch
            print('DescState get')
            return self.value * 10
        def __set__(self, instance, value):    # On attr assign
            print('DescState set')
            self.value = value
    
    # Client class
    class CalcAttrs:
        X = DescState(2)                       # Descriptor class attr
        Y = 3                                  # Class attr
        def __init__(self):
            self.Z = 4                         # Instance attr
    
    obj = CalcAttrs()
    print(obj.X, obj.Y, obj.Z)                 # X is computed, others are not
    obj.X = 5                                  # X assignment is intercepted
    CalcAttrs.Y = 6                            # Y reassigned in class
    obj.Z = 7                                  # Z assigned in instance
    print(obj.X, obj.Y, obj.Z)
    
    obj2 = CalcAttrs()                         # But X uses shared data, like Y!
    print(obj2.X, obj2.Y, obj2.Z)
    
    c:\code> py .3 desc-state-desc.py
    DescState get
    20 3 4
    DescState set
    DescState get
    50 6 7
    DescState get
    50 6 4
    
    >>> class DescBoth:
            def __init__(self, data):
                self.data = data
            def __get__(self, instance, owner):
                return '%s, %s' % (self.data, instance.data)
            def __set__(self, instance, value):
                instance.data = value
    >>> class Client:
            def __init__(self, data):
                self.data = data
            managed = DescBoth('spam')
    
    >>> I = Client('eggs')
    >>> I.managed # Show both data sources
    'spam, eggs'
    >>> I.managed = 'SPAM' # Change instance data
    >>> I.managed
    'spam, SPAM'
    
    >>> I.__dict__
    {'data': 'SPAM'}
    >>> [x for x in dir(I) if not x.startswith('__')]
    ['data', 'managed']
    >>> getattr(I, 'data')
    'SPAM'
    >>> getattr(I, 'managed')
    'spam, SPAM'
    >>> for attr in (x for x in dir(I) if not x.startswith('__')):
            print('%s => %s' % (attr, getattr(I, attr)))
    data => SPAM
    managed => spam, SPAM
    

    (3)__getattr__ and __getattribute__

    • __getattr__ is run for undefined attributes—because it is run only for attributes not stored on an instance or inherited from one of its classes, its use is straightforward.
    • __getattribute__ is run for every attribute—because it is all-inclusive, you must be cautious when using this method to avoid recursive loops by passing attribute accesses to a superclass.

    Unlike properties and descriptors, these methods are part of Python’s general operator overloading protocol—specially named methods of a class, inherited by subclasses, and run automatically when instances are used in the implied built-in operation.

    The __getattr__ and __getattribute__ methods are also more generic than properties and descriptors—they can be used to intercept access to any (or even all) instance attribute fetches, not just a single specific name. Because of this, these two methods are well suited to general delegation-based coding patterns.

    The Basics

    def __getattr__(self, name): # On undefined attribute fetch [obj.name]
    def __getattribute__(self, name): # On all attribute fetch [obj.name]
    def __setattr__(self, name, value): # On all attribute assignment [obj.name=value]
    def __delattr__(self, name): # On all attribute deletion [del obj.name]
    
    class Catcher:
        def __getattr__(self, name):
            print('Get: %s' % name)
        def __setattr__(self, name, value):
            print('Set: %s %s' % (name, value))
            
    X = Catcher()
    X.job # Prints "Get: job"
    X.pay # Prints "Get: pay"
    X.pay = 99 # Prints "Set: pay 99"
    
    class Wrapper:
        def __init__(self, object):
            self.wrapped = object # Save object
        def __getattr__(self, attrname):
            print('Trace: ' + attrname) # Trace fetch
            return getattr(self.wrapped, attrname) # Delegate fetch
    
    X = Wrapper([1, 2, 3])
    X.append(4) # Prints "Trace: append"
    print(X.wrapped) # Prints "[1, 2, 3, 4]"
    

    Avoiding loops in attribute interception methods

    These methods are generally straightforward to use; their only substantially complex aspect is the potential for looping (a.k.a. recursing).

    The code will usually loop until memory is exhausted.

    def __getattribute__(self, name):
        x = self.other # LOOPS!
    

    To avoid this loop, route the fetch through a higher superclass instead to skip this level’s version—because the object class is always a new-style superclass, it serves well in this role.

    def __getattribute__(self, name):
        x = object.__getattribute__(self, 'other') # Force higher to avoid me
    
    def __setattr__(self, name, value):
    object.__setattr__(self, 'other', value) # Force higher to avoid me
    

    A First Example

    class Person:                               # Portable: 2.X or 3.X
        def __init__(self, name):               # On [Person()]
            self._name = name                   # Triggers __setattr__!
    
        def __getattr__(self, attr):            # On [obj.undefined]
            print('get: ' + attr)
            if attr == 'name':                  # Intercept name: not stored
                return self._name               # Does not loop: real attr
            else:                               # Others are errors
                raise AttributeError(attr)
    
        def __setattr__(self, attr, value):     # On [obj.any = value]
            print('set: ' + attr)
            if attr == 'name':
                attr = '_name'                  # Set internal name
            self.__dict__[attr] = value         # Avoid looping here
    
        def __delattr__(self, attr):            # On [del obj.any]
            print('del: ' + attr)
            if attr == 'name':
                attr = '_name'                  # Avoid looping here too
            del self.__dict__[attr]             # but much less common
    
    bob = Person('Bob Smith')           # bob has a managed attribute
    print(bob.name)                     # Runs __getattr__
    bob.name = 'Robert Smith'           # Runs __setattr__
    print(bob.name)
    del bob.name                        # Runs __delattr__
    
    print('-'*20)
    sue = Person('Sue Jones')           # sue inherits property too
    print(sue.name)
    #print(Person.name.__doc__)         # No equivalent here
    
    c:\code> py −3 getattr-person.py
    set: _name
    get: name
    Bob Smith
    set: name
    get: name
    Robert Smith
    del: name
    --------------------
    set: _name
    get: name
    Sue Jones
    
    class Person:                               # Portable: 2.X or 3.X
        def __init__(self, name):               # On [Person()]
            self._name = name                   # Triggers __setattr__!
    
        def __getattribute__(self, attr):                 # On [obj.any]
            print('get: ' + attr)
            if attr == 'name':                            # Intercept all names
                attr = '_name'                            # Map to internal name
            return object.__getattribute__(self, attr)    # Avoid looping here
    
        def __setattr__(self, attr, value):     # On [obj.any = value]
            print('set: ' + attr)
            if attr == 'name':
                attr = '_name'                  # Set internal name
            self.__dict__[attr] = value         # Avoid looping here
    
        def __delattr__(self, attr):            # On [del obj.any]
            print('del: ' + attr)
            if attr == 'name':
                attr = '_name'                  # Avoid looping here too
            del self.__dict__[attr]             # but much less common
    
    bob = Person('Bob Smith')           # bob has a managed attribute
    print(bob.name)                     # Runs __getattr__
    bob.name = 'Robert Smith'           # Runs __setattr__
    print(bob.name)
    del bob.name                        # Runs __delattr__
    
    print('-'*20)
    sue = Person('Sue Jones')           # sue inherits property too
    print(sue.name)
    #print(Person.name.__doc__)         # No equivalent here
    
    c:\code> py −3 getattribute-person.py
    set: _name
    get: __dict__
    get: name
    Bob Smith
    set: name
    get: __dict__
    get: name
    Robert Smith
    del: name
    get: __dict__
    --------------------
    set: _name
    get: __dict__
    get: name
    Sue Jones
    

    __getattr__ and __getattribute__ Compared

    class GetAttr:
        attr1 = 1
        def __init__(self):
            self.attr2 = 2
        def __getattr__(self, attr):            # On undefined attrs only
            print('get: ' + attr)               # Not on attr1: inherited from class
            if attr == 'attr3':                 # Not on attr2: stored on instance
                return 3
            else:
                raise AttributeError(attr)
     
    X = GetAttr()
    print(X.attr1)
    print(X.attr2)
    print(X.attr3)
    print('-'*20)
    
    class GetAttribute(object):                 # (object) needed in 2.X only
        attr1 = 1
        def __init__(self):
            self.attr2 = 2
        def __getattribute__(self, attr):       # On all attr fetches
            print('get: ' + attr)               # Use superclass to avoid looping here
            if attr == 'attr3':
                return 3
            else:
                return object.__getattribute__(self, attr)
    
    X = GetAttribute()
    print(X.attr1)
    print(X.attr2)
    print(X.attr3)
    
    c:\code> py −3 getattr-v-getattr.py
    1
    2
    get: attr3
    3
    --------------------
    get: attr1
    1
    get: attr2
    2
    get: attr3
    3
    

    Intercepting Built-in Operation Attributes

    class GetAttr:
        eggs = 88                    # eggs stored on class, spam on instance
        def __init__(self):
           self.spam = 77
        def __len__(self):           # len here, else __getattr__ called with __len__
            print('__len__: 42')
            return 42
        def __getattr__(self, attr):     # Provide __str__ if asked, else dummy func
            print('getattr: ' + attr)
            if attr == '__str__':
                return lambda *args: '[Getattr str]'
            else:
                return lambda *args: None
    
    class GetAttribute(object):          # object required in 2.X, implied in 3.X
        eggs = 88                        # In 2.X all are isinstance(object) auto
        def __init__(self):              # But must derive to get new-style tools,
            self.spam = 77               # incl __getattribute__, some __X__ defaults
        def __len__(self):
            print('__len__: 42')
            return 42
        def __getattribute__(self, attr):
            print('getattribute: ' + attr)
            if attr == '__str__':
                return lambda *args: '[GetAttribute str]'
            else:
                return lambda *args: None
    
    for Class in GetAttr, GetAttribute:
        print('\n' + Class.__name__.ljust(50, '='))
    
        X = Class()
        X.eggs                   # Class attr
        X.spam                   # Instance attr
        X.other                  # Missing attr
        len(X)                   # __len__ defined explicitly
    
    # New-styles must support [], +, call directly: redefine
    
        try:    X[0]             # __getitem__?
        except: print('fail []')
    
        try:    X + 99           # __add__?
        except: print('fail +')
    
        try:    X()              # __call__?  (implicit via built-in)
        except: print('fail ()')
    
        X.__call__()             # __call__?  (explicit, not inherited)
        print(X.__str__())       # __str__?   (explicit, inherited from type)
        print(X)                 # __str__?   (implicit via built-in)
    
    c:\code> py −2 getattr-builtins.py
    GetAttr===========================================
    getattr: other
    __len__: 42
    getattr: __getitem__
    getattr: __coerce__
    getattr: __add__
    getattr: __call__
    getattr: __call__
    getattr: __str__
    [Getattr str]
    getattr: __str__
    [Getattr str]
    GetAttribute======================================
    getattribute: eggs
    getattribute: spam
    getattribute: other
    __len__: 42
    fail []
    fail +
    fail ()
    getattribute: __call__
    getattribute: __str__
    [GetAttribute str]
    <__main__.GetAttribute object at 0x02287898>
    
    c:\code> py −3 getattr-builtins.py
    GetAttr===========================================
    getattr: other
    __len__: 42
    fail []
    fail +
    fail ()
    getattr: __call__
    <__main__.GetAttr object at 0x02987CC0>
    <__main__.GetAttr object at 0x02987CC0>
    GetAttribute======================================
    getattribute: eggs
    getattribute: spam
    getattribute: other
    __len__: 42
    fail []
    fail +
    fail ()
    getattribute: __call__
    getattribute: __str__
    [GetAttribute str]
    <__main__.GetAttribute object at 0x02987CF8>
    

    Delegation-based managers revisited

    class Person:
        def __init__(self, name, job=None, pay=0):
            self.name = name
            self.job  = job
            self.pay  = pay
        def lastName(self):
            return self.name.split()[-1]
        def giveRaise(self, percent):
            self.pay = int(self.pay * (1 + percent))
        def __repr__(self):
            return '[Person: %s, %s]' % (self.name, self.pay)
    
    class Manager:
        def __init__(self, name, pay):
            self.person = Person(name, 'mgr', pay)      # Embed a Person object
        def giveRaise(self, percent, bonus=.10):
            self.person.giveRaise(percent + bonus)      # Intercept and delegate
    #    def __getattr__(self, attr):
    #        return getattr(self.person, attr)           # Delegate all other attrs
    ##    def __repr__(self):
    ##        return str(self.person)                     # Must overload again (in 3.X)
        def __getattribute__(self, attr):
            print('**', attr)
            if attr in ['person', 'giveRaise']:
                return object.__getattribute__(self, attr)   # Fetch my attrs
            else:
                return getattr(self.person, attr)            # Delegate all others
    
    
    if __name__ == '__main__':
        sue = Person('Sue Jones', job='dev', pay=100000)
        print(sue.lastName())
        sue.giveRaise(.10)
        print(sue)
        tom = Manager('Tom Jones', 50000)    # Manager.__init__
        print(tom.lastName())                # Manager.__getattr__ -> Person.lastName
        tom.giveRaise(.10)                   # Manager.giveRaise -> Person.giveRaise
        print(tom)                           # Manager.__repr__ -> Person.__repr__
    
    C:\code> py −3 getattr-delegate.py
    Jones
    [Person: Sue Jones, 110000]
    ** lastName
    ** person
    Jones
    ** giveRaise
    ** person
    <__main__.Manager object at 0x028E0590>
    

    (4)Example: Attribute Validations

    # File validate_getattr.py
    
    class CardHolder:
        acctlen = 8                                  # Class data
        retireage = 59.5
    
        def __init__(self, acct, name, age, addr):
            self.acct = acct                         # Instance data
            self.name = name                         # These trigger __setattr__ too
            self.age  = age                          # _acct not mangled: name tested
            self.addr = addr                         # addr is not managed
                                                     # remain has no data
        def __getattr__(self, name):
            if name == 'acct':                           # On undefined attr fetches
                return self._acct[:-3] + '***'           # name, age, addr are defined
            elif name == 'remain':
                return self.retireage - self.age         # Doesn't trigger __getattr__
            else:
                raise AttributeError(name)
    
        def __setattr__(self, name, value):
            if name == 'name':                           # On all attr assignments
                value = value.lower().replace(' ', '_')  # addr stored directly
            elif name == 'age':                          # acct mangled to _acct
                if value < 0 or value > 150:
                    raise ValueError('invalid age')
            elif name == 'acct':
                name  = '_acct'
                value = value.replace('-', '')
                if len(value) != self.acctlen:
                    raise TypeError('invald acct number')
            elif name == 'remain':
                raise TypeError('cannot set remain')
            self.__dict__[name] = value                  # Avoid looping (or via object)
    
    # File validate_getattribute.py
    
    class CardHolder(object):                        # Need "(object)" in 2.X only
        acctlen = 8                                  # Class data
        retireage = 59.5
    
        def __init__(self, acct, name, age, addr):
            self.acct = acct                         # Instance data
            self.name = name                         # These trigger __setattr__ too
            self.age  = age                          # acct not mangled: name tested
            self.addr = addr                         # addr is not managed
                                                     # remain has no data
        def __getattribute__(self, name):
            superget = object.__getattribute__             # Don't loop: one level up
            if name == 'acct':                             # On all attr fetches
                return superget(self, 'acct')[:-3] + '***'
            elif name == 'remain':
                return superget(self, 'retireage') - superget(self, 'age')
            else:
                return superget(self, name)                # name, age, addr: stored
    
        def __setattr__(self, name, value):
            if name == 'name':                             # On all attr assignments
                value = value.lower().replace(' ', '_')    # addr stored directly
            elif name == 'age':
                if value < 0 or value > 150:
                    raise ValueError('invalid age')
            elif name == 'acct':
                value = value.replace('-', '')
                if len(value) != self.acctlen:
                    raise TypeError('invald acct number')
            elif name == 'remain':
                raise TypeError('cannot set remain')
            self.__dict__[name] = value                     # Avoid loops, orig names
    

    Decorators(装饰器)

    (1)The Basics

    Function Decorators

    In terms of code, function decorators automatically map the following syntax.

    def F(arg):
    ...
    F = decorator(F) # Rebind function name to decorator result
    F(99) # Essentially calls decorator(F)(99)
    

    Implementation

    A decorator itself is a callable that returns a callable.

    def decorator(F): # On @ decoration
        def wrapper(*args): # On wrapped function call
            # Use F and args
            # F(*args) calls original function
        return wrapper
    
    @decorator # func = decorator(func)
    def func(x, y): # func is passed to decorator's F
        ...
    
    func(6, 7) # 6, 7 are passed to wrapper's *args
    

    To do the same with classes, we can overload the call operation and use instance attributes instead of enclosing scopes.

    class decorator:
        def __init__(self, func): # On @ decoration
            self.func = func
        def __call__(self, *args): # On wrapped function call
            # Use self.func and args
            # self.func(*args) calls original function
    
    @decorator
    def func(x, y): # func = decorator(func)
        ... # func is passed to __init__
    
    func(6, 7) # 6, 7 are passed to __call__'s *args
    

    Decorator Nesting

    @A
    @B
    @C
    def f(...):
        ...
    

    runs the same as the following

    def f(...):
        ...
    f = A(B(C(f)))
    
    def d1(F): return lambda: 'X' + F()
    def d2(F): return lambda: 'Y' + F()
    def d3(F): return lambda: 'Z' + F()
    
    @d1
    @d2
    @d3
    def func(): # func = d1(d2(d3(func)))
        return 'spam'
    
    print(func()) # Prints "XYZspam"
    

    Decorator Arguments

    def decorator(A, B):
        # Save or use A, B
        def actualDecorator(F):
            # Save or use function F
            # Return a callable: nested def, class with __call__, etc.
            return callable
        return actualDecorator
    

    (2)Coding Function Decorators

    Tracing Calls

    # File decorator1.py
    
    class tracer:
        def __init__(self, func):             # On @ decoration: save original func
            self.calls = 0
            self.func = func
        def __call__(self, *args):            # On later calls: run original func
            self.calls += 1
            print('call %s to %s' % (self.calls, self.func.__name__))
            self.func(*args)
    
    @tracer
    def spam(a, b, c):           # spam = tracer(spam)
        print(a + b + c)         # Wraps spam in a decorator object
    
    >>> from decorator1 import spam
    >>> spam(1, 2, 3) # Really calls the tracer wrapper object
    call 1 to spam
    6
    >>> spam('a', 'b', 'c') # Invokes __call__ in class
    call 2 to spam
    abc
    >>> spam.calls # Number calls in wrapper state information
    2
    >>> spam
    <decorator1.tracer object at 0x02D9A730>
    

    Enclosing scopes and nonlocals

    def tracer(func):                        # State via enclosing scope and nonlocal
        calls = 0                            # Instead of class attrs or global
        def wrapper(*args, **kwargs):        # calls is per-function, not global
            nonlocal calls
            calls += 1
            print('call %s to %s' % (calls, func.__name__))
            return func(*args, **kwargs)
        return wrapper
    
    @tracer
    def spam(a, b, c):        # Same as: spam = tracer(spam)
        print(a + b + c)
    
    @tracer
    def eggs(x, y):           # Same as: eggs = tracer(eggs)
        print(x ** y)
    
    spam(1, 2, 3)             # Really calls wrapper, bound to func
    spam(a=4, b=5, c=6)       # wrapper calls spam
    
    eggs(2, 16)               # Really calls wrapper, bound to eggs
    eggs(4, y=4)              # Nonlocal calls _is_ per-decoration here
    
    c:\code> py −3 decorator4.py
    call 1 to spam
    6
    call 2 to spam
    15
    call 1 to eggs
    65536
    call 2 to eggs
    256
    Coding
    

    Class Blunders I: Decorating Methods

    class tracer:
        def __init__(self, func):             # On @ decoration: save original func
            self.calls = 0
            self.func = func
    
        def __call__(self, *args):            # On later calls: run original func
            self.calls += 1
            print('call %s to %s' % (self.calls, self.func.__name__))
            self.func(*args)
    
    class Person:
        def __init__(self, name, pay):
            self.name = name
            self.pay = pay
        
        @tracer
        def giveRaise(self, percent): # giveRaise = tracer(giveRaise)
            self.pay *= (1.0 + percent)
    
        @tracer
        def lastName(self): # lastName = tracer(lastName)
            return self.name.split()[-1]
    
    >>> bob = Person('Bob Smith', 50000) # tracer remembers method funcs
    >>> bob.giveRaise(.25) # Runs tracer.__call__(???, .25)
    call 1 to giveRaise
    TypeError: giveRaise() missing 1 required positional argument: 'percent'
    >>> print(bob.lastName()) # Runs tracer.__call__(???)
    call 1 to lastName
    TypeError: lastName() missing 1 required positional argument: 'self'
    
    >>> bob.giveRaise(.25)
    <__main__.tracer object at 0x02A486D8> (0.25,) {}
    call 1 to giveRaise
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 9, in __call__
    TypeError: giveRaise() missing 1 required positional argument: 'percent'
    

    This happens because Python passes the implied subject instance to self when a method name is bound to a simple function only; when it is an instance of a callable class, that class’s instance is passed instead.

    Using nested functions to decorate methods

    If you want your function decorators to work on both simple functions and class-level methods, the most straightforward solution lies in using one of the other state retention solutions described earlier—code your function decorator as nested defs, so that you don’t depend on a single self instance argument to be both the wrapper class instance and the subject class instance.

    Using descriptors to decorate methods

    Consider the following alternative tracing decorator, which also happens to be a descriptor when used for a class-level method

    class tracer(object): # A decorator+descriptor
        def __init__(self, func): # On @ decorator
            self.calls = 0 # Save func for later call
            self.func = func
        
        def __call__(self, *args, **kwargs): # On call to original func
            self.calls += 1
            print('call %s to %s' % (self.calls, self.func.__name__))
            return self.func(*args, **kwargs)
        
        def __get__(self, instance, owner): # On method attribute fetch
            return wrapper(self, instance)
    
    class wrapper:
        def __init__(self, desc, subj): # Save both instances
            self.desc = desc # Route calls back to deco/desc
            self.subj = subj
            
        def __call__(self, *args, **kwargs):
            return self.desc(self.subj, *args, **kwargs) # Runs tracer.__call__
    
    @tracer
    def spam(a, b, c): # spam = tracer(spam)
            ...same as prior... # Uses __call__ only
    
    class Person:
        @tracer
        def giveRaise(self, percent): # giveRaise = tracer(giveRaise)
            ...same as prior... # Makes giveRaise a descriptor
    
    sue.giveRaise(.10) # Runs __get__ then __call__
    

    Timing Calls

    # File timer-deco1.py
    # Caveat: range still differs - a list in 2.X, an iterable in 3.X
    # Caveat: timer won't work on methods as coded (see quiz solution)
    
    import time, sys
    force = list if sys.version_info[0] == 3 else (lambda X: X) 
    
    class timer:
        def __init__(self, func):
            self.func    = func
            self.alltime = 0
        def __call__(self, *args, **kargs):
            start   = time.clock()
            result  = self.func(*args, **kargs)
            elapsed = time.clock() - start
            self.alltime += elapsed
            print('%s: %.5f, %.5f' % (self.func.__name__, elapsed, self.alltime))
            return result
    
    @timer
    def listcomp(N):
        return [x * 2 for x in range(N)]
    
    @timer
    def mapcall(N):
        return force(map((lambda x: x * 2), range(N)))
    
    result = listcomp(5)                # Time for this call, all calls, return value
    listcomp(50000) 
    listcomp(500000)
    listcomp(1000000)
    print(result)
    print('allTime = %s' % listcomp.alltime)      # Total time for all listcomp calls
    
    print('')
    result = mapcall(5)
    mapcall(50000)
    mapcall(500000)
    mapcall(1000000)
    print(result)
    print('allTime = %s' % mapcall.alltime)       # Total time for all mapcall calls
    
    print('\n**map/comp = %s' % round(mapcall.alltime / listcomp.alltime, 3))
    
    c:\code> py −3 timerdeco1.py
    listcomp: 0.00001, 0.00001
    listcomp: 0.00499, 0.00499
    listcomp: 0.05716, 0.06215
    listcomp: 0.11565, 0.17781
    [0, 2, 4, 6, 8]
    allTime = 0.17780527629411225
    mapcall: 0.00002, 0.00002
    mapcall: 0.00988, 0.00990
    mapcall: 0.10601, 0.11591
    mapcall: 0.21690, 0.33281
    [0, 2, 4, 6, 8]
    allTime = 0.3328064956447921
    **map/comp = 1.872
    

    Adding Decorator Arguments

    A label, for instance, might be added as follows.

    def timer(label=''):
        def decorator(func):
            def onCall(*args): # Multilevel state retention:
                ... # args passed to function
                func(*args) # func retained in enclosing scope
                print(label, ... # label retained in enclosing scope
            return onCall
        return decorator # Returns the actual decorator
    
    @timer('==>') # Like listcomp = timer('==>')(listcomp)
    def listcomp(N): ... # listcomp is rebound to new onCall
    
    listcomp(...) # Really calls onCall
    
    import time
    
    def timer(label='', trace=True):                  # On decorator args: retain args
        class Timer:
            def __init__(self, func):                 # On @: retain decorated func
                self.func    = func
                self.alltime = 0
            def __call__(self, *args, **kargs):       # On calls: call original
                start   = time.clock()
                result  = self.func(*args, **kargs)
                elapsed = time.clock() - start
                self.alltime += elapsed
                if trace:
                    format = '%s %s: %.5f, %.5f'
                    values = (label, self.func.__name__, elapsed, self.alltime)
                    print(format % values)
                return result
        return Timer
    
    >>> from timerdeco2 import timer
    >>> @timer(trace=False) # No tracing, collect total time
    ... def listcomp(N):
    ...     return [x * 2 for x in range(N)]
    ...
    >>> x = listcomp(5000)
    >>> x = listcomp(5000)
    >>> x = listcomp(5000)
    >>> listcomp.alltime
    0.0037191417530599152
    

    (3)Coding Class Decorators

    Singleton Classes

    """
    # 3.X and 2.X: global table
    
    instances = {}             
    
    def singleton(aClass):                          # On @ decoration
        def onCall(*args, **kwargs):                # On instance creation
            if aClass not in instances:             # One dict entry per class
                instances[aClass] = aClass(*args, **kwargs)
            return instances[aClass]
        return onCall
    """
    ##################################################################################
    """
    # 3.X only: nonlocal
    
    def singleton(aClass):                                   # On @ decoration
        instance = None
        def onCall(*args, **kwargs):                         # On instance creation
            nonlocal instance                                # 3.X and later nonlocal
            if instance == None:
                instance = aClass(*args, **kwargs)           # One scope per class
            return instance
        return onCall
    """
    #################################################################################
    """
    # 3.X and 2.X: func attrs
    
    def singleton(aClass):                                   # On @ decoration
        def onCall(*args, **kwargs):                         # On instance creation
            if onCall.instance == None:
                onCall.instance = aClass(*args, **kwargs)    # One function per class
            return onCall.instance
        onCall.instance = None
        return onCall
    """
    ##################################################################################
    
    # 3.X and 2.X: classes
    
    class singleton:
        def __init__(self, aClass):                          # On @ decoration
            self.aClass = aClass
            self.instance = None
        def __call__(self, *args, **kwargs):                 # On instance creation
            if self.instance == None:
                self.instance = self.aClass(*args, **kwargs) # One instance per class
            return self.instance
    
    ##################################################################################
    
    # test code
    
    @singleton                                      # Person = singleton(Person)
    class Person:                                   # Rebinds Person to onCall
         def __init__(self, name, hours, rate):     # onCall remembers Person
            self.name = name
            self.hours = hours
            self.rate = rate
         def pay(self):
            return self.hours * self.rate
    
    @singleton                                      # Spam = singleton(Spam)
    class Spam:                                     # Rebinds Spam to onCall
        def __init__(self, val):                    # onCall remembers Spam
            self.attr = val
    
    bob = Person('Bob', 40, 10)                     # Really calls onCall
    print(bob.name, bob.pay())
    
    sue = Person('Sue', 50, 20)                     # Same, single object
    print(sue.name, sue.pay())
    
    X = Spam(val=42)                                # One Person, one Spam
    Y = Spam(99)
    print(X.attr, Y.attr)
    

    Tracing interfaces with class decorators

    def Tracer(aClass):                                   # On @ decorator
        class Wrapper:
            def __init__(self, *args, **kargs):           # On instance creation
                self.fetches = 0
                self.wrapped = aClass(*args, **kargs)     # Use enclosing scope name
            def __getattr__(self, attrname):
                print('Trace: ' + attrname)               # Catches all but own attrs
                self.fetches += 1
                return getattr(self.wrapped, attrname)    # Delegate to wrapped obj
        return Wrapper
    
    
    if __name__ == '__main__':
    
        @Tracer
        class Spam:                                  # Spam = Tracer(Spam)
            def display(self):                       # Spam is rebound to Wrapper
                print('Spam!' * 8)
    
        @Tracer
        class Person:                                # Person = Tracer(Person)
            def __init__(self, name, hours, rate):   # Wrapper remembers Person
                self.name = name
                self.hours = hours
                self.rate = rate
            def pay(self):                           # Accesses outside class traced
                return self.hours * self.rate        # In-method accesses not traced
    
        food = Spam()                                # Triggers Wrapper()
        food.display()                               # Triggers __getattr__
        print([food.fetches])
    
        bob = Person('Bob', 40, 50)                  # bob is really a Wrapper
        print(bob.name)                              # Wrapper embeds a Person
        print(bob.pay())
    
        print('')
        sue = Person('Sue', rate=100, hours=60)      # sue is a different Wrapper
        print(sue.name)                              # with a different Person
        print(sue.pay())
    
        print(bob.name)                              # bob has different state
        print(bob.pay())
        print([bob.fetches, sue.fetches])            # Wrapper attrs not traced
    
    c:\code> python interfacetracer.py
    Trace: display
    Spam!Spam!Spam!Spam!Spam!Spam!Spam!Spam!
    [1]
    Trace: name
    Bob
    Trace: pay
    2000
    Trace: name
    Sue
    Trace: pay
    6000
    Trace: name
    Bob
    Trace: pay
    2000
    [4, 2]
    
    >>> from interfacetracer import Tracer
    >>> @Tracer
    ... class MyList(list): pass # MyList = Tracer(MyList)
    >>> x = MyList([1, 2, 3]) # Triggers Wrapper()
    >>> x.append(4) # Triggers __getattr__, append
    Trace: append
    >>> x.wrapped
    [1, 2, 3, 4]
    >>> WrapList = Tracer(list) # Or perform decoration manually
    >>> x = WrapList([4, 5, 6]) # Else subclass statement required
    >>> x.append(7)
    Trace: append
    >>> x.wrapped
    [4, 5, 6, 7]
    

    Metaclasses

    (1)To Metaclass or Not to Metaclass

    Just like decorators, though, metaclasses:

    • Provide a more formal and explicit structure
    • Help ensure that application programmers won’t forget to augment their classes according to an API’s requirements
    • Avoid code redundancy and its associated maintenance costs by factoring class customization logic into a single location, the metaclass

    相关文章

      网友评论

          本文标题:Python学习手册

          本文链接:https://www.haomeiwen.com/subject/zmzxqxtx.html