菜鸟初学python入门进阶第七节:面向对象,元类编程


上一篇的连接:菜鸟初学python入门进阶第六节:面向对象,引用、可变性与垃圾回收


❤⭐🍦🍧🌈🌊🎉💄💻✨🎊🎏✨✨老铁萌球球点赞👍 评论📚
乱打的也可以

1.property动态属性🌊🎉

当我们再设计数据库用户时,也许会遇到一些会随着某些因素改变的属性,比如用户年龄

from datetime import date, datetime
class User:
    def __init__(self, name, birthday):
        self.name = name
        self.birthday = birthday
    
    '''用户年龄因为会改变,所以最好用动态修改的方法'''
    def get_age(self):
        return datetime.now().year - self.birthday.year
        
'''这个是当前py文件的程序入口'''
if __name__ == "__main__":
    user = User("Sun-Yuchen", date(year=1990, month=7, day=18))
    print(user.get_age())

但是,如果我们 没有想那么多,一开始就用了固定年龄的写法,如

from datetime import date, datetime
class User:
    def __init__(self, name, birthday, age):
        self.name = name
        self.birthday = birthday
        self.age = age
        
    '''这里还有一大段代码和方法使用到了age属性'''

if __name__ == "__main__":
    user = User("Sun-Yuchen", date(year=1990, month=7, day=18), 30)
    print(user.age)

这样子,如果我们之后加上了动态获取年龄的方法,之前的许多代码都要修改,且修改起来很不方便。
我们有没有不修改之前的代码,而能够改变属性获取方式的方法呢?

我们可以用@property动态属性。

from datetime import date, datetime
class User:
    def __init__(self, name, birthday, age):
        self.name = name
        self.birthday = birthday
        # self.age = age

    @property
    def age(self):
        return datetime.now().year - self.birthday.year

    '''这里还有一大段代码和方法使用到了age属性'''

if __name__ == "__main__":
    user = User("Sun-Yuchen", date(year=1990, month=7, day=18), 30)
    print(user.age)

这样,age这个方法就变成了一个可以动态修改的属性,我们可以用调用属性的方式去调用方法,这样就解决了之前的问题。
然而,作为一个普通的属性(不受保护),我们不仅能获取值,还应该能进行修改。上述方法只能获取值。
我们可以搭配@xxxxx.setter来使用

from datetime import date, datetime
class User:
    def __init__(self, name, birthday, age):
        self.name = name
        self.birthday = birthday
        self._masterbatingtimes = 0
        '''单下划线并不能保护数据,只是作为一种规范'''

    @property
    def masterbatingtimes(self):
        # return (datetime.now().year - self.birthday.year)*100
        return self._masterbatingtimes

    @masterbatingtimes.setter
    def masterbatingtimes(self, times):
        self._masterbatingtimes = times

if __name__ == "__main__":
    user = User("Sun-Yuchen", date(year=1990, month=7, day=18), 30)
    print(user._masterbatingtimes)
    print(user.masterbatingtimes)
    user.masterbatingtimes = 100
    print(user._masterbatingtimes)
    print(user.masterbatingtimes)

输出

0
0
100
100

手冲次数是私人的,上述代码我们可以看到这两个描述符搭配可以从某种程度上替代某个原属性的名字(给原属性加名字)。
当然你也可以使用我注释掉的语句来查看新的动态手冲次数,再通过setter赋给原属性,尽管这看起来会有些奇怪。

from datetime import date, datetime
class User:
    def __init__(self, name, birthday, age):
        self.name = name
        self.birthday = birthday
        self._masterbatingtimes = 0
        '''单下划线并不能保护数据,只是作为一种规范'''

    @property
    def masterbatingtimes(self):
        return (datetime.now().year - self.birthday.year)*100
        '''每年冲100发'''
        # return self._masterbatingtimes

    @masterbatingtimes.setter
    def masterbatingtimes(self, times):
        self._masterbatingtimes = times

if __name__ == "__main__":
    user = User("Sun-Yuchen", date(year=1990, month=7, day=18), 30)
    print(user._masterbatingtimes)
    print(user.masterbatingtimes)
    user.masterbatingtimes = user.masterbatingtimes
    print(user._masterbatingtimes)
    print(user.masterbatingtimes)

输出

0
3000
3000
3000

2.__getattr__与__getattribute__魔法函数🎊🎏

