diff -r 95b3efe3d7b7 Lib/enum.py --- a/Lib/enum.py Mon Sep 16 11:03:59 2013 +0300 +++ b/Lib/enum.py Mon Sep 16 07:26:16 2013 -0700 @@ -1,43 +1,17 @@ import sys from collections import OrderedDict -from types import MappingProxyType +from types import MappingProxyType, VirtualAttribute __all__ = ['Enum', 'IntEnum', 'unique'] -class _RouteClassAttributeToGetattr: - """Route attribute access on a class to __getattr__. - - This is a descriptor, used to define attributes that act differently when - accessed through an instance and through a class. Instance access remains - normal, but access to an attribute through a class will be routed to the - class's __getattr__ method; this is done by raising AttributeError. - - """ - def __init__(self, fget=None): - self.fget = fget - if fget.__doc__ is not None: - self.__doc__ = fget.__doc__ - - def __get__(self, instance, ownerclass=None): - if instance is None: - raise AttributeError() - return self.fget(instance) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - def __delete__(self, instance): - raise AttributeError("can't delete attribute") - - def _is_descriptor(obj): """Returns True if obj is a descriptor, False otherwise.""" return ( hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__')) def _is_dunder(name): """Returns True if a __dunder__ name, False otherwise.""" @@ -487,26 +461,26 @@ class Enum(metaclass=EnumMeta): def __hash__(self): return hash(self._name_) # _RouteClassAttributeToGetattr is used to provide access to the `name` # and `value` properties of enum members while keeping some measure of # protection from modification, while still allowing for an enumeration # to have members named `name` and `value`. This works because enumeration # members are not set directly on the enum class -- __getattr__ is # used to look them up. - @_RouteClassAttributeToGetattr + @VirtualAttribute def name(self): """The name of the Enum member.""" return self._name_ - @_RouteClassAttributeToGetattr + @VirtualAttribute def value(self): """The value of the Enum member.""" return self._value_ class IntEnum(int, Enum): """Enum where members are also (and must be) ints""" def unique(enumeration): diff -r 95b3efe3d7b7 Lib/inspect.py --- a/Lib/inspect.py Mon Sep 16 11:03:59 2013 +0300 +++ b/Lib/inspect.py Mon Sep 16 07:26:16 2013 -0700 @@ -260,35 +260,47 @@ def isabstract(object): return bool(isinstance(object, type) and object.__flags__ & TPFLAGS_IS_ABSTRACT) def getmembers(object, predicate=None): """Return all members of an object as (name, value) pairs sorted by name. Optionally, only return members that satisfy a given predicate.""" if isclass(object): mro = (object,) + getmro(object) else: mro = () results = [] - for key in dir(object): + processed = set() + names = dir(object) + # add any virtual attributes to the list of names + # this may result in duplicate entries if, for example, a virtual + # attribute with the same name as a member property exists + for base in object.__bases__: + for k, v in base.__dict__.items(): + if isinstance(v, types.VirtualAttribute): + names.append(k) + for key in names: # First try to get the value via __dict__. Some descriptors don't # like calling their __get__ (see bug #1785). for base in mro: - if key in base.__dict__: + if key in base.__dict__ and key not in processed: + # handle the normal case first; if duplicate entries exist + # they will be handled second value = base.__dict__[key] break else: try: value = getattr(object, key) except AttributeError: continue if not predicate or predicate(value): results.append((key, value)) - results.sort() + processed.add(key) + results.sort(key=lambda pair: pair[0]) return results Attribute = namedtuple('Attribute', 'name kind defining_class object') def classify_class_attrs(cls): """Return list of attribute-descriptor tuples. For each name in dir(cls), the return list contains a 4-tuple with these elements: @@ -310,37 +322,48 @@ def classify_class_attrs(cls): info, like a __doc__ string. If one of the items in dir(cls) is stored in the metaclass it will now be discovered and not have None be listed as the class in which it was defined. """ mro = getmro(cls) metamro = getmro(type(cls)) # for attributes stored in the metaclass names = dir(cls) + # add any virtual attributes to the list of names + # this may result in duplicate entries if, for example, a virtual + # attribute with the same name as a member property exists + for base in cls.__bases__: + for k, v in base.__dict__.items(): + if isinstance(v, types.VirtualAttribute): + names.append(k) result = [] + processed = set() for name in names: # Get the object associated with the name, and where it was defined. # Getting an obj from the __dict__ sometimes reveals more than # using getattr. Static and class methods are dramatic examples. # Furthermore, some objects may raise an Exception when fetched with # getattr(). This is the case with some descriptors (bug #1785). # Thus, we only use getattr() as a last resort. homecls = None for base in (cls,) + mro + metamro: - if name in base.__dict__: + if name in base.__dict__ and name not in processed: + # handle the normal case first; if duplicate entries exist + # they will be handled second obj = base.__dict__[name] homecls = base break else: obj = getattr(cls, name) homecls = getattr(obj, "__objclass__", homecls) + processed.add(name) # Classify the object. if isinstance(obj, staticmethod): kind = "static method" elif isinstance(obj, classmethod): kind = "class method" elif isinstance(obj, property): kind = "property" elif ismethoddescriptor(obj): kind = "method" diff -r 95b3efe3d7b7 Lib/types.py --- a/Lib/types.py Mon Sep 16 11:03:59 2013 +0300 +++ b/Lib/types.py Mon Sep 16 07:26:16 2013 -0700 @@ -92,10 +92,54 @@ def _calculate_meta(meta, bases): continue if issubclass(base_meta, winner): winner = base_meta continue # else: raise TypeError("metaclass conflict: " "the metaclass of a derived class " "must be a (non-strict) subclass " "of the metaclasses of all its bases") return winner + +class VirtualAttribute: + """Route attribute access on a class to __getattr__. + + This is a descriptor, used to define attributes that act differently when + accessed through an instance and through a class. Instance access remains + normal, but access to an attribute through a class will be routed to the + class's __getattr__ method; this is done by raising AttributeError. + + This allows one to have properties active on an instance, and have virtual + attributes on the class with the same name (see Enum for an example). + + """ + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc or fget.__doc__ or self.__doc__ + + def __get__(self, instance, ownerclass=None): + if instance is None: + raise AttributeError() + elif self.fget is None: + raise AttributeError("unreadable attribute") + return self.fget(instance) + + def __set__(self, instance, value): + if self.fset is None: + raise AttributeError("can't set attribute") + self.fset(instance, value) + + def __delete__(self, instance): + if self.fdel is None: + raise AttributeError("can't delete attribute") + self.fdel(instance) + + def getter(self, fget): + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + return type(self)(self.fget, self.fset, fdel, self.__doc__)