WAYJAM

Python打开类

Ruby可以直接打开一个已定义的类(模块),打开与定义与其他语句没有本质区别,第二次使用class关键字之后,之后的语句就是进入这个类的封闭作用域内进行一些操作。

在Python中,类只允许一次有效定义,每使用一次class关键字,都作为一个独立的定义类操作。

在Python里面,需要定义或修改方法、属性不需要专门的“打开”,在专门的作用域里面操作。

def hi():
    return 'hi'

class Klass:
    pass
Klass.attr1 = 1
klass.method1 = hi

但每次先定义一个函数然后再“赋值”的代码感觉不够优美,这里可以通过装饰器,即定义一个未绑定函数,然后返回装饰后的“绑定”函数。方法参考:在 Python 中实现 Ruby 的 Open Class 和特异方法

Python的面向对象特征是建立在基于函数的环境之上的。Class的方法以函数的方式定义在类中,当访问一个方法的时候:

class Klass: pass
Klass.a_method # => Klass.__dict__[a_method].__get__(Klass) => unbound function in py2k, raw function in py3k
instance = Klass()
instance.a_method # => type(instance).__dict__[a_method].__get__(instance, type(instance)) => bound function

上面的例子说明了,访问一个类方法的时候,实际上是在当前类(实例的元类)的属性字典中根据方法名查找到对应方法(不存在则调用__getattr__方法),然后调用函数的__get__方法(函数也是对象),这个方法的两个参数实际上决定了目标函数是一个普通函数、静态方法、类方法还是实例方法等,以及执行的上下文实体,意思就是描述符在你要调用一个方法的时候将obj.f(*args)转换成了f(obj, *args)Klass.m(*args)转换成了m(*args)

所以,在类中新增方法有一种写法,在实例中新增绑定方法有三种写法(可以理解为将实例绑定到函数对象):

  • 偏函数
  • 描述符
  • Types声明
from functools import partial
from types import MethodType

def attach_method(target):
    if isinstance(target, type):
        # 类
        def decorator(func):
            setattr(target, func.__name__, func)
    else:
        # 实例
        def decorator(func):
            # bound_func = partial(func, target) # partial function
            # bound_func = func.__get__(target)  # descriptor
            bound_func = MethodType(func, target)  # types
            setattr(target, func.__name__, bound_func)
            
    return decorator

或者说再写一个装饰器,装饰新定义的类,将新类的属性和方法attach到原同名类内,达到类似“打开”类操作的效果,但只能重定义,而不能够真正使用原类中的变量或方法 。但是,实例写一个类似的instance_eval的话,只能装饰在def或者class关键字上,使用上并不直观。

def class_eval(obj):
    original_obj = globals().get(obj.__name__)
    for k, v in obj.__dict__.items():
        if k not in['__dict__', '__weakref__']:
            attach_method(original_obj)(v, k) 
    return original_obj

@class_eval
class Klass:  # same class name
    new_attr: 1
    def hi(self):
        return "Hello"

以上就是绑定函数或属性到对象(类)。

2018-06-27 23:00