这两个魔法函数都是对类的属性进行操作的,__getattr__是python动态特性的最根本原因
__getattr__是当查找不到属性时调用

from datetime import date, datetime
class User:
    def __init__(self, name, birthday, age):
        self.name = name
        self.birthday = birthday

    def __getattr__(self, item):
        print("we don't know")
        return "hahaha"

if __name__ == "__main__":
    user = User("Sun-Yuchen", date(year=1990, month=7, day=18), 30)
    print(user.size)
we don't know
hahaha

我们没有size这个属性,所以调用了getattr,getattr最终会输出返回值。
这个魔法函数也可以实现某种程度上属性名称的替代。

还有一个大用处:
我们可以用它来维护一个字典,而不是设好的属性值

class User:
    def __init__(self, info={}):
        self.info = info

    def __getattr__(self, item):
        return self.info[item]

if __name__ == "__main__":
    user = User(info={"name": "Sun-Yuchen", "year": "1990", "month": "7", "day": "18"})
    print(user.year)
    print(user.size)
1990
Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 454, in <module>
    print(user.size)
  File "D:/python/python3/cn-bug/o.py", line 449, in __getattr__
    return self.info[item]
KeyError: 'size'

而getattribute魔法函数更加高级,它会在类的属性访问时被无条件调用,无论属性是否存在,都会调用getattribute函数,某种程度上实现了对所有属性的隐藏。

class User:
    def __init__(self, info={}):
        self.info = info

    def __getattr__(self, item):
        return self.info[item]

    def __getattribute__(self, item):
        return "fuck off"

if __name__ == "__main__":
    user = User(info={"name": "Sun-Yuchen", "year": "1990", "month": "7", "day": "18"})
    print(user.year)
    print(user.size)
fuck off
fuck off

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

类的属性的描述符不是实例的属性的描述符。
先看例子

import numbers
'''实现下列魔法函数中任意一个即可把该类变成属性描述符'''
class IntF:
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError("int value needed")
        self.value = value
    def __delete__(self, instance):
        pass

class User:
    num = IntF()

实现get、set、delete当中任意一个魔法函数,即可把类变成属性描述符。
可以说属性描述符是对类本身的属性的某种限制(描述)
即,类的属性不仅仅只是一个值,而是一个完整的对象,其中可以有方法,有dict。
将类的属性作为属性描述符的实例(变成属性描述符)后,我们仍可以将这个类的属性当作只有一个值来使用,只不过加了一些限制。

上述num是类本身的属性,我们将其限制为整数(user数量是整数),当我们进行如下调用

if __name__ == "__main__":
    user1 = User()
    user1.num = 1 # 通过实例修改了类本身的属性
    print(user1.num)
    user1.num = "sb-csdn"

1
Traceback (most recent call last):
  File "D:/python/python3/cn-bug/o.py", line 21, in <module>
    user1.num = "sb-csdn"
  File "D:/python/python3/cn-bug/o.py", line 9, in __set__
    raise ValueError("int value needed")
ValueError: int value needed

当我们用=的方式对num进行赋值,调用了intf的实例num中的set方法;当我们用print(user1.num)的方式进行调用,调用了intf的实例num中的get方法。

要注意不能用User.num = 1的方式去赋值,因为这相当于改变了指针的指向,覆盖掉了其原来指向的对象,也就是失去了属性描述符的功能。要通过具体实例(对象)来修改
User.num = 1的等于是赋值,而user1.num = 1的等于是调用魔法函数

如果我们打个断点研究,有csdn
对于属性查找过程
csdnm
我本来写了个更易懂且通顺的,现在没有了,垃圾网络,之前写好久的都没了。
这beyond网,tony🐎的。

4.__new__和__init__的区别

class User:
    def __new__(cls, *args, **kwargs):
        pass
    def __init__(self):
        pass

很显然,new是控制类本身的生成过程的,传进去的参数cls是类;init是控制实例的生成过程的,传进去的参数是实例。
new的调用在init之前,init是用来完善对象的,如果new方法不返回对象,则不调用init函数

class User:
    def __new__(cls, *args, **kwargs):
        print("new")
    def __init__(self, name):
        print("init")
        self.name = name

if __name__ == "__main__":
    a = User("jiayueting")
new

参数先传入new的参数中。不是字典传入*args,是字典传入**kwargs ,(如果自己写函数用到这两个东西,一定要把**kwargs写在*args后面,传入参数时也是字典类型参数要写在最后面)
返回类的写法

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

