Hide keyboard shortcuts

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 

3 

4""" 

5=============================================================================== 

6 

7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com). 

8 

9 This file is part of cardinal_pythonlib. 

10 

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 

14 

15 https://www.apache.org/licenses/LICENSE-2.0 

16 

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. 

22 

23=============================================================================== 

24 

25**Functions to help work with Python classes.** 

26 

27""" 

28 

29from typing import Generator, List, Type, TypeVar 

30 

31 

32# ============================================================================= 

33# Does a derived class implement a method? 

34# ============================================================================= 

35 

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 

42 

43In Python 2, you can do this: 

44 return derived_method.__func__ != base_method.__func__ 

45In Python 3.4: 

46 ... 

47 

48class Base(object): 

49 def one(): 

50 print("base one") 

51 def two(): 

52 print("base two") 

53 

54 

55class Derived(Base): 

56 def two(): 

57 print("derived two") 

58 

59 

60Derived.two.__dir__() # not all versions of Python 

61 

62 

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 

66 

67""" 

68 

69T1 = TypeVar('T1') 

70T2 = TypeVar('T2') 

71 

72 

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)? 

79 

80 Args: 

81 derived: a derived class 

82 base: a base class 

83 method_name: the name of a method 

84 

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 

88 

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. 

92 

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 

103 

104 

105# ============================================================================= 

106# Subclasses 

107# ============================================================================= 

108# https://stackoverflow.com/questions/3862310/how-can-i-find-all-subclasses-of-a-class-given-its-name # noqa 

109 

110def gen_all_subclasses(cls: Type) -> Generator[Type, None, None]: 

111 """ 

112 Generates all subclasses of a class. 

113 

114 Args: 

115 cls: a class 

116 

117 Yields: 

118 all subclasses 

119 

120 """ 

121 

122 for s1 in cls.__subclasses__(): 

123 yield s1 

124 for s2 in gen_all_subclasses(s1): 

125 yield s2 

126 

127 

128def all_subclasses(cls: Type) -> List[Type]: 

129 """ 

130 Returns all subclasses of a class. 

131 

132 Args: 

133 cls: a class 

134 

135 Returns: 

136 a list of all subclasses 

137 

138 """ 

139 return list(gen_all_subclasses(cls)) 

140 

141 

142# ============================================================================= 

143# Class properties 

144# ============================================================================= 

145 

146class ClassProperty(property): 

147 """ 

148 One way to mark a function as a class property (logically, a combination of 

149 ``@classmethod`` and ``@property``). 

150 

151 See 

152 https://stackoverflow.com/questions/128573/using-property-on-classmethods. 

153 

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)() 

162 

163 

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``). 

169 

170 See 

171 https://stackoverflow.com/questions/128573/using-property-on-classmethods 

172 """ 

173 def __init__(self, fget): 

174 self.fget = fget 

175 

176 def __get__(self, owner_self, owner_cls): 

177 return self.fget(owner_cls)