page contents

Python 中的面向对象没有意义

许多人都在抨击面向对象,虽然我不认为面向对象本身有什么问题,但我觉得至少在 Python 中没这个必要。

近来,许多人都在抨击面向对象,虽然我不认为面向对象本身有什么问题,但我觉得至少在 Python 中没这个必要。


1、没有必要使用面向对象

举个例子,比如下面的代码完全没有必要使用面向对象。class ApiClient:
def __init__(self, root_url: str, session_cls: sessionmaker):
self.root_url = root_url
self.session_cls = session_cls

def construct_url(self, entity: str) -> str:
returnf"{self.root_url}/v1/{entity}"

def get_items(self,entity: str) -> List[Item]:
resp = requests.get(self.construct_url(entity))
resp.raise_for_status()
return [Item(**n) for n in resp.json()["items"]]

def save_items(self, entity: str) -> None:
with scoped_session(self.session_cls)as session:
session.add(self.get_items(entity))

class ClientA(ApiClient):
def construct_url(self, entity: str) -> str:
returnf"{self.root_url}/{entity}"

class ClientB(ApiClient):
def construct_url(self, entity: str) -> str:
returnf"{self.root_url}/a/special/place/{entity}"

client_a = ClientA("https://client-a",session_cls)
client_a.save_items("bars")这里使用了面向对象,因为我们想把 root_url 绑定到某个对象上,而且不想每次都传递 sessionmaker。我们还想使用继承,在调用的中途访问一个方法。但如果只通过数据传递和函数能实现吗?@dataclass
class Client:
root_url: str
url_layout: str

client_a = Client(
root_url="https://client-a",
url_layout="{root_url}/{entity}",
)

client_b = Client(
root_url="https://client-b",
url_layout="{root_url}/a/special/place/{entity}",
)

def construct_url(client: Client, entity: str) -> str:
returnclient.url_layout.format(root_url=client.root_url, entity=entity)

def get_items(client: Client, entity: str) -> List[Item]:
resp = requests.get(construct_url(client, entity))
resp.raise_for_status()
return [Item(**n) for n in resp.json()["items"]]

def save_items(client: Client, session_cls: session_cls, entity: str) -> None:
withscoped_session(session_cls) as session:
session.add(get_items(client, entity))

save_items(client_a,session_cls, "bars")

我们必须随时传递 Client 和 session_cls。但有什么关系呢?代码量甚至还少了 10%。这样编写的代码很容易理解,而且不需要使用面向对象。有人管这种写法叫做“函数袋”。就是说,整个代码都由有类型的数据和一大堆模块作用域的函数组成。那么全局变量怎么处理?你可以参考这篇文章(https://leontrolski.github.io/sane-config.html),在整个应用程序的生命周期内重用 config 或 db 的 session,接口、抽象类怎么办?实际上你不需要它们,直接写代码就行了。平心而论,Python 有了类型标注之后,函数袋风格才开始发挥真正的魅力。不纯粹的函数怎么办?如果你想采用纯粹的函数式编程,你可能想编写纯粹的类,然后使用不纯粹的“适配器”实例来做一些处理:getting-the-current-datetime/API-calls/talking-to-the-db/other-impure-stuff。这个想法很不错。实际上你可以直接使用 freezegun、responses 等方法来避免大量麻烦。


2、例外

但也有一些例外的情况:

  • 你可能注意到,重构的代码中加入了@dataclass,它们只是记录类型。Python 5 可以直接支持这些,不需要使用“常规”类。
  • 使用 Exception 的子类是没问题的。使用 try: ... except SomeClass: ...,基本上会形成一种层级,不过没关系,只要不要搞得过于复杂。
  • Enum,与上面一样,它们非常适合 Python。
  • 在极罕见的情况下(至少在应用程序的开发中很少遇到),你可能会想出一种非常好用的类型然后到处使用,就像pandas.DataFrame/sqlalchemy.Session 一样。但是一般情况下,不要自欺欺人,不要骗自己说我们正在构建了不起的应用程序。谦虚使人进步。


3、面向对象的弊端

虽然在本文开头,我说过我不认为面向对象本身有什么问题,但实际上我还是觉得面向对象不仅没有帮助性,而且还常常混淆问题,鼓励一些不良做法:

  • 面向对象鼓励你修改数据。函数袋非常反对修改参数。不相信的话,你可以试试看,但可千万别生气。
  • 面向对象只是返回的全局变量。你无法在函数之间共享数据,self 会强迫你使用更小的状态空间编写方便测试的函数。
  • 混合数据和函数会加剧序列化的难度,而在当今 REST API 流行的情况下,序列化非常有用。
  • 面向对象带来了疯狂的继承体系,关于这个话题的讨论到处都是。
  • 最重要的是,面向对象没有任何附加价值,它只会导致你无法专心解决问题,并加剧浏览与理解代码的难度。

attachments-2021-04-DAW8oE2Z6083c16804100.jpg

  • 发表于 2021-04-24 14:57
  • 阅读 ( 639 )
  • 分类:Python开发

0 条评论

请先 登录 后评论
小柒
小柒

1470 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1470 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章