想象有這麼一個應用場景:你想編寫一個多邊形的基類,並在子類繼承它的時候,要驗證子類的邊數是否大於等於 3 。
在python3.6之前,通用的做法是通過編寫元類來驗證子類:
<code>class ValidatePolygon(type):
def __new__(meta, name, bases, class_dict):
if bases:
if class_dict['sides'] < 3:
raise ValueError('Polygons need 3+ sides')
return type.__new__(meta, name, bases, class_dict)
class Polygon(metaclass=ValidatePolygon):
sides = None
@classmethod
def interior_angles(cls):
return (cls.sides - 2) * 180
class Triangle(Polygon):
sides = 3
class Rectangle(Polygon):
sides = 4
class Nonagon(Polygon):
sides = 9
assert Triangle.interior_angles() == 180
assert Rectangle.interior_angles() == 360
assert Nonagon.interior_angles() == 1260/<code>
但是上面的寫法有兩個不好的地方:
- 四個字:難寫,難讀。
- 直接多繼承會出現問題,只有通過一定的繼承技巧才不出現問題。
python3.6之後,引入了 __init_subclass__,這個時候上面的寫法就可以變成:
<code>class BetterPolygon:
sides = None
def __init_subclass__(cls):
super().__init_subclass__()
if cls.sides < 3:
raise ValueError('Polygons need 3+ sides')
@classmethod
def interior_angles(cls):
return (cls.sides - 2) * 180
class Hexagon(BetterPolygon):
sides = 6
assert Hexagon.interior_angles() == 720/<code>
當需要多繼承的時候:
<code>class Ploygon:
sides = None
def __init_subclass__(cls):
super().__init_subclass__()
print(f'Ploygon for {cls}')
if cls.sides < 3:
raise ValueError('Ploygons need 3+ sides')
@classmethod
def interior_angles(cls):
return (cls.sides - 2) * 180
class Filled:
color = None
def __init_subclass__(cls):
super().__init_subclass__()
if cls.color not in ('red', 'green', 'blue'):
raise ValueError('Fills need a valid color for red, green and blue')
class RedTriangle(Filled, Ploygon):
color = 'red'
sides = 3/<code>
下面來驗證一下,當指定的邊數和填充顏色不合法的時候會出現什麼情況。
邊數:
<code>print('Before class')
class BlueLine(Filled, Polygon):
color = 'blue'
sides = 2
print('After class')
>>>
Before class
Traceback ...
ValueError: Polygons need 3+ sides/<code>
填充顏色:
<code>print('Before class')
class BeigeSquare(Filled, Polygon):
color = 'beige'
sides = 4
print('After class')
>>>
Before class
Traceback ...
ValueError: Fills need a valid color for red, green and blue/<code>
要點
- python3.6開始就可以通過 __init_subclass__ 驗證子類了。
- 在__init_subclass__中要調用super().__init_subclass__()
閱讀更多 不止於編程 的文章