《Effective Python》笔记 第六章-元类与属性

阅读Effective Python(第二版)的一些笔记



原文地址:https://www.cnblogs.com/-beyond/p/16214895.html


第44条 用纯属性与修饰器取代旧式的setter与getter方法

有一些编程语法,会为每个属性定义对应的getter和setter,用来获取属性和设置属性值,但是在Python中,不用这么复杂,直接使用对象.属性名来访问即可;

如果在访问属性时需要做特殊的处理,那么可以使用@property实现,并且在使用@property时,不要引发奇怪的副作用,比如进行io操作或者其他耗时的操作。

# coding:utf-8

class Person(object):

    def __init__(self):
        self.name = None
        self._age = None

    @property
    def age(self):
        print("执行@property装饰的age()")
        return self._age

    @age.setter
    def age(self, new_age):
        print("执行@age.setter装饰的age(), new_age:%s" % new_age)
        if new_age < 0:
            raise Exception("age必须大于等0")
        self._age = new_age


p = Person()
p.name = "abc"
p.age = 10   # 执行@age.setter装饰的age(), new_age:10
print(p.name)  # abc
print(p.age)
# 执行@property装饰的age()
# 10

第45条 考虑用@property实现新的属性访问逻辑,不要急着重构原有的代码

如果一个类里面属性访问和设置的逻辑比较复杂,现在需要修改这些逻辑,建议不要重构已有的代码,而是使用@property来改已有的实例属性增加功能(将已有的逻辑迁移到@property中);

另外如果类中的@property比较多了,这时候应该考虑这种重构这个类了,因为每个属性的访问和设置时,都有逻辑,那么这个类就特别复杂了。

第46条 用描述符来改写需要复用的@property方法

