# Bad classCar: car_make: str car_model: str car_color: str # Good classCar: make: str model: str color: str
多使用default arguments
1 2 3 4 5 6 7 8 9 10 11 12
import hashlib
# Bad defcreate_micro_brewery(name): name = "Hipster Brew Co."if name isNoneelse name slug = hashlib.sha1(name.encode()).hexdigest() # etc.
# Good defcreate_micro_brewery(name: str = "Hipster Brew Co."): slug = hashlib.sha1(name.encode()).hexdigest() # etc.
Funcitons
一個function只做一件事
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
from typing import Generator, Iterator
classClient: active: bool
defemail(client: Client): pass
defactive_clients(clients: Iterator[Client]) -> Generator[Client, None, None]: """Only active clients""" return (client for client in clients if client.active) # 使用 generator expression
# 一個function只做一件事,這樣可以讓程式碼更容易閱讀 defemail_client(clients: Iterator[Client]) -> None: """Send an email to a given list of clients. """ for client in active_clients(clients): email(client)
# Bad defcreate_menu(title, body, button_text, cancellable): pass
# Good # - 使用dataclass可以讓class更簡潔,不需要定義__init__和__repr__還有__eq__ # - 使用astuple將class的屬性轉換成tuple並且unpack將tuple的值取出 from dataclasses import astuple, dataclass
# 使用@dataclass可以讓class更簡潔,不需要定義__init__和__repr__還有__eq__ @dataclass classMenuConfig: """A configuration for the Menu. Attributes: title: The title of the Menu. body: The body of the Menu. button_text: The text for the button label. cancellable: Can it be cancelled? """ title: str body: str button_text: str cancellable: bool = False
create_menu( MenuConfig( title="My delicious menu", body="A description of the various items on the menu", button_text="Order now!" ) ) # ...
config = MenuConfig() config.title = "My delicious menu" config.body = "A description of the various items on the menu" config.button_text = "Order now!" # The instance attribute overrides the default class attribute. config.cancellable = True
# This is a module-level name. # It's good practice to define these as immutable values, such as a string. # However... fullname = "Ryan McDermott"
defsplit_into_first_and_last_name() -> None: # The use of the global keyword here is changing the meaning of the # the following line. This function is now mutating the module-level # state and introducing a side-effect! global fullname fullname = fullname.split()
split_into_first_and_last_name()
# MyPy will spot the problem, complaining about 'Incompatible types in # assignment: (expression has type "List[str]", variable has type "str")' print(fullname) # ["Ryan", "McDermott"]
# OK. It worked the first time, but what will happen if we call the # function again?
# The reason why we create instances of classes is to manage state! person = Person("Ryan McDermott") print(person.name) # => "Ryan McDermott" print(person.name_as_first_and_last) # => ["Ryan", "McDermott"]
Classes
Open/Closed Principle (OCP)
“Incoporate new features by extending the system, not by making modifications (to it)”, Uncle Bob
意思是當你要新增新的功能時,應該要透過繼承來擴展系統,而不是修改它
classView: """A simple view that returns plain text responses"""
defget(self, request) -> Response: """Handle a GET request and return a message in the response""" return Response( status=200, content_type='text/plain', body="Welcome to my web site" )
classTemplateView(View): """A view that returns HTML responses based on a template file."""
# 他重寫了整個get()方法,這樣就違反了OCP原則 # 原本的意圖指示更改行為,而不是替換它 defget(self, request) -> Response: """Handle a GET request and return an HTML document in the response""" withopen("index.html") as fd: return Response( status=200, content_type='text/html', body=fd.read() )
classView: """A simple view that returns plain text responses"""
content_type = "text/plain"
# Good: 把render_body()定義在父類,讓子類去實作 並且這樣就不用重新實作get()方法 甚至直接使用父類的get()方法 defrender_body(self) -> str: """Render the message body of the response""" return"Welcome to my web site"
defget(self, request) -> Response: """Handle a GET request and return a message in the response""" return Response( status=200, content_type=self.content_type, body=self.render_body() )
# 定義子類,繼承自父類 View classTemplateView(View): """A view that returns HTML responses based on a template file."""
from dataclasses import dataclass, field from typing import Protocol
@dataclass classResponse: """An HTTP response"""
status: int content_type: str body: str headers: dict = field(default_factory=dict)
classView: """A simple view that returns plain text responses"""
content_type = "text/plain"
defrender_body(self) -> str: """Render the message body of the response""" return"Welcome to my web site"
defget(self, request) -> Response: """Handle a GET request and return a message in the response""" return Response( status=200, content_type=self.content_type, body=self.render_body() )
classTemplateRenderMixin: """A mixin class for views that render HTML documents using a template file Not to be used by itself! """ template_file: str = ""
defrender_body(self) -> str: """Render the message body as HTML""" ifnot self.template_file: raise ValueError("The path to a template file must be given.")
withopen(self.template_file) as fd: return fd.read()
classContentLengthMixin: """A mixin class for views that injects a Content-Length header in the response Not to be used by itself! """
defget(self, request) -> Response: """Introspect and amend the response to inject the new header""" response = super().get(request) # 呼叫父類的get()方法把目前的response取出 response.headers['Content-Length'] = len(response.body) # 添加Content-Length header return response
# 讓這個子類繼承多個Mixin父類 classTemplateView(TemplateRenderMixin, ContentLengthMixin, View): """A view that returns HTML responses based on a template file."""
# Define the Abstract Class for a generic Greeter object classGreeter(metaclass=ABCMeta): """An object that can perform a greeting action."""
@staticmethod # 使用 @staticmethod 裝飾器定義的方法可以直接通過類名來調用,而無需實例化類。 @abstractmethod # 使用 @abstractmethod 裝飾器來定義抽象方法,這樣的方法必須在子類中實現 defgreet(name: str) -> None: """Display a greeting for the user with the given name"""
classFriendlyActor(Greeter): """An actor that greets the user with a friendly salutation"""
@staticmethod defgreet(name: str) -> None: """Greet a person by name""" print(f"Hello {name}!")
defwelcome_user(user_name: str, actor: Greeter): """Welcome a user with a given name using the provided actor""" actor.greet(user_name) # 調用父類的greet()方法
# 把這個拆出來,這樣子類就不需要實作save()方法 等到有需要的時候再繼承就好 classSaveable(DataCarrier): """Can save data to storage"""
@abc.abstractmethod defsave(self) -> None: ...
classPDFDocument(Loadable): """A PDF document"""
@property defdata(self) -> bytes: """The raw bytes of the PDF document""" ... # Code goes here - omitted for brevity
@classmethod defload(cls, name: str) -> None: """Load the file from the local filesystem""" ... # Code goes here - omitted for brevity
defview(request): """A web view that handles a GET request for a document""" requested_name = request.qs['name'] # We want to validate this! return PDFDocument.load(requested_name).data
# 定義一個Mixin,讓子類都擁有相同的輸出格式 classReprMixin: def__repr__(self): s = self.__class__.__name__ + '(' for k, v in self.__dict__.items(): ifnot k.startswith('_'): s += '{}={}, '.format(k, v) s = s.rstrip(', ') + ')'# 将最后一个逗号和空格换成括号 return s
# Person 繼承 MappingMixin 和 ReprMixin 一個類可以繼承多個父類 classPerson(MappingMixin, ReprMixin): def__init__(self, name, gender, age): self.name = name self.gender = gender self.age = age