何时在Python中编写类以及为什么它很重要
何时在Python中编写类以及为什么它很重要
当人们开始喜欢上Python优雅简洁的语法时,他们纠结的事情之一就是OOP(面向对象的编程)。与其说是类的语法问题,不如说是何时和何时不使用它们。如果你是这样,请继续阅读。
在这篇文章中,我将给你一些见解,让你对这个问题有清晰的认识。
类是非常有用和强大的,但你需要知道何时使用它们。这里有一些考虑。
- 你需要数据和变量保持在容器的状态
例如,如果你需要管理一堆学生和成绩,或者当你建立一个游戏,记录尝试分数等(挂机例子)。基本上,当你有数据和行为(=变量和方法)一起时,你会使用一个类。
- 大型项目--类有利于代码组织和重用性
我经常使用报告类的例子。你可以有一个共享属性的基类,如报告名称、位置和行。但是当你进入到具体的格式(xml、json、html)时,你可以在子类中覆盖一个 generate_report 方法。
或者想想车辆。
车辆包括马车、自行车、
机动车(摩托车、汽车、卡车、公共汽车)、
有轨电车(火车、有轨电车)、
水上交通工具(船舶、船只)、
两栖交通工具(螺旋推进的车辆、气垫船)、
飞机(飞机、直升机)和航天器。
当你看到这样的层次结构时,使用类会导致更好的代码组织,减少重复,以及可重复使用的代码。
如果你有数百个子类,而你需要做一个根本性的改变,这就变得特别强大。你可以在基类(父类)中做一个改变,所有的子类都会接受这个改变(保持DRY)
注意:虽然我喜欢继承,但组合通常是首选,因为它更灵活。当我谈到一个真实世界的用例时,我会再写一篇关于它的文章 ...
- 封装
你可以将内部与外部接口分开,隐藏实现细节。你可以更好地隔离或保护你的数据,只给我们的类的消费者某些访问权限(想想API设计)。
这就像驾驶汽车,不必了解机械原理。当我启动我的汽车时,我可以只用我知道的和应该操作的通用接口来操作:油门、刹车、离合器、方向盘等等。我不需要知道发动机是如何工作的。
你的类也可以对消费者隐藏这种复杂性,这使你的代码更容易理解(优雅)。
这样做的另一个关键好处是,所有相关的数据都被归为一组。
谈论person.name、person.age、person.height等等是很好的。想象一下,如果要把所有这些类似的数据放在不同的变量中,很快就会变得很乱,而且无法维护。
强制执行合同
我们在这里举了一个抽象基类(ABC)的例子,它让你强制派生类实现某些行为。通过对基类中的一个方法应用 abstractmethod 装饰器,你可以强迫子类实现这个方法。
附注:更好地理解 Python
在 Python 中,所有的东西都是一个对象。对类和对象的理解使你为使用Python的数据模型和完整的功能集做了更好的准备,这将导致更干净、更 "pythonic "的代码。
......Python 的数据模型,它描述了你可以用来使你自己的对象与最习以为常的语言特性发挥良好的 API。你可以把数据模型看成是对作为框架的 Python 的描述。它正式确定了语言本身的构件的接口,如序列、迭代器、函数、类、上下文管理器,等等。- 摘自《流利的 Python》
最后,很多重要的设计模式都来自于OOP,只要谷歌一下 "面向对象的设计模式",即使你每天不使用类,你也会读到很多有它们的代码。
(有趣的小故事:今天我们从标准库模块中提取了类。)
主要启示
如果你需要保持状态,类是很好的,因为它们将数据(变量)和作用于这些数据的行为(方法)容器化,并且在逻辑上应该被分组。
这使得代码更有条理(更干净),更容易重用。
用OOP并不总是首选的解决方案
综上所述,OOP并不总是最好的解决方案。这里有一些想法,什么时候应该避免使用类。
最直接的原因是如果你的类只有一个构造函数和一个方法。那么就使用函数吧
小的(一次性的)命令行脚本可能不需要类。
如果你能用一个上下文管理器或一个生成器来完成同样的工作,这可能会更干净,更 "Pythonic"
我希望这能帮助你决定何时使用类,何时不使用。
无论如何,你应该在你的武器库中拥有它们。
而且,通过我们的一些资源,没有更好的办法来了解它们。
请看我们的文章。如何编写一个Python类和如何编写一个Python子类。
不过不要花超过10-15分钟的时间。
>>> from cls import get_classes
>>> import csv, string
>>> get_classes(csv)
['Dialect', 'DictReader', 'DictWriter', 'Error', 'Sniffer', 'StringIO']
>>> get_classes(string)
['Formatter', 'Template']
#!python3
#boss_class.py is a script to demo Python Classes and Subclasses
class Boss(object):
def __init__(self, name, attitude, behaviour, face):
self.name = name
self.attitude = attitude
self.behaviour = behaviour
self.face = face
def get_attitude(self):
return self.attitude
def get_behaviour(self):
return self.behaviour
def get_face(self):
return self.face
class GoodBoss(Boss):
def __init__(self,
name,
attitude,
behaviour,
face):
super().__init__(name, attitude, behaviour, face)
def nurture_talent(self):
#A good boss nurtures talent making employees happy!
print("The employees feel all warm and fuzzy then put their talents to good use.")
def encourage(self):
#A good boss encourages their employees!
print("The team cheers, starts shouting awesome slogans then gets back to work.")
class BadBoss(Boss):
def __init__(self,
name,
attitude,
behaviour,
face):
super().__init__(name, attitude, behaviour, face)
def hoard_praise(self):
#A bad boss takes all the praise for him/herself
print("The employees feel cheated and start plotting {}'s demise while he stares at his own reflection.".format(self.name))
def yell(self):
#A bad boss yells! (Ain't nobody got time for that!)
print("Everyone stares while {} yells. Someone shouts, 'Won't somebody PLEASE think of the children!'".format(self.name))
print("{} storms off, everyone comforts the victim and one person offers to arrange an 'accident' for {}.".format(self.name, self.name))
代码块
最好的学习方法是真正的编码!
所以今天就开始我们的OOP学习之路。所以,今天就开始我们的OOP学习之路,开始编写类/OOP代码。
一旦你掌握了基础知识,请阅读我关于Python的魔法/特殊方法的文章。用Dunder(魔法、特殊)方法丰富你的Python类
正如我们之前所说,游戏是很好的OOP练习。试着创造一个纸牌游戏(并在这里提交)。
2020年4月19日在 "技巧"重构机会将提升你的代码质量
2022年1月在 "最佳实践 "中在Python中编写更好的函数的10个技巧
超越基础的OOP
用属性进行封装、计算和重构2017年5月31日在 "代码挑战 "中
在Python中编写更好的函数的10个技巧
作者:Bob Belderbos 于2022年1月19日
函数是你在Python中的主要构建块。它们可以说是非常简单的,但又是非常深刻的。
从一开始,使用函数就有一些主要的好处。
函数使你的代码更加模块化,可重复使用,并且是DRY(不要重复自己)。
这将使你的代码更容易测试!
函数通过范围界定更好地隔离了功能(根据Python的禅宗:命名空间是一个了不起的想法)。
它们通过文档字符串内置了文档。
它们与你有时看到的一切都需要成为一个类的偏见作斗争,这当然是不正确的,特别是在开始做任何事情的时候。
最后,它们的流程很容易推理。1)接收东西(输入)->2)做东西(转换)->3)返回东西(输出)。
因此,在你的代码中使用更多的函数并不难,而且我相信你们中的大多数人已经这样做了(这就是为什么我在这篇文章中不涉及语法,尽管我可能会写一篇后续的文章)。
那么,让我们来看看是什么让函数变得更好?这里有10个提示。
每个函数名称都是一个自我记录代码的机会。你可以使用由下划线分隔的各种词语(按照PEP8的惯例)。拥有多个词并且不把类型放在其中(例如id_list),可以大大增加你的代码的可读性。
根据单一责任原则,一个函数应该只做一件事。这使得它们更容易被测试和重用。很难定义一个长度限制,但超过15行应该让你暂停并反思。
函数通常会变长,因为它们做了太多的事情。在这种情况下,你可以在一个或多个新函数中抽象出行为,并在原函数中调用这些函数。同样,这将使你的代码更容易测试,并给你更多的机会来命名每一块。
保持函数的接口(也就是进入函数的参数)很小。如果你定义的函数有类似的参数,你能不能把这些参数归为一个对象(例如,一个命名的图元或数据类)?
另外,在这一点上,一个类可能更合适。通常我会问自己是否需要保持某种状态?
同时要少用任意的args/关键字args,它们允许任何数量的参数被传递到你的函数中,这可能会妨碍函数的可读性和维护,因为它降低了接口的严格性(少了边界)。
尽早检查输入参数,如果它们不符合要求就引发异常,这将限制你的嵌套水平(Zen:扁平的比嵌套的好)。
这也为函数的调用者提供了一个明确的契约("这就是我们要玩的游戏")。关于异常处理的更多信息,请查看这篇文章。
一个简单的胜利是在靠近你使用它们的地方定义变量。减少滚动,增加可读性。
Python 3 允许仅有位置的参数和仅有关键字的参数。特别是后者可以使你的代码更具可读性,我已经在FastAPI和SQLModel中看到了它的应用,例如。
添加类型提示可以极大地提高你的代码的可读性,并允许工具更早地捕获错误。如果你是新手,可以从这里开始。
使返回类型一致。例如,当返回一个序列时,在 "0项 "的情况下,返回一个空列表而不是None(这也是 "假的")。类型提示可以强制实现这一点。
总结一下,这里还有两个与要避免的常见陷阱有关的提示。
首先,不要在你的函数中使用 "global",因为它允许你访问(改变)外部范围内的变量。函数应该是 "纯 "的,意味着它们没有 "副作用"。改变外部作用域中的东西就违反了这一点,并可能导致隐蔽的错误。
其次,参数被评估一次,所以使用一个可变的默认参数(例如一个列表或一个dict),你可能会惊讶地发现同一个数据结构将在随后的调用中被操作。
这往往不是你想要的,所以建议使用None,并在函数中建立一个新的列表,在每次调用时创建一个新的副本。这里有一个代码例子,可以更清楚地说明这一点。
我希望这能帮助你写出更好的函数。如果您有任何反馈或想进一步讨论这个问题,请加入我们的Slack社区,在#codequality频道中展开对话。
如果你想得到一些专门的指导,建立你的梦想应用程序的端到端(MVP准备),我们通过我们的辅导帮助人们达到这个目标。
除此以外,请保持冷静,用Python编程 🙂
SHAP(Shapley Additive exPlanations) 使用来自博弈论及其相关扩展的经典 Shapley value将最佳信用分配与局部解释联系起来,是一种基于游戏理论上最优的 Shapley value来解释个体预测的方法。
从博弈论的角度,把数据集中的每一个特征变量当成一个玩家,用该数据集去训练模型得到预测的结果,可以看成众多玩家合作完成一个项目的收益。Shapley value通过考虑各个玩家做出的贡献,来公平的分配合作的收益。
Python 项目制式学习
任务一:检查信用卡的有效期
# luhn algorithm
class CreditCard:
def __init__(self, card_no):
self.card_no = card_no
@property
def company(self):
comp = None
if str(self.card_no).startswith('4'):
comp = 'Visa Card'
elif str(self.card_no).startswith(('50', '67', '58', '63',)):
comp = 'Maestro Card'
elif str(self.card_no).startswith('5'):
comp = 'Master Card'
elif str(self.card_no).startswith('37'):
comp = 'American Express Card'
elif str(self.card_no).startswith('62'):
comp = 'Unionpay Card'
elif str(self.card_no).startswith('6'):
comp = 'Discover Card'
elif str(self.card_no).startswith('35'):
comp = 'JCB Card'
elif str(self.card_no).startswith('7'):
comp = 'Gasoline Card'
return 'Company : ' + comp
def first_check(self):
if 13 <= len(self.card_no) <= 19:
message = "First check : Valid in terms of length."
else:
message = "First check : Check Card number once again it must be of 13 or 16 digits long."
return message
def validate(self):
# double every second digit from right to left
sum_ = 0
crd_no = self.card_no[::-1]
for i in range(len(crd_no)):
if i % 2 == 1:
double_it = int(crd_no[i]) * 2
if len(str(double_it)) == 2:
sum_ += sum([eval(i) for i in str(double_it)])
else:
sum_ += double_it
else:
sum_ += int(crd_no[i])
if sum_ % 10 == 0:
response = "Valid Card"
else:
response = 'Invalid Card'
return response
@property
def checksum(self):
return '#CHECKSUM# : ' + self.card_no[-1]
@classmethod
def set_card(cls, card_to_check):
return cls(card_to_check)
card_number = input()
card = CreditCard.set_card(card_number)
print(card.company)
print('Card : ', card.card_no)
print(card.first_check())
print(card.checksum)
print(card.validate())
任务2:存钱计划
描述:
我需要节省一些钱来购买礼物。存钱计划是:
第一周(W0)星期天什么都不存,星期一存1,星期二存2...
第二周(W1) 周一存2个......周六存7个
以此类推,根据下表,日子从0到6编号。
你能告诉我在星期六晚上,在我存了12个钱之后,我将有多少钱来买礼物吗?
你的函数finance(6)应该返回168,这是表格中的存款之和)
一年按365天计,结束时共有多少钱?
def finance(n):
# your code
arr = list(range(7))
w = [sum(arr[i:])+i*len(arr[i:]) for i in arr]
return sum(w)*(n//7) + sum(w[:n%7])
n = 365
print(finance(n))
8757
任务三:200多种语言的日期 dateparser
import dateparser
date = "2021/11/1"
date = "Nov.1,2021"
date = "2021年11月7日"
#date = "1 de febrero de 2020" #西班牙语
date = "1er novembre 2021" #法语
date = "7 novembre 2021" #意大利语
date = "7 de noviembre de 2021" #西班牙语
date = "7 ноября 2021 года" #俄语
date = "1970, 6, 5"
print(dateparser.parse(date))
任务四 游戏
"""
Pygame base template for opening a window
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
-------------------------------------------------
Author for the Brickout game is Christian Bender
That includes the classes Ball, Paddle, Brick, and BrickWall.
"""
import random
#using pygame python GUI
import pygame
# Define Four Colours
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
pygame.init()
# Setting the width and height of the screen [width, height]
size = (700, 500)
screen = pygame.display.set_mode(size)
"""
This is a simple Ball class for respresenting a ball
in the game.
"""
class Ball(object):
def __init__(self, screen, radius, x, y):
self.__screen = screen
self._radius = radius
self._xLoc = x
self._yLoc = y
self.__xVel = 7
self.__yVel = 2
w, h = pygame.display.get_surface().get_size()
self.__width = w
self.__height = h
def getXVel(self):
return self.__xVel
def getYVel(self):
return self.__yVel
def draw(self):
"""
draws the ball onto screen.
"""
pygame.draw.circle(screen, (255, 0, 0), (self._xLoc, self._yLoc), self._radius)
def update(self, paddle, brickwall):
"""
moves the ball at the screen.
contains some collision detection.
"""
self._xLoc += self.__xVel
self._yLoc += self.__yVel
# left screen wall bounce
if self._xLoc <= self._radius:
self.__xVel *= -1
# right screen wall bounce
elif self._xLoc >= self.__width - self._radius:
self.__xVel *= -1
# top wall bounce
if self._yLoc <= self._radius:
self.__yVel *= -1
# bottom drop out
elif self._yLoc >= self.__width - self._radius:
return True
# for bouncing off the bricks.
if brickwall.collide(self):
self.__yVel *= -1
# collision detection between ball and paddle
paddleY = paddle._yLoc
paddleW = paddle._width
paddleH = paddle._height
paddleX = paddle._xLoc
ballX = self._xLoc
ballY = self._yLoc
if ((ballX + self._radius) >= paddleX and ballX <= (paddleX + paddleW)) \
and ((ballY + self._radius) >= paddleY and ballY <= (paddleY + paddleH)):
self.__yVel *= -1
return False
"""
Simple class for representing a paddle
"""
class Paddle(object):
def __init__(self, screen, width, height, x, y):
self.__screen = screen
self._width = width
self._height = height
self._xLoc = x
self._yLoc = y
w, h = pygame.display.get_surface().get_size()
self.__W = w
self.__H = h
def draw(self):
"""
draws the paddle onto screen.
"""
pygame.draw.rect(screen, (0, 0, 0), (self._xLoc, self._yLoc, self._width, self._height), 0)
def update(self):
"""
moves the paddle at the screen via mouse
"""
x, y = pygame.mouse.get_pos()
if x >= 0 and x <= (self.__W - self._width):
self._xLoc = x
"""
This class represents a simple Brick class.
For representing bricks onto screen.
"""
class Brick(pygame.sprite.Sprite):
def __init__(self, screen, width, height, x, y):
self.__screen = screen
self._width = width
self._height = height
self._xLoc = x
self._yLoc = y
w, h = pygame.display.get_surface().get_size()
self.__W = w
self.__H = h
self.__isInGroup = False
def draw(self):
"""
draws the brick onto screen.
color: rgb(56, 177, 237)
"""
pygame.draw.rect(screen, (56, 177, 237), (self._xLoc, self._yLoc, self._width, self._height), 0)
def add(self, group):
"""
adds this brick to a given group.
"""
group.add(self)
self.__isInGroup = True
def remove(self, group):
"""
removes this brick from the given group.
"""
group.remove(self)
self.__isInGroup = False
def alive(self):
"""
returns true when this brick belongs to the brick wall.
otherwise false
"""
return self.__isInGroup
def collide(self, ball):
"""
collision detection between ball and this brick
"""
brickX = self._xLoc
brickY = self._yLoc
brickW = self._width
brickH = self._height
ballX = ball._xLoc
ballY = ball._yLoc
ballXVel = ball.getXVel()
ballYVel = ball.getYVel()
if ((ballX + ball._radius) >= brickX and (ballX + ball._radius) <= (brickX + brickW)) \
and ((ballY - ball._radius) >= brickY and (ballY - ball._radius) \
<= (brickY + brickH)):
return True
else:
return False
"""
This is a simple class for representing a
brick wall.
"""
class BrickWall(pygame.sprite.Group):
def __init__(self, screen, x, y, width, height):
self.__screen = screen
self._x = x
self._y = y
self._width = width
self._height = height
self._bricks = []
X = x
Y = y
for i in range(3):
for j in range(4):
self._bricks.append(Brick(screen, width, height, X, Y))
X += width + (width / 7.0)
Y += height + (height / 7.0)
X = x
def add(self, brick):
"""
adds a brick to this BrickWall (group)
"""
self._bricks.append(brick)
def remove(self, brick):
"""
removes a brick from this BrickWall (group)
"""
self._bricks.remove(brick)
def draw(self):
"""
draws all bricks onto screen.
"""
for brick in self._bricks:
if brick != None:
brick.draw()
def update(self, ball):
"""
checks collision between ball and bricks.
"""
for i in range(len(self._bricks)):
if ((self._bricks[i] != None) and self._bricks[i].collide(ball)):
self._bricks[i] = None
# removes the None-elements from the brick list.
for brick in self._bricks:
if brick == None:
self._bricks.remove(brick)
def hasWin(self):
"""
Has player win the game?
"""
return len(self._bricks) == 0
def collide(self, ball):
"""
check collisions between the ball and
any of the bricks.
"""
for brick in self._bricks:
if brick.collide(ball):
return True
return False
# The game objects ball, paddle and brick wall
ball = Ball(screen, 25, random.randint(1, 700), 250)
paddle = Paddle(screen, 100, 20, 250, 450)
brickWall = BrickWall(screen, 25, 25, 150, 50)
isGameOver = False # determines whether game is lose
gameStatus = True # game is still running
score = 0 # score for the game.
pygame.display.set_caption("Brickout-game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# for displaying text in the game
pygame.font.init() # you have to call this at the start,
# if you want to use this module.
# message for game over
mgGameOver = pygame.font.SysFont('Comic Sans MS', 40)
# message for winning the game.
mgWin = pygame.font.SysFont('Comic Sans MS', 40)
# message for score
mgScore = pygame.font.SysFont('Comic Sans MS', 40)
textsurfaceGameOver = mgGameOver.render('Game Over!', False, (0, 0, 0))
textsurfaceWin = mgWin.render("You win!", False, (0, 0, 0))
textsurfaceScore = mgScore.render("score: " + str(score), False, (0, 0, 0))
# -------- Main Program Loop -----------
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
# --- Screen-clearing code goes here
# Here, we clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
# If you want a background image, replace this clear with blit'ing the
# background image.
screen.fill(WHITE)
# --- Drawing code should go here
"""
Because I use OOP in the game logic and the drawing code,
are both in the same section.
"""
if gameStatus:
# first draws ball for appropriate displaying the score.
brickWall.draw()
# for counting and displaying the score
if brickWall.collide(ball):
score += 10
textsurfaceScore = mgScore.render("score: " + str(score), False, (0, 0, 0))
screen.blit(textsurfaceScore, (300, 0))
# after scoring. because hit bricks are removed in the update-method
brickWall.update(ball)
paddle.draw()
paddle.update()
if ball.update(paddle, brickWall):
isGameOver = True
gameStatus = False
if brickWall.hasWin():
gameStatus = False
ball.draw()
else: # game isn't running.
if isGameOver: # player lose
screen.blit(textsurfaceGameOver, (0, 0))
textsurfaceScore = mgScore.render("score: " + str(score), False, (0, 0, 0))
screen.blit(textsurfaceScore, (300, 0))
elif brickWall.hasWin(): # player win
screen.blit(textsurfaceWin, (0, 0))
textsurfaceScore = mgScore.render("score: " + str(score), False, (0, 0, 0))
screen.blit(textsurfaceScore, (300, 0))
# --- Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(60)
# Close the window and quit.
pygame.quit()
反应要快