欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

程序员文章站 2022-05-17 08:07:22
...

********频道

********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

It is a new selection of tips and tricks about Python and programming from my ********-channel @pythonetc.

这是我的********频道@pythonetc中有关Python和编程的一些新技巧和窍门。

Previous publications

以前的出版物

********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

If an instance of a class doesn’t have an attribute with the given name, it tries to access the class attribute with the same name.

如果类的实例没有具有给定名称的属性,则它将尝试访问具有相同名称的类属性。

>>> class A:
...     x = 2
...
>>> A.x
2
>>> A().x
2

It’s fairly simple for an instance to have attribute that a class doesn’t or have the attribute with the different value:

实例具有类没有的属性或具有不同值的属性非常简单:

>>> class A:
...     x = 2
...     def __init__(self):
...         self.x = 3
...         self.y = 4
...
>>> A().x
3
>>> A.x
2
>>> A().y
4
>>> A.y
AttributeError: type object 'A' has no attribute 'y'

If it’s not that simple, however, if you want an instance behave like it doesn’t have an attribute despite the class having it. To make it happen you have to create custom descriptor that doesn’t allow access from the instance:

如果不是那么简单,那么,如果您希望实例的行为就像它没有一个属性,尽管该类具有该属性。 为此,您必须创建不允许从实例访问的自定义描述符:

class ClassOnlyDescriptor:
    def __init__(self, value):
        self._value = value
        self._name = None  # see __set_name__

    def __get__(self, instance, owner):
        if instance is not None:
            raise AttributeError(
                f'{instance} has no attribute {self._name}'
            )

        return self._value

    def __set_name__(self, owner, name):
        self._name = name


class_only = ClassOnlyDescriptor


class A:
    x = class_only(2)


print(A.x)  # 2
A().x       # raises AttributeError

See also how the Django classonlymethod decorator works: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6

另请参阅Django classonlymethod装饰器的工作方式: https : //github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6

********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

Functions declared in a class body can’t see the class scope. It makes sense since the class scope only exists during class creation.

在类主体中声明的函数看不到类范围。 这是有道理的,因为类作用域仅在类创建期间存在。

>>> class A:
...     x = 2
...     def f():
...         print(x)
...     f()
...
[...]
NameError: name 'x' is not defined

That is usually not a problem: methods are declared inside a class only to become methods and be called later:

通常这不是问题:方法在类内部声明只是成为方法,以后再调用:

>>> class A:
...     x = 2
...     def f(self):
...         print(self.x)
...
>>>
>>>
>>> A().f()
2

Somewhat surprisingly, the same is true for comprehensions. They have their own scopes and can’t access the class scope as well. That really make sense for generator comprehensions: they evaluate expressions after the class creation is already finished.

令人惊讶的是,对于理解也是如此。 它们具有自己的作用域,也无法访问类作用域。 这对于生成器理解确实很有意义:它们在类创建完成之后对表达式进行求值。

>>> class A:
...     x = 2
...     y = [x for _ in range(5)]
...
[...]
NameError: name 'x' is not defined

Comprehensions, however, have no access to self. The only way to make it work is to add one more scope (yep, that’s ugly):

然而,理解无法获得self 。 使它起作用的唯一方法是添加一个范围(是的,这很丑):

>>> class A:
...     x = 2
...     y = (lambda x=x: [x for _ in range(5)])()
...
>>> A.y
[2, 2, 2, 2, 2]
********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

In Python, None is equal to None so it looks like you can check for None with ==:

在Python中, None等于None因此看起来您可以使用==检查None

ES_TAILS = ('s', 'x', 'z', 'ch', 'sh')


def make_plural(word, exceptions=None):
    if exceptions == None:  # ← ← ←
        exceptions = {}

    if word in exceptions:
        return exceptions[word]
    elif any(word.endswith(t) for t in ES_TAILS):
        return word + 'es'
    elif word.endswith('y'):
        return word[0:-1] + 'ies'
    else:
        return word + 's'

exceptions = dict(
    mouse='mice',
)

print(make_plural('python'))
print(make_plural('bash'))
print(make_plural('ruby'))
print(make_plural('mouse', exceptions=exceptions))

This is a wrong thing to do though. None is indeed is equal to None, but it’s not the only thing that is. Custom objects may be equal to None too:

这是错误的做法。 None一个确实等于None ,但这不是唯一的事情。 自定义对象也可能等于“ None

>>> class A:
...     def __eq__(self, other):
...             return True
...
>>> A() == None
True
>>> A() is None
False

