Отвечу про Python.
Отсутствие инкапсуляции и перегрузки функций удивляет.
Инкапсуляция в питоне, хоть и весьма условная, но есть.
Если метод начинается с __, то интерпретатор автоматически добавляет к имени префикс _%current_class% и, соответственно, в другом классе такой метод уже не будет виден. Private Methods:
"""
>>> obj = bar()
>>> obj.__test()
Traceback (most recent call last):
...
AttributeError: 'bar' object has no attribute '__test'
>>> obj.test2()
Traceback (most recent call last):
...
AttributeError: 'super' object has no attribute '_bar__test'
>>> obj._foo__test()
Hello
>>> obj._foo__test
<bound method bar.__test of <__main__.bar object at 0x...>>
"""
import doctest
class foo():
def __test(self):
print('Hello')
class bar(foo):
def test2(self):
super().__test()
doctest.testmod(optionflags=doctest.ELLIPSIS)
Таким образом достигается инкапсуляция атрибутов класса. Конечно-же, все еще можно обратится к такому методу по полному имени. Однако, для примера, и в C# можно обратиться к приватным методам через рефлексию, но это вовсе не значит, что эти методы публичные.
Для создания абстрактных классов используется специальный метакласс c декораторами.
"""
>>> obj = bar()
>>> obj.test()
Hello
>>> obj = baz()
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class baz with abstract methods test
"""
import abc
import doctest
class foo(metaclass=abc.ABCMeta):
@abc.abstractmethod
def test(self):
print('Hello')
class bar(foo):
def test(self):
super().test()
class baz(foo):
pass
doctest.testmod()
Аналогичным образом реализуются и интерфейсы.
Что-же касается перегрузок функций, то в динамическом языке, где методы могут принимать переменное число параметров и значения по умолчанию - это не возможно, да и совершенно не нужно. Пример:
"""
>>> foo(1, 2, 3, 4, 5, arg1=1, arg2=2, bar=False)
(1, 2, (3, 4, 5), False, {'arg1': 1, 'arg2': 2})
"""
import doctest
def foo(first, second, *args, bar=True, **kwargs):
print( (first, second, args, bar, kwargs) )
doctest.testmod()
Непонятно какое значение возвращает какая-либо функция
Да, этот недостаток присущ, наверное, всем динамическим языкам. Однако, в оправдание питона могу сказать, что эта проблема частично решается при помощи аннотаций, полноценного IDE и хорошего документирования функций.
Более того, при помощи аннотаций, метаклассов и декораторов не сложно реализовать и статическую типизацию :) Т.е. примерно следующее:
"""
>>> foo().bar([1, 2, 3])
Traceback (most recent call last):
...
TypeError: 'param' is 'list', but the expected type is 'int'
>>> foo().bar(1)
Traceback (most recent call last):
...
TypeError: function return is 'str', but the expected type is 'bool'
"""
class foo(metaclass=StrongTyping):
def bar(param: int) -> bool:
return 'some text'
Разумеется, что бы пример работал, нужно еще реализовать метакласс StrongTyping.
Код показался не читаемым как раз из-за отсутствия скобок.
Вероятно, это дело привычки. Отсутствие скобок обязывает программиста следить за отступами и использовать единый символ табуляции, что уже, на мой взгляд, не мало. Что же касается визуального разделения методов, то тут помогаю IDE, которые отделяют их горизонтальными линиями.
Где-то наткнулся на совет реже использовать вызовы функций, особенно в циклах т.к. это дорогая операция в питоне. Перед глазами сразу представились огромные методы в 200+ строк.
На мой взгляд, гибкость питона и такие возможности, как генераторы выражений наоборот позволяют писать очень компактные и лаконичные конструкции. Там где для строго-типизированных языков требуются куча циклов и условных конструкций, в питоне решается одной строчкой.
UPD. Какие преимущества у динамической типизации? Имхо, именно в динамичности. Где еще можно написать нечто подобное:
"""
>>> op = ImportantClass()
>>> op.foo(1)
>>> ImportantClass.debug(True)
>>> op.foo(2)
Called <function foo at 0x...> (<__main__.ImportantClass object at 0x...>, 2) {} None
"""
import doctest
import functools
def logging(func):
""" Декоратор функции, который логирует все вызовы """
@functools.wraps(func)
def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
print('Called ', func, args, kwargs, ret)
return ret
# одна из особенностей питона - это то, что все является объектами
# добавляем атрибут к функции
wrapper.original = func
return wrapper
def debugging(cls):
""" Декоратор класса, добавляет метод debug(enable) """
@classmethod
def debug(cls, enable=True):
attr = '__call__' if enable else 'original'
call = lambda f: logging(f) if enable else f.original
# в цикле подменяются все функции на logging(f), либо на оригинальные,
# которые хранятся в декораторе
for func in [call(f) for f in cls.__dict__.values() if hasattr(f, attr)]:
setattr(cls, func.__name__, func)
# добавляем новый метод для декорируемого класс
# именно для класса, декоратор вызывается только один раз
cls.debug = debug
return cls
@debugging
class ImportantClass():
def foo(self, *args, **kwargs):
pass # do something important
def bar(self, *args, **kwargs):
pass # also do something
doctest.testmod(optionflags=doctest.ELLIPSIS)
Декораторы logging и debugging можно вынести в отдельный модуль и применять для любых классов. При этом при выключенной отладке накладных расходов практически не будет, т.к. методы именно заменяеются другими методоми, а не оборачиваются.
С другой стороны именно эта динамичность может служить источником ошибок, по этому приходится использовать ее осторожно и не забывать про тестирование.
Кстати,на самом деле считается, что питон имеет одновременно и строгую, и динамическую типизацию. Т.н. код "1" + 1
выбросит исключение TypeError.