V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lingeo
V2EX  ›  Python

如何为 Python 原生对象添加函数

  •  
  •   lingeo · 99 天前 · 2236 次点击
    这是一个创建于 99 天前的主题,其中的信息可能已经有所发展或是发生改变。

    举个例子,我实现了一个过滤出字符串中所有数字的功能,函数名为filter_number,我想要附加到 str 对象上,这样调用函数可以写成"abc123abc".filter_number()
    我目前知道的做法就是新建对象继承 str ,但是调用前还是要手动转一遍s = CustomStr("abc123abc"),这就有点脱裤子放屁了。
    不知道这个需求能不能实现,最好还要能被 ide 感知,编写时能够提示出来。

    第 1 条附言  ·  99 天前
    看样子应该是不行,初衷也只是为了调用函数时候能方便点,大部分函数功能是对字节和字符串做处理。
    既然 Python 没有相关支持,那也没必要纠结下去,毕竟只是个锦上添花的操作。
    #11 发的项目,名字就叫禁果,python 刚好还是条蛇。😂
    26 条回复    2024-01-22 09:59:08 +08:00
    abersheeran
        1
    abersheeran  
       99 天前   ❤️ 1
    省流:做不了,IDE 感知不到。
    chaleaochexist
        2
    chaleaochexist  
       99 天前
    str 不知道
    我知道 dict,
    有一个 Userdict 是官方标准库提供的, 专门给用户干这事儿的.
    capbone
        3
    capbone  
       99 天前
    ```python
    import builtins


    class _CustomStr(str):
    def filter_number(self):
    pass


    builtins.str = _CustomStr
    ```

    对于显示声明为 str 的对象有效,但是对字面值无效:

    ```
    str("abc123abc").filter_number() # OK
    ```

    ```
    "abc123abc".filter_number()

    AttributeError: 'str' object has no attribute 'filter_number'
    ```
    capbone
        4
    capbone  
       99 天前
    不知道 v2 code block 的语法,抱歉……
    McZoden
        5
    McZoden  
       99 天前
    同关注
    C# 有 extension method 的语法糖,虽然实现方法也是类似于,新定义一个 Static Class ,包含一个 Static Method ,但调用方法就直接是用原生类型的实例去调用了
    Python 可能针对 str ,这种内置的对象,不行,没有这样的语法糖
    mightybruce
        6
    mightybruce  
       99 天前
    在 Python 中,不能直接在 str 类型上添加自定义方法。但是,可以通过创建一个新的类来继承 str 类,并在新类中添加自定义方法。以下是一个示例:


    class MyStr(str):
    def my_custom_method(self):
    # 在这里实现你的自定义方法
    pass

    # 使用自定义类
    my_str = MyStr("Hello, world!")
    my_str.my_custom_method()

    或者修改 str 的 built-in 方法

    一些常见对象的魔术方法
    __len__, __getitem__, __contains__, __add__, __mul__, __mod__, __format__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__, __hash__, __repr__, __str__, __bytes__, __bool__, __int__, __float__, __complex__, __index__, __trunc__, __floor__, __ceil__, __round__, __enter__, __exit__, __new__, __init__, __del__, __call__, __getattr__, __setattr__, __delattr__, __dir__, __getattribute__, __setitem__, __delitem__, __iter__, __next__, __reversed__, __len__, __contains__, __add__, __mul__, __rmul__, __mod__, __format__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__, __hash__, __repr__, __str__, __bytes__, __bool__, __int__, __float__, __complex__, __index__, __trunc__, __floor__, __ceil__, __round__, __enter__, __exit__, __new__, __init__, __del__, __call__, __getattr__, __setattr__, __delattr__, __dir__, __getattribute__, __setitem__, __delitem__, __iter__, __next__, __reversed__, __contains__, __add__, __mul__, __rmul__, __mod__, __format__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__, __hash__, __repr__, __str__, __bytes__, __bool__, __int__, __float__, __complex__, __index__, __trunc__, __floor__, __ceil__, __round__, __enter__, __exit__, __new__, __init__, __del__, __call__, __getattr__, __setattr__, __delattr__, __dir__, __getattribute__, __setitem__, __delitem__, __iter__, __next__, __reversed__, __len__,
    CaptainD
        7
    CaptainD  
       99 天前
    我自己的理解是做不到,不知道有没有什么奇淫巧技

    态添加函数可以有两种方式做到
    1. 继承
    2. 修改对象的内置属性

    而字符串是不可变的,2 做不到,只能是 1 ,也就是你说的“新建对象继承 str”

    字典是可以使用 2 的,因为字典是可变对象,但 UserDict 其实也是用继承
    kmyq
        8
    kmyq  
       99 天前
    昨天刚问 Chatgpt 这个问题,已声明的倒是可以
    --
    在 Python 中,您可以使用 types.MethodType 将一个函数绑定到一个类的实例上。这允许您只为具体的实例添加函数,而不会更改整个类的定义。以下是一个示例:

    python
    import types

    class MyClass:
    def __init__(self, value):
    self.value = value

    # 定义一个将要绑定的函数
    def print_value(self):
    print(self.value)

    # 创建一个 MyClass 的实例
    obj = MyClass(10)

    # 将函数绑定到这个实例
    obj.print_value = types.MethodType(print_value, obj)

    # 调用新绑定的函数
    obj.print_value()
    在上面的示例中,我们在 MyClass 的实例 obj 上绑定了 print_value 函数。具体细节如下:

    首先,我们定义了一个 MyClass 。该类包含了一个__init__构造函数,以及我们希望将其绑定的函数。
    我们定义了 print_value 函数,该函数接受一个参数 self ,它将打印 self.value 。
    然后,我们创建了 MyClass 的实例 obj ,并将要绑定的函数 print_value 绑定到了这个具体的实例上。
    最后,我们调用新绑定的函数:obj.print_value()。
    这样,我们就通过函数式编程功能将一个函数绑定到了 MyClass 的实例上。
    zzhaolei
        9
    zzhaolei  
       99 天前
    没有语言能无副作用的直接为内置类型添加方法。如果你为 str 添加了方法 a ,lib b 也为 str 添加了方法 a 和 b ,那么调用 str.a() 的时候是调用你的 a 还是 lib b 的 a ?而且你直接调用 str.b() 的时候是给你提示 lib b 定义的 b 还是给你报错?这复杂度直接爆炸。

    现在的通用做法是,如果你要给 str 添加方法,那么你就定义一个新的类型,然后在自己定义的类型上定义方法。

    python 可以 class MyStr(str) 继承 str 。
    mightybruce
        10
    mightybruce  
       99 天前
    给内置类型添加是不行的,但是搞一个类模拟函数,给函数添加是可以的

    class NamedFunction:
    def __init__(self, name, f):
    self.f = f
    self.name = name

    def __call__(self, *args, **kwargs):
    return self.f(*args, **kwargs)

    def __str__(self):
    return self.name


    f = NamedFunction("lambda: 'blah'", lambda: 'blah')
    print(f())
    print(f)
    c2const
        12
    c2const  
       99 天前
    或许可以换成 C++语言 :)
    Alias4ck
        13
    Alias4ck  
       99 天前
    xy 问题
    lolizeppelin
        14
    lolizeppelin  
       99 天前   ❤️ 2
    我敢打赌 楼主原来是写 js 的
    msg7086
        15
    msg7086  
       99 天前
    你这 Python 怎么写得一股 Ruby 味啊(
    bybyte
        16
    bybyte  
       99 天前
    修改 py 源码?
    atuocn
        17
    atuocn  
       99 天前
    参考 8 楼,依稀记得 meta class 可以做到
    MiketsuSmasher
        18
    MiketsuSmasher  
       99 天前 via Android
    标准库里提供了一个 collections.UserString ,专门方便你干这种事情的
    codehz
        19
    codehz  
       99 天前
    https://github.com/xpodev/extype
    也不是完全不行,用原生扩展包里改
    bianhui
        20
    bianhui  
       99 天前
    随便定义一个类型继承 str,写个空方法
    然后再使用的试试
    a:自定义类型 = ""或者
    typing 下 cast
    a = cast(a,自定义类型)
    编辑器就可以自己识别了
    frostming
        21
    frostming  
       99 天前
    如 #1 所说,没用,IDE 感知不到,但你非要骚操作的话:

    import gc

    d = gc.get_referents(str.__dict__)[0]
    d['show'] = lambda self: 'hello? are you okay?'
    'foo'.show()
    # hello? are you okay?
    listenerri
        22
    listenerri  
       99 天前
    不要魔改基础库,你改了用着爽了,别人怎么维护

    虽然此例能实现的话目前影响不大,但应该要以小见大,很多深坑都是一点点堆出来的
    forgottencoast
        23
    forgottencoast  
       98 天前
    @zzhaolei
    看#5, #14
    zzhaolei
        24
    zzhaolei  
       98 天前
    @forgottencoast #23 学到了。突然想起来这种有 vm 的语言本身是超级动态的🤔
    351994
        25
    351994  
       96 天前
    可以实现,aHR0cHM6Ly9ibG9nLnplYmVkeS5jb20vcG9zdC82Y2YyZTIwOS5odG1s
    参考这里的,base64
    351994
        26
    351994  
       96 天前   ❤️ 1
    是可以实现的,
    import ctypes


    class PyType(ctypes.Structure):
    pass


    class PyObject(ctypes.Structure):
    Py_ssize_t = (
    ctypes.c_int64 if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_int32
    )
    _fields_ = [
    ("ob_refcnt", Py_ssize_t),
    ("ob_type", ctypes.POINTER(PyType)),
    ]


    class PyTypeObject(PyObject):
    _fields_ = [
    ("dict", ctypes.POINTER(PyObject))
    ]


    def inject(class_, function_name):
    def _(function):
    name_, dict_ = class_.__name__, class_.__dict__
    proxy_dict = PyTypeObject.from_address(id(dict_))
    namespace = {}
    ctypes.pythonapi.PyDict_SetItem(
    ctypes.py_object(namespace),
    ctypes.py_object(name_),
    proxy_dict.dict
    )
    namespace[name_][function_name] = function

    return _

    可以参考#25 中的内容,发不了网址,base64 了一下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2849 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 09:50 · PVG 17:50 · LAX 02:50 · JFK 05:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.