The only proper way to compare with None is to use is None.

None比较的唯一正确方法是使用is None

********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

Python floats can have NaN values. You can get one with math.nan. nan is not equal to anything including itself:

Python浮点数可以具有NaN值。 您可以使用math.nan获得一个。 nan不等于任何事物,包括自身:

>>> math.nan == math.nan
False

Also, NaN object is not unique, you can have several different NaN objects from different sources:

此外,NaN对象不是唯一的,您可以从不同来源获得几个不同的NaN对象:

>>> float('nan')
nan
>>> float('nan') is float('nan')
False

That means that you generally can’t use NaN as a dictionary key:

这意味着您通常不能将NaN用作字典键:

>>> d = {}
>>> d[float('nan')] = 1
>>> d[float('nan')] = 2
>>> d
{nan: 1, nan: 2}
********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月
typing allows you to define type for generators. You can additionally specify what type is yielded, what type can be sent into a generator and what is returned. typing允许您定义生成器的类型。 您还可以指定产生什么类型,可以将什么类型发送到生成器以及返回什么类型。 Generator[int, None, bool] is a generator that yields integers, returns boolean value and doesn’t support Generator[int, None, bool]是生成整数,返回布尔值且不支持 g.send(). g.send()

Here is slightly more complicated example. chain_while yields from other generators until one of them returns something that is a signal to stop according to the condition function:

这是稍微复杂的示例。 chain_while从其他生成器chain_while ,直到其中一个生成器返回一些信号,该信号根据condition函数停止运行:

from typing import Generator, Callable, Iterable, TypeVar

Y = TypeVar('Y')
S = TypeVar('S')
R = TypeVar('R')


def chain_while(
    iterables: Iterable[Generator[Y, S, R]],
    condition: Callable[[R], bool],
) -> Generator[Y, S, None]:
    for it in iterables:
        result = yield from it
        if not condition(result):
            break


def r(x: int) -> Generator[int, None, bool]:
    yield from range(x)
    return x % 2 == 1


print(list(chain_while(
    [
        r(5),
        r(4),
        r(3),
    ],
    lambda x: x is True,
)))
********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月

Annotating a factory method is not as simple as it may seem. The immediate urge is to use something like this:

注释工厂方法并不像看起来那样简单。 紧迫的要求是使用这样的东西:

class A:
    @classmethod
    def create(cls) -> 'A':
        return cls()

However, that is not a right thing to do. The catch is, create doesn’t return A, it returns an instance of cls that is A or any of its descendants. Look at this code:

但是,这不是正确的事情。 问题是, create不返回A ,它返回的是cl实例A 或其任何后代。 看下面的代码:

class A:
    @classmethod
    def create(cls) -> 'A':
        return cls()


class B(A):
    @classmethod
    def create(cls) -> 'B':
        return super().create()

The mypy check result is error: Incompatible return value type (got "A", expected "B"). Again, the problem is super().create() is annotated to return A while it clearly returns B in this case.

mypy检查结果为error: Incompatible return value type (got "A", expected "B") 。 同样,问题是super().create()被注释为返回A而在这种情况下显然返回B

You can fix that by annotating cls with TypeVar:

您可以通过用TypeVar注释cls来解决此问题:

AType = TypeVar('AType')
BType = TypeVar('BType')


class A:
    @classmethod
    def create(cls: Type[AType]) -> AType:
        return cls()


class B(A):
    @classmethod
    def create(cls: Type[BType]) -> BType:
        return super().create()

Now create returns the instance of the cls class. However, this annotations are too loose, we lost the information that cls is a subtype of A:

现在, create返回cls类的实例。 但是,此注释过于宽松,我们丢失了clsA的子类型的信息:

AType = TypeVar('AType')


class A:
    DATA = 42
    @classmethod
    def create(cls: Type[AType]) -> AType:
        print(cls.DATA)
        return cls()

The error is "Type[AType]" has no attribute "DATA".

错误是"Type[AType]" has no attribute "DATA"

To fix that you have to explicitly define AType as a subtype of A with the bound argument of TypeVar:

要解决此问题,您必须使用ATypebound参数将TypeVar明确定义为A的子类型:

AType = TypeVar('AType', bound='A')
BType = TypeVar('BType', bound='B')


class A:
    DATA = 42
    @classmethod
    def create(cls: Type[AType]) -> AType:
        print(cls.DATA)
        return cls()


class B(A):
    @classmethod
    def create(cls: Type[BType]) -> BType:
        return super().create()

翻译自: https://habr.com/en/company/mailru/blog/466317/

********频道