Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/cardinal_pythonlib/classes.py : 72%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# cardinal_pythonlib/classes.py
4"""
5===============================================================================
7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com).
9 This file is part of cardinal_pythonlib.
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
15 https://www.apache.org/licenses/LICENSE-2.0
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
23===============================================================================
25**Functions to help work with Python classes.**
27"""
29from typing import Generator, List, Type, TypeVar
32# =============================================================================
33# Does a derived class implement a method?
34# =============================================================================
36"""
37https://stackoverflow.com/questions/1776994
38https://docs.python.org/3/library/inspect.html
39https://github.com/edoburu/django-fluent-contents/issues/43
40https://bytes.com/topic/python/answers/843424-python-2-6-3-0-determining-if-method-inherited # noqa
41https://docs.python.org/3/reference/datamodel.html
43In Python 2, you can do this:
44 return derived_method.__func__ != base_method.__func__
45In Python 3.4:
46 ...
48class Base(object):
49 def one():
50 print("base one")
51 def two():
52 print("base two")
55class Derived(Base):
56 def two():
57 print("derived two")
60Derived.two.__dir__() # not all versions of Python
63derived_class_implements_method(Derived, Base, 'one') # should be False
64derived_class_implements_method(Derived, Base, 'two') # should be True
65derived_class_implements_method(Derived, Base, 'three') # should be False
67"""
69T1 = TypeVar('T1')
70T2 = TypeVar('T2')
73def derived_class_implements_method(derived: Type[T1],
74 base: Type[T2],
75 method_name: str) -> bool:
76 """
77 Does a derived class implement a method (and not just inherit a base
78 class's version)?
80 Args:
81 derived: a derived class
82 base: a base class
83 method_name: the name of a method
85 Returns:
86 whether the derived class method is (a) present, and (b) different to
87 the base class's version of the same method
89 Note: if C derives from B derives from A, then a check on C versus A will
90 succeed if C implements the method, or if C inherits it from B but B has
91 re-implemented it compared to A.
93 """
94 derived_method = getattr(derived, method_name, None)
95 if derived_method is None:
96 return False
97 base_method = getattr(base, method_name, None)
98 # if six.PY2:
99 # return derived_method.__func__ != base_method.__func__
100 # else:
101 # return derived_method is not base_method
102 return derived_method is not base_method
105# =============================================================================
106# Subclasses
107# =============================================================================
108# https://stackoverflow.com/questions/3862310/how-can-i-find-all-subclasses-of-a-class-given-its-name # noqa
110def gen_all_subclasses(cls: Type) -> Generator[Type, None, None]:
111 """
112 Generates all subclasses of a class.
114 Args:
115 cls: a class
117 Yields:
118 all subclasses
120 """
122 for s1 in cls.__subclasses__():
123 yield s1
124 for s2 in gen_all_subclasses(s1):
125 yield s2
128def all_subclasses(cls: Type) -> List[Type]:
129 """
130 Returns all subclasses of a class.
132 Args:
133 cls: a class
135 Returns:
136 a list of all subclasses
138 """
139 return list(gen_all_subclasses(cls))
142# =============================================================================
143# Class properties
144# =============================================================================
146class ClassProperty(property):
147 """
148 One way to mark a function as a class property (logically, a combination of
149 ``@classmethod`` and ``@property``).
151 See
152 https://stackoverflow.com/questions/128573/using-property-on-classmethods.
154 However, in practice we use :class:`classproperty`, a slightly different
155 version.
156 """
157 # https://stackoverflow.com/questions/128573/using-property-on-classmethods
158 # noinspection PyMethodOverriding
159 def __get__(self, cls, owner):
160 # noinspection PyUnresolvedReferences
161 return self.fget.__get__(None, owner)()
164# noinspection PyPep8Naming
165class classproperty(object):
166 """
167 Decorator to mark a function as a class property (logically, a combination
168 of ``@classmethod`` and ``@property``).
170 See
171 https://stackoverflow.com/questions/128573/using-property-on-classmethods
172 """
173 def __init__(self, fget):
174 self.fget = fget
176 def __get__(self, owner_self, owner_cls):
177 return self.fget(owner_cls)