mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-29 00:37:43 +08:00
124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
from abc import ABCMeta, abstractmethod
|
|
|
|
from six import with_metaclass, iteritems
|
|
|
|
# Consistent error to be thrown in various cases regarding overriding
|
|
# `final` attributes.
|
|
_type_error = TypeError('Cannot override final attribute')
|
|
|
|
|
|
def bases_mro(bases):
|
|
"""
|
|
Yield classes in the order that methods should be looked up from the
|
|
base classes of an object.
|
|
"""
|
|
for base in bases:
|
|
for class_ in base.__mro__:
|
|
yield class_
|
|
|
|
|
|
def is_final(name, mro):
|
|
"""
|
|
Checks if `name` is a `final` object in the given `mro`.
|
|
We need to check the mro because we need to directly go into the __dict__
|
|
of the classes. Because `final` objects are descriptor, we need to grab
|
|
them _BEFORE_ the `__call__` is invoked.
|
|
"""
|
|
return any(isinstance(getattr(c, '__dict__', {}).get(name), final)
|
|
for c in bases_mro(mro))
|
|
|
|
|
|
class FinalMeta(type):
|
|
"""A metaclass template for classes the want to prevent subclassess from
|
|
overriding a some methods or attributes.
|
|
"""
|
|
def __new__(mcls, name, bases, dict_):
|
|
for k, v in iteritems(dict_):
|
|
if is_final(k, bases):
|
|
raise _type_error
|
|
|
|
setattr_ = dict_.get('__setattr__')
|
|
if setattr_ is None:
|
|
# No `__setattr__` was explicitly defined, look up the super
|
|
# class's. `bases[0]` will have a `__setattr__` because
|
|
# `object` does so we don't need to worry about the mro.
|
|
setattr_ = bases[0].__setattr__
|
|
|
|
if not is_final('__setattr__', bases) \
|
|
and not isinstance(setattr_, final):
|
|
# implicitly make the `__setattr__` a `final` object so that
|
|
# users cannot just avoid the descriptor protocol.
|
|
dict_['__setattr__'] = final(setattr_)
|
|
|
|
return super(FinalMeta, mcls).__new__(mcls, name, bases, dict_)
|
|
|
|
def __setattr__(self, name, value):
|
|
"""This stops the `final` attributes from being reassigned on the
|
|
class object.
|
|
"""
|
|
if is_final(name, self.__mro__):
|
|
raise _type_error
|
|
|
|
super(FinalMeta, self).__setattr__(name, value)
|
|
|
|
|
|
class final(with_metaclass(ABCMeta)):
|
|
"""
|
|
An attribute that cannot be overridden.
|
|
This is like the final modifier in Java.
|
|
|
|
Example usage:
|
|
>>> from six import with_metaclass
|
|
>>> class C(with_metaclass(FinalMeta, object)):
|
|
... @final
|
|
... def f(self):
|
|
... return 'value'
|
|
...
|
|
|
|
This constructs a class with final method `f`. This cannot be overridden
|
|
on the class object or on any instance. You cannot override this by
|
|
subclassing `C`; attempting to do so will raise a `TypeError` at class
|
|
construction time.
|
|
"""
|
|
def __new__(cls, attr):
|
|
# Decide if this is a method wrapper or an attribute wrapper.
|
|
# We are going to cache the `callable` check by creating a
|
|
# method or attribute wrapper.
|
|
if hasattr(attr, '__get__'):
|
|
return object.__new__(finaldescriptor)
|
|
else:
|
|
return object.__new__(finalvalue)
|
|
|
|
def __init__(self, attr):
|
|
self._attr = attr
|
|
|
|
def __set__(self, instance, value):
|
|
"""
|
|
`final` objects cannot be reassigned. This is the most import concept
|
|
about `final`s.
|
|
|
|
Unlike a `property` object, this will raise a `TypeError` when you
|
|
attempt to reassign it.
|
|
"""
|
|
raise _type_error
|
|
|
|
@abstractmethod
|
|
def __get__(self, instance, owner):
|
|
raise NotImplementedError('__get__')
|
|
|
|
|
|
class finalvalue(final):
|
|
"""
|
|
A wrapper for a non-descriptor attribute.
|
|
"""
|
|
def __get__(self, instance, owner):
|
|
return self._attr
|
|
|
|
|
|
class finaldescriptor(final):
|
|
"""
|
|
A final wrapper around a descriptor.
|
|
"""
|
|
def __get__(self, instance, owner):
|
|
return self._attr.__get__(instance, owner)
|