Что такое внутренности Pythons str.join()? (Скрытие паролей с выхода)

Я просто наткнулся на интересный (?) способ скрыть пароли (и другие персональные данные) от общего вывода с экрана на лог файлы.

В своей книге Как ошибаться в Python Майк Пирнат предлагает реализовать класс для чувствительных строк и перегрузить его __str__ - и __repr__ -методы.

Я экспериментировал с этим и получил это:

class secret(str):

    def __init__(self, s):
        self.string = s

    def __repr__(self):
        return "'" + "R"*len(self.string) + "'"

    def __str__(self):
        return "S" * len(self.string)

    def __add__(self, other):
        return str.__add__(self.__str__(), other)

    def __radd__(self, other):
        return str.__add__(other, self.__str__())

    def __getslice__(self, i, j):
        return ("X"*len(self.string))[i:j]

(Я знаю, что использование len предоставляет информацию о скрытом содержимом. Это просто для тестирования.)

В этом случае он отлично работает:

pwd = secret("nothidden")

print("The passwort is " + pwd)                  # The passwort is SSSSSSSSS
print(pwd + " is the passwort.")                 # SSSSSSSSS is the password.

print("The passwort is {}.".format(pwd))         # The password is SSSSSSSSS.
print(["The", "passwort", "is", pwd])            # ['The', 'password', 'is', 'RRRRRRRRR']
print(pwd[:])                                    # XXXXXXXXX

Однако это не работает:

print(" ".join(["The", "password", "is", pwd]))  # The password is nothidden

Итак, как str.join() работает внутри? Какой метод мне придется перегружать, чтобы затенять строку?

Ответ 1

Проблема заключается в том, что вы наследуете от str, что, вероятно, реализует __new__, что означает, что даже если вы избежали вызова родительского конструктора в своем классе, базовый объект C все еще инициализируется им.

Теперь join, вероятно, проверяет, имеет ли он подкласс str и, будучи внедренным на C, он напрямую обращается к базовой структуре C или использует другую связанную с str функцию, которая обходит __str__ и __repr__ (подумайте об этом: если значение является строкой или строковым подклассом, почему код вызывает __str__ или __repr__, чтобы получить его значение? Он просто обращается к основному массиву символов каким-то образом!)

Чтобы исправить это: do не наследовать от str! К сожалению, это означает, что вы не сможете использовать этот объект в точности как строка в некоторых ситуациях, но это почти неизбежно.


Альтернативой, которая может работать, является реализация __new__ и передача другого значения методу str __new__:

class secret(str):
    def __new__(cls, initializer):
        return super(secret, cls).__new__(cls, 'X'*len(initializer))
    def __init__(self, initializer):
        self.text = initializer
    def __repr__(self):
        return "'{}'".format("R"*len(self))
    def __str__(self):
        return "S"*len(self)
    def __add__(self, other):
        return str(self) + other
    def __radd__(self, other):
        return other + str(self)

Результат:

In [19]: pwd = secret('nothidden')

In [20]: print("The passwort is " + pwd)                  # The passwort is SSSSSSSSS
    ...: print(pwd + " is the passwort.")                 # SSSSSSSSS is the password.
    ...: 
    ...: print("The passwort is {}.".format(pwd))         # The password is SSSSSSSSS.
    ...: print(["The", "passwort", "is", pwd])            # ['The', 'password', 'is', 'RRRRRRRRR']
    ...: print(pwd[:])
The passwort is SSSSSSSSS
SSSSSSSSS is the passwort.
The passwort is SSSSSSSSS.
['The', 'passwort', 'is', 'RRRRRRRRR']
XXXXXXXXX

In [21]: print(" ".join(["The", "password", "is", pwd]))
The password is XXXXXXXXX

Однако я не вижу, как это полезно. Я имею в виду: цель этого класса - избежать ошибок программирования, которые в конечном итоге отображают конфиденциальную информацию? Но тогда срабатывание исключения будет лучше, чтобы вы могли идентифицировать ошибки! Для этого лучше всего raise NotImplementedError внутри __str__ и __repr__ вместо бесшумного предоставления бесполезного значения... убедитесь, что вы не просачиваете секрет, но поиск ошибок становится очень трудным.