1. property动态属性

‘’’A文件的调用语句写在main函数外部,B文件importA文件的函数时时会自动调用函数’’’

Python programming provides us with a built-in @property decorator which makes usage of getter and setters much easier in Object-Oriented Programming.

1.1 class without getters and setters

a class that stores the temperature in degress Celsius

  1. class Celsius:
  2. def __init__(self,temperature=0):
  3. self.temperature = temperature
  4. def to_fahrenheit(self):
  5. return (self.temperature*1.8)+32
  6. if __name__ == '__main__':
  7. '''python设置属性和获取属性的基本方法'''
  8. # create a new project
  9. human = Celsius()
  10. human.temperature = 37
  11. # get attr
  12. print(f'get the human temperature attribute {human.temperature}')
  13. # call the method
  14. print(f"get the human to_fahrenheit method {round(human.to_fahrenheit(),2)}")
  15. '''
  16. get the human temperature attribute 37
  17. get the human to_fahrenheit method 98.60000000000001
  18. '''

每当我们temperature如上所述分配或检索任何对象属性时,Python都会在对象的内置__dict__字典属性中进行搜索。
print(human.dict) # {‘temperature’: 37}

1.2 class with getters and setters

假设我们要扩展 temperature上面定义的类。我们知道任何物体的温度都不能低于-273.15摄氏度(热力学中的绝对零)
让我们更新代码以实现此值约束。
上述限制的一个明显解决方案是隐藏属性temperature(将其设为私有),并定义新的getter和setter方法来对其进行操作。可以按以下步骤完成:

class Celsius:
    def __init__(self,temperature=0):
        self.set_temperature(temperature)
    def to_fahrenheit(self):
        return (self.get_temperature()*1.8)+32
    # getter
    def get_temperature(self):
        return self._temperature
    # setter
    def set_temperature(self,value):
        if value <-237.15:
            raise ValueError("temperature below -273.5 is wrong!")
        self._temperature = value

if __name__ == '__main__':
    human = Celsius(37)
    # get attr via a getter
    print(human.get_temperature())  # 37
    # get method by the method itself
    print(round(human.to_fahrenheit(),2)) # 98.6
    # new constraint implementation
    human.set_temperature(-300) # ValueError: temperature below -273.5 is wrong!
    # Get the to_fahreheit method
    print(human.to_fahrenheit())

如我们所见,以上方法引入了两个new get_temperature()set_temperature()方法。
此外,temperature被替换为_temperature_开头的下划线用于表示Python中的私有变量。
然而这种方法让我们实现所有我们之前的类的程序都必须修改obj的代码。在代码量多的项目中重构代码可能会出现问题。既代码不能向后兼容,因此需要@property

1.3 the @property decorator

property() 是一个python的内置函数,用于创建和返回一个property对象。

property(fget=None,fset=None,fdel=None,doc=None)
'''
    fget 是获取属性值的函数
    fset 是设置属性值的功能
    fdel 是删除属性的功能
    doc 是一个字符串(如评论)
'''
>>> property()
<property object at 0x0000000003239B38>

对象的属性有三种方法,getter()setter(),和deleter()。指定fgetfsetfdel

temperature = property(get_temperature, set_temperature)
和下面的代码是等效的
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

上述的代码结构可以应用成decorator。我们甚至不需要定义get_temperature()set_temperature()函数,因为他们不需要而且会污染命名空间。
只需要使用decorator重用temperature的同样的名字来定义getter()setter()