@property是很方便,但是还是有一些无法处理的问题,我们需要明确的为某个属性定义加上@property和@property.setter``,如果我们给A属性加了,但是没有给B属性加,那么B就不能像A那样使用`@property的便捷。

那么如何做,才能实现B不加@property也能使用@property的便捷呢?可以使用描述符;

什么是描述符,可以先看下面这个例子:

class Person(object):
    
    def __init__(self):
        self.name = None
        

person = Person()

当为person的name属性复制时,person.name = "abc",翻译为Person.__dict__['name'].__set__(person, "abc");

当获取person的name属性值时,print(person.name),翻译为print(Person.__dict__['name'].__get__(person, "abc"))

也就是说,当访问对象的属性时,Python会转为在类的层面查找,查询Person类里面有没有这样一个属性。如果有,而且还是个实现了__get____set__方法的对象,那么系统就认定你想通过描述符协议定义这个属性的访问行为。

将上面的翻译拆分一下Person.__dict__['name'].__set__(person, "abc"),首先是Person.__dict__['name'],这个结果肯定是个对象,然后调用这个对象的__set__(person, "abc")__get__(person, "abc"));前面我们提到类中的属性不加@property就不能使用@property的便捷,那么这里看来,只要Person.__dict__['xxx']都支持__set____get__即可。

第47条 针对惰性属性使用__getattr__、getattribute__及__setattr

  • 如果访问一个类中未定义的属性(注意不是实例属性),就会调用该类的__getattr__方法
  • 当要给对象的属性设置值时候,就会调用__setattr__方法。
  • 访问一下类中的属性,不论该属性是否存在,都会调用__getattr__方法

一般情况下,对象可能会包含很多数据,这些数据可以在类加载的时候被初始化,这种情况可能不是最优的,也许加载的数据很久用不到,甚至运行过程不会用到,此时就可以使用懒加载,实现的时候,可以自己定义懒加载方法,另外一种方式是使用__getattr__,这种方式动态的加载数据并动态为类增加一个属性。

第48条 用__init_subclass__验证子类写得是否正确

当子类继承了父类后,父类的一些接口如果要子类保证数据的某些规则,这个时候父类怎么对子类进行限制呢?

一种方式就是父类中定义check方法,子类继承后,由子类主动调用check方法来校验参数是否符合条件,如下所示

#coding:utf-8

class BaseClass(object):

    DATA = None

    @classmethod
    def check_data(cls):
        if cls.DATA is None:
            raise RuntimeError
        elif not isinstance(cls.DATA, dict):
            raise TypeError


class ChildClass(BaseClass):
    # 设置属性值
    DATA = dict()


child = ChildClass()
# 手动调用check
child.check_data()

还有一种方式,就是使用__init_subclass__,简单的示例如下

#coding:utf-8


class SuperClass(object):

    def __init_subclass__(cls, **kwargs):
        print("run SuperClass.__init_subclass__")
        print("cls:%s" % cls)
        print("**kwargs:%s" % kwargs)
        
        # 类属性demo_value设置为abc-xyz
        cls.demo_value = "abc-xyz"


# 定义子类
class SubClass(SuperClass):
    # 子类也定义了demo_value
    demo_value = "qaq"


sub = SubClass()
print(sub.demo_value)
# run SuperClass.__init_subclass__
# cls:<class '__main__.SubClass'>
# **kwargs:{}
# abc-xyz

# 定义另外一个子类
class SecondClass(SubClass):
    demo_value = "second"

second = SecondClass()
print(second.demo_value)
# run SuperClass.__init_subclass__
# cls:<class '__main__.SecondClass'>
# **kwargs:{}
# abc-xyz

看了上面__init_subclass__的例子,就可以看出,父类是可以修改子类的数据;

现在需要的是父类中增加子类参数的校验,肯定是办不到的,因为父类始终覆盖了子类的数据,其实在继承的时候,还可以指定参数,传给__init_subclass_的kwargs。示例如下:

class SuperClass(object):

    def __init_subclass__(cls, **kwargs):
        print("run SuperClass.__init_subclass__")
        print("cls:%s" % cls)
        print("**kwargs:%s" % kwargs)
        
        # 进行校验
        if "demo_value" not in kwargs:
            raise RuntimeError

        if len(kwargs['demo_value']) < 0:
            raise RuntimeError

        cls.demo_value = kwargs['demo_value']


# 在继承的时候,还可以指定参数,传给`__init_subclass_`的kwargs
class ThirdClass(SuperClass, demo_value="abc", other_value="dfadfasf"):
    pass


third = ThirdClass()
print(third.demo_value)
# run SuperClass.__init_subclass__
# cls:<class '__main__.ThirdClass'>
# **kwargs:{'demo_value': 'abc', 'other_value': 'dfadfasf'}
# abc

于是,这样就在父类中定义了子类数据的check。

第49条 用__init_subclass__记录现有的子类

如果定义了一个类后,怎么知道这个类有哪些子类呢?比较简单的大概有以下几种思路,首先肯定会有一个几个来保存有哪些子类,每当有新的子类时,就加入到该集合:

方式1:子类初始化方法中,手动将自己加入到集合中

#coding:utf-8
class SuperClass(object):
    # 保存继承该类的子类,key为名称,value为type
    SUB_CLASS_DICT = {}

    
class OneClass(SuperClass):

    def __init__(self):
        SuperClass.SUB_CLASS_DICT.setdefault(self.__class__.__name__, self.__class__)


class TwoClass(SuperClass):

    def __init__(self):
        SuperClass.SUB_CLASS_DICT.setdefault(self.__class__.__name__, self.__class__)


one = OneClass()
two = TwoClass()
print(SuperClass.SUB_CLASS_DICT)
# {'OneClass': <class '__main__.OneClass'>, 'TwoClass': <class '__main__.TwoClass'>}

方式2:父类扩展__new__方法,那么子类就不需要手动将自己加入到集合中;

class SuperClassV2(object):

    # key为名称,value为type
    SUB_CLASS_DICT = {}

    def __new__(cls, *args, **kwargs):
        cls.SUB_CLASS_DICT.setdefault(cls.__name__, cls)


class OneClassV2(SuperClassV2):
    pass

class TwoClassV2(SuperClassV2):
    pass


one = OneClassV2()
two = TwoClassV2()
print(SuperClassV2.SUB_CLASS_DICT)
# {'OneClassV2': <class '__main__.OneClassV2'>, 'TwoClassV2': <class '__main__.TwoClassV2'>}

方式3:使用__init_subclass__来实现,功能就是创建对象后会回调该方法,该方法定义在父类中,这样的话,子类也不需要手动将自己加入到集合中;

class SuperClassV3(object):

    # key为名称,value为type
    SUB_CLASS_DICT = {}

    def __init_subclass__(cls, **kwargs):
        cls.SUB_CLASS_DICT.setdefault(cls.__name__, cls)


class OneClassV3(SuperClassV3):
    pass

class TwoClassV3(SuperClassV3):
    pass


one = OneClassV3()
two = TwoClassV3()
print(SuperClassV3.SUB_CLASS_DICT)
# {'OneClassV3': <class '__main__.OneClassV3'>, 'TwoClassV3': <class '__main__.TwoClassV3'>}

第50条 用__set_name__给类属性加注解

todo

第51条 优先考虑通过类修饰器来提供可组合的扩充功能,不要使用元类

假设有一个装饰器,可以记录方法执行的入参和返回值,比如下面这样:

# coding:utf-8
import time

def log(func):
    def wraper(*args, **kwargs):
        print("func_name:%s, args:%s, kwargs:%s" % (func.__name__, args, kwargs))
        result = func(*args, **kwargs)
        print("func_name:%s, result:%s" % (func.__name__, result))
        return result

    return wraper


@log
def do_repeat(msg, repeat_times):
    time.sleep(1)
    return msg * repeat_times


do_repeat("abc", 10)
# func_name:do_repeat, args:('abc', 10), kwargs:{}
# func_name:do_repeat, result:abcabcabcabcabcabcabcabcabcabc

如果一个类中有10个方法,每个方法都需要有这个功能,怎么做呢?只需要给每个方法都加上@log装饰器即,如果有20个呢?30个呢?

这个时候如果还手动添加,就不太好了,怎么做可以只加一次呢?

方式1:可以使用元类,在__new__中统一为所有方法添加该装饰器(相当于装饰后覆盖)

# 类中需要装饰的类型
handle_types = (
    types.MethodType,
    types.FunctionType,
    types.BuiltinMethodType,
    types.BuiltinFunctionType,
    types.MethodDescriptorType,
    types.ClassMethodDescriptorType
)


class SuperClass(object):

    def __new__(cls, *args, **kwargs):
        clazz = super().__new__(cls, *args, **kwargs)
        # 遍历类的方法,
        for key in dir(clazz):
            value = getattr(clazz, key)

            # 如果是function上面的哪几种类型,就装饰后进行覆盖
            if isinstance(value, handle_types):
                wraped_func = log(value)
                setattr(clazz, key, wraped_func)
		# 返回方法被装饰后的类
        return clazz



class SubClass(SuperClass):

    def say(self, msg):
        return "say msg----------" + msg

    def show(self, msg):
        return "show msg________" + msg


sub = SubClass()
sub.say("hello")
# func_name:say, args:('hello',), kwargs:{}
# func_name:say, result:say msg----------hello

sub.show("yes")
# func_name:show, args:('yes',), kwargs:{}
# func_name:show, result:show msg________yes

方式2:使用类装饰器,前面都是介绍的函数装饰器,其实也有类装饰器,是对类进行增强,返回一个新的类,这样的话,就可以将上面__new__中的逻辑提到类装饰器中,示例如下:

def log_class(clazz):

    # 遍历类的方法
    for key in dir(clazz):
        value = getattr(clazz, key)

        # 如果是function上面的哪几种类型,就装饰后进行覆盖
        if isinstance(value, handle_types):
            wraped_func = log(value)
            setattr(clazz, key, wraped_func)

    return clazz


@log_class
class TwoClass(object):
    def say(self, msg):
        return "say msg----------" + msg

    def show(self, msg):
        return "show msg________" + msg

two = TwoClass()
# func_name:__new__, args:(<class '__main__.TwoClass'>,), kwargs:{}
# func_name:__new__, result:<__main__.TwoClass object at 0x108962a00>

two.say("dadfd")
# func_name:say, args:(<__main__.TwoClass object at 0x108962a00>, 'dadfd'), kwargs:{}
# func_name:say, result:say msg----------dadfd

two.show("erwer")
# func_name:show, args:(<__main__.TwoClass object at 0x108962a00>, 'erwer'), kwargs:{}
# func_name:show, result:show msg________erwer

推荐使用类装饰器,这样并不会修改需要使用该装饰器的代码;因为前两种方式,要么需要修改类中的每个方法,也不需要修改类的继承,对类的侵入性比较小。

posted @ 2022-05-02 11:45  寻觅beyond  阅读(125)  评论(0编辑  收藏  举报
返回顶部