Python中关于__getattr__()方法的几个问题,求解答

初学 Python,最近在看这个项目 zhihu-py3

基本情况

  • 定义了一个 ZhihuClient 类,主要实现登录相关的操作,同时创建一个网络会话( requests.session );
  • 定义了一个 Author(url, session) 类,传入某作者的知乎域名,传入一个 session,返回 Author 对象,比如 Author.name 可以获取用户名

目标

实现这样子调用:

client = ZhihuClient()
client.author  # 这是一个 Author 对象

实现方法(我的第一反应)

我只能想到的是,在 ZhihuClient 里定义一个 author 方法,用来创建并返回 Author 对象

def author(url, session=self.session):
    author = Author(url, session)
    return author

试了一下是能实现的。但是除了 Author 之外还有好多的类,如果一个一个定义过去那得多麻烦。所以看作者的实现方法

实现方法(作者的)

ZhihuClient 里定义 __getattr__(),实现动态调用。下面是作者的源码

def __getattr__(self, item: str):
    """本函数用于获取各种类,如 `Answer` `Question` 等.
:支持的形式有:
    1. client.answer()
    2. client.author()
    3. client.collection()
    4. client.column()
    5. client.post()
    6. client.question()
    7. client.topic()

    参数均为对应页面的 url,返回对应的类的实例。
"""
def getter(url):
    return getattr(module, item.capitalize())(url, session=self._session)
attr_list = ['answer', 'author', 'collection',
             'column', 'post', 'question', 'topic']
if item.lower() in attr_list:
    module = importlib.import_module('.'+item.lower(), 'zhihu')
    return getter

我的疑问

  1. 我的方法有没有啥问题?(除了麻烦以外)
  2. 作者的方法是不是比较常见的方法?如果不是,希望知道解决这类问题最常见的方法
  3. 作者方法里定义的 getter(url) 方法是用来传递参数的。我尝试将它去掉,直接 return getattr(module, item.capitalize())(url, session=self._session),但是这样子就会报错:参数 url 未定义。我总感觉定义一个 getter 有点多余(没有任何讽刺作者的意思),有没有更好的解决办法?(如何在 __getattr__() 里传递参数)

Python中关于__getattr__()方法的几个问题,求解答

5 回复

1、没问题,稍微有个小瑕疵
def author(self, url):
author = Author(url, self.session)
return author

2、如果是我自己写的话,我也会写类似的方法
3、该函数返回的是类,而不是实例。


__getattr__ 这玩意儿挺有意思,它只在正常属性查找失败时才被调用。很多人容易把它跟 __getattribute__ 搞混,那哥们儿是每次属性访问都触发。

看个例子就明白了:

class DynamicAPI:
    def __init__(self):
        self.existing_attr = "I'm real"
    
    def __getattr__(self, name):
        # 只有访问不存在的属性时才进来
        if name.startswith('get_'):
            # 动态创建方法
            def dynamic_method(*args):
                return f"Calling {name} with args: {args}"
            return dynamic_method
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

# 使用示例
api = DynamicAPI()
print(api.existing_attr)  # 正常访问,不走 __getattr__
print(api.get_user(123))  # 动态创建并调用
print(api.get_product("apple"))  # 另一个动态方法

关键点:

  1. __getattr__ 接收属性名作为参数,需要返回一个值或抛出 AttributeError
  2. 常用于实现动态API、代理模式、惰性加载
  3. 如果属性存在,Python根本不会调用它
  4. 配合 __setattr____delattr__ 可以实现完整的动态属性控制

想玩得更花的话,可以看看 __getattribute__,但小心递归调用问题。

一句话:用 getattr 处理缺失属性,实现动态功能。

当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过__getattr__魔法方法来定义类的行为

感谢纠正

  1. 代码不可复用。
    2. 常见,但由于效率较低,所以不到万不得已一般也不用。Ruby 的话倒是经常这么干。除此以外还可以用 descriptor 机制(get),代码量会多一些,但是 IDE 可以识别这些属性,避免出现警告。
    3. url 是传给 Answer(self, url) 的,这是你要暴露出去的参数,干掉就没法初始化了。你实在不想给 getter 起名,可以 return lambda url: getattr(module, item.capitalize())(url, session=self._session)。
回到顶部