if __name__ == "__main__":
    a = User("jiayueting")
new
init

jiayueting会被传递到 init 的参数里

5.自定义元类

python是动态语言,python可以在函数中定义类和创建出类的逻辑
type是可以动态创建类的,可以看看type的描述

    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass

于是,我们可以这样创建类

def gejiucai(self):
    print("市场规律哈哈哈")

sb = type("sunyuchen", (), {"gejiucai":gejiucai})
luoyonghao = sb()
print(luoyonghao)
luoyonghao.gejiucai()
<__main__.sunyuchen object at 0x000002A730AFF788>
市场规律哈哈哈

type的小括号里面写继承关系

所以什么是元类?
metaclass
元类是创建类的类
type是元类,可以通过继承的方式获得元类,在继承时加上”metaclass=“。
在python类的实例化过程中,在代码运行前会先寻找类的metaclass属性,如果找不到就去类的继承链上找,找到了就用这个metaclass来创建类,实在没有就调用type去创建类。

class sb(type):
    def __new__(cls, *args, **kwargs):
        print("first is a sb")
        return super().__new__(cls, *args, **kwargs)

class liar(metaclass=sb):
    pass

class thief(metaclass=sb):
    pass

syc_kind = liar()
print(syc_kind)

first is a sb
first is a sb
<__main__.liar object at 0x000001EA99F50C08>

由于有两个类要先生成,且这两个类都是通过元类sb创建的,所以先输出了两行字。

6.通过元类实现orm

orm对象关系映射(Object Relational Mapping),也就是一个数据库管理系统

# 需求
import numbers


class Field:
    pass

class IntField(Field):
    # 数据描述符
    def __init__(self, db_column, min_value=None, max_value=None):
        self._value = None
        self.min_value = min_value
        self.max_value = max_value
        self.db_column = db_column
        if min_value is not None:
            if not isinstance(min_value, numbers.Integral):
                raise ValueError("min_value must be int")
            elif min_value < 0:
                raise ValueError("min_value must be positive int")
        if max_value is not None:
            if not isinstance(max_value, numbers.Integral):
                raise ValueError("max_value must be int")
            elif max_value < 0:
                raise ValueError("max_value must be positive int")
        if min_value is not None and max_value is not None:
            if min_value > max_value:
                raise ValueError("min_value must be smaller than max_value")

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError("int value need")
        if value < self.min_value or value > self.max_value:
            raise ValueError("value must between min_value and max_value")
        self._value = value


class CharField(Field):
    def __init__(self, db_column, max_length=None):
        self._value = None
        self.db_column = db_column
        if max_length is None:
            raise ValueError("you must spcify max_lenth for charfiled")
        self.max_length = max_length

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("string value need")
        if len(value) > self.max_length:
            raise ValueError("value len excess len of max_length")
        self._value = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        if name == "BaseModel":
            return super().__new__(cls, name, bases, attrs, **kwargs)
        fields = {}
        for key, value in attrs.items():
            if isinstance(value, Field):
                fields[key] = value
        attrs_meta = attrs.get("Meta", None)
        _meta = {}
        db_table = name.lower()
        if attrs_meta is not None:
            table = getattr(attrs_meta, "db_table", None)
            if table is not None:
                db_table = table
        _meta["db_table"] = db_table
        attrs["_meta"] = _meta
        attrs["fields"] = fields
        del attrs["Meta"]
        return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
    def __init__(self, *args, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        return super().__init__()

    def save(self):
        fields = []
        values = []
        for key, value in self.fields.items():
            db_column = value.db_column
            if db_column is None:
                db_column = key.lower()
            fields.append(db_column)
            value = getattr(self, key)
            values.append(str(value))

        sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta["db_table"],
                                                                   fields=",".join(fields), values=",".join(values))
        pass

class User(BaseModel):
    name = CharField(db_column="name", max_length=10)
    age = IntField(db_column="age", min_value=1, max_value=100)

    class Meta:
        db_table = "user"


if __name__ == "__main__":
    user = User(name="bobby", age=28)
    # user.name = "bobby"
    # user.age = 28
    user.save()


下一篇的链接:


就到这里😙😙

❤⭐🍦🍧🌈🌊🎉💄💻✨🎊🎏✨✨d=====( ̄▽ ̄*)b

U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*UU•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值