故事的开始来自今天发现本月5号忘记了leetcode每日一题打卡。为了保证本人的第一次月打卡徽章,我打算补打这个卡。很快我发现这是一道并不困难的“困难”题,因此我决定写一个很Coooooooool的函数,题目是这样的:

给你一个以字符串形式表述的 布尔表达式(boolean) expression,返回该式的运算结果。

有效的表达式需遵循以下约定:

  • “t”,运算结果为 True
  • “f”,运算结果为 False
  • “!(expr)”,运算过程为对内部表达式 expr 进行逻辑 非的运算(NOT)
  • “&(expr1,expr2,…)”,运算过程为对 2 个或以上内部表达式 expr1, expr2, … 进行逻辑 与的运算(AND)
  • “|(expr1,expr2,…)”,运算过程为对 2 个或以上内部表达式 expr1, expr2, … 进行逻辑 或的运算(OR)

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/parsing-a-boolean-expression
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个是我写的代码。

from collections import deque

DEBUG = False

def boolean(x: str) -> bool or str:
    if x not in "tf":
        return x
    return True if x == "t" else False

class BoolExpression:

    def __repr__(self):
        return f"A Bool Expression parser"

    def cal_symbol_result(self, x1: bool, grammar_symbol: str, x2: bool = None) -> bool:
        assert (grammar_symbol in "!|&")                # calculate in three types of char
        if DEBUG:
            print(f"checking {x1}, {x2}")
        if grammar_symbol == "!":
            return not x1
        elif grammar_symbol == "&":
            return x1 & x2 if x2 != None else x1
        else:
            return x1 | x2 if x2 != None else x1

    def parse_bool_expr(self, expression: str) -> bool:
        # print(expression)
        grammar_stack = deque()
        char_stack = deque()
        for expression_char in expression:              # iter through expression char
            if DEBUG:
                print(f"showing {expression_char}, {grammar_stack}, {char_stack}")
            if expression_char in "!&|":                # if char is grammar sign
                grammar_stack.append(expression_char)
            elif expression_char == ")":                # or end of the expression
                cur_grammar_char = grammar_stack.pop()
                cur_variable_char = char_stack.pop()
                cur_result = None
                while cur_variable_char != "(":         # iter through all expression need to be calculate
                    if DEBUG:
                        print(f"popping {cur_variable_char}, {cur_grammar_char}")
                    cur_result = self.cal_symbol_result(cur_variable_char, cur_grammar_char, cur_result)
                    if DEBUG:
                        print(f"getting {cur_result}")
                    cur_variable_char = char_stack.pop()
                    if DEBUG:
                        print(f"popping {cur_variable_char}, remaining {char_stack}")
                char_stack.append(cur_result)
            elif expression_char != ",":                # or some other chars
                char_stack.append(boolean(expression_char))
        # print(char_stack)
        assert (len(char_stack) == 1)
        return char_stack[0]

其实我一开始是想把boolean这个函数写进类里的,但是为了省去写self.boolean的调用,我绞尽脑汁回想起了曾经见过的一个装饰器@staticmethod。我清晰地记得似乎这个方法可以不用写self,但很快,事实告诉我并非如此,我依然要使用self.boolean去调用。显然我贫瘠的记忆并不足以支持我继续干想,动手试一试的想法很快涌入了我的脑海。那就去试一试学一学吧,我想。

类的方法

因此,今天的内容,就从这里开始。首先我们要提到的是装饰器带来的类方法。凭借我的记忆和一些简单的查找,我找到了三种方法:@property, @classmethod, @staticmethod

class Solution:
    """
    This is description which will be showed
    The class of solution
    """
    def add(self) -> int:
        return self.a + self.b

    @property
    def add1(self) -> int:
        return self.a + self.b

    @classmethod
    def add2_1(cls):
        print("add2_1")

    @classmethod
    def add2(cls) -> str:
        print(f"self.instance is {cls.__name__}")
        print(cls.a)
        return cls.__name__

    @staticmethod
    def add3(a: int) -> int:
        return a + 1

    def __repr__(self):
        return f"a solution class with {self.a}, {self.b}"

@property

@property 将函数包装为类的属性,这个属性是直接获取属性,使用和类的普通属性完全相同。

In [7]: Solution.add1
Out[7]: <property at 0x2812ab37778>

In [8]: s.add1
Out[8]: 5

个人觉得主要用途是用于在类中创建只读的属性,即将私有属性安全化,例如本例中,如果我们不希望实例对象可以修改 _add1 属性,则可以在不定义 setattr 的情况下将 add1 变为只读属性。

@classmethod

@classmethod 将函数变为类的方法,该方法只能调用类的属性,而不能调用实例的属性,因此无法完成 self.a + self.b 的操作,只能使用类的属性

In [1]: s.add2()
self.instance is Solution
<member 'a' of 'Solution' objects>
Out[1]: 'Solution'

@staticmethod

@staticmethod 确实可以少写self,很遗憾,是在定义的时候,是在定义 add3 时,无需写self,因为它不只是实例的方法,它是类的方法,我们可以随意调用。

In [3]: s.add3(1)
Out[3]: 2

In [4]: Solution.add3(1)
Out[4]: 2

Summary

简单看一下这三个函数的区别:

In [13]: Solution.add
Out[13]: <function __main__.Solution.add(self) -> int>

In [14]: Solution.add1
Out[14]: <property at 0x1cf238e7728>

