阅读Effective Python(第二版)的一些笔记
原文地址:https://www.cnblogs.com/-beyond/p/16214895.html
有一些编程语法,会为每个属性定义对应的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
如果一个类里面属性访问和设置的逻辑比较复杂,现在需要修改这些逻辑,建议不要重构已有的代码,而是使用@property
来改已有的实例属性增加功能(将已有的逻辑迁移到@property
中);
另外如果类中的@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__
即可。
__getattr__
方法__setattr__
方法。__getattr__
方法一般情况下,对象可能会包含很多数据,这些数据可以在类加载的时候被初始化,这种情况可能不是最优的,也许加载的数据很久用不到,甚至运行过程不会用到,此时就可以使用懒加载,实现的时候,可以自己定义懒加载方法,另外一种方式是使用__getattr__
,这种方式动态的加载数据并动态为类增加一个属性。
当子类继承了父类后,父类的一些接口如果要子类保证数据的某些规则,这个时候父类怎么对子类进行限制呢?
一种方式就是父类中定义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。
如果定义了一个类后,怎么知道这个类有哪些子类呢?比较简单的大概有以下几种思路,首先肯定会有一个几个来保存有哪些子类,每当有新的子类时,就加入到该集合:
方式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'>}
todo
假设有一个装饰器,可以记录方法执行的入参和返回值,比如下面这样:
# 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
推荐使用类装饰器,这样并不会修改需要使用该装饰器的代码;因为前两种方式,要么需要修改类中的每个方法,也不需要修改类的继承,对类的侵入性比较小。