描述一个过程:”门铃响了,快递小哥tiger敲门,狗叫,jimmy开门“
这里面的 3 个对象:敲门的人,动物和开门的人,需要我们灵活设置。对应于每次敲门、动物叫声和开门的人的不同变化。
输入:敲门人姓名,动物名称,主人姓名
输出:门铃响声 -> 谁在敲门 -> 对应的动物叫声 -> 主人开门 -> 主人对来访者打招呼;
遵循松耦合的原则:
每个类的方法只做一件事情;
修改和添加新的对象避免影响到其他代码;
灵活且安全地实现追加新的类属性和方法;
仅为参考做法:
共 3 个类有专属的方法。
Animal会叫且叫声不同,
但Door和Person不会叫,门有门铃响的方法;主人能开门,但动物和门都不能自己开门;
家里没有猫和狗,或者有其他动物时,Animal里添加新的方法等;
class Door:
def bell(self):
print('叮咚叮咚')
class Animal:
def __int__(self,name):
self.name = name
def bark(self):
return print('汪汪汪')
def meow(self):
return print("喵喵喵")
def calling(self):
if self.name == 'Dog':
return self.bark()
if self.name == 'Cat':
return self.meow()
else:return "Mute"
class Person:
def __int__(self, name):
self.name = name
def knock(self):
return print(f'{self.name}'+'在敲门')
def open(self):
print(f"hello {self.name}") #stranger
第二步是整个过程定义为函数 knock_door
def knock_door(): #门铃响声的方法
front_door = Door()
Door.bell(front_door)
Courier = Person() # 来访者
Courier.name = 'tiger'
Person.knock(Courier)
pet = Animal() # 动物
pet.name = 'Dog'
Animal.calling(pet)
owner = Person # 开门人
owner.name = 'Jimmy'
Person.open(Courier)
knock_door()
输出结果:
叮咚叮咚
tiger在敲门
汪汪汪
hello tiger
主函数稍作修改,传入变量参数:
def knock_door(visitor,pet,homeowner):
front_door = Door()
Door.bell(front_door)
Courier = Person() # Courier
Courier.name = visitor
Person.knock(Courier)
pets = Animal() #'Dog'
pets.name = pet
Animal.calling(pets)
owner = Person
owner.name = homeowner
Person.open(Courier)
visitor,pet,homeowner = 'Ada','Rooster','爸爸'
增加一种动物是公鸡并且添加鸡叫
... ...
def calling(self):
if self.name == 'Dog':
return self.bark()
if self.name == 'Cat':
return self.meow()
if self.name == 'Rooster':
return self.crowing()
else:return print("Mute")
... ...
执行主函数输出结果:
叮咚叮咚
Ada在敲门
咯咯咯
hello Ada
说实话,这样定义类class
会不会让你感到更舒服?
对象Object:现实世界的对象有2个主要特征,状态和行为。
动物有状态(名称,颜色)和行为(叫,吃);
人类有状态(姓名、年龄)和行为(跑步、睡觉、开门、敲门);
汽车有状态(当前速度、当前档位)和行为(刹车、换档、提速);
对象在概念上与现实世界的对象相似,也由状态和相关行为组成。一个对象在字段中存储其状态,并通过方法公开其行为。
Class类:是一个 "模板"/"蓝图",用于创建对象。
基本上,类将由字段、静态字段、方法、静态方法和构造函数组成。
字段用于保存类的状态例如:动物的名称。
方法用于表示类的行为例如:动物的叫声。
构造函数用于创建一个新的类的实例。
实例:一个实例是代表一个对象的类的唯一副本。当一个新的类的实例被创建时,JVM将为该类实例分配一个内存空间。
实例属性:
实例属性是在任何类中定义的那些属性,并且需要借助类名本身创建的实例。
如果不创建类的实例,我们可能无法访问类中定义的这些属性。
即使我们尝试在不创建类实例的情况下访问这些属性,我们也可能会得到“undefined”作为输出,说明此特定属性未为用户尝试搜索的特定类定义。
揭开 Python 的神奇方法的神秘面纱
-何时使用类,私有属性和封装
-释放 dunder 方法的强大功能
收藏谷歌python风格指南:
https://google.github.io/styleguide/pyguide.html#316-naming
实例使用__add__
魔法来简化他们的Python代码。
在QuantumPay Solution,两名开发人员Ada和Biden被指派为一家著名的银行机构构建一个复杂的系统。
该系统的核心需要一种机制来处理复杂的金融交易,特别是货币金额的算术。
他们的代码库有一个表示货币值的类,具有货币和金额的属性,必须执行的频繁操作之一是将两个对象加在一起。他们最初的方法有点麻烦:
自定义方法Money
来添加金额并确保货币相同:
class Money:
def __init__(self, currency, amount):
self.currency = currency
self.amount = amount
def add_money_objects(money1, money2):
if money1.currency != money2.currency:
return "币种不一致,不能相加"
return Money(money1.currency, money1.amount + money2.amount)
测试用例A
Ada, Biden = Money('USD',120),Money('USD',80)
result = add_money_objects(Ada,Biden)
print(result.amount,result.currency)
200 USD
测试用例B
Ada, Biden = Money('RMB',120),Money('USD',80)
result = add_money_objects(Ada, Biden)
print(result.amount,result.currency)
币种不一致,不能相加
Ada是一位经验丰富的Python开发人员,他看到机会可以向团队中的新开发人员Biden介绍优化代码。
如果我告诉你我们可以添加两个对象,就像我们在Python中添加两个整数或浮点数一样:100 + 120?
我们可以让我们的类表现得像一个内置的数据类型。阿玛拉好奇地问道。卡登随后向她介绍了神奇的方法__add__
Ada解释说:“如果我们在类中定义一个方法,我们可以直接在对象上使用'+'运算符。
当我们使用“+”时,会自动调用这个神奇的方法。它使我们的代码更清晰、更易于理解。
解释👇重构类:Money
class Money:
def __init__(self, currency, amount):
self.currency = currency
self.amount = amount
def __add__(self, other):
if self.currency != other.currency:
return "币种不一致,不能相加"
return Money(self.currency, self.amount + other.amount)
测试用例
Limin = Money('rmb',100)
Eric = Money('USD',120)
Jason = Money('USD',120)
print(Limin + eric)
币种不一致,不能相加
print((Eric + Jason).amount)
240
更改后的Money
可以像内置 Python 数据类型一样快速地添加两个对象:
money1 + money2
有些小意外惊叹于Python的魔法方法如何使他们的代码更加优雅和直观,如何利用它们使您的代码更加“Pythonic”
类是 Python 中的一个基本概念,是实现面向对象编程原则的关键。
无论您是初学者还是经验丰富的 Python 主义者,了解如何有效地编写类对于构建健壮且可维护的程序至关重要。
探讨用 Python 编写优秀类的七个基本最佳实践。通过遵循这些最佳实践,您将能够创建结构良好、可重用且高效的类。
-
知道什么时候不使用类
一般来说:方法是属于一个类的函数,函数可以在代码的任何其他范围内,所以你可以声明所有方法都是函数,但并非所有函数都是方法:
以以下 python 为例:
class Door:
def open(self):
print 'hello stranger'
def knock_door():
a_door = Door()
Door.open(a_door)
knock_door()
给出的示例显示了一个名为“Door”的类,它有一个名为“open”的方法或操作,它被称为方法,因为它是在类中声明的。
下面还有另一部分带有“def”的代码,它定义了一个函数,它是一个函数,因为它没有在类中声明,这个函数调用我们在类中定义的方法,如您所见,最后函数被自己调用。
如您所见,您可以在任何地方调用函数,但是如果要调用方法,则必须传递与声明该方法的类类型相同的新对象Class.method(object)
,或者必须在对象中调用该方法 Method()
,至少在python中是这样。
将方法视为只有一个实体可以做的事情,因此,如果您有一个 Dog 类,那么仅在该类中有一个 bark 函数是有意义的,这将是一个方法;
如果您还有一个 Person 类,那么编写一个不属于任何类的函数“feed”可能是有意义的。
因为人类和狗都可以被喂食例如婴儿;您可以将其称为函数,因为它不属于任何特定的类。
如果我们不需要编写类,我们甚至不需要考虑所有其他最佳实践。
Python不是一种纯粹的面向对象的编程语言,我们也不会被迫为每个任务使用类。在许多情况下,最好使用函数而不是类。
如果任务很简单,我们不需要状态管理或封装,那么就不需要使用类。例如,如果我们需要计算一个数字的平方,一个函数就绰绰有余了。
对于如此简单的任务,使用类不会带来显着的好处,同时会引入不必要的复杂性。
# Using Function
def calculate_square(number):
return number ** 2
result = calculate_square(5)
print(result) # Output: 25
# Using Class
class Calculator:
def __init__(self, number):
self.number = number
def calculate_square(self):
return self.number ** 2
calculator = Calculator(5)
result = calculator.calculate_square()
print(result) # Output: 25
另一种情况是,事先知道创建一个类的多个实例时并没有用武之地。
这种情况下,仅使用带有函数的模块会简单得多。此外,使用后一种解决方案,我们可以避免实例化类的开销。
-
小而精的类减少依赖和交互
单一责任原则:一个类应该只有一个改变的理由。进行更改或修复问题变得职责清晰。
此外,具有明确职责的小类可以更好地组织代码并降低引入错误的可能性。
其次,小类减少了代码重复的同时又鼓励了代码的可重用性,我们可以轻松地在代码库的不同部分重用它们。
类越小,它的依赖和交互就越少。降低了对一个类的更改影响许多其他部分的可能性。
下面是一个 God 类的示例:
class OrderProcessor:
def __init__(self):
# Initialization code
def process_order(self, order):
# Code to process the order
# ...
def calculate_total(self, order):
# Code to calculate the total price of the order
# ...
def update_inventory(self, order):
# Code to update inventory based on the order
# ...
def send_confirmation_email(self, order):
# Code to send order confirmation email to the customer
# ...
def generate_invoice(self, order):
# Code to generate an invoice for the order
# ...
# ... more methods related to order processing
在此示例中,类具有多个职责,例如处理订单、计算总价、更新库存、发送确认电子邮件和生成发票。
OrderProcessor #处理订单
OrderValidator
InventoryManager
EmailSender
InvoiceGenerator
更好的解决方案是将其重构为具有不同职责的单独类。
class OrderProcessor:
def __init__(self, order_validator, inventory_manager, email_sender, invoice_generator):
self.order_validator = order_validator
self.inventory_manager = inventory_manager
self.email_sender = email_sender
self.invoice_generator = invoice_generator
def process_order(self, order):
if self.order_validator.validate(order):
self.inventory_manager.update_inventory(order)
self.email_sender.send_confirmation_email(order)
self.invoice_generator.generate_invoice(order)
class OrderValidator:
def validate(self, order):
# Code to validate the order
# ...
class InventoryManager:
def update_inventory(self, order):
# Code to update inventory based on the order
# ...
class EmailSender:
def send_confirmation_email(self, order):
# Code to send order confirmation email to the customer
# ...
class InvoiceGenerator:
def generate_invoice(self, order):
# Code to generate an invoice for the order
# ...
在重构版本中,类的职责已被提取到具有特定任务的单独类中。
类验证订单,
类处理库存更新,
类负责发送确认电子邮件,
类生成发票。
OrderProcessor
OrderValidator
InventoryManager
EmailSender
InvoiceGenerator
考虑一个出现新要求的假设情况:订单处理系统需要与第三方支付网关集成。
PaymentGateway支付网关在订单处理流程中需要额外的验证步骤和特定处理。
在 God 类版本中需要修改类,添加新的支付相关功能。但是,这引入了破坏现有代码的风险,并使类更加臃肿和复杂。
相反,使用单独类的重构版本,例如您可以轻松集成新的支付功能,而无需修改,只需为支付网关创建一个新类并将其作为依赖项注入到中即可。
这种方法使职责保持隔离,使代码库更易于维护,并允许在不影响现有功能的情况下更轻松地集成新功能。
-
__init__中的显式实例属性
Python 不会强迫我们在__init___
中定义所有实例属性。
实际上在创建实例后的后续操作中定义其他实例属性。
下面是一个示例:
class Car:
def __init__(self, brand):
self.brand = brand
def set_owner(self, owner):
self.owner = owner
def set_color(self, color):
self.color = color
在方法外部定义其他属性可能会使代码不太清晰且更难理解。
为什么在方法中定义所有实例属性是一种很好的做法,以下是一些原因:
可读性和代码组织:将所有属性定义放在方法中,为理解类的数据结构提供了一个清晰而集中的位置。我们只需查看方法即可轻松识别和理解正在初始化的属性。
初始化:在 Python 中方法专门设计用于初始化对象的状态。通过在此方法中定义属性,我们清楚地表明这些属性是必不可少的,应该在创建实例时设置。它可促进一致性,并有助于避免某些属性丢失或未初始化的情况。
代码可维护性:将属性定义放入其中可以更轻松地修改或扩展类。如果我们想添加或删除属性,则无需在整个类中查找分散的属性定义。所有更改都可以在一个位置进行
错误预防:在方法中定义属性有助于防止与属性相关的错误。如果在定义属性之前访问了该属性,Python 会引发异常。
通过在__init__
中初始化属性,可以确保它们从一开始就可用,并避免此类运行时错误AttributeError
建议用于最初无法设置的属性。这有助于避免在为这些属性分配值之前尝试访问这些属性时出现的潜在异常 NoneAttributeError
下面是上面示例的改进版本:__init__
class Car:
def __init__(self, brand):
self.brand = brand
self.owner = None # 车主 初始化添加属性
self.color = None # 颜色 初始化添加属性
def set_owner(self, owner):
self.owner = owner
def set_color(self, color):
self.color = color
-
有意义名称
类有意义的描述性名称。名称良好的类有助于传达代码的目的和职责,并使您和其他开发人员将来更容易使用和扩展系统。
不仅可以提高代码的可读性和理解,还可以促进代码重用。
如果为类指定有意义的名称,则其他开发人员更有可能在自己的代码中重用它们。这可以节省时间和精力,还有助于提高代码的整体质量。
此外,一个好的名字可以通过明确类应该做什么来帮助你避免错误。
例如,如果您有一个表示汽车的类,则应将其命名为Car
而不是Cat
重要的是,描述性和有意义的名称不仅对类很重要,而且对它们的属性和方法也很重要。
Google Python Style Guide 是此最佳实践的良好资源。请再次访问篇首推荐的风格指南链接。
-
正确使用 @property
而不是getter
和setter
,看起来更像pythonic。
使用属性修饰器定义属性后,可以使用点表示法访问和修改它,就好像它是常规属性一样。
但是在后台,属性是使用函数实现的,这些函数在访问属性或分配值时调用。
下面的示例显示如何使用 getter
和 setter
实现@property
#栗子之一
class Circle:
def __init__(self, radius):
self.__radius: float = radius
@property
def radius(self):
return self.__radius
@radius.setter
def radius(self, value: float):
if value > 0:
self.__radius = value
else:
raise ValueError("Radius must be a positive value.")
# 创建圆形的类的一个实例
my_circle = Circle(5)
# 访问半径 getter
print(my_circle.radius)
# Output: 5
# setter 修改半径
my_circle.radius = 7
print(my_circle.radius)
# Output: 7
# 不合理取值的引发错误
my_circle.radius = -2
# Raises a ValueError
属性有几个好处:
属性提供对属性访问和修改的控制和封装级别!
然而,过度使用属性会增加复杂性并使代码更难理解和维护。类的接口变得混乱,很难确定哪些属性是必需的或具有特殊行为。
如果属性不提供任何其他好处,例如验证、计算值或封装,应该考虑使用常规实例属性。
多数情况下,属性可以替换为实例属性。简化代码,使其更加简单。
#栗子之二
class Person:
def __init__(self, first_name):
self.__firstname = __firstname
# Getter function
@property
def first_name(self):
return self.__first_name
# Setter function
@first_name.setter
def first_name(self, value):
self.first_name = value
p = Person('John')
# Access the getter
print(p.first_name) # John
# Set a new value using the setter
p.first_name = 'Mary'
print(p.first_name) # Mary
如您所见,我们可以像访问普通属性一样访问first_name,但是在幕后,getter和setter方法在需要时被调用。
这种语法更加优雅和Pythonic。相比于明确调用 getter和setter方法,使用点表示法访问属性更加自然和易读。
所以,@property
装饰器允许我们实现面向对象编程中的封装,同时保持Python简洁的语法。
这也是Python为什么不需要明确的getter和setter方法的原因之一。
希望两个代码栗子有助于理解@property的作用。
-
使用依赖注入
在软件开发中,实现类之间的松散耦合非常重要。
这促进了模块化、可测试性和灵活性。实现此目标的一种方法是使用依赖关系注入。
依赖关系注入是一种设计模式,它涉及外部化依赖关系并将其从外部提供给类,而不是让类在内部创建或获取其依赖关系,除非绝对必要。
示例 1:无依赖注入,紧密耦合
class Logger:
def log(self, message: str):
# Code to log the message
print(message)
class User:
def __init__(self):
self.logger = Logger() # Creating an instance of Logger internally
def register(self, username: str):
# Code to register the user
self.logger.log(f"User '{username}' registered successfully.")
user = User()
user.register("John Cena")
在此示例中,类直接在其方法中创建类的实例。该类与的具体实现紧密耦合。
如果我们想出于测试目的更改或模拟类,它变得具有挑战性,因为类直接依赖于具体的类。
示例 2:使用依赖关系注入松散耦合
from abc import ABC, abstractmethod
class ILogger(ABC):
@abstractmethod
def log(self, message: str):
pass
class Logger(ILogger):
def log(self, message: str):
# Code to log the message
print(message)
class LoggerMeme(ILogger):
def log(self, message: str):
# Code to log the message
print(f"{message} You can\"t see me")
class User:
def __init__(self, logger: ILogger):
self.logger = logger # Injecting the logger dependency
def register(self, username: str):
# Code to register the user
self.logger.log(f"User '{username}' registered successfully.")
我们所做的更改是在 的方法中。该类不再负责创建实例,而是依赖于从外部提供的对象依赖注入。
logger = Logger()
user = User(logger) # Injecting the logger dependency into the User class
user.register("John Cena")
logger_meme = LoggerMeme()
user_meme = User(logger_meme) # Injecting the logger dependency into the UserMeme class
user_meme.register("John Cena")
这使得类与类松散耦合,并允许更大的灵活性。我们可以轻松地交换接口的不同实现例如__init__和User,而无需修改类。
测试期间的模拟也变得容易得多。
-
封装
封装是Python和面向对象编程中的一个关键概念。基本上,它鼓励类内的信息隐藏和数据保护。以下是封装的一些好处:
防止未经授权或意外地修改对象状态。这促进了数据完整性并降低了出现错误或错误的风险。
隐藏类的内部实现详细信息,仅公开基本接口或公共方法。这种抽象保护了类的使用者免受不必要的复杂性的影响,并在内部工作和外部用法之间提供了明确的界限。
在 Python 中,我们使用双下划线__
前缀来指示属性和方法的预期可访问性。
以单个_
下划线开头的属性和方法通常被认为是类的“受保护”或内部属性和方法;
而以双下划线__
开头的属性和方法通常被认为是类的“私有”。
值得注意的是,Python 不像其他一些编程语言那样有严格的访问修饰符。
没有像Java那样的东西,而只是一种机制来指示属性和方法的预期可访问性。
但是,如果需要,我们仍然可以访问这些属性和方法。简而言之,Python中的封装更像是一种约定或协议,而不是严格的规则。
原因是Python的创造者Guido van Rossum认为:
“programmers are consenting adults”
大家都是成年人,知道怎么做的后果——意思是程序员应该有足够的技能和判断力来决定自己要使用什么工具、框架或语言。
我们不应该被迫使用某种特定的技术,而是应该根据项目的需求和我们自己的优势自由选择。
Guido在创造Python时的思想是使其成为一种简单易学、可读性强、包容的语言,不会强迫程序员采用任何特定的编程范式。
class EncapsulationClass:
def __init__(self):
self.__private_attribute = 23
def __private_method(self):
return "This is a private method."
def public_method(self):
return self.__private_attribute * 3
# 创建类的实例
obj = EncapsulationClass()
# 访问公共方法
result = obj.public_method()
print(result) # Output: 69
# 试图直接访问私有属性
# 注意名称修饰将属性名更改为_ClassName__attribute
private_attr = obj._EncapsulationClass__private_attribute
print(private_attr) # Output: 23
# 试图直接调用私有方法
# 注意名称修饰将方法名更改为_ClassName__method
private_method_result = obj._EncapsulationClass__private_method()
print(private_method_result)
# Output: "This is a private method."
# 试图在没有名称修饰的情况下访问
# 私有属性会导致
# AttributeErrorlmangling results in AttributeError
print(obj.__private_attribute)
#输出:
'EncapsulationClass' object has no attribute '__private_attribute'
在上面的例子中,我们有一个具有私有属性和私有方法的类。
双下划线前缀触发名称重整,将属性或方法的名称分别更改为 或。EncapsulationClass__private_attribute__private_method_EncapsulationClass__attribute_EncapsulationClass__method
事实上,Python支持多种范式,如面向对象编程、函数式编程和过程式编程。这种“自愿的成年人”的想法体现在Python社区的
"There should be one-- and preferably only one --obvious way to do it."
这句口号中。这意味着Python不会强迫您采取一种特定的方法来完成某项任务,而是尽量为每个问题提供一种最简单和最易理解的解决方案。
但是作为程序员,您有足够的技能来选择更复杂或高级的方法,如果您认为适合的话。
总之,这句话体现了Python语言和社区文化的一个关键理念:信任和赋权给程序员。
我们有能力和判断力来对自己的工具和技术作出明智的选择。Python试图为我们提供便利,而不是限制我们的自由。
为什么要使用元类而不是函数? 既然可以接受任何可调用的,为什么要使用类 因为它显然更复杂?__metaclass__
这样做有几个原因:
意图是明确的。当你阅读时,你知道 接下来会发生什么UpperAttrMetaclass(type) 您可以使用 OOP。元类可以从元类继承,覆盖父方法。元类甚至可以使用元类。 如果指定了元类类,则类的子类将是其元类的实例,但不指定元类函数。 您可以更好地构建代码。您从不将元类用于像上面示例这样微不足道的事情。它通常是为了复杂的事情。能够创建多个方法并将它们分组到一个类中对于使代码更易于阅读非常有用。 您可以挂上 和 。这将允许你做不同的事情,即使通常你可以在 , 有些人只是更习惯使用 .new____init____call____new____init 这些被称为元类,该死的!它一定意味着什么! 为什么要使用元类? 现在最大的问题。为什么要使用一些晦涩难懂的容易出错的功能?
好吧,通常你不会:
元类是更深层次的魔力, 99%的用户永远不必担心它。 如果你想知道你是否需要它们, 你没有(那些实际上 需要他们肯定地知道 他们需要它们,并且不需要 解释原因)。
蟒蛇大师蒂姆·彼得斯
元类的主要用例是创建 API。
一个典型的例子是Django ORM。它允许您定义如下内容:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是,如果您这样做:
person = Person(name='bob', age='35')
print(person.age)
它不会返回对象。甚至直接从数据库中获取它 IntegerFieldint
这是可能的,因为定义和 它使用了一些魔法,可以用简单的语句将你刚刚定义的 到数据库字段的复杂挂钩中。
models.Model__metaclass__Person
Django 通过公开一个简单的 API 使复杂的东西看起来很简单 并使用元类,从此 API 重新创建代码以完成实际工作 幕后花絮。
参考链接:
https://python-textbok.readthedocs.io/en/1.0/Classes.html
https://www.python.org/download/releases/2.2.3/descrintro/
本文由 mdnice 多平台发布