In [15]: Solution.add2
Out[15]: <bound method Solution.add2 of <class '__main__.Solution'>>

In [16]: Solution.add3
Out[16]: <function __main__.Solution.add3(a: int) -> int>

----------------------------------------------------------------------------------

In [9]: s.add
Out[9]: <bound method Solution.add of a solution class with 2, 3>

In [10]: s.add1
Out[10]: 5

In [11]: s.add2
Out[11]: <bound method Solution.add2 of <class '__main__.Solution'>>

In [12]: s.add3
Out[12]: <function __main__.Solution.add3(a: int) -> int>

类的属性

这一部分我想说的是类的属性,其实也没什么特别难的,可以通过 dir 自己查到类的方法

In [17]: dir(s)
Out[17]: 
['__annotations__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '_add1',
 'a',
 'add',
 'add1',
 'add2',
 'add2_1',
 'add3',
 'b',
 'instance']

这里还是上面这个类,我们添加一些方法的重载。

class Solution:
    """
    This is description which will be showed
    The class of solution
    """
    __name__: str = "Sub solution"
    __slots__ = ["a", "b", "_add1"]
    instance = 0

    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            cls.instance = super().__new__(cls)
        return cls.instance

    def __init__(self, a: int, b: int):
        self.a = a
        self.b = b
        self._add1 = 5

    def _in_slots(self, attr) -> bool:
        for cls in type(self).__mro__:
            if attr in getattr(cls, '__slots__', []):
                return True
        return False

    def __setattr__(self, attribute: 'attribute', item: int):
        if attribute == "_add1":
            print("PLEASE DO NOT CHANGE add1")
            return
        if attribute == "a" and item > 100:
            print(f"self.a do not support assign over 100")
        if self._in_slots(attribute):
            object.__setattr__(self, attribute, item)
            return
        # print("SET ERROR, try for subclass")
        raise AttributeError(f"Something has gone wrong. {attribute} may not be assigned")

    def add(self) -> int:
        return self.a + self.b

    @property
    def add1(self) -> int:
        return self.a + self.b

    @classmethod
    def add2_1(cls):
        print("add2_1")

    @classmethod
    def add2(cls) -> str:
        print(f"self.instance is {cls.__name__}")
        print(cls.a)
        return cls.__name__

    @staticmethod
    def add3(a: int) -> int:
        return a + 1

    def __repr__(self):
        return f"a solution class with {self.a}, {self.b}"

如果我们这里实现了 __slot__ 那么类将不需要 __dict__ 从而可以节省内存,并且固定类的属性。当事先知道类的属性的时候,可以__slots__来节省内存以及获得更快的属性读取。注意不应当把防止创造__slots__之外的新属性作为使用__slots__的原因,可以使用decorators以及getters,setters来实现属性控制,使类只接受实现的属性。

In [1]: s.a = 101
self.a do not support assign over 100

In [2]: s._add1 = 10
PLEASE DO NOT CHANGE add1

In [3]: s.a = 5

In [4]: s
Out[4]: a solution class with 5, 3

In [5]: s.a = 4

In [6]: s
Out[6]: a solution class with 4, 3

__annotations__

这个属性可以看到类的注释,当然函数也有这个属性。

In [18]: s.__annotations__
Out[18]: {'__name__': str}

In [19]: s.__init__.__annotations__
Out[19]: {'a': int, 'b': int}

__setattr__

实现了该方法后,在对属性赋值时会调用,因此可以在函数里限制属性赋值。
可以禁止不允许的赋值。

__getattribute__

实现该方法可以对类的方法调用进行额外的包装,例如对类的函数调用实现监听,详情见:python给类的所有方法加上装饰器

class ChiseAipFace(AipFace):              #AipFace为百度的人脸识别http封装的类。
    def __getattribute__(self, item):
    """建议对item进行一下判断,不要全局增加"""
        ret = super().__getattribute__(item)
        if type(
                ret) == "<class 'method'>":  # 类里面的成员分为三种,method(类方法和实例方法),function(实例方法),int,str...(变量成员),具体需要的时候还是通过type进行判断或者直接通过item来判断
            def res(*args, **kwargs):
                retu = None
                t = 0
                for i in range(10):
                    if i > 0:
                        t = time.time()
                    retu = ret(*args, **kwargs)
                    if retu['error_code'] == 18:#主要解决瞬时并发超限的问题,通过随机值将超限的并发随机在之后的一段时间里面进行接口访问。
                        time.sleep(random.random() * i * 5)
                    else:
                        if t:
                            logger.warning("接口访问延时18:" + str(time.time() - t) + ",name:" + item)
                        return retu
                logger.error("接口失败18:" + item)
                return retu
            return res
        else:
            return ret

__new__ 与 __init__

new是对类的实例化,因此__new__接收 cls 对象,并实例化后返回实例化的对象;而__init__是给实例化的对象赋予属性,新词接受的是 self 实例属性。

这里的__new__主要是用于唯一的对象,即一个类同一时刻只能实例化一个对象(例如音乐播放器或垃圾箱)

Fun Fact

In [2]: type(s)
Out[2]: __main__.Solution

In [3]: type(type(s))
Out[3]: type

In [4]: type.__mro__
Out[4]: (type, object)

In [5]: object.__mro__
Out[5]: (object,)

type 是创建类(包括它本身)的类,而 object 是创建实例的类,为继承的类提供了内置方法。
但是 __mro__ 显示 type 继承了 type 和 object 而 object 继承了 object。