目錄
- 認證
- 登錄接口
- 路由
- 視圖類
- 認證類
- 權限
- 權限類
- 頻率
- 頻率類
- 視圖類
- 配置文件
- 自定義頻率類
- drf內置認證類、權限類、頻率類
- 認證類、權限類、頻率類源碼分析
- 認證類
- 權限類
- 頻率類
- 排序
- 過濾
- 全局異常處理
- 使用步驟
- 自動生成接口文檔
- RBAC介紹
- 補充1
- 回顧
- 補充2
- 補充3
認證
登錄認證《————某些接口必須要登錄以後才能訪問
登錄接口————》登錄成功返回隨機字符串————》攜帶隨機字符串【認證】通過再繼續訪問接口
APIView源碼————》三大認證實在視圖類的方法之前執行的
# 寫一個登錄接口
用户表 用户token表
前端傳入用户名密碼--視圖類--登錄方法--
--校驗用户名密碼--正確生成隨機字符串存入數據庫--把隨機字符串返回給前端
# 再隨便寫一個接口 --登錄後才能訪問
# 寫認證類
1.寫一個類 繼承BaseAuthentication
2.再類中重寫authenticate方法
3.在方法中做驗證 如果通過--返回兩個值
不通過--拋AuthenticationFailed異常
# 使用認證類
局部使用-->視圖類中
class BookView(APIView):
authentication_classes = [LoginAuth, ]
全局使用-->配置文件中
REST_FRAMEWORK = {
# 全局使用認證類
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
}
局部禁用
class BookView(APIView):
authentication_classes = []
登錄接口
# 模型類
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.IntegerField(choices=((1, '管理員'), (2, '普通用户'), (3, '遊客')), default=3)
class UserToken(models.Model):
token_code = models.CharField(max_length=32)
user = models.OneToOneField(to='User', on_delete=models.CASCADE)
路由
from .views import UserView, BookView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user', UserView, 'user')
router.register('book', BookView, 'book')
urlpatterns = [
]
urlpatterns+=router.urls
視圖類
class UserView(ViewSet):
authentication_classes = [] # 局部禁用 總不能登錄也要先登錄才能登錄吧
# 127.0.0.1:8080/user/user/login--->post
@action(methods=['post',], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 登錄成功-->生成隨機字符串-->uuid能夠生成一個隨機不重複字符串
token = str(uuid.uuid4())
# 存到UserToken表內 有就更新 沒有新建
# defaults=None,**kwargs 根據傳入的關鍵字參數去查 查到就用defaults給的更新 此處為user
UserToken.objects.update_or_create(user=user, defaults={'token_code': token})
return Response({'code': 100, 'msg': '登錄成功', 'token_code': token})
else:
return Response({'code': 101, 'msg': '用户名或密碼錯誤'})
認證類
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 做驗證 驗證用户是否登錄 把生成的token放到請求頭裏
if request.method in ['POST', 'PUT', 'DELETE']:
# 增刪改需要登錄才能操作
token = request.query_params.get('token')
# 拿到請求頭裏的token 去數據庫查
user_token_obj = UserToken.objects.filter(token_code=token).first()
if user_token_obj:
# 驗證通過返回兩個值————當前登錄用户,token
return user_token_obj.user, token
else:
raise AuthenticationFailed('登錄才可以操作哦')
權限
登錄成功後 但有的接口區分權限 有權限的人能才能操作
User表的user_type字段來區分權限
# 權限類的寫法
1.寫一個類 繼承BasePermission
2.重寫has_permission方法
3.在has_permission中進行權限的判斷
有權限返回True,沒權限返回False
返回的中文提示信息使用message字段標識
# 權限類的使用
1.局部使用--->視圖類中
class BookView(APIView):
permission_classes = [PermissionAuth, ]
2.全局使用--->配置文件
REST_FRAMEWORK = {
# 全局使用認證類
'DEFAULT_PERMISSION_CLASSES':['app01.auth.PermissionAuth',]
}
3.局部禁用
class BookView(APIView):
permission_classes = []
權限類
from rest_framework.permissions import BasePermission
class PermissionAuth(BasePermission):
message = '你沒有權限'
def has_permission(self, request, view):
if request.method == 'DELETE':
if request.user.user_type == 3:
print('有權限 刪除成功')
return True
else:
print('沒權限 刪除失敗')
self.message = '你是 %s 用户, 不能操作'%request.user.get_user_type_display()
return False
else:
print('沒進行刪除操作')
return True
頻率
# 某個接口,限制訪問頻率---->可以根據IP,用户id
# 頻率類的編寫
1.寫一個類 繼承SimpleRateThrottle
2.重寫get_cache_key方法
3.返回什麼 就以什麼做限制
4.寫一個類屬性
scope = 'xxx' # 這個屬性值用於配置
5.配置文件中配置:
REST_FRAMEWORK = {
# 頻率類中scope對應的值
'xxx':'3/m' # 數字/s m h d
}
6.局部使用和全局使用
-局部:視圖類中
class BookView(APIView):
throttle_classes = [IPThrottle, ]
-全局:配置文件
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
# 頻率類中scope對應的值
'xxx': '3/m' # 數字/s m h d
},
'DEFAULT_THROTTLE_CLASSES':['app01.throttling.IPThrottle', ]
}
頻率類
class IPThrottle(SimpleRateThrottle):
# 寫一個類屬性,字符串
scope = 'xxx'
def get_cache_key(self, request, view):
# return 什麼就以什麼做限制 返回ip/返回用户id
return request.META.get('REMOTE_ADDR')
# return request.user.id # 返回用户id
視圖類
class BookView(APIView):
throttle_classes = [IPThrottle, ]
def get(self, request):
return Response('ok')
def throttled(self, request, wait):
from rest_framework.exceptions import Throttled
class MyThrottled(Throttled):
default_detail = '超限制了'
extra_detail_singular = '還有 {wait} 秒.'
extra_detail_plural = '超出了 {wait} 秒.'
raise MyThrottled(wait)
配置文件
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
# 頻率類中scope對應的值
'3_min': '3/m', # 數字/s m h d
'anon':'4/h',
'user':'5/m'
},
'DEFAULT_THROTTLE_CLASSES':['app01.throttling.IPThrottle',]
}
自定義頻率類
# 邏輯:
1. 取出訪問者ip {192.168.1.12:[訪問時間3,訪問時間2,訪問時間1],192.168.1.12:[],192.168.1.14:[]}
2. 判斷ip在不在訪問字典內,不在則添加進去 並加入第一次訪問時間
3. 不是第一次 則根據ip取出訪問時間的列表 循環判斷列表不為空,並且當前時間減去列表的最後一個時間大於60s,把這種數據pop掉,這樣列表中只有60s以內的訪問時間,
4. 判斷列表<3 説明訪問次數不足三次 將當前時間插到列表第一個位置 返回True 表示通過
5. >=3 説明訪問超過三次 返回False驗證失敗
class MyThrottling():
visit_record = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
ip = request.META.get('REMOTE_ADDR') # 1.拿到ip
import time
ctime = time.time()
# 2.如果ip不在訪問字典裏 表示第一次訪問 將ip,和第一次訪問時間添加進去
if ip not in self.visit_record:
self.visit_record[ip] = [ctime, ]
# {192.168.1.12:[訪問時間3,訪問時間2,訪問時間1],192.168.1.12:[],192.168.1.14:[]}
return True
# 3.不是第一次 則 取出ip對應的時間列表
self.history = self.visit_record.get(ip) # 時間列表[時間3,時間2,時間1]
while self.history and ctime - self.history[-1] > 60: # 將現在時間和列表從後往前比 超過60s的去掉
self.history.pop()
# 4.列表<3 説明訪問不足三次 便把當前時間插到列表第一個 返回True
if len(self.history) < 3:
self.history.insert(0, ctime)
return True
else:
return False
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
drf內置認證類、權限類、頻率類
# 內置的認證---》跟咱們項目都不貼和,咱們不用,咱們自己根據自己的規則寫
-SessionAuthentication:之前老的session認證登錄方式用,後期都不用了
-BasicAuthentication :基本認證方式,咱們不用
-TokenAuthentication :使用token認證方式,有用,但是咱們也是自己寫的
# 內置的權限類
-IsAdminUser :校驗是不是auth的超級管理員權限
-IsAuthenticated:後面會用到,驗證用户是否登錄了,登錄了才有權限,沒登錄就沒有權限
-IsAuthenticatedOrReadOnly:知道有這個東西即可
# 內置的頻率類
-UserRateThrottle :限制登錄用户的頻率,需要配置配置文件
-AnonRateThrottle:登錄用户不限制,未登錄用户限制,需要配置配置文件
認證類、權限類、頻率類源碼分析
# 研究的第一個點:三大認證的執行順序
1.APIView--->dispatch--->三大認證
self.initial(request, *args, **kwargs) # 執行三大認證
'self'為視圖類的對象----如BookView的對象
2.研究initial方法:執行了三個方法
self.perform_authentication(request) # 認證
self.check_permissions(request) # 權限
self.check_throttles(request) # 頻率
# 相關源碼
class APIView(View):
def dispatch(self, request, *args, **kwargs):
......
try:
self.initial(request, *args, **kwargs) # 先認證再執行請求
if request.method.lower() in self.http_method_names:
...
def initial(self, request, *args, **kwargs):
......
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
認證類
# 為什麼寫了認證類,配置在視圖類上,就會走認證?
# 入口:認證類怎麼執行的---->self.perform_authentication(request)
1.self.perform_authentication源碼
def perform_authentication(self, request):
request.user
2.user是新的request類中的 user(屬性?方法?)
---->'Request'類內找到user 是一個方法 但加了@property
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate() # 一開始沒有_user 於是執行這句
return self._user
3.self._authenticate() ---->self為Request的對象---->執行的是Request的_authenticate方法
def _authenticate(self):
# authenticator配置的認證類的對象
for authenticator in self.authenticators: # 見第四點
# 你配置在視圖類上authentication_classes = [你寫的認證類,]---->[你寫的認證類1(),你寫的認證類2()]
try:
# 調用認證類對象的authenticate方法 傳了兩個參數!一個認證類自己 一個self(Request類的對象)
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException: # 拋的是AuthenticationFailed,捕獲的是APIException
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
# 如果返回了兩個值,第一個值給了request.user,第二個值給了request.auth
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
4.self.authenticators到底是啥?
# 你配置在視圖類上authentication_classes = [你寫的認證類,]---->[你寫的認證類1(),你寫的認證類2()]
Reqeust這個類實例化的時候,傳入的,如果不傳就是空元組
找Request的實例化----> dispatch中包裝了新的Request
request = self.initialize_request(request, *args, **kwargs)
authenticators=self.get_authenticators()
# APIView中的get_authenticators
return [auth() for auth in self.authentication_classes]
self.authentication_classes為配在視圖類上的認證類列表
點擊查看相關源碼
# 相關源碼
class APIView(View):
def perform_authentication(self, request):
request.user
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
def initialize_request(self, request, *args, **kwargs):
...
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), 這裏 !!!!!!!!!!
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
class Request:
def __init__(self,request...authenticators=None...):
self._request = request
self.authenticators = authenticators or ()
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate() # 一開始沒有_user 於是執行這句
return self._user
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
權限類
1. 執行權限類 self.check_permissions(request)
def check_permissions(self, request):
for permission in self.get_permissions(): # 配在視圖類上的權限類列表對象
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
2. 研究self.get_permissions()
def get_permissions(self):
return [permission() for permission in self.permission_classes]
頻率類
1. self.check_throttles(request)
def check_throttles(self, request):
throttle_durations = []
for throttle in self.get_throttles(): # 配在視圖類上頻率類列表 頻率類的對象
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
# 自定義頻率類一定要重寫allow_request, 返回True就是沒有頻率顯示,返回False就是被頻率限制了
# 聯想我們寫過的繼承SimpleRateThrottle的類
SimpleRateThrottle重寫了allow_request方法
class SimpleRateThrottle(BaseThrottle):
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate() # self.rate現在是 '3/m'
# 3 60
self.num_requests, self.duration = self.parse_rate(self.rate) # 從parse_rate方法獲取
def get_rate(self): #
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
def parse_rate(self, rate):
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
def allow_request(self, request, view):
# 只要配置文件配了 就有值 在init中
if self.rate is None:
return True
# 現在的唯一字符串 ip地址
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
# [時間2, 時間1]
self.history = self.cache.get(self.key, [])
self.now = self.timer()
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop() # 把和現在差60s的數據都剔除 self.history只剩60s內的訪問時間
if len(self.history) >= self.num_requests: # >=配置的數字 3
return self.throttle_failure() # return False
return self.throttle_success() # 把當前的時間插入 return True
排序
# 排序功能接口只針對於:獲取所有接口
# 繼承了GenericAPIView的視圖類,只要加入,兩個類屬性
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [OrderingFilter, ]
ordering_fields = ['price','id']
# 訪問的時候
http://127.0.0.1:8000/api/v1/books?ordering=price # 按price升序
http://127.0.0.1:8000/api/v1/books?ordering=-price # 按price降序
http://127.0.0.1:8000/api/v1/books?ordering=price,id # 先按價格升序排,價格一樣,再按id升序排
過濾
1. 內置的過濾使用:不能指定查詢哪個字段 '模糊查詢'
繼承了GenericAPIView的視圖類,只要加入,兩個類屬性
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [SearchFilter,]
search_fields=['name','price'] # 按name或price過濾
# 使用
http://127.0.0.1:8000/api/v1/books?search=關鍵字 # name like xx or price like xx
2. 第三方django-filter: 指定字段精確查詢
-安裝:pip3 install django-filter
繼承了GenericAPIView的視圖類,只要加入,兩個類屬性
from django_filters.rest_framework import DjangoFilterBackend
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [DjangoFilterBackend, ]
filter_fields = ['name', 'price']
# 使用
http://127.0.0.1:8000/api/v1/books?name=三國&price=11
3.自定義過濾器---->完成更多查詢操作
-寫一個類,繼承BaseFilterBackend
-重寫filter_queryset方法
-配置在視圖類中
from .throttling import FilterName
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [FilterName, ]
# 既有過濾又有排序
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [FilterName, OrderingFilter]
ordering_fields = ['price', ]
# 源碼分析,為什麼這麼配置就生效
-GenericAPIView的方法
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
全局異常處理
drf配置文件中 已經配置了 但不符合我們想要的要求
# drf的配置文件:
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
如果拋了異常 就會執行exception_handler函數
現在我們重寫一個函數 拋異常時 執行我們寫的函數
# drf默認的異常處理 只處理了drf自己的異常:
所有drf中拋的異常 都有detail,django的異常 拋出很長的xml數據
{
"detail": "it's wrong"
}
# 我們想要的
{
"code": 999,
"msg": "it's wrong"
}
使用步驟
1.寫一個函數
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
# 正常來講,在這裏需要記錄日誌---》如何在django中記錄日誌後面講
# 日誌記錄,越詳細越好:哪個用户(id,ip),在什麼時間,執行哪個視圖函數時報了錯,請求地址是什麼
print(context['view']) # 視圖類對象
print(context['request']) # 當前請求對象----->可以取出ip,用户id,當前時間,請求地址來
view = context['view']
request = context['request']
print('ip地址為: %s 的用户 訪問了:%s 視圖類, 報錯 請求地址為:%s' % (request.META.get('REMOTE_ADDR'), str(view), request.path))
response = exception_handler(exc, context)
if response: # 這是drf的異常 drf已處理 但不是我們想要的格式{'code': 100, 'msg': ''}
res = Response({'code': 999, 'msg': request.data.get('detail')})
else:
# res=Response({'code':998,'msg':'服務器錯誤,請聯繫系統管理員'})
res = Response({'code': 998, 'msg': str(exc)})
return res
2.把函數配置在配置文件中
REST_FRAMEWORK = {
# 自己寫的全局異常捕獲
'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler',
}
自動生成接口文檔
# 後端人員,寫好接口,提供接口文檔給前端用
# 如何編寫接口文檔
1. 使用word寫,md寫---->提交到git上
2.公司有接口文檔平台---->後端人員在文檔平台錄入數據---->公司自己開發,yapi(百度開源),第三方
3.自動生成接口文檔---->項目寫好了,一鍵生成接口文檔---->一鍵生成---->導出---->導入到yapi
# drf中自動生成接口文檔
-coreapi,swagger(更通用一些,go,java)
-安裝 pip3 install coreapi
-在項目中配置
-加入路由:
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站點頁面標題'))
]
-配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
-儘管寫接口,寫註釋,會自動生成
RBAC介紹
# RBAC
基於角色的訪問控制(Role-Based Access Control)
在RBAC中:
權限與角色相關聯 用户通過成為適當角色的成員而獲得角色的權限
這就極大地簡化了權限的管理。
這樣管理都是層級相互依賴的,權限賦予給角色,而把角色又賦予用户,這樣的權限設計很清楚,管理起來很方便
# 這種設計,在公司內部系統用的多,對外的系統基本不用
# 權限:真正的權限,比如發工資,招人,申請測試機
# 角色:(組,部門) 角色下有很多員工
# 用户:一個個的人
# 用户和角色關係是多對多,中間表
# 權限和角色關係是多對多,中間表
# 權限和用户的中間表
#前後端分離項目控制權限---》權限類中
#前後端混合
-django框架在公司裏用的很多,寫內部項目,肯定要用權限控制,用的就是rbac,自己實現一套,django內置了後台管理,自帶rbac(admin+auth)
-基於django的admin二次開發
-美化:xadmin(基本不用了,2.x以後django不兼容多,作者棄坑了)
-simpleui:
-django+drf+vue=djagnovueadmin 一款前後端分離的自帶rbac的後台管理框架
# django的admin基於rbac
-auth_user # 用户表
-auth_permission # 權限表
-auth_group # 組,角色表
-auth_user_groups # 用户和組中間表
-auth_group_permissions # 組和權限中間表
-auth_user_user_permissions # 用户和權限中間表
補充1
# external libraries
-Python 解釋器--內置模塊和包:os,sys,json 。。
-import os
-裝的第三模塊:site-packages中
-真正的位置:python解釋器安裝路徑下的site-package中
-有些第三方模塊一安裝---》釋放可執行文件---》一定要注意,釋放的位置釋放在環境變量裏
# 安裝第三方模塊
-pychram中圖形化界面裝-----》必須有pycharm---》換源
-pip install django==2.2.2 -i 國內源
-敲的pip 是哪個解釋器的pip 確認好
# 換源
-裝第三方模塊---》本質就是從遠端某個位置拉了一個包 xx.whl--->釋放到了python解釋器的site-package文件夾下,把可執行文件釋放到scripts文件夾下
-用官方源:在國外---》下載慢
-使用國內鏡像:豆瓣,阿里,清華
回顧
# http回顧
-http 請求:特點
-1 http基於tcp協議之上的協議---->tcp處於osi七層的傳輸層 http處於應用層
-2 基於請求響應----必須是客户端發起請求,服務端響應 不能服務端主動推送信息
-3 無狀態,無連接 cookie和session
-http協議有版本
-0.9 1.1 2.x:多路複用 3.x
-http分請求協議和響應協議
-請求協議
-請求首行: 請求方式,請求地址(get地址中數據),協議版本
-請求頭: key:value 客户端類型,客户端ip地址,請求編碼,cookie...
-請求體: 所謂的body體,posy,put請求真正攜帶的數據
-urlencoded: name=lqz&age=19&gender=male
-json : {"name":"lqz","age":19,"gender":"male"}
-form-data: 兩部分:數據部分,文件部分中間用 -----------分割
-響應協議:
-響應首行: 協議版本,響應狀態碼,響應狀態短語
-響應頭: 響應編碼格式,cookie...
-響應體: html,json,xml.....
# websocket協議 應用層協議
-服務端主動向客户端推送的情況,使用websocket
# http請求 輪詢
# http請求 長輪詢:發過去,等一會,再回來
# websocket 協議主動推送
補充2
python 動態強類型語言
go 靜態強類型語言
java 靜態強類型語言 ---隱式類型轉換
js 動態弱類型語言
# 動態:解釋性語言
# 強弱:
數據類型強:不同類型之間不允許直接運算
數據類型弱:不同類型之間不需要轉換可以直接運算
補充3
# 鴨子類型(面試重點)
-接口概念:規範子類的行為 你當成:父類,有一些方法 ---》Duck類,speak,run方法
-只要子類,繼承了Duck,我們就認為,這些子類,他們是同一類,都是鴨子Duck這個類
-python不推崇這個,推崇鴨子類型
-現在只要有一個類,中有speak和run 方法 ,這個類就是鴨子類
-abc裝飾器,裝飾父類中的方法,子類必須實現該方法,如果不實現,就拋異常---》正好違背了鴨子類型
-djagno中常用的方式,父類拋異常的方式,子類必須重寫該方法:
def authenticate(self, request):
raise NotImplementedError(".authenticate() must be overridden.")
# 函數
def add(a:int,b:int):->int
return a+b
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。