class Celsius:
    def __init__(self,temperature=0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return self.temperature*1.8 +32

    @property
    def temperature(self):
        print('getting value...')
        return self._temperature

    @temperature.setter
    def temperature(self,value):
        print('setting value...')
        if value < -273.15:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value

if __name__ == '__main__':
    human = Celsius(37)
    '''
        >>> setting value...
    '''
    print(human.temperature)
    '''
         >>> getting value...
         >>> 37
    '''
    print(human.to_fahrenheit())
    '''
         >>> getting value...
         >>> 98.60000000000001
    '''
    coldest_thing = Celsius(-300)
    '''
        >>> setting value...
        >>> ValueError: Temperature below -273 is not possible
    '''

2. __getattr____getattribute__魔法函数

__getattr__(self,attr)

  • 触发时机:获取不存在的对象成员时触发
  • 参数:1、接收当前对象的self,2、获取成员名称的字符串
  • 返回值: 必须有值
  • 作用:为访问不存在的属性设置值
  • 注意:__getattribute__(self,attr)无论何时都会在__getattr__(self,attr)之前触发,触发了__getattribute__(self,attr)有返回值就不会在触发__getattr__(self,attr)

__getattribute__(self,attr)

  • 触发时机:使用对象成员时触发,无论成员是否存在
  • 参数:1、接收当前对象的self,2、获取成员名称的字符串
  • 返回值: 必须有值
  • 作用:在具有封装操作(私有化时),为程序开部分访问权限使用

当获取正确属性名称的属性时,触发了__getattribute__(self,attr),要访问对象的name成员。
写错类的属性名称时Name.

from datetime import time,date
class Person:
    def __init__(self,name,birthday,info={}):
        self.Name =name
        self.birthday = birthday
        self.info = info

if __name__ == '__main__':

    p = Person('ck',date(year=1996,month=4,day=29),info={'company':'cmb'})
    print(p.Name) # ck 触发了__getattribute__(),要访问对象的name成员
    print(p.name)   # AttributeError: 'Person' object has no attribute 'age'

添加 __getattr__(self, attr)

    def __getattr__(self, item):
        return f'not find attr: {item}'
>>>not find attr: name

3. 属性描述符和属性查找过程

属性描述符对象:只要实现 get set delete 这3个当中的任何一个方法,这个对象就是一个属性描述符的对象; 通过属性描述符,可以控制赋值时的一些行为。

import numbers

class IntField():
    # 数据属性描述符
    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        # 检查参数是否是int类型
        if not isinstance(value, numbers.Integral):
            raise ValueError("int value need")

        if value < 0:
            raise ValueError("positive value need")
        # 如果value值是int,需要将这个值保存起来
        # 此时不能将值保存在instance里,比如 instance.age = value,如果这样保存,此instance又会调用IntField的 __set__方法,
        # 这样就陷入死循环,这里将值保存在IntField class里,所以是将值放在self里
        # 因为是将值放在了 IntField类里,所以__get__时,需要将它的值 return回去
        self.value = value

    def __delete__(self, instance):
        pass

class NonDataField:
    # 只实现一个 __get__ 方法,称为: 非数据属性描述符
    def __get__(self, instance, owner):
        return self.value

class User:
    age = IntField()

if __name__ == '__main__':
    user = User()
    user.age = 30  # 当对user对象设置age属性时,它会调用IntField类的 __set__ 方法, 所以当需要对设置的属性值做些检查时,可以把逻辑写在__set__方法中
    print(user.age)

    # user.age = "abc"  # 会抛出异常
    # print(user.age)

“”” 属性查找过程 如果user是某个类的实例,那么user.age(以及等价的getattr(user, ‘age’))首先调用getattribute。 如果类定义了getattr方法,那么在getattribute抛出AttributeError的时候就会调用到getattr , 而对于描述符(get)的调用,则是发生在getattribute内部的。

user = User(),那么user.age 顺序如下:

(1) 如果”age”是出现在User或其基类的dict中, 且age是data descriptor(数据属性描述符), 那么调用其get方法,否则

(2) 如果”age”出现在obj(这里指user对象)的dict中, 那么直接返回obj.dict[“ages”], 否则

(3) 如果”age”出现在User或其基类的dict

(3.1) 如果age是non-data descriptor(非数据属性描述符),那么调用其__get__方法,否则

(3.2) 返回__dict__["age"]

(4) 如果User有getattr方法,调用getattr方法,否则

(5) 抛出AttributeError

“””

4. __new____init__的区别

  1. __new__是一个静态方法,__init__是一个实例方法
  2. __new__方法返回一个创建的实例,而__init__什么都不返回
  3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用
  4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__

ps: __metaclass__在创建类时起作用,我们可以分别用__metaclass____new____init__来分别在类创建、实例创建和实例初始化的时候做一些工作。

class User:

    def __init__(self,name):
        print('in init')
        self.name = name
    def __new__(cls, *args, **kwargs):
        print("in new")
        return super().__new__(cls)

        '''
new是用来控制对象的生成过程,在对象生成之前
init是用来完善对象的

        '''
if __name__ == '__main__':
    user = User(name='ck')
>>>in new
in init
class User:

    def __init__(self, name):
        print('in init')
        self.name = name
    def __new__(cls, *args, **kwargs):
        print('in new')
        # return super().__new__(cls)
'''
    如果new不返回对象,则不会调用init函数
'''

if __name__ == '__main__':
    user =User(name='ck')
>>>in new

5. 自定义元类

元类是创建类的类 .元类的主要用途是创建API

先来看一个简单的动态创建类的函数

https://jeza-chen.com/2020/01/09/Python3-Metaclass/

'''

    类也是对象,type创建类的类

'''
'''动态创建类'''
def create_class(name):
    if name == 'User':
        class User:
            def __str__(self):
                return 'user'
        return User
    elif name == 'Company':
        class Company:
            def __str__(self):
                return 'company'
        return Company
if __name__ == '__main__':
    MyClass = create_class('User')
    my_obj = MyClass()
    print(my_obj)
    print(type(my_obj)) # <class '__main__.create_class.<locals>.User'>

上面的函数实现逻辑很僵硬不灵活。我们可以用type实现灵活的动态创建类。type是元类。

    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict) # type(name,基类,属性)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass
''':type动态创建类'''
if __name__ == '__main__':
    # MyClass = create_class('User')
    MyClass = type("User", (), {})  # () {}表示不继承基类,只继承object,{}表示先不添加属性
    my_obj = MyClass()
    print(my_obj)   # <__main__.User object at 0x7f0b8c34b940>
    print(type(my_obj)) # <class '__main__.User'>
''':type动态创建类'''

def say(self):
    return "i am user method : name = " + self.name

class BaseClass:
    def answer(self):
        return 'i am base class'

if __name__ == '__main__':
    # MyClass = create_class('User')
    MyClass = type("User", (BaseClass,), {"name":"user","say":say}) 
    '''
        (BaseClass,)中的 ","不能少
    '''
    my_obj = MyClass()

    print(my_obj.name)  # user

    print(my_obj.say)  # <bound method say of <__main__.User object at 0x7f4b18e74a20>>

    print(my_obj.say()) # i am user method : name = user

    print(my_obj.answer())  # i am base class

使用metaclass创建元类

'''
    什么是元类,元类是创建类的类,对象<-class(对象)<-type
'''
class ThisIsMetaClass(type): #  需要继承type
    pass

class User(metaclass=ThisIsMetaClass):
    pass

python中类的实例化过程,会首先寻找metaclass属性,如果找到,通过metaclass去创建User类。找不到就去基类找,基类中没有去模块中找。

type去创建类对象,实例化

class ThisIsMetaClass(type): #需要继承type
    def __new__(cls, *args, **kwargs):
        pass

class BaseClass(metaclass=ThisIsMetaClass):
    def answer(self):
        return 'i am base class'

class User(BaseClass):
    pass

my_obj = User()
cls = {type} <class ’__main__.ThisIsMetaClass'>

元类编程 - 图1